Integrating mixed-mode assemblies (X86/X64) in .Net

Amyuni's Development Team would like to share with fellow developers general information about Windows and Web development. These are issues encountered by Amyuni's team and were either solved or pending solution.
Post Reply
Devteam
Posts: 119
Joined: Fri Oct 14 2005
Location: Montreal
Contact:

Integrating mixed-mode assemblies (X86/X64) in .Net

Post by Devteam »

Using mixed-mode assemblies (X86/X64 dlls) in a .Net project configured as AnyCPU.

Visual Studio 2003 introduced the concept of creating mixed-mode .Net assemblies, which are assemblies that contain pieces of native code for one specific CPU architecture. Mixed-mode assemblies enhanced support for interoperability of .Net applications with C and C++ based libraries.

There are several reasons for building mixed-mode assemblies, for example it can be costly to rewrite certain library, achieving a better performance can be a concern in certain scenarios, and obfuscation of certain pieces of software can also be reasonable argument. However it might not be obvious for a .Net developer how to combine mixed-mode assemblies in a project not configured for a particular CPU architecture (namely, a project configured as AnyCPU, according to the default configuration in Visual Studio).

A project configured as AnyCPU will generate code in MSIL (Microsoft intermediate language) when it is compiled. During execution, the JIT (Just in time Compiler) from .Net framework will translate this MSIL instructions into native code, according to the CPU architecture of the operating system where the application is being executed. However, native code compiled as X64 cannot load and execute any native code compiled as X86 and vice-versa. So if an AnyCPU-based project that is using an X86 mixed-mode assembly is compiled by the JIT as X64, it will generate a runtime error when it tries to load the assembly.

Let us assume now that we have a mixed-mode assembly MyLibrary.dll with two versions, one for the X86 CPU architecture and one for X64, and we want to use these libraries in a C# project configured as AnyCPU. There are several approaches that we can take:

1- Creating two versions of the target application
The most obvious solution would be to have several versions of the host application (X86 vs X64) instead of one configured as AnyCPU, and decide which version to use during the installation on a specific operating system.

We can create new specific CPU configurations in any .Net application using Visual Studio:

Step 1: Displaying the configuration manager
Image

Step 2: Creating a new CPU configuration for the whole solution
Image

Step 3: Copying the project settings from a previous CPU configuration (AnyCPU for example)
Image

2- Using the Global Assembly Cache
If the mixed-mode libraries are properly built and they are Strong-Named, then it should be possible to place them in the GAC (Global Assembly Cache) during installation (and during development) of the application, and keep using the AnyCPU configuration as we would do with any "pure managed” assembly.

In order to add a library to the GAC we can use the following command line from Visual Studio Command Prompt (also labeled Developer Command Prompt in Visual Studio 2012).

gacutil -i MyLibrary.dll

During development, after adding the library to the GAC we will add a reference to the target project that matches the CPU architecture of the Operating System being used for development. For example if Windows 8.1 X64 is being used for development, then we will add a reference to the X64 bit version of MyLibrary.dll.

Once the GAC is properly configured, the JIT will take care of loading the right assembly from it at runtime.

3- Choosing the right version of the mixed-mode assembly at runtime
A third (and probably the best) choice is to put the target dlls in different folders, for example \X86\MyLibrary.dll and \X64\MyLibrary.dll, then keep the AnyCPU configuration, and use the event AppDomain.AssemblyResolve to choose the right assembly according to the CPU configuration at runtime. The system property IntPtr.Size can be used to determine if the application is running as X86

Code: Select all

(IntPtr.Size = 4)
or X64

Code: Select all

(IntPtr.Size = 8)
Sample code:

Code: Select all

class MixedModeAssemblyLoader
{

static MixedModeAssemblyLoader()
{
        AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ResolverHanlder);
}

private static System.Reflection.Assembly ResolverHanlder(
	object sender, ResolveEventArgs args)
{
	if (args.Name.StartsWith("MyLibrary"))
	{
		String appDir = Path.GetDirectoryName(
			Assembly.GetExecutingAssembly().Location);

		// Here we detect the type of CPU architecture 
		// at runtime and select the mixed-mode library 
		// from the corresponding directory.
		// This approach assumes that we only have two 
		// versions of the mixed mode assembly, 
		// X86 and X64, it will not work however on 
		// ARM-based applications or any other non X86/X64 
		// platforms
		String relativeDir = 
			String.Format("{0}\\MyLibrary.dll", 
					(IntPtr.Size == 8) ? "X64" : "X86");
		String libraryPath = Path.Combine(appDir, relativeDir);

		// we do not need this resolver anymore
		AppDomain.CurrentDomain.AssemblyResolve -= ResolverHanlder;

		return Assembly.LoadFile(libraryPath);
	}
	return null;
}

}
Important remark: Setting the AssemblyResolve event has to be done very early in the process live-cycle, the best place would be at the very beginning of the main method of the main application that uses the library.

Written by Yoisel Melis Santana, Software Development Manager at Amyuni Technologies.
Amyuni Development Team

Efficient and accurate conversion to PDF, XPS, PDF/A and more. Free trials at - https://www.amyuni.com
Post Reply