essay on programming languages, computer science, information techonlogies and all.

Thursday, May 10, 2018

C++/CLI to expose C++ code to C#

Define interface with C#. And also base class if needed.

    // IFoo.cs
    public interface IFoo
    {
        string Bar(int arg1, string arg2);
    }

    // FooBase.cs 
    public abstract class FooBase : IFoo
    {
        public virtual string Bar(int arg1, string arg2)
        {
            return arg1.ToString() + arg2;
        }
    }
Now C++/CLI code. First make Native C++ class - NativeFoo - that suit whatever need. Then define managed class Foo that sit between C++ and C# code. Note how the Foo::Bar() convert C# String to C++ std::string and vice versa.

// Foo.hpp
#include 

class NativeFoo
{
public:
	std::string Bar(int arg1, const std::string& arg2)
	{
		return arg2 + std::to_string(arg1);
	}
};

public ref class Foo : public FooBase
{
private:
	NativeFoo* m_Native;

public:
	Foo() 
		: m_Native( new NativeFoo )  
	{}

	~Foo()  
	{
		delete m_Native;
	}

	String^ Bar(int arg1, String^ arg2) override
	{
		std::string sarg2 = msclr::interop::marshal_as< std::string >(arg2);
		std::string bared = m_Native->Bar(arg1, sarg2);
		String^ ret = msclr::interop::marshal_as< String^ >(bared);
		return ret;
	}
};
Then the managed Foo can be created and Bar() can be used like any C# class.

  IFoo f = new Foo();
  string ret = f.Bar(42, "The answer is ");


Now callback from C++ to C#. First define callback in C# using delegate - OnFooFn.

    public delegate void OnFooFn(IFoo foo, string msg);

    public interface IFoo
    {
        OnFooFn OnFoo
        {
            get;
            set;
        }
    }
Then FooBase has a helper to call callback - FireOnFoo()

    public abstract class FooBase : IFoo
    {
...
        public OnFooFn OnFoo { get; set; }

        public void FireOnFoo( string msg)
        {
            if (OnFoo != null)
            {
                OnFoo( this, msg);
            }
        }
    }
Now native C++ class has a callback fn that will callback whatever call Bar().

class NativeFoo
{
public:
	std::string Bar(int arg1, const std::string& arg2)
	{
		m_FooFn("What is the question ?");
		return arg2 + std::to_string(arg1);
	}

	using CallbackFooFn = std::function< void(std::string) >;
	void SetCallbackFoo(CallbackFooFn fn) { m_FooFn = fn; }

private:
	CallbackFooFn m_FooFn = [](const std::string& arg) {};  // default dummy callback
};
Managed class can't set the above callback directly. It needs a bridge - CallbackBridgeFoo. Using this class, the C++ callback can be made through C#.

class CallbackBridgeFoo
{
private:
	NativeFoo* m_Native;
	gcroot< FooBase^ > m_CLR;  // FooBase has FireOnFoo()

public:
	CallbackBridgeFoo(NativeFoo* native, FooBase^ clr)
	{
		m_Native = native;
		m_CLR = clr;

		m_Native->SetCallbackFoo([this](const std::string& msg)
		{
			String^ smsg = msclr::interop::marshal_as< String^ >(msg);
			this->m_CLR->FireOnFoo(smsg);
		});
	}
};

public ref class Foo : public FooBase
{
private:
	NativeFoo* m_Native;
	CallbackBridgeFoo* m_Bridge;

public:
	Foo() 
		: m_Native( new NativeFoo )  
	{
		m_Bridge = new CallbackBridgeFoo(m_Native, this);
	}

	~Foo()  
	{
		delete m_Bridge;
		delete m_Native;
	}
...
};

Finally, C# can enjoy the C++ callback.

            IFoo f = new Foo();
            f.OnFoo = (sender, msg) => { Console.WriteLine(msg); }; 
            string ret = f.Bar(42, "The answer is ");   

No comments: