C# COM-сервер, возвращающий E_POINTER из GetIDsOfNames - PullRequest
1 голос
/ 01 марта 2020

Я пытаюсь создать COM-сервер в C# для предоставления объекта автоматизации OLE. Я хотел бы реализовать интерфейс IDispatch вручную, так как мне нужно сделать некоторое количество динамического поиска c для членов. Когда я вызываю свой пользовательский GetIDsOfNames, запускается метод C#, а затем клиент выдает ошибку E_POINTER. Но давайте начнем с самого начала

После нескольких часов чтения и немалых проб и ошибок мне удалось собрать что-то вместе, я думаю, что это должно сработать. У меня очень простой интерфейс

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

, который может вернуть другой COM-объект. Этот первый COM-объект использует реализацию IDispatch C# по умолчанию и работает нормально. EchoObject - это та, в которой реализована пользовательская реализация IDispatch, поскольку она не является частью C# SDK, давайте посмотрим, как я ее определил

[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);
}

Я использую определенный интерфейс IDispatch в определении интерфейса IEchoObject.

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

Затем реализация этого интерфейса должна реализовать ICustomQueryInterface, чтобы разрешить приведение IDispatch.

[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;
    }
}

Я, конечно, реализовал другие методы IDispatch, но я не верю, что они важны для моей проблемы. Обратите внимание, что GetIDsOfNames печатает на консоль.

Я могу создать начальный SimpleObject просто отлично. Я также могу вызвать метод testNest и получить экземпляр EchoObject. Однако при поиске идентификатора testman метода для этого EchoObject клиент возвращает E_POINTER (неверный указатель) из win32 вместо результата.

Я проверил его из Excel , VBScript и пользовательский COM-клиент C ++. Все получают ошибку Invalid Pointer , и клиент C ++ показывает, что это из GetIDsOfNames. Как ни странно, хотя метод C# запускается, он с радостью выводит строку в консоль, возвращает, а затем C ++ COM-клиент получает ошибку.

Если отключить все мои пользовательские IDispatch и вместо этого использовать значение по умолчанию C# Реализация (вызов метода testint вместо testman) работает нормально. Я подозреваю, что мой Marshaling может быть где-то не так, но я смотрел на него часами, и я не вижу проблемы.

РЕДАКТИРОВАТЬ: я сделал еще один тест. Если я изменяю тип возвращаемого значения GetIDsOfNames с int на void, это работает. Что там происходит? Согласно MSDN тип возвращаемого значения - HRESULT (32-разрядное целое число со знаком), но когда я возвращаю это, я получаю ошибку.

...