[英]How to implement COM callback interface in C++, from C# assembly, using `#import` and `tlb` file?
以下虛擬和最小的例子解釋了這個問題。
您有一個C#/。NET庫,它通過COM導出接口以從C ++使用。
[C++ Application] --- is using ---> [via COM] [C#/.NET Library]
C#/ .NET端的庫看起來像這樣:
[Guid("example-0000-0000-0000-0000000000000")]
public class MyObj : IMyObj
{
void SetLogger(ILogger logger);
void DoSomething(string someArgument);
}
編譯程序集並導出名為MyObj.tlb
的tlb
文件。 使用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"));
// ...
}
一切正常,但您希望為C#/ .NET庫啟用日志記錄。 C#/ .NET庫的內部應該能夠將日志消息發送回C ++應用程序,以便使用那里已有的日志記錄環境來編寫日志消息。
[C++ Logging System] <--- log message --- [C#/.NET Component]
您已在C#/ .NET組件中聲明了記錄器的接口。
[Guid("example-0000-0000-0000-0000000000000")]
public interface ILogger
{
void WriteLine(string line);
}
問題是:
使用ILogger
接口為C ++應用程序實現Logger
類的最簡單方法是什么?
(不使用ATL或MFC)
ILogger
接口派生自IDispatch
,但C#interop層實際上並不使用IDispatch
接口。 因此,只需要實現IUnknown
接口。
請參閱下面的“使用IUnknown
C#中的接口限制為早期綁定”部分,如何更改C#組件以完全避免使用IDispatch
接口。
問題中描述的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;
}
請注意以下有關此實現的重要事項:
IDispatch
方法只返回錯誤代碼。 它們永遠不會被C#interop調用。 有關如何實現這些方法的詳細信息,請參閱“ IDispatch
工作實現”一節。 tlb
的導入類型都放在自己的命名空間中。 class
聲明和實現應該在頭文件和實現文件中分開。 代碼在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
C#中的接口限制為早期綁定 User Astrotrain指出了刪除IDispatch
的簡化:如果您的組件剛剛被此C ++應用程序使用,因此不需要使用IDispatch
接口進行后期綁定,則可以將InterfaceType
屬性添加到接口以刪除一個綁定。
在我們的示例中,這將是這樣的:
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("example-0000-0000-0000-0000000000000")]
public interface ILogger
{
void WriteLine(string line);
}
ILogger
接口現在直接來自IUnknown
。 現在您可以省略IDispatch
接口的所有空實現。
缺點是您的組件只能用於支持早期綁定的語言。 如果此C ++應用程序是組件的唯一用戶,則這沒有問題。
IDispatch
工作實現 用戶Paulo Madeira提供了一個完整的示例,說明如何使用ITypeInfo
實現所有IDispatch
方法。 以下示例顯示了Logger
類,省略了本答案開頭示例中顯示的所有方法。 請務必閱讀以下注釋。
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;
請注意以上關於此示例的以下內容:
LIBID_MyObj
替換為庫的庫標識符。 它有類似的命名。 class
聲明和實現應該在頭文件和實現文件中分開。 static_cast<ILogger*>(this)
強制轉換是確保指針(作為void*
指針傳遞)指向正確的vtable所必需的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.