Managed DCOM server

October 24th, 2009 by Andy Leave a reply »

For my current project I have some old unmanaged C++ code that needs to talk to a new .NET server, remotely.   The channel needs to be fast and secure.   There are a lot of options, but I’ve narrowed it down to two:

  1. WWSAPI for the C++ client, WCF for the .NET server, using NetTcpBinding.  (See here for details.)
  2. DCOM

Option 1 was the strong favourite until Microsoft decided to play silly buggers with the redistribution rights for WWSAPI, so I’ve been forced to investigate option 2.

COM interop through loading .NET components into an unmanaged process is well documented and generally works fine, but accessing a .NET server remotely via (D)COM is not so well documented, and indeed it’s not even clear if it’s supported by Microsoft.   But it does seem to work, and it’s surprisingly simple once you discover the RegisterTypeForComClients method on the RegistrationServices class.   This is basically a wrapper for COM’s CoRegisterClassObject.

Here’s the C# server code:

[ComVisible(true)]
public interface ICalculator
{
    int Add(int x, int y);
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Calculator : ICalculator
{
    public int Add(int x, int y) { return x + y; }
}

class Program
{
    [MTAThread]
    static void Main(string[] args)
    {
        var regServices = new RegistrationServices();

        int cookie = regServices.RegisterTypeForComClients(
            typeof(Calculator),
            RegistrationClassContext.LocalServer | RegistrationClassContext.RemoteServer,
            RegistrationConnectionType.MultipleUse);

        Console.WriteLine("Ready"); Console.ReadKey();

        regServices.UnregisterTypeForComClients(cookie);
    }
}

and the C++ client code:

#import "dcomserver.tlb" no_namespace raw_interfaces_only

int _tmain(int argc, _TCHAR* argv[])
{
    CoInitializeEx(0, COINIT_MULTITHREADED);

    {
        CComPtr<ICalculator> spCalc;
        spCalc.CoCreateInstance(__uuidof(Calculator), 0, CLSCTX_LOCAL_SERVER);

        long result = 0;
        spCalc->Add(10, 20, &result);

        cout << result << endl;
    }

    CoUninitialize();
    return 0;
}

(The simplest way to generate the tlb file to #import is to run regasm /tlb DcomServer.exe)

There may well be some gotchas with this approach – I haven’t tested it thoroughly yet – but it seems a promising option if the WWSAPI licensing issues can’t be sorted out.

Advertisement

6 comments

  1. Alessandro says:

    I did a trial with your proposed solution, and it works, but not as expected.
    The .exe external process, is not called over dcom, but the .exe is loaded INTERNALLY at the client.
    I have read other similar threads and it's the expected behavior.
    Mayb it's needed to change reg keys to point to the external com server.

  2. Andy McMullan says:

    Hi Alessando – you need to specify CLSCTX_LOCAL_SERVER AND/OR CLSCTX_REMOTE_SERVER on the client. If you specify CLSCTX_INPROC_SERVER you'll hit the problem you describe.

  3. 衣田 says:

    How to access this server by VB6?
    Thanks

  4. Andy McMullan says:

    I believe VB6 CreateObject allows you to specify a server name as the second parameter, so you'd need to do that:

    Dim myComp as Object
    Set myComp = CreateObject("MyProgID", "MyServerName")

    In the case where everything is on one machine, using "localhost" as the server name should work.

  5. Hi,
    I have tried this on Windows 7 and can’t get it to work. In the C++ client, I can obtain a pointer to the IClassFactory interface with CoGetClassObject but when I try to create an instance of ICalculator from the class factory I get E_NOINTERFACE. Any ideas would be highly appreciated.. Thanks

  6. Andy says:

    Andrei – it sounds like the ICalculator interface is not registered. Did you do the regasm step?