[英]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.tlb
的tlb
文件。 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: 请注意以下有关此实现的重要事项:
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
工作实现”一节。 tlb
are all placed in a own namespace. 在实际项目中, tlb
的导入类型都放在自己的命名空间中。 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;
}
}
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 ++应用程序是组件的唯一用户,则这没有问题。
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: 请注意以上关于此示例的以下内容:
LIBID_MyObj
with the library identifier of your library. 您必须将LIBID_MyObj
替换为库的库标识符。 It has a similar naming. 它有类似的命名。 class
declaration and implementation should be separated in a header and implementation file. class
声明和实现应该在头文件和实现文件中分开。 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.