简体   繁体   English

如何在C ++汇编中使用`#import`和`tlb`文件在C ++中实现COM回调接口?

[英]How to implement COM callback interface in C++, from C# assembly, using `#import` and `tlb` file?

The following fictive and minimal example explains the question. 以下虚拟和最小的例子解释了这个问题。

You have a C#/.NET library which exports the interface via COM to use from C++. 您有一个C#/。NET库,它通过COM导出接口以从C ++使用。

[C++ Application] --- is using ---> [via COM] [C#/.NET Library]

The library on the C#/.NET side looks like this: C#/ .NET端的库看起来像这样:

[Guid("example-0000-0000-0000-0000000000000")]
public class MyObj : IMyObj
{
    void SetLogger(ILogger logger);
    void DoSomething(string someArgument);
}

The assembly is compiled and a tlb file is exported called MyObj.tlb . 编译程序集并导出名为MyObj.tlbtlb文件。 This tlb file is imported in the Application using the #import statement from Visual-C++: 使用Visual-C ++中的#import语句在Application中导入此tlb文件:

#import "MyObj.tlb" named_guids auto_rename

void someFunc()
{
    MyObjPtr myObj;
    myObj.CreateInstance(CLSID_MyObj);
    // How to set the logger?
    myObj->DoSomething(_bstr_t(L"foo"));
    // ...
}

Everything works really fine, but you would like to enable logging for the C#/.NET library. 一切正常,但您希望为C#/ .NET库启用日志记录。 The internals of the C#/.NET library should be able to send the log messages back to the C++ application in order to use the already existing logging environment there to write log messages. C#/ .NET库的内部应该能够将日志消息发送回C ++应用程序,以便使用那里已有的日志记录环境来编写日志消息。

[C++ Logging System] <--- log message --- [C#/.NET Component]

You already declared the interface for the logger in the C#/.NET component. 您已在C#/ .NET组件中声明了记录器的接口。

[Guid("example-0000-0000-0000-0000000000000")]
public interface ILogger
{
    void WriteLine(string line); 
}

The question is: 问题是:

What is the simplest way to implement a Logger class, using the ILogger interface, for the C++ application? 使用ILogger接口为C ++应用程序实现Logger类的最简单方法是什么?

(Without using ATL or MFC) (不使用ATL或MFC)

The ILogger interface derives from IDispatch , but the C# interop layer does not actually use the IDispatch interface. ILogger接口派生自IDispatch ,但C#interop层实际上并不使用IDispatch接口。 Therfore only the IUnknown interface needs to be implemented. 因此,只需要实现IUnknown接口。

See the section "Limiting the interface in C# to early binding using IUnknown " below, how you can change your C# component to avoid the IDispatch interface completely. 请参阅下面的“使用IUnknown C#中的接口限制为早期绑定”部分,如何更改C#组件以完全避免使用IDispatch接口。

The implementation for the ILogger interface described in the question will look like this: 问题中描述的ILogger接口的实现如下所示:

#import "MyObj.tlb" named_guids auto_rename

class Logger : public ILogger
{
public:
    Logger(MyLogger log)
        : _log(log), _refCount(1)
    {
    }

    virtual ~Logger()
    {
    }

public: // Implement ILogger
    virtual HRESULT __stdcall raw_WriteLine(BSTR message) {
        // Convert BSTR and write to _log.
        return S_OK;
    }

public: // Implement IDispatch
    virtual HRESULT __stdcall GetTypeInfoCount(UINT *pctinfo)
    {
        return E_NOTIMPL;
    }

    virtual HRESULT __stdcall GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
    {
        return E_NOTIMPL;
    }

    virtual HRESULT __stdcall GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
    {
        return E_NOTIMPL;
    }

    virtual HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
        return E_NOTIMPL;
    }

public: // Implement IUnknown
    virtual HRESULT __stdcall QueryInterface(REFIID riid, void **ppvObject)
    {
        if (riid == IID_IUnknown) {
            *ppvObject = static_cast<IUnknown*>(this); 
            AddRef();
            return S_OK;
        }
        if (riid == IID_IDispatch) {
            *ppvObject = static_cast<IDispatch*>(this); 
            AddRef();
            return S_OK;
        }
        if (riid == IID_ILogger) {
            *ppvObject = static_cast<ILogger*>(this) ;
            AddRef();
            return S_OK;
        }
        *ppvObject = nullptr;
        return E_NOINTERFACE;
    }

    virtual ULONG __stdcall AddRef()
    {
        return InterlockedIncrement(&_refCount);
    }

    virtual ULONG __stdcall Release()
    {
        return InterlockedDecrement(&_refCount);
    }

private:
    MyLogger _log;
    long _refCount;
}

Please note the following important things about this implementation: 请注意以下有关此实现的重要事项:

  • The reference count is just implemented to look like a COM reference count. 引用计数实现为看起来像COM引用计数。 Because this object will be owned by the C++ application, there is no need to implement the destruction of the object. 因为此对象将由C ++应用程序拥有,所以不需要实现对象的销毁。
  • All IDispatch methods just return an error code. 所有IDispatch方法只返回错误代码。 They are never called by C# interop. 它们永远不会被C#interop调用。 See the section "Working implementation of IDispatch " for details how to implement these methods. 有关如何实现这些方法的详细信息,请参阅“ IDispatch工作实现”一节。
  • IN a real project, the imported types form the tlb are all placed in a own namespace. 在实际项目中, tlb的导入类型都放在自己的命名空间中。
  • The class declaration and implementation should be separated in a header and implementation file. class声明和实现应该在头文件和实现文件中分开。

The code is used like this in the C++ application: 代码在C ++应用程序中使用如下:

bool MyApp::start()
{
    try {
        HRESULT hresult;
        MyObjPtr myObj;
        hresult = myObj.CreateInstance(CLSID_MyObj);
        if (hresult != S_OK) { 
            return false;
        }
        // Create the logger object which acts as callback for the C# library
        _logger = new Logger(_myLogger);
        // Assign this logger
        myObj->SetLogger(_logger);
    } catch (const _com_error &comError) {
        return false;
    }
}

Limiting the interface in C# to early binding using IUnknown 使用IUnknown C#中的接口限制为早期绑定

User Astrotrain pointed out a simplification to remove IDispatch : If your component ist just used by this C++ Application and therefore does not require late binding using the IDispatch interface, you can add the InterfaceType attribute to the interface to remove one binding. User Astrotrain指出了删除IDispatch的简化:如果您的组件刚刚被此C ++应用程序使用,因此不需要使用IDispatch接口进行后期绑定,则可以将InterfaceType属性添加到接口以删除一个绑定。

This would look like this in our example: 在我们的示例中,这将是这样的:

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
 Guid("example-0000-0000-0000-0000000000000")]
