Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cpp / Cli Fire an Event

I have a cpp project, a cpp cli project and a c# win forms project. I want to fire a method from my native cpp code and catch it in c# project. How can i do this?

like image 753
Seçkin Durgay Avatar asked Mar 29 '12 11:03

Seçkin Durgay


1 Answers

There can be multiple approaches to answer this question, because the dependency requirement between those projects is important. I will try to answer for the most common (i guess) case: in which you already have a native C++ library and you want to use that library in a C# application. In that scenario the C# project depends on the native library project. In such a case you can utilize a gateway cli/c++ library to transform native c++ events to .NET events.

Here is a complete code sample, but before that, please note:

  • It may not be the shortest solution, but it works fine. Also it can provide more control on transforming native data to .net types.
  • I used this approach in VS 2005. I dont know if there is a better instrument in newer versions of VS for that specific interoperability purpose.
  • If your native event is triggered from a thread other than the GUI thread, then beware of that.


The Native Library:

#ifndef _NATIVE_CODE_H_
#define _NATIVE_CODE_H_

//NativeCode.h
//A simple native library which emits only one event.

#include <stdlib.h>
#include <iostream>
using namespace std;

#define NATIVELIBRARY_API __declspec(dllexport)

//An argument class to wrap event parameters
class NativeEventArgs{
public:
    //a 32bit integer argument
    //any other primitives can be here, just be careful about the byte size
    int argInt32;

    //null terminated ascii string
    const char* argString;

    //null terminated wide/unicode string
    const wchar_t* argWString; 
};

//A simple mechanism to fire an event from native code.
//Your library may have a DIFFERENT triggering mechanism (e.g. function pointers)
class INativeListener
{
public:
    virtual void OnEvent(const NativeEventArgs& args)=0;
};

//The actual native library code, source of native events
class NATIVELIBRARY_API NativeCode
{
public:
    NativeCode()
        :theListener_(NULL)
    {}

    //Listener registration method
    void registerListener(INativeListener* listener) {
        theListener_ = listener;
    }

    //this is the very first source of the event
    //native code emits the event via the listener mechanism
    void eventSourceMethod() {
        //... other stuff

        //fire the native event to be catched
        if(theListener_){
            //prepare event parameters
            NativeEventArgs args;
            wstring wstr(L"A wide string");
            string str("A regular string");

            //build-up the argument object
            args.argInt32 = 15;
            args.argString = str.c_str();
            args.argWString = wstr.c_str();

            //fire the event using argument
            theListener_->OnEvent( args );
        }
    }

private:

    //native code uses a listener object to emit events
    INativeListener* theListener_;
};

#endif


Gateway Library Sample:

//GatewayCode.h
//GatewayLibrary is the tricky part,
//Here we listen events from the native library
//and propagate them to .net/clr world

#ifndef _GATEWAY_CODE_H_
#define _GATEWAY_CODE_H_

#include "../NativeLibrary/NativeCode.h" //include native library
#include <vcclr.h> //required for gcroot
using namespace System;
using namespace System::Runtime::InteropServices;

namespace GatewayLibrary{

    //.net equvelant of the argument class
    public ref class DotNetEventArg{
    internal:

        //contructor takes native version of argument to transform
        DotNetEventArg(const NativeEventArgs& args) {

            //assign primitives naturally
            argInt32 = args.argInt32;

            //convert wide string to CLR string
            argWString = Marshal::PtrToStringUni( IntPtr((void*)args.argWString) );

            //convert 8-bit native string to CLR string
            argString = Marshal::PtrToStringAnsi( IntPtr( (void*)args.argString) );

            //see Marshal class for rich set of conversion methods (e.g. buffers)
        }
    private:
        String^ argString;
        String^ argWString;
        Int32 argInt32;

    public:
        //define properties
        property String^ ArgString {
            String^ get() {
                return argString;
            }
        }

        property String^ ArgWString {
            String^ get() {
                return argWString;
            }
        }

        property Int32 ArgInt32 {
            Int32 get() {
                return argInt32;
            }
        }
    };

    //EventGateway fires .net event when a native event happens.
    //It is the actual gateway class between Native C++ and .NET world.
    //In other words, It RECEIVES NATIVE events, TRANSFORMS/SENDS them into CLR.
    public ref class EventGateway {
    public:

        //ctor, its implementation placed below
        EventGateway();

        //required to clean native objects
        ~EventGateway();
        !EventGateway();

        //the SENDER part
        //.net event stuff defined here
        delegate void DotNetEventHandler(DotNetEventArg^ arg);
        event DotNetEventHandler^ OnEvent;

    private:
        //our native library code
        //notice you can have pointers to native objects in ref classes.
        NativeCode* nativeCode_; 

        //the required device to listen events from the native library
        INativeListener* nativeListener_; 

    internal: //hide from .net assembly

        //the RECEIVER part, called when a native event received
        void OnNativeEvent(const NativeEventArgs& args){
            //you can make necessary transformation between native types and .net types

            //create .net argument using native argument
            //required conversion is done by DotNetEventArg class
            DotNetEventArg^ dotNetArgs = gcnew DotNetEventArg(args);

            //fire .net event
            OnEvent( dotNetArgs );
        }

    };
}

//A concrete listener class. we need this class to register native library events.
//Its our second gateway class which connects Native C++ and CLI/C++
//It basically gets events from NativeLibary and sends them to EventGateway
class NativeListenerImp : public INativeListener {
public:
    NativeListenerImp(gcroot<GatewayLibrary::EventGateway^> gatewayObj ){
        dotNetGateway_ = gatewayObj;
    }

    //this is the first place we know that a native event has happened
    virtual void OnEvent(const NativeEventArgs& args) {

        //inform the .net gateway which is responsible of transforming native event to .net event
        dotNetGateway_->OnNativeEvent(args);
    }

private:
    //class member to trigger .net gateway.
    //gcroot is required to declare a CLR type as a member of native class.
    gcroot<GatewayLibrary::EventGateway^> dotNetGateway_;
};

////ctor and dtors of EventGateway class
GatewayLibrary::EventGateway::EventGateway()
{
    nativeCode_ = new NativeCode();

    //note; using 'this' in ctor is not a good practice
    nativeListener_ = new NativeListenerImp(this);

    //register native listener
    nativeCode_->registerListener(nativeListener_);
}

GatewayLibrary::EventGateway::~EventGateway()
{
    //call the non-deterministic destructor
    this->!EventGateway();
}

GatewayLibrary::EventGateway::!EventGateway()
{
    //clean up native objects
    delete nativeCode_;
    delete nativeListener_;
}

#endif


And the final application in C# (or in any other .net language):

//Program.cs
//C# the final evet consumer application

using System;
using System.Collections.Generic;
using System.Text;
using GatewayLibrary;

namespace SharpClient
{
    class Program
    {
        static void Main(string[] args)
        {
            //create the gateway
            EventGateway gateway = new EventGateway();

            //listen on .net events using the gateway
            gateway.OnEvent += new EventGateway.DotNetEventHandler(gateway_OnEvent);

        }

        static void gateway_OnEvent( DotNetEventArg args )
        {
            //use the argument class
            Console.WriteLine("On Native Event");
            Console.WriteLine(args.ArgInt32);
            Console.WriteLine(args.ArgString);
            Console.WriteLine(args.ArgWString);
        }
    }
}
like image 91
xaero99 Avatar answered Sep 21 '22 19:09

xaero99