2012-07-03 1 views
3

У меня есть код на C++, в котором размещается clr, чтобы использовать Managed.dll, написанный на C#.Разрешить управляемый код в среде размещения для вызова неуправляемого кода

Это .net есть метод, как в следующем, что позволяет код для регистрации на уведомления о событиях:

public void Register(IMyListener listener); 

интерфейс выглядит примерно так

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

я хотел бы сделать вещи в части C++ программы, вызванной событиями в мире .net. Я бы даже не возражал против создания другой управляемой dll с единственной целью сделать Managed.dll более дружественным к C++, если это необходимо.

Какие у меня варианты? Только один я уверен, что я мог бы осуществить это:

  • Написать другим удалось DLL, ожидающего тех событий, очереди их и позволяет ++ доступ к исходному коду С очереди посредством опроса

Это, конечно, переходить от стиля «прерывания» к стилю «опроса» со всеми его преимуществами и недостатками и необходимостью обеспечения очереди. Можем ли мы обойтись без опроса? Могу ли я каким-то образом вызвать управляемый код и предоставить ему указатель функции в мир C++ в качестве аргумента?

Update Благодаря ответ и комментарии Стейн, я надеюсь, что я переехал немного в правильном направлении, но я думаю, главная проблема все еще открыта, как передать указатель Fn из неуправляемых земель в CLr принимал окружающую среду.

Скажем, у меня есть «Int п (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: %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 вызов приведет к a 0x80131512 результат.

Что-то кажется неправильным в том, как я передаю указатель на NativeFun в управляемый мир или как мои функции определены. При использовании параметра String^вместо fn ptr я могу успешно вызвать функцию CLR.

+0

ваш метод CLI использует 'native_fun &', но вы передаете 'native_fun', может быть, проблема? Вы пытались использовать указатели вместо (например, 'native_fun *')? Можете ли вы присоединить отладчик (в конце концов, помещая «DebugBreak» где-нибудь) и посмотреть, правильно ли передано значение для аргумента? – stijn

ответ

3

Вы можете написать отдельную dll в C++/CLI и реализовать там интерфейс и перенаправить логику на 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

Практически там, надеюсь. Еще нужно выяснить, как создать правильный тип dll C++/CLI. Я попробовал '/ clr' и'/clr: pure' (/ clr: safe и/clr: oldSyntax прервал компиляцию с ошибками), но оба будут приводить к ошибке '0x8007000b' при выполнении' AppDomain-> Load_2() '. Otoh Интересно, если бы просто передать неуправляемый указатель fn на управляемую землю C#, не существует способа просто «typedef» fn-сигнатуры где-то в C# и передать указатель на управляемую dll (в которой у меня нет проблем с загрузкой через AppDomain-> Load_2 и методы вызова в) - увидит, что имеет в наличии 'GetDelegateForFunctionPointer' –