Search code examples
c#com

Cross process .net COM IPC throws E_NOINTERFACE


I am trying to use IRunningObjectTable to allow a client caller to access an object in another process for purposes of UI automation.

+----------------+                 +-----------+
| Client Process |---------------->| UI server |
| .Net WPF       |                 | .Net WPF  |
+----------------+                 +-----------+

I have implemented an example interface with implementation as follows, and am seeing the registered object in the running object table, but get E_NOINTERFACE when casting to the typed interface.

Server

Interface.cs:

[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IChatTarget))]
[ComVisible(true), Guid("FEF67638-6654-47DB-A40D-F11FE72795A9")]
public class ChatInterface : IChatTarget
{
    public void PrintString(string text)
    {
        Console.WriteLine(text);
    }
}

[ComVisible(true)]
[Guid("9A3164C9-F800-4B7D-9AB0-29E9F5D325B2")]
public interface IChatTarget
{
    void PrintString(string text);
}

Program.cs:

class Program
{
    static void Main(string[] args)
    {
        var ct = new ChatInterface();

        var rot = NativeMethods.GetRunningObjectTable(0);
        IMoniker moniker = NativeMethods.CreateItemMoniker("!", "TestMoniker");

        var hRotEntry = rot.Register(NativeMethods.ROTFLAGS_REGISTRATIONKEEPSALIVE, (IChatTarget)ct, moniker);

        MSG msg;
        while (NativeMethod.GetMessage(out msg, IntPtr.Zero, 0, 0))
        {
            NativeMethod.TranslateMessage(ref msg);
            NativeMethod.DispatchMessage(ref msg);
        }

        rot.Revoke(hRotEntry);
    }
}

Client

Proxy.cs:

public class ChatProxy : IChatTarget
{
    IChatTarget target;

    public ChatProxy()
    {
        var rot = NativeMethods.GetRunningObjectTable(0);
        var moniker = NativeMethods.CreateItemMoniker("!", "TestMoniker");

        object utobj;
        if (rot.GetObject(moniker, out utobj) != 0 /* S_OK */)
        {
            throw new InvalidOperationException("Moniker not in table");
        }
        // This throws ComException(E_NOINTERFACE)
        target = (IChatTarget)utobj;
    }

    public void PrintString(string text)
    {
        target.PrintString(text);
    }
}

Program.cs:

class Program
{
    static void Main(string[] args)
    {
        var cp = new ChatProxy();
        while (true)
            cp.PrintString(Console.ReadLine());
    }
}

I am registering the server assembly using regasm.


Solution

  • In order to get the typed proxy, the assembly containing the interface must be strongly named.

    Sign the assembly with a strong name key, and all should be well.

    Other pitfalls:

    • regasm must be called with the /tlb flag to register for OLE marshalling, otherwise you can get E_NOINTERFACE. You can check by looking for the ProxyStubClsid32 key. Complete command should be regasm assembly.dll /tlb and regasm assembly.dll /tlb /u to unregister.
    • For an in-process server, the assembly must be in the GAC, or the /codebase flag must be passed to regasm
    • Registration should be using the same bitness as the client (Wow6432Node)
    • For an assembly marked with Any CPU, you must register with both the 32-bit and 64-bit versions of regasm