2

I’m writing a wrapper in C++/CLI (VS 2015) for given C++ code to use finally the generated managed dll in C#. Therefore I created a managed class which calls a native class. This native class links to the given C++ code due to a factory method which returns a unique_ptr<>.

Now I got stuck because the C++ code has a callback:

Callback(std::function<void(const Result &>)  

Result is of type

std::vector<std::pair<float, float>> 

Questions:

  1. How can I map the callback argument std::function<> in C++/CLI?
  2. Where & how do I have to transform the C++ resulting structure (vector of pairs) to .NET data structure (list of tuples) in terms of the callback to access it in C#?

  3. Is it possible to write lambda expressions (with .NET collections) that will map to the unmanaged data structure (vector)?

    unsigned int cnt = 0;
    
    nativeClass->Callback([&cnt] (const Result &v) { 
     auto it = d::max_element(v.begin(), v.end(), 
          [](const Pair &a, const Pair &b) {
               return a.second < b.second;
          });
    
          // do something with iterator it
          // …
    
          cnt++;
    });
    

    Do I have to write a lambda expression wrapper therefore?

fantaghiro
  • 21
  • 2

1 Answers1

1

I´ll sligthly rephrase your original questions to better fit my proposed solution:

  1. How do I wrap a C# method as an unmanaged C++ std::function<> for use as a callback?
  2. How do I convert the callback method´s data from C++ (std::vector<std::pair<>>) to C# (List<Tuple<>>)?
  3. Is it possible to use C# lambda expressions including captured variable as callback methods?

See below for a solution. Please keep in mind that it is not pretty and does not do any error or resource handling. It answers the above questions as follows (Inner being the given C++ class you want to wrap):

  1. The callback method can be provided as a C# Action<List<Tuple<>>> (see Middle::SetCallback). It gets called by a wrapper method Middle::WrapCallback. This in turn can be marshalled to an unmanaged function pointer which is provided to the inner class (see constructor).
  2. I am not aware of any marshalling helpers for collections, so this is simply done by copying the respective (assumed to be read only) data structures (see Middle::WrapCallback). Middle::DoCallback is simply there to trigger the callback from outside, so it needs to do that conversion in the reverse direction.
  3. Sure, that´s possible, but it is entirely up to the C# client code using this wrapper.
using namespace System;
using namespace System::Collections::Generic;
using namespace System::Runtime::InteropServices;

namespace Wrapper {
    delegate void CallbackPointer(const Result &data);
    typedef void(*UnmanagedPointer)(const Result &data);

    Middle::Middle()
    {
        instance = new Inner();

        CallbackPointer^ managed = gcnew CallbackPointer(this, &Middle::WrapCallback);
        IntPtr stubPointer = Marshal::GetFunctionPointerForDelegate(managed);
        UnmanagedPointer UnmanagedCallback = static_cast<UnmanagedPointer>(stubPointer.ToPointer());

        instance->SetCallback(UnmanagedCallback);
    }

    void Middle::SetCallback(Action<List<Tuple<float, float>^>^>^ callback)
    {
        handler = callback;
    }

    void Middle::DoCallback(List<Tuple<float, float>^>^ data)
    {
        Result *buffer = new Result();

        for (int i = 0; i < data->Count; i++)
        {
            std::pair<float, float> *el = new std::pair<float, float>(data[i]->Item1, data[i]->Item2);
            buffer->push_back(*el);
        }

        instance->Callback(*buffer);
    }

    void Middle::WrapCallback(const Result &data)
    {
        List<Tuple<float, float>^>^ buffer = gcnew List<Tuple<float, float>^>();

        for (Result::size_type i = 0; i < data.size(); i++)
        {
            buffer->Add(Tuple::Create(data[i].first, data[i].second));
        }

        handler->Invoke(buffer);
    }
}
bastian schmick
  • 464
  • 3
  • 11