Evgeniy Berezovsky
Evgeniy Berezovsky

Reputation: 19258

Allow managed code in hosted environment to call back unmanaged code

I have C++ code that hosts a clr in order to make use of Managed.dll, written in c#.

This .net has a method like the following that allows code to register for notification of events:

public void Register(IMyListener listener);

The interface looks something like this

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

I'd like to do stuff in the C++ part of the program, triggered by the events in the .net world. I would not even mind creating another managed dll for the sole purpose of making Managed.dll more C++-friendly, if that is necessary.

What are my options here? The only one I am sure I could implement is this:

This would of course change from an 'interrupt' style to a 'polling' style with all its advantages and disadvantages and the need to provide for queuing. Can we do without polling? Could I somehow call managed code and provide it a function pointer into the C++ world as the argument?

Update Thanks to stijn's answer and comments I hope I moved a bit in the right direction, but I guess the main problem still open is how to pass a fn pointer from unmanaged land into the clr hosted environment.

Say I have an "int fn(int)" type of function pointer that I want to pass to the managed world, here are the relevant parts:

Managed code (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);
}

Unmanaged code

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);

The spType->InvokeMember_3 call will lead to a 0x80131512 result.

Something seems to be wrong with the way I pass the pointer to NativeFun over to the managed world, or how my functions are defined. When using a String^ param instead of the fn ptr, I can call the CLR function successfully.

Upvotes: 3

Views: 2350

Answers (2)

user13119880
user13119880

Reputation:

If someone still needs a better way for this , you can simply pass c++ function to CLR using intptr_t in variant and long in managed , then use Marshall and delegate to invoke your native function , super easy and works like charm.

if you need a code snippet , let me know.

Upvotes: 0

stijn
stijn

Reputation: 35921

You can write a seperate dll in C++/CLI and implement the interface there, and forward the logic to C++. From my experience with mixing managed/unmanaged I can say using an intermediate C++/CLI step is the way to go. No fiddling with DllImport and functions only, but a solid bridge between both worlds. It just takes some getting used to the syntax and marshalling, but once you have that it's practically effortless. If you need to hold C++ objects in the managed class, best way is to use something like clr_scoped_ptr.

Code would look like this:

//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 ) );
}

update Here's a simple solution to call callbacks in C++ from the managed world:

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;
}

you can also wrap this in an Action if you want. Another way is using GetDelegateForFunctionPointer, for example as in this SO question

Upvotes: 3

Related Questions