public interface ILogger
{
    void WriteLine(string line); 
}

The ILogger interface is now directly derived from IUnknown . ILogger接口现在直接来自IUnknown Now you can omit all empty implementations of the IDispatch interface. 现在您可以省略IDispatch接口的所有空实现。

The downside is your component can only be used from languages which are supporting early binding. 缺点是您的组件只能用于支持早期绑定的语言。 If this C++ Application is the only user of your component, this is no problem. 如果此C ++应用程序是组件的唯一用户,则这没有问题。

Working implementation of IDispatch IDispatch工作实现

User Paulo Madeira provided a complete example how to implement all IDispatch methods using ITypeInfo . 用户Paulo Madeira提供了一个完整的示例,说明如何使用ITypeInfo实现所有IDispatch方法。 The following example shows the Logger class, omitting all methods which are shown in the example at the begin of this answer. 以下示例显示了Logger类,省略了本答案开头示例中显示的所有方法。 Make sure you read the notes below. 请务必阅读以下注释。

class Logger : public ILogger
{
    // ctor, dtor, ILogger and IUnknown implementation

public: // Implement IDispatch
    virtual HRESULT __stdcall GetTypeInfoCount(UINT *pctinfo)
    {
        if (pctinfo == nullptr) {
            return E_POINTER;
        }
        *pctinfo = (getTypeInfo() != nullptr) ? 1 : 0;
        return S_OK;
    }

