2015-07-14 13 views
8

Voglio passare circa 100 - 10.000 punti da un C++ a C# non gestito.Passaggio di un vettore/array da C++ non gestito a C#

Il C++ lato si presenta così:

__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# 
} 

Ora il mio C# lato assomiglia a questo:

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 */); 
    } 
} 

Come devo modificare il mio codice al fine di superare lo std :: vector dal C++ non gestito con tutto il suo contenuto in C#?

+0

Così si vuole restituire il vettore fro la funzione? – deviantfan

+0

Come mai in C++ la tua funzione restituisce 'void' ma in C# restituisce' int'? Inoltre, sembra che tu stia cercando di passare un vettore/array * da * C# * a * C++, e non viceversa (come afferma il tuo titolo). – Jashaszun

+0

@deviantfan: si – nali

risposta

11

Finché il il codice gestito non ridimensiona il vettore, puoi a accedere al buffer e passarlo come puntatore con vector.data() (per C++ 0x) o &vector[0]. Ciò si traduce in un sistema a zero copie.

Esempio C++ API:

#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; 
} 

Caller:

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 
+0

Interessante. +1. –

+0

Grazie per la gentilezza di questa soluzione, solo per trasferire un intero modello di poligono generato in tempo reale da una DLL in un gioco Unity, ma potrei chiedere a chi è richiesto l'intero sistema con "ItemsSafeHandle"? Ho testato il sistema completamente senza il primo argomento e ha funzionato. Aggiunge una sorta di "sicurezza"? – DragonGamer

+0

@DragonGamer, il problema è che il tuo codice non gestito sta allocando memoria non nota al GC .Net. 'SafeHandle' consente al GC di funzionare anche in presenza di codice buggato che altrimenti verrebbe a mancare. Vedi [Perché 'SafeHandle'?] (Https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safehandle (v = vs.110) .aspx # Osservazioni) – Mitch

5

Potrei pensare a più di un'opzione, ma tutti includono comunque la copia dei dati dell'array. Con [out] Parametri si potrebbe provare:

codice 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# codice

[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); 
} 
+1

+1, ma vorrei ribadire che l'array restituito [deve essere allocato con 'CoTaskMemAlloc'] (https://msdn.microsoft.com/en-us/library/z6cfh6e6 (v = vs.110) .aspx) a evitare la corruzione o perdite. – Mitch

+0

@ Mitch Thx. Inoltre, potrei fare una copia profonda non necessaria. –

+1

a volte il mio conteggio ritorna come un numero diverso dall'array .. Non sto usando il doppio, ma una struttura personalizzata. Da dove viene il valore 2? – user1000247

6

ho implementato questo in C++ CLI wrapper. C++ CLI è uno dei tre possibili approcci per l'interoperabilità C++ C#. Gli altri due approcci sono P/Invoke e COM. (Ho visto alcune brave persone consigliare l'uso di C++ CLI rispetto agli altri approcci)

Per eseguire il marshalling delle informazioni dal codice nativo al codice gestito, è necessario prima avvolgere il codice nativo all'interno di una classe gestita C++ CLI. Crea un nuovo progetto per contenere il codice nativo e il suo wrapper C++ CLI. Assicurarsi di abilitare lo switch del compilatore /clr per questo progetto. Costruisci questo progetto in una DLL. Per usare questa libreria, aggiungi semplicemente il suo riferimento all'interno di C# e fai chiamate contro di essa. Puoi farlo se entrambi i progetti sono nella stessa soluzione.

Ecco i file sorgente per un semplice programma per il marshalling di std::vector<double> da codice nativo a codice gestito C#.

1) Progetto EntityLib (C++ CLI dll) (codice nativo con Wrapper)

File NativeEntity.h

#pragma once 

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

File 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 
    }; 

} 

File 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) Progetto 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]); 
     } 
    } 
} 
Problemi correlati