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.