Friday, September 18, 2009

Beware the Image object !

One requirement on the project I am working on (.NET 2.0, Windows Forms) was to make the end-user able to load a background image to an InkPicture (which is just a special PictureBox control, with the ability to be drawn upon on a TabletPC). That did not seem very difficult at first : just create an OpenFileDialog and there you go.

// WRONG ! DO NOT USE THIS CODE !
if (_OpenFileDialog.ShowDialog()==DialogResult.OK)
using (Stream ims=_OpenFileDialog.OpenFile())
_InkPicture.Image=Image.FromStream(ims);

Clean, straightforward and ... wrong ! It took me quite some time to realize it though. My application started to throw occasional and rather erratic exceptions in completely unrelated places, and it kind of reminded me of the strange ways a native application could crash after a buffer had been overrun. But in a managed application ?...

As often, the answer is in the documentation (check the Remarks section) :

You must keep the stream open for the lifetime of the Image.

Uh ? Any more information on why I should do this, and perhaps more importantly how I should do this ? Because my Image could be replaced from anywhere in the code. And even if I wanted to track the changes, there is no ImageChanged event on a PictureBox. And it feels weird to retain a reference to a Stream I never use...

As for the first question, the answer lies in KB 814675 : Image is just a wrapper around the native GDI+ library, which has specific requirements :

To retain access to the source bits, GDI+ locks any source file, and forces the application to maintain the life of any source stream, for the life of the Bitmap or the Image object.

As for the second question, tips can be found in the referenced article above. And here is my solution : use a copy of the stream based image that is unrelated to the original stream. Here is some code to create such a copy :

public static Image Copy(Image original)
{
// cf. http://support.microsoft.com/?id=814675

Image ret=new Bitmap(original.Width, original.Height);
using (Graphics g=Graphics.FromImage(ret))
{
g.DrawImageUnscaled(original, 0, 0);
g.Save();
}

return ret;
}

And here is the correct code on how to load an Image into an InkPicture :

if (_OpenFileDialog.ShowDialog()==DialogResult.OK)
using (Stream ims=_OpenFileDialog.OpenFile())
using (Image image=Image.FromStream(ims))
_InkPicture.Image=Copy(image);

Friday, May 15, 2009

Encrypt configuration file in a custom managed action on x64

When I install a .NET (or ASP .NET) application, I like to encrypt the sensitive parts of the configuration file like the connection strings. So I have been used to create a Custom Installer class to achieve this. There are a few tricky things to take into account, like :

  • when your configuration is loaded by the installer, it should be able to resolve embedded dependencies (like Enterprise Library assemblies) even when they are not in the GAC.
  • this class accepts a EXEPATH parameter that contains the path of the application which application file is to be encrypted. It should then be terminated by an extra \ character.

For the record, here is how I do this :

[RunInstaller(true)]
public partial class EncryptConfigInstaller:
Installer
{

public EncryptConfigInstaller()
{
InitializeComponent();
}

public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);

string exePath=Context.Parameters["exepath"].TrimEnd('\\');

AppDomain.CurrentDomain.SetData("EXEDIR", Path.GetDirectoryName(exePath));
AppDomain.CurrentDomain.AssemblyResolve+=_AssemblyResolver;
try
{
EncryptConfiguration(ConfigurationManager.OpenExeConfiguration(exePath));
} finally
{
AppDomain.CurrentDomain.AssemblyResolve-=_AssemblyResolver;
}
}

private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
AssemblyName name=new AssemblyName(args.Name);

return Assembly.LoadFile(Path.Combine((string)AppDomain.CurrentDomain.GetData("EXEDIR"), string.Concat(name.Name, ".dll")));
}

private static void EncryptConfiguration(Configuration config)
{
EncryptSection(config.GetSection("connectionStrings"));

config.Save(ConfigurationSaveMode.Modified);
}

private static void EncryptSection(ConfigurationSection section)
{
if ((section!=null) && (!section.SectionInformation.IsProtected))
{
section.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
section.SectionInformation.ForceSave=true;
}
}

private static ResolveEventHandler _AssemblyResolver=new ResolveEventHandler(CurrentDomain_AssemblyResolve);

}

