Search code examples
c#.netcomcom-interopidl

Can this COM interface be implemented in C#?


A COM component is currently implemented in C++ and the next version must be implemented in C#. The component is called from C++ (not CLI) code. The major part of the not so small component is already ported to C#, but I have trouble to figure out how one specific method can be translated.

Interface definition

The following imports are used in the IDL:

import "oaidl.idl";
import "ocidl.idl";

The major part of the interface is already translated to the corresponding C# attributes, interfaces and classes. Most of it works without problems.

But one of the interface members is defined in IDL as

    [
        object,
        uuid(<guid>),
        helpstring("Help"),
        pointer_default(unique)
    ]
    interface IStuff : IUnknown
    {
...
        HRESULT GetShortText([in] int i, [in] BOOL b, [out, string] TCHAR shortText[10]);
...
    }

Usage

To use the interface, a local TCHAR[10] array is passed as the name of the array (therefore, as a TCHAR*). The COM server must put a short string in the TCHAR array.

Problem

I can't manage to call the method. Breakpoints set in the C# COM server in the GetShortText method are never hit. COM simply doensn't find my implementation in .NET.

How can this method that is called with a fixed size TCHAR* be correctly implemented in C#?


Solution

  • This sort of interface can be implemented in .Net by specifying the parameter as an IntPtr and manually marshalling the string. A full demo can be found on Github.

    Example Implementation:

    [ComVisible(true), Guid("E559D616-4C46-4434-9DF7-E9D7C91F3BA5"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IStuff
    {
        void GetShortTest(int i, [MarshalAs(UnmanagedType.Bool)] bool b, IntPtr shortText);
    }
    
    [ComVisible(true), ProgId("InProcTargetTest.Class1"), Guid("BA5088D4-7F6A-4C76-983C-EC7F1BA51CAA"), ClassInterface(ClassInterfaceType.None)]
    public class Class1 : IStuff
    {
        public void GetShortTest(int i, bool b, IntPtr shortText)
        {
            var data = Encoding.Unicode.GetBytes("Hello");
            Marshal.Copy(data, 0, shortText, data.Length);
        }
    }
    

    Example caller:

    if (FAILED(CoInitialize(nullptr))) {
        std::cout << "Failed to initialize COM\n";
    }
    
    IStuff *pStuff = nullptr;
    CLSID clsClass1 = { 0 };
    if (FAILED(CLSIDFromProgID(L"InProcTargetTest.Class1", &clsClass1))
        || FAILED(CoCreateInstance(clsClass1, nullptr, CLSCTX_INPROC_SERVER, IID_IStuff, (LPVOID*)&pStuff))) {
        std::cout << "Failed to create COM instance\n";
    }
    
    TCHAR test[10] = { 0 };
    pStuff->GetShortTest(5, true, test);
    
    std::cout << "From C#: " << test << "\n";
    

    Example IDL:

    import "unknwn.idl";
    
    [
        odl,
        uuid(E559D616-4C46-4434-9DF7-E9D7C91F3BA5),
        version(1.0),
        pointer_default(unique),
        custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "InProcTargetTest.IStuff")
    
    ]
    interface IStuff : IUnknown {
        HRESULT _stdcall GetShortTest(
            [in] long i,
            [in] BOOL b,
            [out, string] TCHAR shortText[10]);
    };