emmenlau
emmenlau

Reputation: 1039

Best way to signal from (pure) native code via C++/CLI to (pure) managed code?

I would like to trigger a function call in (pure) managed code, whenever a certain trigger is received in native code. I do not specifically a specific kind of trigger, it can be a callback, virtual method override, signal, event, or something along those lines...

My software stack is a large native code base in C++, then a thin C++/CLI wrapper, and finally a C# application with WPF GUI. I can edit the full software stack. The C++ code base is event-driven using signals/slots, like libevent or Qt employ them. Now, consider a C++ slot viewUpdateRequested() that should trigger a re-rendering in the C# WPF GUI.

I found that I can get a trigger from C++/CLI to C#, by creating an event handler in C++/CLI, that C# can subscribe to. Then I can trigger the event in C++/CLI, and even pass some arguments. However, that gets me only half the way, from C++/CLI upwards in the software stack. I still need to go from native to C++/CLI.

I'm under the impression that in pure native code, I can not emit a C# event, because that would require compiling with /clr, and that in turn makes the class a shallow dummy that can not hold any native members, which finally breaks my signal/slot implementation. Am I somehow missing something here?

I did find some way to go from native code to C++/CLI, by passing a function pointer from C++/CLI to native code. But this leads to the following code stack, that looks ugly to me:

native code -(function pointer)-> C++/CLI -(event handler)-> C#/WPF

So what are my options to get the event or trigger all the way from pure native code all the way to C#?

Is there something I'm overlooking? What would be a "clean design" solution?

Upvotes: -1

Views: 100

Answers (1)

JonasH
JonasH

Reputation: 36629

It was some time ago I coded c++/cli, and even longer since I coded pure c++. But assuming your c++/cli project references your native project you should be able to do something like this.

  1. Create an abstract class in your c++ project, something like
class MyNativeInterface{
public: 
    virtual void RaiseEvent() = 0;
  1. Implement this class in your C++/Cli project, that just forward the call to a managed interface:
public interface class IMyManagedInterface{
    void RaiseEvent();
}

class MyNativeClass : MyNativeInterface{
    gcroot<IMyManagedInterface^> managed;
public:
    MyNativeClass(IMyManagedInterface^ managedReference){
        managed = managedReference;
    }
    void RaiseEvent(){
        managed->RaiseEvent();
    }
}
  1. The c# side could look something like this:
public class MyManagedClass : IMyManagedInterface{
    public event EventHandler MyEvent ;
    public void RaiseEvent()=> MyEvent?.Invoke(this, EventArgs.Empty);
}

The difficult part is often how to create/connect all the objects to each other, but this depends on your overall architecture.

because that would require compiling with /clr, and that in turn makes the class a shallow dummy that can not hold any native members, which finally breaks my signal/slot implementation.

I'm not really a c++/cli expert, but I was able to turn on the /clr flag for some c++ projects with no or minimal changes. This allows both managed (ref) classes to hold pointers to native classes and native classes to hold references to managed classes (with gcroot<>). In my experience it made interop quite a bit easier compared to our original plan with a separate c++/cli interop layer. But our goal was to migrate to C#, so the .Net dependency was not a problem. I don't know what you mean by "shallow dummy", nor your "signal/slot implementation", but it might be worth an hour or two just trying to turn on the flag and see if things break.

Upvotes: 1

Related Questions