Allow managed code in hosted environment to call back unmanaged code

3

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:

  • Write another managed dll that listens for those events, queues them and lets the C++ code access the queue via polling

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.

c++
.net
c++-cli
clr-hosting
asked on Stack Overflow Jul 3, 2012 by Evgeniy Berezovsky • edited Dec 13, 2014 by Deduplicator

2 Answers

4

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

answered on Stack Overflow Jul 3, 2012 by stijn • edited May 23, 2017 by Community
1

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.

answered on Stack Overflow Jun 7, 2020 by PinnedObject

User contributions licensed under CC BY-SA 3.0