2

I want to invoke a method on a COM object that is defined in .NET from another .NET application using the DispID.

I have the following class and interface defined in a .NET project:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

namespace TestComObject
{
   [ComVisible(true)]
   [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
   [Guid("99476c04-574a-4271-abc8-f1d2615c3d93")]
   public interface IKnowEverything
   {
      [DispId(1030)]
      string GetTheTruth();
   }

   [ComVisible(true)] 
   [Guid("9B869BAF-622A-458B-BF80-2CD6D8A3C541")]
   [ClassInterface(ClassInterfaceType.None)]   
   public class AllKnowing : IKnowEverything
   {
      public string GetTheTruth()
      {
         return "I am King!";
      }
   }
}

I build it and it is set to register for COM interop, it is signed with a strong key.

Now I want to invoke this method using its DispID from another .NET application. I wrote the following test method:

static void Main(string[] args)
{
   Guid clsid = new Guid("9B869BAF-622A-458B-BF80-2CD6D8A3C541");
   Type type = Type.GetTypeFromCLSID(clsid, true);
   object instance = Activator.CreateInstance(type);

   string methodName = "GetTheTruth";
   string methodDispIdName = "[DispID=1030]";

   string s1 = (string)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, instance, null);
   Console.WriteLine("Via name: {0}", s1);

   string s2 = (string)type.InvokeMember(methodDispIdName, BindingFlags.InvokeMethod, null, instance, null);
   Console.WriteLine("Via DispID: {0}", s2);
}

The first invocation is successful and the application prints "Via name: I am King!".

But on the second invocation (using the DispID) I get an exception:

Unhandled Exception: System.MissingMethodException: Method 'TestComObject.AllKnowing.[DispID=1030]' not found.

The type library looks okay to me when looking at it in the OLE-COM Object Viewer:

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: TestComObject.tlb

[
  uuid(139DAA99-BF76-4057-BCC8-DCB35C153920),
  version(1.0),
  custom(90883F05-3D28-11D2-8F17-00A0C9A6186D, "TestComObject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=58b6bd22fbd98427")

]
library TestComObject
{
    // TLib :     // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
    importlib("mscorlib.tlb");
    // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole2.tlb");

    // Forward declare all types defined in this typelib
    dispinterface IKnowEverything;

    [
      uuid(99476C04-574A-4271-ABC8-F1D2615C3D93),
      version(1.0),
      custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "TestComObject.IKnowEverything")    

    ]
    dispinterface IKnowEverything {
        properties:
        methods:
            [id(0x00000406)]
            BSTR GetTheTruth();
    };

    [
      uuid(9B869BAF-622A-458B-BF80-2CD6D8A3C541),
      version(1.0),
      custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "TestComObject.AllKnowing")
    ]
    coclass AllKnowing {
        interface _Object;
        [default] dispinterface IKnowEverything;
    };
};

What am I doing wrong here?

DeCaf
  • 5,731
  • 1
  • 25
  • 44
  • From Hans Passant's answer here: http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/0cc7543e-2678-4e0f-84c8-bc2ad56e09e5/, when using "[DispID=1030]" as the name of the member, you should call `InvokeMember` on the type instance retrieved from `Type.GetTypeFromProgID()`, not `Type.GetTypeFromCLSID` – phoog Feb 14 '12 at 20:47
  • The `Type` returned ought to be the same whether I get it by ProgID or CLSID, shouldn't it? – DeCaf Feb 15 '12 at 07:58
  • I would think so, but the documentation for GetTypeFromProgID, seems to say that the return value is always `System.__ComObject`, while the documentation for the other method doesn't say this. Of course, this wouldn't be the first instance of inconsistency or error in the framework documentation. Since Hans gave a different answer to your question, I guess you are right about the returned `Type`. – phoog Feb 15 '12 at 15:20
  • Interesting, I will try it and see if there is a difference, and if not file a bug report about the MSDN documentation then I guess ;) – DeCaf Feb 15 '12 at 16:27

1 Answers1

3

This doesn't work because you implemented the COM server in .NET. The instance member is an actual .NET object, not an RCW. The can tell from the debugger, it is not a System.__ComObject but an object of the actual .NET type.

So when you use InvokeMember(), you get the System.Reflection version of it, not the IDispatch version. Another way to see it is to use "getTheTruth". Note how the first attempt fails now as well. IDispatch is case-insensitive, System.Reflection is not. The "[DispID=1030]" trick doesn't work because System.Reflection doesn't know anything about disp-ids.

Feature, not a bug, it doesn't make sense for the CLR to create the RCW when it can create the .NET object directly. Nor would that be easy to implement.

Hans Passant
  • 873,011
  • 131
  • 1,552
  • 2,371
  • Was not aware of this, thanks for the information. It makes sense in some ways I guess. – DeCaf Feb 15 '12 at 07:58