    virtual HRESULT __stdcall GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
    {
        if (ppTInfo == nullptr) {
            return E_POINTER;
        }
        *ppTInfo = nullptr;
        if (iTInfo != 0) {
            return DISP_E_BADINDEX;
        }
        ITypeInfoPtr typeInfo(getTypeInfo());
        if (typeInfo == nullptr) {
            return E_NOTIMPL;
        }
        *ppTInfo = typeInfo.Detach();
        return S_OK;
    }

    virtual HRESULT __stdcall GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
    {
        if (rgDispId == nullptr) {
            return E_POINTER;
        }
        *rgDispId = 0;
        if (!IsEqualIID(riid, IID_NULL)) {
            return E_INVALIDARG;
        }
        ITypeInfoPtr typeInfo(getTypeInfo());
        if (typeInfo == nullptr) {
            return E_NOTIMPL;
        }
        if (cNames == 0) {
            return E_INVALIDARG;
        }
        return typeInfo->GetIDsOfNames(rgszNames, cNames, rgDispId);
    }

    virtual HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
        if (pDispParams == nullptr) {
            return E_POINTER;
        }
        // IDispatch and ITypeInfo allows this to be NULL
        if (pVarResult != nullptr) {
            VariantInit(pVarResult);
        }
        // IDispatch and ITypeInfo allows this to be NULL
        if (pExcepInfo != nullptr) {
            ZeroMemory(pExcepInfo, sizeof(EXCEPINFO));
        }
        // IDispatch allows this to be NULL, ITypeInfo does not
        UINT argErr;
        if (puArgErr == nullptr) {
            puArgErr = &argErr;
        }
        *puArgErr = 0;
        if (!IsEqualIID(riid, IID_NULL)) {
            return E_INVALIDARG;
        }
        ITypeInfoPtr pTypeInfo(getTypeInfo());
        if (pTypeInfo == nullptr) {
            return E_NOTIMPL;
        }
        return pTypeInfo->Invoke(
            static_cast<ILogger*>(this),
            dispIdMember,
            wFlags,
            pDispParams,
            pVarResult,
            pExcepInfo,
            puArgErr);
    }

private:
    static ITypeInfo* getTypeInfo()
    {
        if (!_hasTypeLib) {
            ITypeLibPtr typeLib;
            if (SUCCEEDED(LoadRegTypeLib(LIBID_MyObj, 1, 0, 0, &typeLib))) {
                ITypeInfoPtr typeInfo;
                if (SUCCEEDED(typeLib->GetTypeInfoOfGuid(IID_IDispatch, &typeInfo))) {
                    if (!InterlockedCompareExchange(&_hasTypeLib, 1, 0)) {
                        _typeInfo.Attach(typeInfo.Detach());
                    }
                }
            }
        }
        return _typeInfo.GetInterfacePtr();
    }

private:
    static LONG volatile _hasTypeLib;
    static ITypeInfoPtr _typeInfo;
    // other variables
};

// Static definitions in cpp file:
LONG volatile Logger::_hasTypeLib;
ITypeInfoPtr Logger::_typeInfo;

Note the following things about this example above: 请注意以上关于此示例的以下内容:

  • You have to replace the LIBID_MyObj with the library identifier of your library. 您必须将LIBID_MyObj替换为库的库标识符。 It has a similar naming. 它有类似的命名。
  • The full implementation is only required if your COM component requires late binding. 只有在COM组件需要后期绑定时才需要完整实现。
  • The class declaration and implementation should be separated in a header and implementation file. class声明和实现应该在头文件和实现文件中分开。
  • The static_cast<ILogger*>(this) casts are required to make sure the pointer, which is passed as void* pointer, points to the right vtable . static_cast<ILogger*>(this)强制转换是确保指针(作为void*指针传递)指向正确的vtable所必需的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM