在托管环境中允许托管代码回叫非托管代码

问题描述 投票:3回答:2

我有托管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 ++世界的函数指针作为参数吗?

更新感谢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: %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 ^参数而不是fn ptr时,我可以成功调用CLR函数。

c++ .net c++-cli clr-hosting
2个回答
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;
}

您也可以将其包装在Action中。另一种方法是使用GetDelegateForFunctionPointer,例如as in this SO question


0
投票

[如果有人仍然需要更好的方法,您可以简单地将c ++函数传递给CLR使用C变体中的intptr_t和受管中的long,然后使用Marshall和委托来调用您的本机函数,非常简单就像魅力一样。

如果您需要代码段,请告诉我。

© www.soinside.com 2019 - 2024. All rights reserved.