2012-07-03 246 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'在商店 –