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:
[assembly: ComVisible(true)]
[assembly: Guid("f2149837-10a0-44cf-a0d9-987aa62eb0b2")]
Now go on and define a sample class which should be accessible from outside through COM:
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace BitComTest
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[GuidAttribute("D698BA94-AEFF-3D4F-9D11-BC6DE81D330B")]
public class ComServer
{
public ComServer() { }
public void TestMe()
{
MessageBox.Show("Hello from the 64-bit world!");
}
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:
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace _32BitClient
{
class Program
{
static void Main(string[] args)
{
Type ComType = Type.GetTypeFromProgID("BitComTest.ComServer");
object ComObject = Activator.CreateInstance(ComType);
ComType.InvokeMember("TestMe", BindingFlags.InvokeMethod, null, ComObject, null);
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());
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:
This indicates a running 64-bit process (dllhost.exe) as an out-of-process COM Surrogate!
In addition, the expected output looks like this:
History
- 2017-08-02: Initial post
- 2017-08-05: Updated somehow misleading article title
- 2019-07-24: Editorial refresh