2

I have a c++ class that triggers some method like an event.

class Blah {

   virtual void Event(EventArgs e);
}

How can I wrap it so whenever the method is called a C# (managed) event will be called?

I thought of inheriting that class and overloading the event method, and then somehow call the managed event. I'm just not sure how to actually do it.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185

3 Answers3

6

Something like this (now compile-tested):

#include <vcclr.h>

struct blah_args
{
    int x, y;
};

struct blah
{
    virtual void Event(const blah_args& e) = 0;
};

public ref class BlahEventArgs : public System::EventArgs
{
public:
    int x, y;
};

public ref class BlahDotNet
{
public:
    event System::EventHandler<BlahEventArgs^>^ ItHappened;
internal:
    void RaiseItHappened(BlahEventArgs^ e) { ItHappened(this, e); }
};

class blah_event_forwarder : public blah
{
    gcroot<BlahDotNet^> m_managed;

public:
    blah_event_forwarder(BlahDotNet^ managed) : m_managed(managed) {}

protected:
    virtual void Event(const blah_args& e)
    {
        BlahEventArgs^ e2 = gcnew BlahEventArgs();
        e2->x = e.x;
        e2->y = e.y;
        m_managed->RaiseItHappened(e2);
    }
};
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • `BlahEventHandler` is undefined, but in any case one should prefer `System::EventHandler^` to defining a new delegate. – ildjarn Mar 27 '11 at 20:34
  • @ildjam: That requires an extra parameter, but it is the norm for .NET. I'll incorporate that suggestion. – Ben Voigt Mar 27 '11 at 21:10
4

You need to do some work to reflect the Event() method call so it can be hooked by a managed class. Lets implement a concrete blah class that does so:

#pragma managed(push, off)

struct EvenAtrgs {};

class blah {
public:
   virtual void Event (EvenAtrgs e) = 0;
};

typedef void (* CallBack)(EvenAtrgs);

class blahImpl : blah {
    CallBack callback;
public:
    blahImpl(CallBack fp) {
        this->callback = fp;
    }
    virtual void Event(EvenAtrgs e) { 
        callback(e); 
    }
};
#pragma managed(pop)

You can now construct a blahImpl and pass it a function pointer that is called when the Event() method is called. You can use Marshal::GetFunctionPointerForDelegate() to get such a function pointer, it creates a stub for a delegate that makes the transition from unmanaged code to managed code and can store a instance as well. Combined with the boilerplate code to wrap an unmanaged class:

public ref class blahWrapper {
    blahImpl* instance;
    delegate void managedCallback(EvenAtrgs e);
    managedCallback^ callback;
    void fireEvent(EvenAtrgs e) {
        // Todo: convert e to a managed EventArgs derived class
        //...
        Event(this, EventArgs::Empty);
    }
public:
    event EventHandler<EventArgs^>^ Event;
    blahWrapper() {
        callback = gcnew managedCallback(this, &blahWrapper::fireEvent);
        instance = new blahImpl((CallBack)(void*)System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(callback));
    }
    ~blahWrapper() { delete instance; }
    !blahWrapper() { delete instance; }
};

The C# code can now write an event handler for the Event event. I left the spelling error in tact, you need to do some work to convert EvenAtrgs to a managed class that derives from EventArgs. Modify the managed Event accordingly.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • That's more a p/invoke style, not the preferred approach with C++/CLI. – Ben Voigt Mar 27 '11 at 20:49
  • This is *not* pinvoked, a public ref class is visible outside an assembly and usable to any managed language. Pah. – Hans Passant Mar 27 '11 at 21:02
  • @Hans: You're not using p/invoke between C# and C++, but between C++/CLI and C++. `Marshal::GetFunctionPointerForDelegate` is a p/invoke API. C++ interop would use `gcroot`. For one thing, your code risks having the delegate and function pointer get freed by the garbage collector. – Ben Voigt Mar 27 '11 at 21:04
  • In fact, you have made no attempt to keep the delegate alive, it becomes unreachable and eligible for garbage collection as soon as the `blahWrapper` constructor returns. -1 – Ben Voigt Mar 27 '11 at 21:09
  • @Hans: Now instead of definitely having a problem, you just likely have a problem. But it's better at least. downvote removed. – Ben Voigt Mar 27 '11 at 21:23
  • Ugh, what likely problem now? Post a complete solution so the OP has something he can actually compile and compare. – Hans Passant Mar 27 '11 at 21:28
  • @Hans: You haven't actually fixed the race condition, you've just made it a little better. If the caller forgets to dispose an instance of `blahWrapper`, the delegate can get collected before the native object using it. Order of collection is undetermined. Not to mention that now you're yanking `instance` out from under (with `delete`) whatever native code you passed it to to make it actually do something useful. The solution is `gcroot`, so the native code can keep the managed delegate alive for as long as it needs it. Which has been in my answer from the very beginning. – Ben Voigt Mar 27 '11 at 21:35
  • Hmya, you get to choose what code you want to crash if you don't stop the unmanaged code from firing events. Your way crashes the managed code when it is shutdown but the event is raised anyway. My way makes it simple, use the unmanaged class destructor to shut it down. Given that the managed code creates the unmanaged object, it must also be the one that controls its lifetime. – Hans Passant Mar 27 '11 at 21:50
  • @Hans: Nothing in my approach will crash. The C# consumer is free to unsubscribe at any time they want to stop receiving events, and then the native code will continue to run perfectly fine, but the event won't be delivered anywhere. Which may or may not be the desired behavior, but it's easy enough to tweak, and is in any case better than causing an access violation in unmanaged code, which is what can happen to yours. Once again, storing the delegate in an instance member does not fix the bug because finalizers do not run in any particular order. – Ben Voigt Mar 27 '11 at 22:02
  • It is free to *not* unsubscribe the event. That's a kaboom. The mistake is that you leave it up to the C# code to create the unmanaged class instance but don't give it any way to destroy it. It's a leak. Finish your code and you'll see this. – Hans Passant Mar 27 '11 at 22:16
  • @Hans: No, that's not kaboom. The event target is kept alive by the `gcroot`. It works exactly the same way as every other event in .NET. Since the question asked for something to interop with an existing native library, I'm led to believe that said library is going to determine when it no longer need the `blah`-derived object, and if that assumption is false, it's up to whatever code already exists to configure and run that native library code to create, attach, detach, and free the `blah`-derived object. – Ben Voigt Mar 27 '11 at 22:58
  • This question wasn't "how do I call native code", it's "I'm using native code, it's "I'm already using native code just fine, except I'm stymied trying to handle its events". Hopefully @Yochai will jump in at some point and clarify what the lifetime of the native object needs to be and whether the library handles deletion, his existing C++/CLI code does, or he wants the new event-management code to do it. I consider the last to be the least likely. – Ben Voigt Mar 27 '11 at 22:59
1

Create a class that inherits from blah and have it take a reference to your managed wrapper in the constructor. Override the Event() method and when it gets called you can just forward that method on to the managed wrapper class instance you are storing.

Note that you can't raise an event from outside of the containing class, so you'll have to either make it a plain delegate or call a helper method on the managed class to raise it for you.

MikeP
  • 7,829
  • 33
  • 34
  • Right, but I'm not sure how to actually reference the managed functions, or even how to create the event in the language's syntax – Yochai Timmer Mar 27 '11 at 19:04