Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Using 64-bit DLLs in 32-bit Processes with Out-of-process COM

4.82/5 (31 votes)
1 Jul 2020CPOL3 min read 76K  
Brief description of how to access 64-bit DLLs from 32-bit processes using a COM bridge
This article shows how to build a 64-bit COM server which is accessible from a 32-bit process to circumvent the process architecture restrictions. Then we make a (simple) 32-bit client to consume our 64-bit COM server component.

Introduction

During application migration tasks, usually a common problem is to use 32-bit DLLs within 64-bit processes as not all used (or even 3rd party) components might be available in a 64-bit version. There are several articles available around in the web which describe how to achieve this.

This article describes the opposite way - using a 64-bit DLL within a 32-bit process using a COM bridge.

Background

In general, the .NET Framework does not support accessing DLLs of different process architectures vice versa by referencing those DLLs within the project directly. Trying to do so will likely end up in a System.BadImageFormatException! Following this Microsoft article about Interprocess Communication between 32-bit and 64-bit applications, there is a little hint on achieving this using a COM bridge approach - but typically, how to do it in detail is not described.

The following example shows how to built a 64-bit COM server which is accessible from a 32-bit process to circumvent the process architecture restrictions.

64-bit COM Server

First of all, every .NET assembly DLL is a COM DLL by default if ensured both 2 lines:

C#
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(true)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f2149837-10a0-44cf-a0d9-987aa62eb0b2")]

Now go on and define a sample class which should be accessible from outside through COM:

C#
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace BitComTest
{
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [GuidAttribute("D698BA94-AEFF-3D4F-9D11-BC6DE81D330B")]
    public class ComServer
    {
        /// <summary>
        /// Default constructor - necessary for using with COM
        /// </summary>
        public ComServer() { }

        /// <summary>
        /// Test method to be called by COM sonsumer
        /// </summary>
        public void TestMe()
        {
            MessageBox.Show("Hello from the 64-bit world!");
        }

        /// <summary>
        /// Test function to be called by COM consumer
        /// </summary>
        /// <param name="text">Any text message</param>
        /// <returns>4711 fixed returncode</returns>
        public int TestMeWithResult(string text)
        {
            MessageBox.Show("Hello from the 64-bit world, you provided the text:\n" + text);
            return 4711;
        }
    }
}

Now you're almost done with the COM server component - just don't forget to set the target processor architecture to x64 in order to build a 64-bit forced DLL. In order to register the DLL to be accessible through COM, you have to use the framework tool reagasm.exe - remember to use the 64-bit version to register the DLL in the correct registry hive, i.e.:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\regasm.exe BitComTest.dll /codebase

Don't forget the /codebase switch - you even get a warning on using this without having a strong name for your assembly, it will enter the codebase (DLL target filepath) into the corresponding registry key.

Now comes the magic part. After registering the COM server like shown above, you even would get a ClassNotRegisteredException while trying to use it within a 32-bit client. This is because actually the 32-bit world does not know about the 64-bit COM server by default. To get the ring closure, you have to modify the registry - what else :-). This is slightly easy as the key part is around the CLSID which is set by our COM server using the GuidAttribute attribute.

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\AppID\{D698BA94-AEFF-3D4F-9D11-BC6DE81D330B}]
"DllSurrogate"=""

[HKEY_CLASSES_ROOT\CLSID\{D698BA94-AEFF-3D4F-9D11-BC6DE81D330B}]
"AppID"="{D698BA94-AEFF-3D4F-9D11-BC6DE81D330B}"

Those 2 additional entries define the CLSID as an AppID - so the component will be visible within the 32-bit world. Using the entry DllSurrogate will instantiate the COM server as an out-of-process server using the corresponding processor architecture of the COM server DLL (in this case x64).

32-bit COM Client

Now that all things are prepared in the 64-bit world, we need a (simple) 32-bit client to consume our 64-bit COM server component. An easy example could look like this:

C#
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace _32BitClient
{
    class Program
    {
        static void Main(string[] args)
        {
            // Access COM Object through registered Class Id
            Type ComType = Type.GetTypeFromProgID("BitComTest.ComServer");

            // Create an instance of the COM object
            // This will invoke the default constructor of class ComServer
            object ComObject = Activator.CreateInstance(ComType);

            // Calling the Method "TestMe" from 64-Bit COM server
            ComType.InvokeMember("TestMe", BindingFlags.InvokeMethod, null, ComObject, null);

            // Calling the Method "TestMeWithResult" 
            // from 64-Bit COM server passing through a text
            // an retrieve the resulting int return value
            object[] methodArgs = new object[1];
            methodArgs[0] = "Greetings from the 32-bit world!";
            int result = (int)ComType.InvokeMember("TestMeWithResult", 
                                                   BindingFlags.InvokeMethod, null, 
                                                   ComObject, methodArgs);
            MessageBox.Show("Result of \"TestMeWithResult\" is: " + result.ToString());
            
            // Don't forget to release the late bound COM object,
            // otherwise the surrogate process (dllhost.exe) would live further ...
           if (Marshal.IsComObject(ComObject))
                Marshal.ReleaseComObject(ComObject);
        }
    }
}

Don't forget to set the target processor architecture to x86 in order to build a 32-bit forced assembly!

Once you have started the client and instantiated the COM server, you'll notice an additional dllhost.exe process within the Taskmanager. It should look like this:

Image 1

This indicates a running 64-bit process (dllhost.exe) as an out-of-process COM Surrogate!

In addition, the expected output looks like this:

Image 2Image 3 Image 4

History

  • 2017-08-02: Initial post
  • 2017-08-05: Updated somehow misleading article title
  • 2019-07-24: Editorial refresh

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)