1

I'm trying to create a COM server in C# to expose an OLE automation object. I'd like to implement the IDispatch interface manually, since I need to do some amount of dynamic lookup for the members. When i call my custom GetIDsOfNames the C# method runs, and then the client errors with E_POINTER. But lets start at the start

After hours of reading and no small amount of trial and error, I've managed to cobble something together i would think should work. I have a very simple interface

[Guid("7B95C174-9320-4400-88AB-6960B019CEDE")]
[ComVisible(true)]
public interface ISimpleObject {
    EchoObject testNest();
}

That can return another COM object. This first COM object uses the default C# IDispatch implementation and works fine. The EchoObject is the one with the custom IDispatch implementation, since that's not part of the C# SDK let's look at how I've defined it

[ComImport]
[Guid("00020400-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDispatch {
    int GetTypeInfoCount(out uint pctinfo);
    int GetTypeInfo(uint iTInfo, int lcid, out IntPtr info);

    int GetIDsOfNames(
        ref Guid iid,
        [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)] string[] names,
        int cNames,
        int lcid,
        [Out][MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.I4, SizeParamIndex = 2)] int[] rgDispId);

    int Invoke(
        int dispId,
        ref Guid riid,
        int lcid,
        System.Runtime.InteropServices.ComTypes.INVOKEKIND wFlags,
        ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams,
        [Out][MarshalAs(UnmanagedType.LPArray)] object[] result,
        IntPtr pExcepInfo,
        IntPtr puArgErr);
}

I use the defined IDispatch interface in the definition of the IEchoObject interface.

[Guid("D3E9B530-DFD5-4B54-88B9-4FBC8B0926E2")]
[ComVisible(true)]
public interface IEchoObject: IDispatch {
    string testint();
}

The implementation of this interface then has to implement ICustomQueryInterface to allow the IDispatch casting to take place.

[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IDispatch))]
[Guid("F625A833-3B4E-455D-90A9-89B1953147CD"), ComVisible(true)]
public class EchoObject : ReferenceCountedObject, IEchoObject, ICustomQueryInterface {
    public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv) {
        Console.WriteLine("Are we a: " + iid + "?");
        ppv = IntPtr.Zero;
        if (typeof(IDispatch).GUID == iid)
        {
            Console.WriteLine("Yes");
            ppv = Marshal.GetComInterfaceForObject(this, typeof(IDispatch), CustomQueryInterfaceMode.Ignore);
            return CustomQueryInterfaceResult.Handled;
        }
        return CustomQueryInterfaceResult.NotHandled;
    }

    public int GetIDsOfNames(
        ref Guid iid,
        [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)] string[] names,
        int cNames,
        int lcid,
        [Out][MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.I4, SizeParamIndex = 2)] int[] rgDispId
    ) {
        bool unknown = false;

        if(cNames == 1) {
            Console.WriteLine("Getting id of " + names[0]);
            if (names[0] == "testman") {
                Console.WriteLine("Member found");
                rgDispId[0] = 1;
            } else {
                rgDispId[0] = -1;
            }
        }

        return unknown ? HRESULT.DISP_E_UNKNOWNNAME : HRESULT.S_OK;
    }
}

I have of course implemented the other methods of IDispatch, but i don't believe them to be important to the problem I'm having. Notice that the GetIDsOfNames prints to console.

I can create the initial SimpleObject just fine. I can also call the testNest method on it and get the EchoObject instance. When looking up the ID of the testman method on that EchoObject though, the client gets back a E_POINTER (Invalid Pointer) from win32 instead of the result.

I have tested it with from Excel, VBScript, and a custom C++ COM client. All get an Invalid Pointer error, and the C++ client shows that it's from GetIDsOfNames. Oddly enough though, the C# method runs, it happily prints the line to console, returns, and then the C++ COM client gets an error.

If disable all my custom IDispatch and instead use the default C# implementation (calling the testint method instead of testman) it works fine. I suspect my Marshaling might be wrong somewhere, but I've been staring at it for hours and I can't see the problem.

EDIT: I did another test. If i change the return type of GetIDsOfNames from int to void it works. What's going on there? According to the MSDN the return type is HRESULT (a 32 bit signed int) but when i return that i get an error.

Delusional Logic
  • 808
  • 10
  • 32
  • @HansPassant In general you're probably right, but for my usecase I don't know what methods are available at compile time, so i need to look them up at runtime. That's not possible with the automatic IDispatch implementation. The IReflect interface is also not suitable since i still need to know all the methods i can provide at startup. – Delusional Logic Feb 29 '20 at 23:31
  • @HansPassant I don't understand how i can be wrong about my own requirements. I'm implementing a proxy between COM and some external system. I don't know which methods the objects of this external system has, but if I have a name i can go out and ask it if it has that particular member. I do not know of any way i can implement this without implementing IDispatch myself. The default IDispatch implementation only answers to members known at compile time (they have to be defined in the interface). – Delusional Logic Mar 01 '20 at 10:56

0 Answers0