Sunday, March 15, 2009

Image file handle adventures.

Today I had an issue with not being able to delete a file programmatically.
While looking for the reason the file was 'locked' (the actual error message was "The process cannot access the file 'file.jpg' because it is being used by another process") I discovered a tool which can help finding out the process which is locking the file.

Process Explorer

Anyway, here is how the image was processed by the application:

Bitmap b = (Bitmap)Image.FromFile(_Filepath);
pictureBox1.Image = b;

// later in the code

System.IO.MemoryStream ms = new System.IO.MemoryStream();
pictureBox1.Image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);

// even later in the code

File.Delete(_Filepath);

At the point where File.Delete() was called, the handle to the file existed (under some circumstances).

Here is how I implemented the fix initially:

public Bitmap getBitmapFromFile(string filename)
{
Image i = null;
using (Stream s = new FileStream(filename, FileMode.Open))
{
i = Image.FromStream(s);
s.Close();
}
return (Bitmap)i;
}

pictureBox1.Image = getBitmapFromFile(_Filepath);

// later in the code

System.IO.MemoryStream ms = new System.IO.MemoryStream();
pictureBox1.Image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);

// even later in the code

File.Delete(_Filepath);

The approach did not work, however, this time returning me the error "A generic error occurred in GDI+" at the line where I tried to save the image.

Here is the reason why this happened:

Bitmap and Image constructor dependencies

In my case, obviously, "Additionally, if the stream was destroyed during the life of the Bitmap object, you cannot successfully access an image that was based on a stream" - see how I tried to close a stream, trying to release a handle to the file this way?

The possible solutions can be found here:

Understanding "A generic error occurred in GDI+." Error

That's how I fixed my problem eventually:

public Bitmap getBitmapFromFile(string filePath)
{
Image img = Image.FromFile(filePath);
Bitmap bmp = img as Bitmap;
Graphics g = Graphics.FromImage(bmp);
Bitmap bmpNew = new Bitmap(bmp);
g.DrawImage(bmpNew, new Point(0, 0));
g.Dispose();
bmp.Dispose();
img.Dispose();
return bmpNew;
}

pictureBox1.Image = Syco.Common.Util.getBitmapFromFile(_Filepath);

// later in the code

System.IO.MemoryStream ms = new System.IO.MemoryStream();
pictureBox1.Image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);

// even later in the code

File.Delete(_Filepath);

Now the image is saved properly and no handle is held against the file, so it is deleted properly too.

by . Also posted on my website

Saturday, March 14, 2009

Moving Ahead of Technology.

Remember that Windows Service I wrote not long ago? Well, today it was time to install it into actual testing environment. That did not work exactly as expected. The first thing I get when trying to run the setup project was this error message



Did not take long to find out that the service was developed in VS.NET 2008 and required .NET Framework 3.5 to run. Now, what do you think would take longer in a large company, to rebuild the application in the previous version of VS.NET or obtaining the permission to install .NET Framework on a server? That's what I did ...

Therefore, moving fast and using the 'latest tools' might actually get you into some trouble...

by . Also posted on my website

Friday, March 6, 2009

A Twisted Code Snippet.

The application is able to save images for an ID. When the changes are being saved, the comma-separated list of file names has to be inserted into the XML file. Easy. This small snippet have been sitting in the production code for about two years, until someone had to attach more than 2 images to a single ID.

