Passing a vector/array from unmanaged C++ to C#
Solution 1
As long as the managed code does not resize the vector, you can access the buffer and pass it as a pointer with vector.data()
(for C++0x) or &vector[0]
. This results in a zero-copy system.
Example 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
Solution 2
I implemented this using C++ CLI wrapper. C++ CLI is one the three possible approaches for C++ C# interop. The other two approaches are P/Invoke and COM. (I have seen a few good people recommend using C++ CLI over the other approaches)
In order to marshall information from native code to managed code, you need to first wrap the native code inside a C++ CLI managed class. Create a new project to contain native code and its C++ CLI wrapper. Make sure to enable the /clr
compiler switch for this project. Build this project to a dll. In order to use this library, simply add its reference inside C# and make calls against it. You can do this if both projects are in the same solution.
Here are my source files for a simple program to marshal a std::vector<double>
from native code into C# managed code.
1) Project EntityLib (C++ CLI dll) (Native Code with 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 (Wrapper Class)
#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) Project 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]);
}
}
}
Solution 3
I could think of more than one option, but all include copying the data of the array anyways. With [out] parameters you could try:
C++ code
__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# code
[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);
}
Related videos on Youtube
nali
Updated on July 09, 2022Comments
-
nali almost 2 years
I want to pass around 100 - 10,000 Points from an unmanaged C++ to C#.
The C++ side looks like this:
__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# }
Now my C# side looks like this:
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 */ ); } }
How do I need to alter my code in order to pass the std::vector from unmanaged C++ with all it's content to C#?
-
deviantfan almost 9 yearsSo you want to return the vector fro the function?
-
Jashaszun almost 9 yearsHow come in C++ your function returns
void
but in C# it returnsint
? And also, it seems like you're trying to pass a vector/array from C# to C++, not the other way (as your title states). -
nali almost 9 years@Jashaszun: Mistake during code simplification
-
Jashaszun almost 9 years@nali Then please fix your question. As it is, there are problems that can confuse potential answerers (such as me).
-
nali almost 9 years@Jashaszun: Here you go
-
nali almost 9 years@pm100: Which information is missing? The goal is to call a C++ DLL function with arguments detect_targets( ... ) which 'somehow' returns a vector or array or something which stores the detected coordinates.
-
kshitij Sabale almost 9 yearsAs long as the C# side is not changing the length of the vector, can't you just pass it as an array (or
double*
, to avoid the copy)?id_x_y_z.data()
or&id_x_y_z[0]
-
nali almost 9 years@Mitch: C# does not know the size of the C++ vector. It's dynamic and unpredictable. Maybe I understood you wrong, could you paste some source code examples?
-
kshitij Sabale almost 9 years@nail, provided below. You can just return the size with the pointer.
-
Matt Arnold over 3 years@Mitch How do you declare
std::array
parameter without a size or assign to a C-style array which the C++ didn't create though? This is why I'm attempting to use astd::vector
instead. -
kshitij Sabale over 3 years@MattArnold, I doubt
std::array
could cross an ABI boundary since it is mostly compile-time sugar for a constant size array. You can still deconstruct astd::array
to raw pointer + size usingstd::array<,>::data()
andstd::array<,>::size()
. (In much the same way as thestd::vector
example below.) Careful to make sure that the pointer remains valid untilReleaseItems
is called.
-
-
kshitij Sabale almost 9 years+1, but I would reiterate that the returned array must be allocated with
CoTaskMemAlloc
to avoid corruption or leaks. -
The Vivandiere about 8 years@Mitch Thx. Also, I might be making an unnecessary deep copy.
-
DragonGamer about 7 yearsThank you kindly for this solution - just needed it to transfer an entire realtime-generated polygon model from a DLL into an Unity game - but may I ask what the entire system with "ItemsSafeHandle" is required for? I've tested the system completely without the first argument and it did work. Does it add some sort of "safety"?
-
kshitij Sabale about 7 years@DragonGamer, the problem at hand is that your unmanaged code is allocating memory not known to the .Net GC.
SafeHandle
allows the GC to work even in the face of buggy code which would otherwise leak. See WhySafeHandle
? -
user1000247 almost 7 yearssometimes my count returns as a different number than the array.. I am not using doubles, but a custom struct. Where does the value 2 come from?
-
Andy about 6 years@user1000247 if I'm looking at the documentation correctly, it's the zero based index of the array size. So in this case, count is the third parameter, and therefore index 2. msdn.microsoft.com/en-us/library/…
-
calveeen almost 5 yearsHey may I know what the "^" operator means when you place it at the array<double>^ Managed::Entity::GetVec() function ? and also at the array<double> ^GetVec()
-
Cole Tobin over 4 years@calveeen stackoverflow.com/questions/202463/…