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 #?
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
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]);
}
}
}
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);
}