web-dev-qa-db-fra.com

Passer un vecteur / tableau de C ++ non managé à C #

Je veux transmettre environ 100 à 10 000 points d'un C++ non géré à C #.

Le côté C++ ressemble à ceci:

__declspec(dllexport) void detect_targets( char * , int  , /* More arguments */ )
{
    std::vector<double> id_x_y_z;
    // Now what's the best way to pass this vector to C#
}

Maintenant, mon côté C # ressemble à ceci:

using System;
using System.Runtime.InteropServices;

class HelloCpp
{

    [DllImport("detector.dll")]

    public static unsafe extern void detect_targets( string fn , /* More arguments */ );

    static void Main()
    {
        detect_targets("test.png" , /* More arguments */ );
    }
}

Comment dois-je modifier mon code afin de passer le vecteur std :: du C++ non managé avec tout son contenu au C #?

16
nali

Tant que le code managé ne redimensionne pas le vecteur, vous pouvez accéder au tampon et le passer en tant que pointeur avec vector.data() (pour C++ 0x) ou &vector[0]. Il en résulte un système sans copie.

Exemple d'API C++:

#define EXPORT extern "C" __declspec(dllexport)

typedef intptr_t ItemListHandle;

EXPORT bool GenerateItems(ItemListHandle* hItems, double** itemsFound, int* itemCount)
{
    auto items = new std::vector<double>();
    for (int i = 0; i < 500; i++)
    {
        items->Push_back((double)i);
    }

    *hItems = reinterpret_cast<ItemListHandle>(items);
    *itemsFound = items->data();
    *itemCount = items->size();

    return true;
}

EXPORT bool ReleaseItems(ItemListHandle hItems)
{
    auto items = reinterpret_cast<std::vector<double>*>(hItems);
    delete items;

    return true;
}

Votre interlocuteur:

static unsafe void Main()
{
    double* items;
    int itemsCount;
    using (GenerateItemsWrapper(out items, out itemsCount))
    {
        double sum = 0;
        for (int i = 0; i < itemsCount; i++)
        {
            sum += items[i];
        }
        Console.WriteLine("Average is: {0}", sum / itemsCount);
    }

    Console.ReadLine();
}

#region wrapper

[DllImport("Win32Project1", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
static unsafe extern bool GenerateItems(out ItemsSafeHandle itemsHandle,
    out double* items, out int itemCount);

[DllImport("Win32Project1", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
static unsafe extern bool ReleaseItems(IntPtr itemsHandle);

static unsafe ItemsSafeHandle GenerateItemsWrapper(out double* items, out int itemsCount)
{
    ItemsSafeHandle itemsHandle;
    if (!GenerateItems(out itemsHandle, out items, out itemsCount))
    {
        throw new InvalidOperationException();
    }
    return itemsHandle;
}

class ItemsSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    public ItemsSafeHandle()
        : base(true)
    {
    }

    protected override bool ReleaseHandle()
    {
        return ReleaseItems(handle);
    }
}

#endregion
25
Mitch

J'ai implémenté cela en utilisant le wrapper CLI C++. CLI C++ est l'une des trois approches possibles pour l'interopérabilité C++ C #. Les deux autres approches sont P/Invoke et COM. (J'ai vu quelques bonnes personnes recommander d'utiliser C++ CLI par rapport aux autres approches)

Afin de rassembler les informations du code natif au code managé, vous devez d'abord envelopper le code natif dans une classe gérée CLI C++. Créez un nouveau projet pour contenir le code natif et son wrapper CLI C++. Assurez-vous d'activer le /clr commutateur du compilateur pour ce projet. Générez ce projet en une DLL. Pour utiliser cette bibliothèque, ajoutez simplement sa référence dans C # et faites des appels contre elle. Vous pouvez le faire si les deux projets sont dans la même solution.

Voici mes fichiers source pour un programme simple pour rassembler un std::vector<double> du code natif au code géré C #.

1) Project EntityLib (DLL CLI C++) (Code natif avec wrapper)

Fichier NativeEntity.h

#pragma once

#include <vector>
class NativeEntity {
private:
    std::vector<double> myVec;
public:
    NativeEntity();
    std::vector<double> GetVec() { return myVec; }
};

Fichier NativeEntity.cpp

#include "stdafx.h"
#include "NativeEntity.h"

NativeEntity::NativeEntity() {
    myVec = { 33.654, 44.654, 55.654 , 121.54, 1234.453}; // Populate vector your way
}

File ManagedEntity.h (Classe Wrapper)

#pragma once

#include "NativeEntity.h"
#include <vector>
namespace EntityLibrary {
    using namespace System;

    public ref class ManagedEntity {
    public:
        ManagedEntity();
        ~ManagedEntity();

        array<double> ^GetVec();
    private:
        NativeEntity* nativeObj; // Our native object is thus being wrapped
    };

}

Fichier ManagedEntity.cpp

#include "stdafx.h"
#include "ManagedEntity.h"

using namespace EntityLibrary;
using namespace System;


ManagedEntity::ManagedEntity() {
    nativeObj = new NativeEntity();
}

ManagedEntity::~ManagedEntity() {
    delete nativeObj;

}

array<double>^ ManagedEntity::GetVec()
{
    std::vector<double> tempVec = nativeObj->GetVec();
    const int SIZE = tempVec.size();
    array<double> ^tempArr = gcnew array<double> (SIZE);
    for (int i = 0; i < SIZE; i++)
    {
        tempArr[i] = tempVec[i];
    }
    return tempArr;
}

2) Projet SimpleClient (exe C #)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EntityLibrary;

namespace SimpleClient {

    class Program {
        static void Main(string[] args) {
            var entity = new ManagedEntity();
            for (int i = 0; i < entity.GetVec().Length; i++ )
                Console.WriteLine(entity.GetVec()[i]);
        }
    }
}
11
The Vivandiere

Je pourrais penser à plus d'une option, mais toutes incluent de toute façon la copie des données du tableau. Avec [out] paramètres, vous pouvez essayer:

Code C++

__declspec(dllexport) void __stdcall detect_targets(wchar_t * fn, double **data, long* len)
{
    std::vector<double> id_x_y_z = { 1, 2, 3 };

    *len = id_x_y_z.size();
    auto size = (*len)*sizeof(double);

    *data = static_cast<double*>(CoTaskMemAlloc(size));
    memcpy(*data, id_x_y_z.data(), size);
}

Code C #

[DllImport("detector.dll")]
public static extern void detect_targets(
    string fn, 
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] out double[] points, 
    out int count);

static void Main()
{
    int len;
    double[] points;

    detect_targets("test.png", out points, out len);
}
7
Nikolay