3

Managed.dll を使用するために clr をホストする C# で記述された C++ コードがあります。

この .net には、次のようなメソッドがあり、コードでイベントの通知を登録できます。

public void Register(IMyListener listener);

インターフェースはこんな感じ

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

.net の世界でのイベントによってトリガーされる、プログラムの C++ 部分で何かをしたいと思います。必要に応じて、Managed.dll をより C++ に適したものにするためだけに、別のマネージド DLL を作成してもかまいません。

ここでのオプションは何ですか? 私が実装できると確信しているのはこれだけです:

  • これらのイベントをリッスンし、それらをキューに入れ、C++ コードがポーリングを介してキューにアクセスできるようにする別のマネージ DLL を作成します。

もちろん、これは「割り込み」スタイルから「ポーリング」スタイルに変わり、すべての長所と短所、およびキューイングを提供する必要があります。ポーリングなしでできますか?どうにかしてマネージ コードを呼び出し、引数として C++ の世界への関数ポインターを提供することはできますか?

更新 stijnの回答とコメントのおかげで、正しい方向に少し進んだことを願っていますが、まだ未解決の主な問題は、管理されていない土地からclrホスト環境にfnポインターを渡す方法だと思います。

「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: %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 へのポインターをマネージ ワールドに渡す方法、または関数の定義方法に問題があるようです。fn ptr の代わりに String^ param を使用すると、CLR 関数を正常に呼び出すことができます。

4

2 に答える 2

3

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の質問のように使用することです

于 2012-07-03T07:49:38.087 に答える