Then even if I know that managed actions are considered evil, as I do not think I have a choice here, I am using the well known WiX Custom Managed Actions trick. So everything is fine in a perfect world.

Except when a client tried to install one of my applications on a x64 platform (namely Windows Server 2008). Once solved the obvious native libraries problem, I was still puzzled by the fact that the user who had installed the application was the only one able to run it ! It was only after a couple of days of wrong conjectures, poor tricks and unproductive Google searches that I cornered the problem : the encryption process was broken.

Extensive reasons behind that can be found here, but basically it is because InstallUtilLib.dll, which is used to launch Installer classes, is a native library. As such, you should use the 64 bits version on a x64 platform. So I ended up copying both versions in an independent lib folder, and here is the magic corresponding WiX line :

<Binary Id="InstallUtilBinary" SourceFile="$(sys.CURRENTDIR)..\..\lib\$(sys.BUILDARCH)\InstallUtilLib.dll" />

Yes, I learned a lot about x64 lately. But I guess I am not done yet :-)

Oracle Instant Client in Visual Studio

In my previous post, I mentioned the fact that I added the Oracle Instant Client files as Content files in a Visual Studio project. I would like to write more about this here.

If you intend to use your application with Instant Client, you will want to be able to debug it with Instant Client. Which means that the libraries have to be copied along your generated application in the bin\Debug folder. The best way to achieve this is to include then as Content files in your project.

But it gets more tricky if you want to be able to debug it on 32 bits platform as well as on a 64 bits platform : the source code is the same (I am obviously writing about a .NET application, here), you just have to pick the correct library depending on the platform you are debugging on.

My way to do this is :

  • I store the Instant Client libraries in an independent folder, say lib\Oracle\Instant Client.
  • Each platform lies in a dedicated subfolder : x86 for the 32 bits version, x64 for the 64 bits version.
  • I manually tweak the .csproj project file so that it picks the right version depending on the platform I am running Visual Studio on :
    <PropertyGroup>
    <ProcessorArchitecture>x86</ProcessorArchitecture>
    <ProcessorArchitecture Condition=" '$(PROCESSOR_ARCHITECTURE)' == 'AMD64' ">x64</ProcessorArchitecture>
    </PropertyGroup>
    <ItemGroup>
    <Content Include="..\..\lib\Oracle\InstantClient\$(ProcessorArchitecture)\oci.dll">
    <Link>oci.dll</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="..\..\lib\Oracle\InstantClient\$(ProcessorArchitecture)\orannzsbb11.dll">
    <Link>orannzsbb11.dll</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="..\..\lib\Oracle\InstantClient\$(ProcessorArchitecture)\oraociei11.dll">
    <Link>oraociei11.dll</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    </ItemGroup>
    <ItemGroup Condition=" '$(ProcessorArchitecture)' == 'x86' ">
    <Content Include="..\..\lib\Oracle\InstantClient\$(ProcessorArchitecture)\msvcr71.dll">
    <Link>msvcr71.dll</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    </ItemGroup>

imageThis code is based on the PROCESSOR_ARCHITECTURE environment variable. This will be set to x86 on a 32 bits platform, as expected, but to AMD64 on a 64 bits platform. That is why I am using a custom property to get the x64 value back.

I could also simply rename the library subfolder to AMD64, but I like x64 more (I know : I am picky). And besides, it makes my WiX files more straightforward…

Oracle (Not So) Instant Client

Developing database oriented .NET applications is quite a no brainer once you are used to your API (ADO.NET, Enterprise Library Data Application Block…) or your ORM (NHibernate…). Just pick your database vendor ADO .NET provider, which usually consists of one assembly that you distribute with your application, and that’s it. That is how it works with SQL Server (of course), but also TeradataMySQL, PostgreSQL, SQLite… You name it, that is the way it works.

Oracle, you said ? Well, that must be the exception that confirms the rule (along with DB2, but I would like to focus my rant on Oracle, if I may). It works quite like this. You need an ADO .NET provider AND a native client. The problems with this are :

