2

I have a piece of code written in (native) c++ and now I have to integrate it with a C# GUI application. I already built the wrapper in c++/cli, and I can successfully communicate with the native piece of code through this wrapper. The problem, however, is that this native c++ code uses the Observer Pattern, and I can't make it to work. I had a look at this similar question but that solves the notification from the c++/cli component up to the c# application through a delegate. I can't observe the native from to the wrapper instead.

Here's a skeleton of what I have. I'll leave some classes implementations since they are not useful in this scope.


observer_interface.h

This is a simple interface that each observer needs to implement. This is in the native part.

class observer_interface
{
public:
    virtual void implement_me(observe_me *o) = 0;
}

observe_me.h and .cpp

This is the native piece of code. It maintains a list of observers, and have two public methods that allow the registration/deregistration of observers. It has another public method that I can call.

class observe_me
{
protected:
    vector<observer_interface*> observers;
    void notifySomething() {
        for (size_t i = 0; i < observers.size(); i++)
            observers[i].implmement_me(this);
    }

public:
    void registerObserver(observer_interface*);
    void deregisterObserver(observer_interface*);
    int thisIsFine() {
        return 12345;
    }
}

observerWrapper.h and .cpp

This is the wrapper in c++/cli, and this is the class that needs to observe the observe_me class,

public ref class observerWrapper
{
event System::EventHandler^ implement_me_event;

protected:
    observe_me *o;

public:
    // Constructor & destructor
    observerWrapper() {
        o = new observe_me();
        o->registerObserver(this); // <--- this gives me error
    }
    ~observerWrapper() {
        delete o;
    }

    // Let's call the native method
    int thisIsFine() {
        return o->thisIsFine();
    };

    // This should be called by the native c++
    void implement_me() {
        implement_me_event(this, System::EventArgs::Empty);
    }
}

frmMain.cs

This is the GUI that should finally receive the notification through the event in the wrapper.

public partial class frmMain : Form
{
    observerWrapper o = new observerWrapper();

    public frmMain()
    {
        InitializeComponent();
        o.implement_me_event += implement_me;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show(o.thisIsFine());
    }

    public void implement_me(object sender, EventArgs args)
    {
        MessageBox.Show("Finally solved!");
    }
}

I can't implement the observer_interface in the observerWrapper, nor I can register it as observer without making it observer_interface compliant. How do I solve?

Community
  • 1
  • 1
xmas79
  • 5,060
  • 2
  • 14
  • 35
  • 1
    The compiler stops you from shooting your leg off. The value of `this` randomly changes when the garbage collector compacts the heap. But the GC cannot update the pointer in observe_me::observers, it does not know it exists. Your observer object must be pinned or must be native. Pinning for that long is very unwise. Consider using `gcroot<>` to call an internal method of observerWrapper when the callback occurs. – Hans Passant Dec 01 '16 at 13:12
  • @HansPassant Thanks for your suggestion, but I really fail to see how it can be used in this case and how it can help me.Currently I cannot get the callback from the unmanaged code back to the wrapper, because I cannot find a way to register the wrapper as an observer. I was looking a way to solve that problem first (is possible to solve this in first place?). Do I have to abandon the idea and change the structure of the projects? Thanks. – xmas79 Dec 01 '16 at 15:13

1 Answers1

0

I was looking for an answer also as it's my first time working with C++/CLI. So all this native managed and unmanaged combo was bit confusing to me as i come from (standard) C++.

In a nutshell: Ref classes cannot inherit non-ref classes. And non-ref classes cannot have normal reference or pointer to a ref class because it's in garbage collected heap, which relocates objects. Once it's relocated, stored reference/pointer will keep pointing to the same place which contains something else. This is where gcroot comes.

In your C++/CLI wrapper you need to have non-ref class that will implement observer pattern. That class should somehow have or obtain managed reference to the ref class which it can keep with gcroot member. This gcroot member reference will be the bridge between unmanaged and managed code. Here's a working code.

There is also object pinning in gc heap but you cannot keep the object pinned for the whole life of the process, as pinning have to be done in some function on stack. I imagine that sort of thing could be possible but it's bad design and bad performance.

