2 votes

Mapping des fonctionnalités spécifiques de C++ vers C++/CLI

Je suis en train d'écrire un wrapper en C++/CLI (VS 2015) pour le code C++ donné afin d'utiliser enfin la dll managée générée en C#. J'ai donc créé une classe managée qui appelle une classe native. Cette classe native est liée au code C++ donné grâce à une méthode factory qui renvoie un unique_ptr<>.

Maintenant, je suis bloqué car le code C++ a un rappel:

Callback(std::function)  

Résultat est de type

std::vector> 

Questions:

  1. Comment puis-je mapper l'argument de rappel std::function<> en C++/CLI?

  2. Où et comment dois-je transformer la structure résultante en C++ (vector de paires) en structure de données .NET (liste de tuples) en termes de rappel pour y accéder en C#?

  3. Est-il possible d'écrire des expressions lambda (avec des collections .NET) qui se mapperont à la structure de données non managée (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;
          });
    
          // faire quelque chose avec l'itérateur it
          // …
    
          cnt++;
    });

    Dois-je donc écrire un wrapper d'expression lambda à cet effet?

1voto

bastian schmick Points 329

Je vais légèrement reformuler vos questions originales pour mieux correspondre à ma solution proposée :

  1. Comment envelopper une méthode C# en tant que std::function<> non managée en C++ pour l'utiliser comme un rappel (callback) ?
  2. Comment convertir les données de la méthode de rappel de C++ (std::vector>) en C# (List>) ?
  3. Est-il possible d'utiliser des expressions lambda C# incluant des variables capturées en tant que méthodes de rappel ?

Voir ci-dessous pour une solution. Veuillez garder à l'esprit qu'elle n'est pas très élégante et ne gère pas les erreurs ou les ressources. Elle répond aux questions ci-dessus comme suit (Inner étant la classe C++ donnée que vous voulez envelopper) :

  1. La méthode de rappel peut être fournie sous la forme d'une Action>> en C# (voir Middle::SetCallback). Elle est appelée par une méthode d'enrobage Middle::WrapCallback. Celle-ci peut être marshalled vers un pointeur de fonction non managé qui est fourni à la classe interne (voir le constructeur).
  2. Je ne connais pas d'aides à la marshalling pour les collections, donc cela se fait simplement en copiant les structures de données respectives (supposées en lecture seule) (voir Middle::WrapCallback). Middle::DoCallback est simplement là pour déclencher le rappel de l'extérieur, donc il doit effectuer cette conversion dans le sens inverse.
  3. Biensûr, c'est possible, mais c'est entièrement à la charge du code client C# utilisant cette classe enveloppe.

    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(stubPointer.ToPointer());
    
        instance->SetCallback(UnmanagedCallback);
    }
    
    void Middle::SetCallback(Action^>^>^ callback)
    {
        handler = callback;
    }
    
    void Middle::DoCallback(List^>^ data)
    {
        Result *buffer = new Result();
    
        for (int i = 0; i < data->Count; i++)
        {
            std::pair *el = new std::pair(data[i]->Item1, data[i]->Item2);
            buffer->push_back(*el);
        }
    
        instance->Callback(*buffer);
    }
    
    void Middle::WrapCallback(const Result &data)
    {
        List^>^ buffer = gcnew List^>();
    
        for (Result::size_type i = 0; i < data.size(); i++)
        {
            buffer->Add(Tuple::Create(data[i].first, data[i].second));
        }
    
        handler->Invoke(buffer);
    }

    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X