Using a 32bit Native DLL in Windows Azure

With the Mix2009 release of Windows Azure tools, we now support native code execution. This also includes the ability to debug native code. As you would imagine, you can do PInvoke’s to system DLL’s or to your packaged native DLL with your role.

The image in Windows Azure runs on a 64bit OS. Role hosts (web and worker hosts) are also 64 bit executables which result in your web and worker roles runing as 64bit binaries. That means if you want to PInvoke directly into your native DLL, your native DLL must be compiled for 64bit platform, otherwise you would get the error:

An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)

The simplest solution is to rebuild the assembly we are trying to load to 64bit, but there are some cases that we cannot do that like if we don’t own the source code. One way is to host the 32bit dll in a 32bit program that runs on WoW. Then we can use WCF to marshal all calls between the role process (which is 64bit) and our out-of-proc dll (which is 32bit).

I will show you an example on how to do that with a WebRole. My demo involves a 32bit NativeLibrary.dll that contains an Add function; Also, a managed DllHost86.exe that acts as a WCF server and as a host for the Native Library. The managed DllHost86 is configured to be built for x86 specific:

imageBoth the NativeLibrary and the Dll-Host are configured to output their binaries to the webrole Redist folder. Also, we added these files as contents to the web role project to make sure they are packaged when launching under the devfabric or deployed to the Cloud:

imageThe interactions between the Dll-Host and the web role goes like the following:

  1. On any request to the web role (using Application_BeginRequest event), we check if the DllHost86 process is running.
    1. If not running, launch the process.
  2. Also, on any request to the web role, try to connect the WCF client to the WCF server hosted inside the DllHost86 process. Once connected, cache the client object in the server current session (Application session). Next time we actually get the client from the session and just check if the connection is valid.
  3. The binding used on WCF is NetNamedPipeBinding for optimal performance on the same machine communication.
  4. The time out on the WCF link is set to infinity.

The code (from Global.asax) is shown next:

 private const int ClientInitTimeOut = 20; // in seconds
  
 protected void Application_BeginRequest(object sender, EventArgs e)
 {
     // Make sure that our dll host is running
     EnsureDllHostRunning();
  
     // Make sure the client is connected
     EnsureCalcServiceClientConnected();
 }
  
 private void EnsureDllHostRunning()
 {
     Process[] p = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(dllHostPath));
     if (p.Length == 0)
     {
         Application["CalcServiceClient"] = null;
         ProcessStartInfo psi = new ProcessStartInfo(dllHostPath);
         Process dllHost = Process.Start(psi);
     }
 }
  
 private void EnsureCalcServiceClientConnected()
 {
     CalcServiceClient client;
     client = (CalcServiceClient)Application["CalcServiceClient"];
     if (client == null || client.State != System.ServiceModel.CommunicationState.Opened)
     {
         client = GetCalcServiceClient();
         Application["CalcServiceClient"] = client;
     }
 }
  
 private CalcServiceClient GetCalcServiceClient()
 {
     CalcServiceClient serv = null;
  
     int retryCount = 0;
     bool connected = false;
     while (retryCount < ClientInitTimeOut * 10)
     {
         try
         {
             
             EndpointAddress address = new EndpointAddress("net.pipe://localhost/CalculatorService");
             NetNamedPipeBinding binding = new NetNamedPipeBinding();
             binding.ReceiveTimeout = TimeSpan.MaxValue;
  
             serv = new CalcServiceClient(binding, address);
             serv.Open();
             Debug.WriteLine("state = " + serv.State);
             if (serv.State == System.ServiceModel.CommunicationState.Opened)
             {
                 connected = true;
                 break;
             }
         }
         catch (Exception e)
         {
             Debug.WriteLine(e.Message);
         }
  
         retryCount++;
         System.Threading.Thread.Sleep(100);
     }
  
     if (!connected)
     {
         throw new TimeoutException("Couldn't connect to the calculator service.");
     }
  
     return serv;
 }

So far, we setup the web role to startup the dll host and setup the client connection to the WCF server inside the DllHostx86. The code there is just a standard code that uses ServiceHost and just waits forever. Here is the code from the Program class:

         static void Main(string[] args)
         {
             Uri address = new Uri("net.pipe://localhost/CalculatorService");
  
             NetNamedPipeBinding binding = new NetNamedPipeBinding();
             binding.ReceiveTimeout = TimeSpan.MaxValue;
             
             using (ServiceHost host = new ServiceHost(typeof(Calculator)))
             {
                 host.AddServiceEndpoint(typeof(ICalcService), binding, address);
  
                 ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
                 host.Description.Behaviors.Add(metadata);
                 Binding mexBinding = MetadataExchangeBindings.CreateMexNamedPipeBinding();
                 Uri mexAddress = new Uri("net.pipe://localhost/CalculatorService/Mex");
                 host.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, mexAddress);
                 
                 host.Open();
  
                 Console.WriteLine("The receiver is ready");
                 Console.ReadLine();
             }
         }

 

So, the service we expose contains an Add methods that is implemented internally to call the native method using PInvoke. Here is a sample of the implementation:

     class Calculator : ICalcService
     {
         [DllImport("NativeLibrary.dll", EntryPoint="Add")]
         static extern UInt32 NativeAdd(UInt32 a, UInt32 b);
  
         #region ICalcService Members
  
         public int Add(int num1, int num2)
         {
             Console.WriteLine("Received numbers: {0}, {1}", num1, num2);
  
             return (int)NativeAdd((uint)num1, (uint)num2);
         }
  
         #endregion
     }

 

I tested the sample above in the cloud and it works fine. I also attached it for your reference. Here is how it looks like:

image

So, in this blog, I demonstrated how to use a 32bit dll in the Cloud by means of hosting it under a 32bit process and then use WCF communication to do the marshaling.

Hope it helps.

Hostx86Demo.zip

Comments

  • Anonymous
    March 19, 2009
    PingBack from http://blog.a-foton.ru/index.php/2009/03/20/using-a-32bit-native-dll-in-the-windows-azure/

  • Anonymous
    March 19, 2009
    The comment has been removed

  • Anonymous
    March 22, 2009
    Good idea.. You should be able to run anything on your image. There are some few restrictions. Even that you run under full-trust. The user account is not administrator. So, you are some how restricted to xcopy deployment and you can only write files to the role's local store. Let me know.

  • Anonymous
    August 09, 2009
    getting an error "An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)"