Hosting a .NET DLL as an Out-Of-Process COM Server (EXE)

January 7, 2014

tags: , ,
3 comments

I recently came across the need of hosting a .NET assembly (DLL) as an out-of-process COM server. While .NET ships with excellent support for COM interoperability, this particular scenario isn’t directly addressed by the interop layer. While there are some fairly complex solutions such as COM+ hosting or custom class factory registration, I would like to show you how to accomplish this task using dllhost.exe, the default COM surrogate process, with just a handful of registry modifications.

Why?

The reason I was looking into doing this is quite simple. Suppose you have a 32-bit only unmanaged DLL that you can’t recompile or otherwise port to 64-bit. You can’t use it directly from a 64-bit application — WoW64 does not support it. Therefore, you must host your 32-bit DLL in a separate 32-bit process and communicate with it from your 64-bit application. COM has all the facilities for inter-process communication and making your 32-bit component then accessible to a variety of languages. But writing COM servers in C++ (e.g. using ATL) is something many people would shy away from. That’s where .NET COM interop and the COM surrogate process join hands.

Step 1: Build your .NET assembly with COM-visible types and interfaces

In this step, you develop a standard .NET class library that wraps the 32-bit component you would like to use (you can do the wrapping using P/Invoke or even C++/CLI if you prefer). The facade that will be exported through COM should consist of a set of interfaces and classes that implement them, all decorated with the [ComVisible(true)] attribute. At this step, you should also sign your assembly (giving it a strong name), because we’ll want to put it in the GAC later.

Step 2: Register your .NET assembly for COM interop

Next, you register the assembly for COM interop using the Regasm.exe tool. You can elect to generate a type library at this step as well, which will make it easier for unmanaged clients to consume your component. Just run regasm /tlb:component.tlb component.dll from a Visual Studio Developer Command Prompt.

Step 3: Install your .NET assembly in the GAC

You should now install the .NET assembly in the GAC so that COM clients can locate it later. For development purposes, you can also use Regasm’s /codebase switch, but in the production environment you’re very encouraged to put the assembly in the GAC. You can do this by simply dropping it into C:\Windows\Assembly, or by using the Gacutil.exe tool: gacutil.exe /i component.dll

Step 4: Configure the default COM surrogate process for your assembly

At this step, we’re getting to the crux of the issue. You’ll configure COM so that it hosts your managed assembly in a separate dllhost.exe process, instead of loading it into the client’s process. The surrogate process will be a 32-bit process, and you can talk to it from a 64-bit or a 32-bit process through COM.

To perform this configuration, run Regedit.exe and navigate to the HKLM\SOFTWARE\Wow6432Node\Classes\CLSID sub-key for your COM object. If you want control over the CLSID, use the [Guid(...)] attribute on your .NET classes that are exposed through COM. Under that sub-key, add a REG_SZ value with the name “AppID” and pick some value as the GUID. You can reuse the CLSID for this.

Next, navigate to the Classes\AppID key and create a sub-key named after the GUID you chose in the previous step for the AppID value. Under that sub-key, add a “(Default)” REG_SZ value with the name of your COM component (e.g. “MyAssembly.MyComponent”), and add another REG_SZ value with the name “DllSurrogate” and an empty value. You’re all set now for hosting your .NET assembly in a system-provided dllhost.exe process.

Step 5: Activate your COM object using CLSCTX_LOCAL_SERVER

If you try using your COM object now, you might discover that it’s still loaded into the client’s process. The reason is that you have to tell COM you’re interested in out-of-process activation. If you’re using C/C++, pass CLSCTX_LOCAL_SERVER as the dwClsContext argument of the CoCreateInstance function. If you’re using C#/VB.NET, you’ll have to P/Invoke into CoCreateInstance for this.

Step 6: For .NET clients…

If your client is a .NET application, you might find it difficult to generate an interop assembly for your COM object. If you try using the Visual Studio “Add References” dialog, you’ll see an error that says you can’t add a COM reference to a .NET assembly. If you try using the command-line Tlbimp.exe tool, you’ll again see that the TLB generated in step 2 can’t be used with Tlbimp.exe. The easiest way around this is to simply embed the interfaces you’re going to use into your .NET client assembly, as follows:

[ComImport]
[Guid("<YOUR INTERFACE GUID HERE>")]
interface IMyInterface {
  // methods here
}

Once you’ve done that, you can call CoCreateInstance and simply cast the resulting object to IMyInterface. The cast will involve the COM interop magic machinery that will call IUnknown::QueryInterface on your behalf and give you back a proxy to the COM object.

That’s it! You now have a 32-bit dllhost.exe process loading and hosting your .NET assembly and exposing it to callers through COM.


I am posting short links and updates on Twitter as well as on this blog. You can follow me: @goldshtn

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

3 comments

  1. IgorJune 13, 2014 ב 8:24 AM

    Hi Sasha,

    Thanks for your sharing.
    Is it works for primitive types only?
    I have COM object with very complex objects. Can I expose native COM interfaces or I need create .NET COM wrappers?

    Reply
    1. Sasha Goldshtein
      Sasha GoldshteinJuly 6, 2014 ב 9:41 AM

      You can marshal complex structures; you just need to replicate their definition on the .NET side. Quite similar to what you would have done with P/Invoke.

      Reply
  2. KapilJune 18, 2014 ב 8:03 PM

    thank you, this is exacly what I was looking for. Now I can instantiate my COM from remote machine using CreateInstance (Or CreateObject in vbs)

    Reply