Quiero pasar alrededor de 100 - 10,000 Puntos de un C++ no administrado a C#.
El lado de C++ se ve así:
__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# }Ahora mi lado C# se ve así:
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 */ ); } }¿Cómo necesito modificar mi código para pasar el std::vector de C++ no administrado con todo su contenido a C#?
Siempre que el código administrado no cambie el tamaño del vector, puede acceder al búfer y pasarlo como un puntero con vector.data() (para C++0x) o &vector[0] . Esto da como resultado un sistema de copia cero.
Ejemplo de API de 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; }Llamador:
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); } } #endregionImplementé esto usando el contenedor C++ CLI. C++ CLI es uno de los tres enfoques posibles para la interoperabilidad de C++ C#. Los otros dos enfoques son P/Invoke y COM. (He visto a algunas buenas personas recomendar el uso de C++ CLI sobre los otros enfoques)
Para ordenar la información del código nativo al código administrado, primero debe envolver el código nativo dentro de una clase administrada de la CLI de C++. Cree un nuevo proyecto para que contenga código nativo y su contenedor C++ CLI. Asegúrese de habilitar el modificador del compilador /clr para este proyecto. Compile este proyecto en una dll. Para usar esta biblioteca, simplemente agregue su referencia dentro de C# y realice llamadas contra ella. Puede hacer esto si ambos proyectos están en la misma solución.
Aquí están mis archivos fuente para un programa simple para ordenar un std::vector<double> desde el código nativo al código administrado de C#.
1) Proyecto EntityLib (C++ CLI dll) (Código nativo con contenedor)
Archivo NativeEntity.h
#pragma once #include <vector> class NativeEntity { private: std::vector<double> myVec; public: NativeEntity(); std::vector<double> GetVec() { return myVec; } };Archivo 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 }Archivo ManagedEntity.h (clase contenedora)
#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 }; }Archivo 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) Proyecto SimpleClient (C# exe)
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]); } } }Podría pensar en más de una opción, pero todas incluyen copiar los datos de la matriz de todos modos. Con [out] parámetros podrías probar:
código 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); }código 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); }