Native dll written in pure C++ (unmanaged), observable class and observer interface:

// PureCppDll.h
#include "stdafx.h"
#ifdef PURECPPDLL_EXPORTS
    #define PURECPPDLL_API __declspec(dllexport)
#else
    #define PURECPPDLL_API __declspec(dllimport)
#endif

struct PURECPPDLL_API Feature
{
    int val;
};

class PURECPPDLL_API IFeatureObserver
{
    public:
        virtual ~IFeatureObserver() = default;

        virtual void onFeatureUpdate(Feature* feature) = 0; // observer takes ownership of the feature
};


class PURECPPDLL_API NativeDllAPI
{
    public:
        virtual ~NativeDllAPI() = default;

        virtual void addFeatureObserver(IFeatureObserver* observer) = 0;
        virtual void removeFeatureObserver(IFeatureObserver* observer) = 0;
};

class PURECPPDLL_API NativeDllApiFactory
{
    public:
        static NativeDllAPI* create();
};

C++/CLI dll code, wiring native (unmanaged) dll and C#:

// FeatureObserver.h
#pragma once

#include "PureCppDll.h"
#include <vcclr.h>


namespace CppCLI
{
    ref class CppCLIWrapper;

    class FeatureObserver : public IFeatureObserver
    {
        public:
            FeatureObserver(CppCLIWrapper^ cppcliwrapper);
            ~FeatureObserver();

            void onFeatureUpdate(Feature* feature) override;

        private:
            gcroot<CppCLIWrapper^> m_cppCliWrapper;
    };

}
//FeatureObserver.cpp
#include "stdafx.h"
#include "FeatureObserver.h"
#include "CppCLIWrapper.h"

namespace CppCLI
{
    FeatureObserver::FeatureObserver(CppCLIWrapper^ cppcliwrapper)
        : m_cppCliWrapper{ cppcliwrapper }
    {

    }

    FeatureObserver::~FeatureObserver()
    {
        System::Console::WriteLine("~FeatureObserver : dieded.");
    }

    void FeatureObserver::onFeatureUpdate(Feature * feature)
    {
        System::Console::WriteLine("FeatureObserver : onFeatureUpdate.");
        m_cppCliWrapper->NotifyFeatureChange(feature);
    }
}
// CppCLIWrapper.h
#pragma once

#include "ManagedObject.h"
#include "PureCppDll.h"
#include "FeatureObserver.h"

#include <memory>


using namespace System;

namespace CppCLI 
{
    public ref class CppCLIWrapper
    {
        public:
            CppCLIWrapper();
            ~CppCLIWrapper();
            !CppCLIWrapper();

            // slot:
            void NotifyFeatureChange(Feature* feature);

            // signal:
            event EventHandler<MyFeatureEventArgs^>^ FeatureChangedEvent;

        private:
            NativeDllAPI* m_nativeDllApi;
            FeatureObserver* m_featureObserver;
    };
}
//CppCLIWrapper.cpp
#include "stdafx.h"
#include "CppCLIWrapper.h"

using namespace System;

namespace CppCLI
{
    CppCLIWrapper::CppCLIWrapper()
        : m_nativeDllApi { NativeDllApiFactory::create() }
        , m_featureObserver{ new FeatureObserver{this} }
    {
        m_nativeDllApi->addFeatureObserver(m_featureObserver);
    }

    CppCLIWrapper::~CppCLIWrapper()
    {
        if (m_nativeDllApi != nullptr)
        {
            m_nativeDllApi->removeFeatureObserver(m_featureObserver);
            delete m_nativeDllApi;
        }

        if (m_featureObserver != nullptr)
        {
            delete m_featureObserver;
        }

        System::Console::WriteLine("~CppCLIWrapper : dieded");
    }

    CppCLIWrapper::!CppCLIWrapper()
    {
        if (m_nativeDllApi != nullptr)
        {
            m_nativeDllApi->removeFeatureObserver(m_featureObserver);
            delete m_nativeDllApi;
        }

        if (m_featureObserver != nullptr)
        {
            delete m_featureObserver;
        }

        System::Console::WriteLine("!CppCLIWrapper : finalized");
    }

