2012-07-03 185 views
3

我有托管clr的C++代码,以便使用C#编写的Managed.dll。允许托管环境中的托管代码回拨非托管代码

这.Net有类似下面的方法,它允许代码,以注册事件通知:

public void Register(IMyListener listener); 

界面看起来是这样的

public interface IMyListener 
{ 
    void Notify(string details); 
} 

我想要做的东西在程序的C++部分中,由.net世界中的事件触发。如果必要,我甚至不介意创建另一个托管dll,以使Managed.dll更适合C++ - 友好的唯一目的。

我在这里有什么选择?唯一的一个,我相信我可以实现的是:

  • 写另一个托管DLL侦听这些事件,队列它们并通过轮询让C++代码访问队列

这当然会从“中断”风格转变为“轮询”风格,具有所有优点和缺点,并且需要提供排队。我们可以不用投票吗?我能否以某种方式调用托管代码并将其作为参数提供给C++世界的函数指针?

更新 由于斯泰恩的答案和意见,我希望我搬到了正确的方向了一点,但我想主要的问题仍然是开放的是如何从非托管土地FN指针传递到CLR托管环境。

说我有一个 “INT(INT)FN” 类型的函数指针,我想传递给管理世界,这里有相关部分:

托管代码(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: %d\n", 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%08lx\n", hr); 

spType->InvokeMember_3调用将导致一个0x80131512的结果。

我将指向NativeFun的指针传递到托管的世界,或者我的函数是如何定义的方式似乎有些不对。当使用String^param而不是fn ptr时,我可以成功调用CLR函数。

+0

你的CLI方法需要'native_fun&'但是你传递'native_fun',这可能是问题吗?你是否尝试过传递指针(例如'native_fun *')?你可以附加一个调试器(最终通过在某处放置一个“DebugBreak”)并查看该参数的值是否正确传递? – stijn

回答

3

你可以用C++/CLI编写一个单独的dll,并在那里实现接口,并将逻辑转发给C++。根据我对混合托管/非托管的经验,我可以说使用中间的C++/CLI步骤是一条可行的路线。没有摆弄DllImport和功能,而是两个世界之间的坚实桥梁。它只是需要一些习惯的语法和编组,但一旦你有,它几乎毫不费力。如果您需要在托管类中保存C++对象,最好的方法是使用like 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,例如as in this SO question

+0

因此,如果我想从非托管代码传递一个fn指针(或一个类的实例)到托管C++/CLI代码,我会更改MyListener构造函数以接受impl参数的值。看起来它可以工作,将检查出来。 –

+1

是的,这是一种可能性。在这种情况下,我通常会提供一个工厂方法,它使用托管参数来告知什么以及如何创建某些内容,然后创建一个将非托管参数传递给MyListener的构造函数的实例。你也可以让一个托管代理调用一个非托管函数,反之亦然,搜索'GetFunctionPointerForDelegate'示例 – stijn

+0

我希望这里几乎就是这样。仍然需要弄清楚如何构建正确类型的C++/CLI dll。我尝试了'/ clr'和'/ clr:pure'(/ clr:safe和/ clr:oldSyntax会中止编译错误),但是在执行'AppDomain-> Load_2()'时会导致错误0x8007000b。 Otoh我想知道,如果仅仅将一个非托管的fn指针传递给托管的C#land,没有办法在C#中的某个地方'typedef'fn签名并将指针传递给托管的dll(我没有任何问题可以加载通过AppDomain-> Load_2和调用中的方法) - 将看到什么'GetDelegateForFunctionPointer'在商店 –