But thanks to the Oracle guys, there is another solution : Oracle Instant Client. It consists of just a few DLLs that you can distribute along with your application and that allow connection to Oracle databases. If you are not too picky about character sets and supported languages, you can trim it down do 19Mb.

I became quite efficient with this : I add the necessary files as Content in my Visual Studio project, and a simple Setup project makes a good enough installer for my solution.

Then this week, a client called me about how one of these applications just crashed after he installed it on a Windows 2008 Server. The fact was : it was a 64 bits Windows. As the .NET application was compiled as AnyCPU, it was automatically launched as a 64 bits application, and then you can guess by yourself what happened when the 32 bits Oracle client was loaded in memory. At this point, my options were :

  • compile the application as x86, so that it would be launched as a 32 bits application even on a x64 platform. But why tweak my application when it is really Oracle that was at fault ?
  • leave the application as AnyCPU, but distribute a different client depending on the platform it is installed on. This is the (more interesting) road I took.

I decided to give up on the standard Setup project (had I any other option ?) and use WiX 3.0 instead. My first attempt was to use a single installer to be used on both platforms (x86 and x64), and distribute the Oracle Instant Client 11 depending on the platform (I found that version 10 made my application crash on Windows Vista x64, while version 11 worked fine…). This is how I achieved this :

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLLOCATION">
<?if $(sys.BUILDARCH) = "x86" ?>
<Component Id="OracleInstantClientFiles_x86" Guid="{AA0076CE-F7B6-4cd8-9B67-199F665A8E77}" KeyPath="yes">
<Condition>
<![CDATA[Installed OR NOT VersionNT64]]>
</Condition>
<File Id="OracleInstantClientFiles_x86_msvcr71.dll" Name="msvcr71.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\msvcr71.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x86_oci.dll" Name="oci.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\oci.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x86_orannzsbb11.dll" Name="orannzsbb11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\orannzsbb11.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x86_oraociei11.dll" Name="oraociei11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\oraociei11.dll" DiskId="1" />
</Component>
<?endif ?>
<Component Id="OracleInstantClientFiles_x64" Guid="{3CCDBDB6-D45A-4523-8CC7-730D3A8851D3}" KeyPath="yes">
<Condition>
<![CDATA[Installed OR VersionNT64]]>
</Condition>
<File Id="OracleInstantClientFiles_x64_oci.dll" Name="oci.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x64\oci.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x64_orannzsbb11.dll" Name="orannzsbb11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x64\orannzsbb11.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x64_oraociei11.dll" Name="oraociei11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x64\oraociei11.dll" DiskId="1" />
</Component>
</DirectoryRef>
</Fragment>

<Fragment>
<ComponentGroup Id="OracleInstantClientFiles">
<?if $(sys.BUILDARCH) = "x86" ?>
<ComponentRef Id="OracleInstantClientFiles_x86" />
<?endif ?>
<ComponentRef Id="OracleInstantClientFiles_x64" />
</ComponentGroup>
</Fragment>

</Wix>

This works alright. Just reference the OracleInstantClientFiles component and there you have it. But I was annoyed by the fact that my application would install in the Program Files (x86) folder by default. So I went with the dual installer solution. And the source code became :

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLLOCATION">
<Component Id="OracleInstantClientFiles" Guid="{AA0076CE-F7B6-4cd8-9B67-199F665A8E77}" KeyPath="yes">
<?if $(sys.BUILDARCH) = "x86" ?>
<File Id="OracleInstantClientFiles_x86_msvcr71.dll" Name="msvcr71.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\msvcr71.dll" DiskId="1" />
<?endif ?>
<File Id="OracleInstantClientFiles_oci.dll" Name="oci.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\$(sys.BUILDARCH)\oci.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_orannzsbb11.dll" Name="orannzsbb11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\$(sys.BUILDARCH)\orannzsbb11.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_oraociei11.dll" Name="oraociei11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\$(sys.BUILDARCH)\oraociei11.dll" DiskId="1" />
</Component>
</DirectoryRef>
</Fragment>

</Wix>

So now I can distribute the correct Oracle files (worth 130Mb per platform) along with my less than 2Mb application !