    void CppCLIWrapper::NotifyFeatureChange(Feature * feature)
    {
        System::Console::WriteLine("CppCLIWrapper : NotifyFeatureChange {0}", feature->val);

        auto myfeat = gcnew MyFeature{};
        auto myfeatargs = gcnew MyFeatureEventArgs{};

        myfeat->Value = feature->val;
        myfeatargs->Feature = myfeat;

        FeatureChangedEvent(this, myfeatargs);
        delete feature;
    }
}

The DataModel class library in C# for event raising from C++/CLI to C#:

using System;

namespace DataModel
{
    public class MyFeature
    {
        public int Value { get; set; }
    }

    public class MyFeatureEventArgs : EventArgs
    {
        public MyFeature Feature { get; set; }
    }
}

Finally, C# console application:

// Test.cs
using System;
using CppCLI;


namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting C++/CLI wrapper for native code.");
            Console.WriteLine("Thread will start producing and notifying Feature objects.");
            Console.WriteLine("Press any key to stop.");

            CppCLIWrapper cli = new CppCLIWrapper();
            cli.FeatureChangedEvent += onFeatureChangedEvent;

            Console.ReadKey();
            Console.WriteLine("Program ending...");
        }
    }
}

I know it's an old question, I hope this helps someone.

Here's also the code for NativeDllApi class, in case you want to test it.

// PureCppDll.cpp

#include "stdafx.h"
#include "PureCppDll.h"

#include <thread>
#include <mutex>

class NativeDllAPIImpl : public NativeDllAPI
{
public:
    NativeDllAPIImpl();
    ~NativeDllAPIImpl();

    // Inherited via NativeDllAPI
    virtual void addFeatureObserver(IFeatureObserver* observer) override;
    virtual void removeFeatureObserver(IFeatureObserver* observer) override;

    void run();

private:
    void NotifyObservers();

private:
    std::mutex m_mutex;
    int m_value;
    bool m_work;
    std::thread m_worker;

    std::vector<IFeatureObserver*> m_observers;
};

NativeDllAPI* NativeDllApiFactory::create()
{
    return new NativeDllAPIImpl{};
}

NativeDllAPIImpl::NativeDllAPIImpl()
    : NativeDllAPI {}
    , m_value{ 42 }
    , m_work{ true }
    , m_worker{&NativeDllAPIImpl::run, this}
{

}

NativeDllAPIImpl::~NativeDllAPIImpl()
{
    std::cout << "~NativeDllAPIImpl : destroying...\n";
    m_work = false;

    if (m_worker.joinable())
    {
        std::cout << "~NativeDllAPIImpl : join thread....\n";
        m_worker.join();
    }

    std::cout << "~NativeDllAPIImpl : dieded...\n";
}

void NativeDllAPIImpl::addFeatureObserver(IFeatureObserver* observer)
{
    std::cout << "NativeDllAPIImpl : addFeatureObserver\n";
    std::lock_guard<std::mutex> lock{ m_mutex };
    m_observers.push_back(observer);
}

void NativeDllAPIImpl::removeFeatureObserver(IFeatureObserver* observer)
{
    std::cout << "NativeDllAPIImpl : removeFeatureObserver\n";
    std::lock_guard<std::mutex> lock{ m_mutex };
    m_observers.erase(std::remove(m_observers.begin(), m_observers.end(), observer), m_observers.end());
}

void NativeDllAPIImpl::run()
{
    std::cout << "NativeDllAPIImpl : Running...\n";

    while (m_work)
    {
        NotifyObservers();
        Sleep(3000);
    }
}

void NativeDllAPIImpl::NotifyObservers()
{
    std::cout << "NativeDllAPIImpl : Notifying observers with value: " << m_value << '\n';
    std::lock_guard<std::mutex> lock{ m_mutex };
    for (auto observer : m_observers)
    {
        observer->onFeatureUpdate(new Feature{ m_value });
    }

    ++m_value;
}
Nenad
  • 335
  • 1
  • 7