tags:

views:

119

answers:

2

I need to integrate a native C++ library into a C# project. Now in this C++ library there is class with virtual functions that I need to inherit from in C++/CLI. So in C++/CLI I wrote someting like

class C++CliClass : public C++Class
{ 
  C++CliClass(Callback callback) { iCallback = callback; }
  virtual VirualFunctionCallFromC++(int x, int y, int z, SomeClass *p)
  { 
     // I need to call C++/CLI here
     iCallback(x, y, z, p);
  }

 private:
   Callback iCallback;
}

I defined the callback function as:
typedef int (__cdecl *Callback)(int x, int y, int z, SomeClass *p);

The idea is now that C++ library calls the virtual function of the C++Cli 
    class which on his turn calls the call back which gets me hopefully into C#.

// This delegate definition is needed to setup the callback for the C++ class
delegate int CallbackDelegate(int x, int y, int z, SomeClass *p);

So now I defined a managed C++/CLI class
public ref class GCClass
{
  public: 
    delegate <Byte>^ GetDataDelegate();
    GCClass(GetData^ getDataDelegate) { iGetDataDelegate = getDataDelegate };

  private:
    GetDataDelegate ^iGetDataDelegate;
    int GetData(int x, int y, int z, SomeClass *p)
    {
       // call delegate into C#
       <Byte>^ data = iGetDataDelegate->Invoke();
    }
  public:
    void SomeFunctionWhichEventuallyCallsC++Libary
    {
       // create a delegate for the method that will call the C# delegate
       CallbackDelegate ^d = gcnew CallbackDelegate(this, &GCClass::GetData);
       IntPtr del = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(d);

       // Install in the C++ class
       C++CliClass(del.ToPointer());

       // Setup the C++ library and install my C++ class into the library
       SomeObjectOfTheLibrary->Install(&C++CliClass);
       SomeObjectOfTheLibrary->DoSometing() // will call the C++ virtual function and end up in C#

       // The delegate is no longer needed anymore
    }

Until here the code. So what I was hoping to achieve is that someone can call a method of my managed C++/CLI class which uses a native C++ library to do his stuff. The C++ library calls on his turn the C++/CLI callback and finally a C# delegate is called. Now finally the question: everything goes fine in debug mode. In release mode however sometimes an AccesException is thrown or sometimes the application just hangs. I suspect that it has something to do with different calling conventions for C++/CLI and C++. For example I observed that the second time the callback is called the value of iCallback is different from the first time it was called. However for all next calls the value of iCallback does not change anymore. I would expect that the iCallback value should be always the same but I'm not sure because I don't know how the framework internally works to be able to call a delegate from C++. I also tried to define the calling convention of the CallbackDelegate with [UnmanagedFunctionPointer(Cdecl)]. I tried all options but had no result: I always end up in an exception or the application hangs forever. Can someone give me a hint of what might be wrong?

+2  A: 

Make sure the delegate is not garbage collected when it's still needed. In class C++CliClass you could add a member of type CallbackDelegate and set it to d. If the instance of C++CliClass only exists during execution of SomeFunction.. GC.KeepAlive(d) at the end of the function might be sufficient.

Perhaps even simpler: in C++CliClass define a memeber of type gcroot<GCClass^> then directly call the GetData function on this memeber in VirtualFunction without the need for a delegate.

Henrik
+1  A: 

One of the problems with your code above is that d is a managed reference, which means that it can be moved around at runtime. This in turn will invalidate your callback pointer.

In order to pass the delegate to native code, you need to tell the garbage collector not to move it, using GCHandle::Alloc:

   CallbackDelegate^ d = gcnew CallbackDelegate(this, &GCClass::GetData);

   // As long as this handle is alive, the GC will not move or collect the delegate
   // This is important, because moving or collecting invalidate the pointer
   // that is passed to the native function below
   GCHandle delegate_handle = GCHandle::Alloc(d);

   IntPtr del = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(d);

   C++CliClass native(del.ToPointer());

   SomeObjectOfTheLibrary->Install(&native);
   SomeObjectOfTheLibrary->DoSometing() // will call the C++ virtual function and end up in C#

   // Release the handle
   delegate_handle.Free();
Bojan Resnik
Henrik's answer is much better, the delegate object doesn't need to be pinned. Also, you probably *really* don't want to use the Invoke() method, that starts a new thread.
Hans Passant
I tried Bojan suggestion and it worked. The Invoke is there to call the C# delegate from C++/CLI. Can I take another approach to do this?
kvd
@nobugz `Invoke` doesn't start a new thread. Calling `Invoke` on a delegate is exactly the same as doing `del()`.
Bojan Resnik
You are correct. Still, pinning is not required.
Hans Passant
Actually, pinning is required if the delegate is passed to native code, as is the case when pointers are passed from managed to native code. It is often the easiest solution as well. Of course, when the solution does not involve delegates, there is no need for pinning.
Bojan Resnik
@all I also tried Henrik's suggestion to use a gcroot member in the C++ class. It also works and is indeed a lot cleaner: no delegate, so no calling conventions and garbage collection issues to worry about. I will go for this solution. Thanks all for helping me out.
kvd
No problem, but please change your accepted answer to Henrik's.
Bojan Resnik