Quantcast
Channel: Visual Studio – Marcel Meijer Blog
Viewing all articles
Browse latest Browse all 27

Using 32 bit (Legacy) DLL on Windows Azure

$
0
0

On Windows Azure (almost) everything is possible. You are not limited to just .NET applications. But alos Java, PHP, NodeJS and many more languages are very usable on the Windows Azure platform, Windows Azure Websites and Windows Azure Virtual Machine.

The base of Windows Azure is a 64 bits Windows 2008 server park. But then you are also not limited to just 64 bits applications or dll’s. This is very handy, because now you don’t have to rewrite your ‘legacy’ software. Whether the legacy was written in a Microsoft language or something else, does not really matter.

For a customer we did a Windows Azure Proof of Concept, where we had to deal with a 32 bits C++ dll with some sort of memory leak. The result of the POC should run in the Cloud and scaling should be possible.

legacydll

How did we handle it?

First lets have a look at the C++ dll. As said this component had a kind of memory leak. Well to be precise the dll was never meant to be used in a distributed environment and only within a stand alone desktop application. The problem was if the component was accessed by 2 websites simultaneous the result of the calculation was mixed. The dll used a small shared memory, which was nog unique for a specific proces en resulting in a mixure. In .NET is this can be solve relatively easy by using a Mutex.

 1: public int Calculate32Mutex()

 2: {

 3:     int result = 0;

 4: 

 5:     mutex.WaitOne();

 6:     try

 7:     {

 8:         result = Calculate32(); // calls the actual 32bit dll function

 9:     }

 10:     finally

 11:     {

 12:         mutex.ReleaseMutex();

 13:     }

 14: 

 15:     return result;

 16: }

Bij using the Mutex every call has to wait until the previous call has ended. Not good if you expect a lot of load. But we are going to host it in Windows Azure, so with the infinitive amount of Cloud Power we can scale to the load.

Now the 32 bits problem stays. There are two solutions for it.

  1. We could host the DLL in a 32 bits Console application.
  2. We could change the app pool of IIS to 32 bits mode.

Lets have a look at both.

Solution 1: Host the DLL in a 32 bits Console application.

We make a Console application and set the Build option Platform target to x86. In the main of the application we put this code.

 1: static void Main(string[] args)

 2: {

 3:     Uri address = new Uri("net.pipe://localhost/CalculatorService");

 4: 

 5:     NetNamedPipeBinding binding = new NetNamedPipeBinding();

 6:     binding.ReceiveTimeout = TimeSpan.MaxValue;

 7: 

 8:     using (ServiceHost host = new ServiceHost(typeof(CalculatorDll)))

 9:     {

 10:         var ff = host.AddServiceEndpoint(typeof(ICalcService), binding, address);

 11:         ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();

 12: 

 13:         host.Description.Behaviors.Add(metadata);

 14:         host.Description.Behaviors.OfType<ServiceDebugBehavior>()

 15:             .First().IncludeExceptionDetailInFaults = true;

 16: 

 17:         Binding mexBinding = MetadataExchangeBindings.CreateMexNamedPipeBinding();

 18:         Uri mexAddress = new Uri("net.pipe://localhost/CalculatorService/Mex");

 19:         host.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, mexAddress);

 20:

 21:         host.Open();

 22: 

 23:         Console.WriteLine("The receiver is ready");

 24:         Console.ReadLine();

 25:     }

 26: }

By using a named net pipe the service becomes available. The method of this service will do the actual call to the function in de 32 bits dll.

 1: [DllImport("Win32Project1.dll", SetLastError = true)]

 2: public static extern Int32 Calculate(Int32 delay);

 3: 

 4: public int RekenDllExample()

 5: {

 6:     return Calculate(2000);

 7: }

To be able to scale this a little I chose to wrap it with a ‘normal’ WCF service. Of course you can use it directly from the website too. But that makes scaling a little bit limited.

