views:

1604

answers:

2

I am using boost::signal in a native C++ class, and I now I am writing a .NET wrapper in C++/CLI, so that I can expose the native C++ callbacks as .NET events. When I try to use boost::bind to take the address of a member function of my managed class, I get compiler error 3374, saying I cannot take the address of a member function unless I am creating a delegate instance. Does anyone know how to bind a member function of a managed class using boost::bind?

For clarification, the following sample code causes Compiler Error 3374:

#include <boost/bind.hpp>

public ref class Managed
{
public:
    Managed()
    {
     boost::bind(&Managed::OnSomeEvent, this);
    }

    void OnSomeEvent(void)
    {
    }
};
+1  A: 

After googling some more, I finally found a nice blog post about how to do this. The code in that post was a little more than I needed, but the main nugget was to use a global free function that takes an argument of the managed this pointer wrapped in a gcroot<> template. See the SomeEventProxy(...) in the code below for an example. This function then turns around and calls the managed member I was trying to bind. My solution appears below for future reference.

#include <msclr/marshal.h>

#include <boost/bind.hpp>
#include <boost/signal.hpp>
#include <iostream>

#using <mscorlib.dll>

using namespace System;
using namespace msclr::interop;

typedef boost::signal<void (void)> ChangedSignal;
typedef boost::signal<void (void)>::slot_function_type ChangedSignalCB;
typedef boost::signals::connection  Callback;


class Native
{
public:

 void ChangeIt() 
 {
  changed();
 }

 Callback RegisterCallback(ChangedSignalCB Subscriber)
 {
  return changed.connect(Subscriber);
 }

 void UnregisterCallback(Callback CB)
 {
  changed.disconnect(CB);
 }

private:
 ChangedSignal changed;
};



delegate void ChangeHandler(void);


public ref class Managed
{
public:
 Managed(Native* Nat);
 ~Managed();
 void OnSomeEvent(void);

 event ChangeHandler^ OnChange;

private:
 Native* native;
 Callback* callback;
};


void SomeEventProxy(gcroot<Managed^> This)
{
 This->OnSomeEvent();
}


Managed::Managed(Native* Nat)
 : native(Nat)
{
 native = Nat;
 callback = new Callback;
 *callback = native->RegisterCallback(boost::bind( SomeEventProxy, gcroot<Managed^>(this) ) );
}

Managed::~Managed()
{
 native->UnregisterCallback(*callback);
 delete callback;
}

void Managed::OnSomeEvent(void)
{
 OnChange();
}


void OnChanged(void)
{
 Console::WriteLine("Got it!");
}

int main(array<System::String ^> ^args)
{
 Native* native = new Native;
 Managed^ managed = gcnew Managed(native);

 managed->OnChange += gcnew ChangeHandler(OnChanged);

 native->ChangeIt();

 delete native;
 return 0;
}
Brian Stewart
+2  A: 

While your answer works, it exposes some of your implementation to the world (Managed::OnSomeEvent). If you don't want people to be able to raise the OnChange event willy-nilly by invoking OnSomeEvent(), you can update your Managed class as follows (based on this advice):

public delegate void ChangeHandler(void);
typedef void (__stdcall *ChangeCallback)(void);

public ref class Managed
{
public:
    Managed(Native* Nat);
    ~Managed();

    event ChangeHandler^ OnChange;

private:
    void OnSomeEvent(void);
    Native* native;
    Callback* callback;
    GCHandle gch;
};

Managed::Managed(Native* Nat)
 : native(Nat)
{
    native = Nat;
    callback = new Callback;

    ChangeHandler^ handler = gcnew ChangeHandler( this, &Managed::OnSomeEvent );
    gch = GCHandle::Alloc( handler );
    System::IntPtr ip = Marshal::GetFunctionPointerForDelegate( handler );
    ChangeCallback cbFunc = static_cast<ChangeCallback>( ip.ToPointer() );

    *callback = native->RegisterCallback(boost::bind<void>( cbFunc ) );
}

Managed::~Managed()
{
    native->UnregisterCallback(*callback);
    delete callback;
    if ( gch.IsAllocated )
    {
        gch.Free();
    }
}

void Managed::OnSomeEvent(void)
{
    OnChange();
}

Note the alternate bind<R>() form that's used.

BKewl