(Easy to see that the logic works for one or two files, forming a string of '1.jpg,2.jpg'. If more files are to be saved, the string will look like 1.jpg,2.jpg3.jpg4.jpg' and all images except the first one will be lost).

id += "";

bool firstImage = true;
foreach (IDImage image in idType.IDImages)
{
id += image.ImageFilename;
if (firstImage)
{
firstImage = false;
id += ",";
}
}
id += "
";

I like bugs that are easy to fix and it looks like magic to a unsuspecting observer that a bug is fixed in a with a few keystrokes. How did the original developer come up with the idea, though, and why did it pass testing ...

id += "";

foreach (IDImage image in idType.IDImages)
{
id += image.ImageFilename;

//if not last image, add ','
if (idType.IDImages.IndexOf(image) < idType.IDImages.Count - 1)
{
id += ",";
}
}
id += "
";
by . Also posted on my website

C++ Runtime Libraries Adventures

Almost no day passes without one of those "WHAT is happening?" moments. Yesterday there were 3 that happened to me. One required to delete temporary Internet Explorer files. Of course, I was stupid enough to require a colleague's advice on that. Another one, ironically, was caused by that same colleague adding a line of code in the wrong place. This one I fixed myself.

Now the third one was a bit more interesting and looks like a classic "worked on my machine!" situatuion.
Remember the application that uses the sophisticated scanner? Well, now it also uses a webcam. I was preparing the new version to ship. I build the installation package and installed the application on the test computer. When I try to use the webcam, I get our 'generic' error message, that can be caused by almost anything. But it worked on my machine, I swear!

Anyway, there is a way to find out what really happened - Event Log.

So, something is wrong with the webcam library QuickCamLib.dll. First thing that comes to mind - somehow not registered during installation process? Possible or not? Go go, regsvr32

Oh well, this is not my application problem. But what is wrong? The webcam drivers were installed and I can actually make webcam work (outside of the application).
Fortunately, I'm not the first one to have this problem.
This application has failed to start because the application configuration is incorrect

The thread suggests to look in the System part of the Event Log for a 'side by side' error - and there it is!

The thread also contains the solution, which is somewhat complicated but precise, except in my case I have '90' instead of '80'.

"You need to copy CRT DLL into your application local folder together with the manifest. Please take a look on this post on my blog, http://blogs.msdn.com/nikolad/archive/2005/03/18/398720.aspx. Basically go to windows\winsxs folder. Find a folder like x86_Microsoft.VC80.CRT and copy DLL from there to your application local folder. From what I see in your code you need msvcrt80.dll and msvcp80.dll (perhaps msvcrt80d.dll and msvcp80d.dll if this is Debug mode application). Then go to windows\winsxs\manifests folder and copy x86_*_Microsoft.VC80.CRT*.manifest to Microsoft.VC80.CRT.manifest to your application local folder."

I copy the 4 files msvcm90.dll, msvcp90.dll, msvcr90.dll and Microsoft.VC90.CRT.manifest into my application folder on the test computer, and it works like a charm. I add these files to the installation package, reinstall the application and it works again. All is well that ends well I guess.

by . Also posted on my website

Monday, February 23, 2009

Bootstrapper: scrap the bootstrapper

Although the "bootstrapper" solution worked fine, the decision was made to scrap it completely and do things in a different way.

There are a few valid reasons for that:
- The application comes together with a few other device driver packages
- Currently, these packages are bundled into the .msi package and installed on the client computer, and from there they have to be installed manually
- This brings the current .msi package size to over 100MB in size
- Not every client computer would use all of the device drivers
- If the application will need to support other hardware in the future, using the same approach will keep the .msi package bloating more and more.

So, the task was changed. The requirement now is to have an application in a separate package, and all the device drivers in separate packages, but the installation process will let users select which device drivers they want to install.

After some investigation I came to the conclusion that this can not be done using the Visual Studio Setup and Deployment project. The main limiting issue here is the fact that an .msi installer can not be started from withing another .msi installer. Therefore, I can not launch my application installer, show the dialog with options, and proceed to installing these optional drivers.

A simple solution I came up with was to write a small 'wrapper' Windows Forms application. The application would present the user with multiple checkboxes - one for each optional component.

After the user makes the choices and presses the 'Install' button, the application would first read the xml file which lists all available components






...


The name/path pairs would be added to the

Dictionary packages;

DriverPackage1, ..., MainAppPackage will be set up as tags for the checkboxes at design time to simplify the functionality.

The application will then loop through all checkboxes and, if the checkbox is checked, will add the setup file path to the list.


string startupPath = Application.StartupPath;

string path;
List components = new List();

foreach (Control control in this.Controls)
{
CheckBox checkBox = control as CheckBox;

if (checkBox != null && checkBox.Checked)
{
if (packages.TryGetValue(checkBox.Tag.ToString(), out path))
{
components.Add(path);
}
}
}

Finally, the application will loop through the list of setup files, executing the installation process for each file and waiting for it to finish before launching next one.


foreach (string componentPath in components)
{
InstallComponent(startupPath + componentPath);
}

// ..........

private void InstallComponent(string filePath)
{
System.Diagnostics.Process installerProcess;

installerProcess = System.Diagnostics.Process.Start(filePath);

while (installerProcess.HasExited == false)
{
//indicate progress to user
Application.DoEvents();
System.Threading.Thread.Sleep(250);
}
}

Other small details include a progress bar, success messages etc., but the main idea should be clear.

by . Also posted on my website

Sunday, February 22, 2009

Bootstrapper adventures

I'm now at the stage of creating a setup package for the application that is going to use that magnificent 3M scanner. It has a setup and deployment project already, so I just rebuild it and try installing on the test desktop. However, during the setup process I end up with an error message.

After some research, I find out that there is some driver package that needs to be installed before my application install. If I install it manually first, the installation runs smoothly. So I have to include it in the installation somehow. How hard can that be? I have no experience with installation packages, but I get a hint that a custom action can help.

I do some research and soon enough I find out that

Custom Actions Management in Deployment

Actions can only be run at the end of an installation.

That is not what I need, the drivers absolutely have to be installed prior to the application installation. I research more and find a couple of links, which teach me how to use Orca and how to execute my Custom Actions whenever I like.

MSI Custom Action DLLCustom Action Run EXE

Great! Problem solved. I edit my place my Custom Action before the installation process starts. This time, however, I encounter a '2731' error. I'm not the first one to ever get this error, of course.

Problem when trying to install .NET framwork 2.0 during MSI install

"It is probably failing because you are trying to invoke an installer when an installer is already running. You need to install separate installers sequentially, not from within one another. You would need a bootstrapper to do that. "

Well, that's what I should have known in the very beginning. OK then, now to create a bootstrapper. (And what is the bootstrapper, by the way?)

Use the Visual Studio 2005 Bootstrapper to Kick-Start Your InstallationCreating a bootstrapper for a VS Shell application

These 2 pages give me some ideas. I locate my C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\BootStrapper\Packages folder, create product.xml and package.xml to be as simple as possible, and now I can choose my package from MySetupProject->Properties->Prerequisites.

The application now can be installed smoothly, but there is still one thing I am not happy about. The package consists of setup.exe, the .msi package, and a subfolder with my driver package. I do not want the subfolder, that might be confusing for the user or the subfolder may get 'lost' somewhere in the process of application distribution. I'm looking for the soluton:

Bootstrapper: How to compile the application and prerequisite in single .msi package?
IExpress Installer

And the IExpress seems to work fine for me. I create the single-file installation package, copy it to the test desktop and run ... just to be presented with another error. After examining the installation log, I realise that the IExpress did not extract my driver to the subfolder, but the installer expected to find it in the subfolder. Apparently, IExpress does not support the subfolders. I need another trick. A google search returns me to the page I have seen already and I read it again, carefully ... to the end.

Creating a bootstrapper for a VS Shell application

There it is, my solution:

Unfortunately, the MSBuild task doesn't provide the option to have the configuration resource use prerequisite installers found in the target directory, so you must manually update the appropriate resource file to remove the hard-coded path that looks for prerequisites in a sub-directory of the same name.

- Open the Setup.exe program in Visual Studio's resource editor
- Double-click the resource named, SETUPCFG in the 41 folder
- Search for the "Vs Shell\" string and delete the two occurrences that appear
- Save the resource file and the Setup.exe executable will be updated automatically
- Run iexpress
- Create a new package by following the IExpress wizard's steps and make sure to include the following files ...

Some careful setup.exe editing follows (first attempt was unsuccessful, I spoiled the .exe and had to rebuild my project again) and I have the complete solution - my single-file installation package, that has a prerequisite that is installed before the installation of the main application.

However, that was not the end ...

by . Also posted on my website

Wednesday, February 18, 2009

Mysterious validation function.

Making changes to some application and having some problems with validation, I came across this validation function:

public new bool Validate(bool someParameter)
{
bool blnResult = true;

if (name != null)
{
if (!name.Validate())
blnResult = false;
}

if (address != null)
{
if (!address.Validate(someParameter))
blnResult = false;
}

if (somethingElse != null)
{
if (!somethingElse.Validate())
blnResult = false;
}

if (someMore != null)
{
if (!someMore.Validate())
blnResult = false;
}

return blnResult;
}

So I asked myself, why would this function go through all validations each time even if it knows after the very first one that the blnResult is false and that will not change?

After some thought and investigation, the most likely answer is that the application was growing little by little. So, whenever its functionality was extended, say, from just keeping names to keeping names and addresses, the person currently in charge of the application would just copy and paste this bit

if (name != null)
{
if (!name.Validate())
return false;
}

replace name with address and move on.

After I made some small change, the function does not look much different, but I have much less troubles with validation now.

public new bool Validate(bool someParameter)
{
if (name != null)
{
if (!name.Validate())
return false;
}

if (address != null)
{
if (!address.Validate(someParameter))
return false;
}

if (somethingElse != null)
{
if (!somethingElse.Validate())
return false;
}

if (someMore != null)
{
if (!someMore.Validate())
return false;
}

return true;
}
by . Also posted on my website