On the WCF service there has to be a connection to this Named Net pipe thing. This is done by adding code to the Global.asax in the Application_BeginRequest method. There are two methods: one to start the DllHost and one to make the connection.

 1: string dllHostPath = @"Redist\DllHostx86.exe";

 2: private const int ClientInitTimeOut = 20; // in seconds

 3: 

 4: protected void Application_BeginRequest(object sender, EventArgs e)

 5: {

 6:     // Make sure that our dll host is running

 7:     EnsureDllHostRunning();

 8: 

 9:     // Make sure the client is connected

 10:     EnsureCalcServiceClientConnected();

 11: }

 12: 

 13: private void EnsureDllHostRunning()

 14: {

 15:     Process[] p = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(dllHostPath));

 16:     if (p.Length == 0)

 17:     {

 18:         Application["CalcServiceClient"] = null;

 19:         ProcessStartInfo psi = new ProcessStartInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dllHostPath).ToString());

 20:         Process dllHost = Process.Start(psi);

 21:     }

 22: }

 1: private void EnsureCalcServiceClientConnected()

 2: {

 3:     CalcServiceClient client;

 4:     client = (CalcServiceClient)Application["CalcServiceClient"];

 5:     if (client == null || client.State != System.ServiceModel.CommunicationState.Opened)

 6:     {

 7:         client = GetCalcServiceClient();

 8:         Application["CalcServiceClient"] = client;

 9:     }

 10: }

 11: 

 12: private CalcServiceClient GetCalcServiceClient()

 13: {

 14:     CalcServiceClient serv = null;

 15: 

 16:     int retryCount = 0;

 17:     bool connected = false;

 18:     while (retryCount < ClientInitTimeOut * 10)

 19:     {

 20:         try

 21:         {

 22:             EndpointAddress address =

 23:                 new EndpointAddress("net.pipe://localhost/CalculatorService");

 24:             NetNamedPipeBinding binding = new NetNamedPipeBinding();

 25:             binding.ReceiveTimeout = TimeSpan.MaxValue;

 26: 

 27:             serv = new CalcServiceClient(binding, address);

 28:             serv.Open();

 29:             if (serv.State == System.ServiceModel.CommunicationState.Opened)

 30:             {

 31:                 connected = true;

 32:                 break;

 33:             }

 34:         }

 35:         catch (Exception e)

 36:         {

 37:         }

 38: 

 39:         retryCount++;

 40:         System.Threading.Thread.Sleep(100);

 41:     }

 42: 

 43:     if (!connected)

 44:     {

 45:         throw new TimeoutException(

 46:             "Couldn't connect to the calculator service.");

 47:     }

 48: 

 49:     return serv;

 50: }

In your project you need a Service reference to the DllHost Console application. You start the Console application and choses Add Service Reference. After this in the Web.config endpoint information is added. This endpoint is net.pipe://localhost/CalculatorService. No worries about the Localhost part, because the DllHost application and the WCF service run on the same instance, it will always be Localhost. also in the Cloud.

Don’t forget to add a Folder with the DllHost console application and the 32 bits dll. Set the property ‘Copy to Output Directory’ to ‘Copy Always’ or ‘Copy if newer’ for both files.

Solution 2: We could set the app pool of IIS in 32 bits mode

This solution is the most easy one.. The trick part was (I am not IT pro) how to do this from script. Remember on a Windows Azure instance you can do anything as long as you automate or script it. Then you can make a Startup task to execute the script.

The command to get it done:

 1: REM make apppools 32bit

 2: %windir%\system32\inetsrv\appcmd set config

 3:     -section:applicationPools

 4:     -applicationPoolDefaults.enable32BitAppOnWin64:true

Put this command in a startup.cmd file and add it to the project. Define in the ServiceDefinition.csdef a startup task and we are good to go.

 1: <Startup>

 2:   <Task commandLine="startup.cmd" executionContext="elevated" taskType="simple" />

 3: </Startup>

Don’t forget to add a Folder with the DllHost console application and the 32 bits dll. Set the property ‘Copy to Output Directory’ to ‘Copy Always’ or ‘Copy if newer’ for both files.

Downside of the solutions:

If there is some kind of dynamic configuration needed, the Console application is less handy. A Console application uses a app.config and by Default it can not use the Service configuration settings. The second solution of course can do so.

Test

To test the two solutions I made a small frontend with 4 buttons. The first two use the WCF service using the Dll Host console application. The other two call WCF service where the AppPool is in 32 bits mode. The labels result show a 0 or 1 of higher. Zero if the Calculation module was called and nothing was mixed. Higher then one means the result of two or more computation were mixed. You can test this by opening two browsers and click on the buttons simultaneous (there is time Winking smile).

When using the Mutex buttons, both results will be zero.

http://rekenmoduletest.cloudapp.net/

clip_image002

What does this mean for the load of the services, I will answer that in a different blogpost. Of course we are using a Cloud service to test the max load.

The story above is not only for Windows Azure, but in fact for every 64 bits server environment on premise or with other hosting.

References:


Viewing all articles
Browse latest Browse all 27

Trending Articles