我有C++托管 clr 的代码,以便使用用 c# 编写的 Managed.dll。
此 .net 具有如下所示的方法,该方法允许代码注册事件通知:
public void Register(IMyListener listener);
界面看起来像这样
public interface IMyListener
{
void Notify(string details);
}
我想在程序的C++部分做一些事情,由 .net 世界中的事件触发。如果有必要,我什至不介意创建另一个托管 dll,其唯一目的是使托管.dll C++友好。
我在这里有什么选择?我唯一确定我可以实现的是:
- 编写另一个托管 dll,侦听这些事件,对它们进行排队,并允许C++代码通过轮询访问队列
这当然会从"中断"风格转变为"轮询"风格,具有所有优点和缺点,并且需要提供队列。我们可以不投票吗?我是否可以以某种方式调用托管代码并为其提供指向C++世界的函数指针作为参数?
更新感谢 stijn 的回答和评论,我希望我朝着正确的方向前进了一点,但我想仍然存在的主要问题是如何将 fn 指针从未管理的土地传递到 clr 托管环境中。
假设我有一个"int fn(int)"类型的函数指针,我想传递给托管世界,以下是相关部分:
托管代码 (C++/CLI)
typedef int (__stdcall *native_fun)( int );
String^ MyListener::Register(native_fun & callback)
{
return "MyListener::Register(native_fun callback) called callback(9): " + callback(9);
}
非托管代码
typedef int (__stdcall *native_fun)( int );
extern "C" static int __stdcall NativeFun(int i)
{
wprintf(L"Callback arrived in native fun land: %dn", i);
return i * 3;
}
void callCLR()
{
// Setup CLR hosting environment
...
// prepare call into CLR
variant_t vtEmpty;
variant_t vtRetValue;
variant_t vtFnPtrArg((native_fun) &NativeFun);
SAFEARRAY *psaMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
LONG index = 0;
SafeArrayPutElement(psaMethodArgs, &index, &vtFnPtrArg);
...
hr = spType->InvokeMember_3(bstrMethodName, static_cast<BindingFlags>(
BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public),
NULL, vtEmpty, psaMethodArgs, &vtRetValue);
if (FAILED(hr))
wprintf(L"Failed to invoke function: 0x%08lxn", hr);
spType->InvokeMember_3
调用将导致0x80131512
结果。
我将指向 NativeFun 的指针传递给托管世界的方式或我的函数定义方式似乎有问题。当使用 String^ 参数而不是 fn ptr 时,我可以成功调用 CLR 函数。
您可以在 C++/CLI 中编写单独的 dll 并在那里实现接口,并将逻辑转发给 C++。根据我混合托管/非托管的经验,我可以说使用中间 C++/CLI 步骤是要走的路。没有摆弄DllImport和功能,而是两个世界之间的坚实桥梁。只需要一些时间来适应语法和编组,但是一旦你有了它,它实际上就毫不费力了。如果需要在托管类中保存C++对象,最好的方法是使用 clr_scoped_ptr 之类的东西。
代码将如下所示:
//header
#using <Managed.dll>
//forward declare some native class
class NativeCppClass;
public ref class MyListener : public IMylIstener
{
public:
MyListener();
//note cli classes automatically implement IDisposable,
//which will call this destructor when disposed,
//so used it as a normal C++ destructor and do cleanup here
~MyListener();
virtual void Notify( String^ details );
private:
clr_scoped_ptr< NativeCppClass > impl;
}
//source
#include "Header.h"
#include <NativeCppClass.h>
//here's how I marshall strings both ways
namespace
{
inline String^ marshal( const std::string& i )
{
return gcnew String( i.data() );
}
inline std::string marshal( String^ i )
{
if( i == nullptr )
return std::string();
char* str2 = (char*) (void*) Marshal::StringToHGlobalAnsi( i );
std::string sRet( str2 );
Marshal::FreeHGlobal( IntPtr( str2 ) );
return sRet;
}
}
MyListener::MyListener() :
impl( new NativeCppClass() )
{
}
MyListener::~MyListener()
{
}
void MyListener::Notify( String^ details )
{
//handle event here
impl->SomeCppFunctionTakingStdString( marshal( details ) );
}
更新下面是从托管环境调用C++回调的简单解决方案:
pubic ref class CallbackWrapper
{
public:
typedef int (*native_fun)( int );
CallbackWrapper( native_fun fun ) : fun( fun ) {}
void Call() { fun(); }
CallbackWrapper^ Create( ... ) { return gcnew CallbackWrapper( ... ); }
private:
native_fun fun;
}
如果需要,您也可以将其包装在操作中。另一种方法是使用 GetDelegateForFunctionPointer
,例如在这个 SO 问题中
如果有人还需要更好的方法,你可以简单地使用变体中的intptr_t
和托管中的long
将c ++函数传递给CLR,然后使用Marshall和委托来调用你的本机函数,超级简单,工作起来就像charm。
如果您需要代码片段,请告诉我。