Passing a Vector/Array from Unmanaged C++ to C#

Passing a vector/array from unmanaged C++ to C#

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

how to directly read a vector data from unmanaged in managed code using it's pointer

I’m not sure you got your data types right. The code you have now works for 64-bit builds, fails in 32-bit builds. Here‘s one way to fix:

[DllExport]
static public void ReadThisVector( IntPtr v_ptr, uint datasize )
// C++
ReadThisVector( demo_vector_1.data(), (uint32_t)demo_vector_1.size() );

Now, on your question — you can call Marshal.ReadByte for that.

Or, if you have modern .NET and you OK with unsafe code, you can create a ReadOnlySpan<byte> from your 2 values. If you need that data for IO, modern .NET has stuff like Stream.Write(ReadOnlySpan<byte>) i.e. streams can write directly from unmanaged buffers without making copies.

Unfortunately, there’s no way to directly cast unmanaged memory into C# array or list. You either need to copy, or change APIs from arrays/lists into spans/pointers.

Array from C++ to C#

You should be able to allocate memory in XCode using malloc and free it in C# using Marshal.FreeCoTaskMem. To be able to free it however, you need to have the IntPtr for it:

C++ code

extern "C" ABA_API void getArray(long* len, double **data)
{
*len = delArray.size();
auto size = (*len)*sizeof(double);
*data = static_cast<double*>(malloc(size));
memcpy(*data, delArray.data(), size);
}

C# code

[DllImport("AudioPluginSpecDelay")]
private static extern void getArray(out int length, out IntPtr array);

int theSize;
IntPtr theArrayPtr;
double[] theArray;

getArray(out theSize, out theArrayPtr);
Marshal.Copy(theArrayPtr, theArray, 0, theSize);
Marshal.FreeCoTaskMem(theArrayPtr);

// theArray is a valid managed object while the native array is already freed

Edit

From Memory Management I gathered that Marshal.FreeCoTaskMem would most likely be implemented using free(), so the fitting allocator would be malloc().

There are two ways to be really sure:

  1. Allocate the memory in CLI using Marshal.AllocCoTaskMem, pass it to native to have it filled, and then free it in the CLI again using Marshal.FreeCoTaskMem.
  2. Leave it as it is (native allocates memory with malloc()), but do not free the memory in CLI. Instead, have another native function like freeArray(double **data) and have it free() the array for you once CLI is done using it.

Passing vector struct between C++ and C#

You could marshal a C# array as a C++ std::vector, but it would be very complicated and is simply not a good idea because the layout and implementation of std::vector is not guaranteed to be the same between compiler versions.

Instead you should change the parameter into a pointer to an array, and add a parameter specifying the length of the array:

int calibrate_to_file(points* pontos, int length);

And in C# declare the method as taking an array and apply the MarshalAs(UnmanagedType.LPArray) attribute:

static extern int calibrate_to_file([MarshalAs(UnmanagedType.LPArray)]] Point[] pontos, int length);

Note also that your C++ point structure is not compatible with System.Windows.Point. The latter doesn't have a z member.

But a bigger issue with your code is that you can't really expect to DLL-import an instance method and be able to call it just like that. An instance method needs an instance of its class, and there's no easy way to create an instance of a non-COM C++ class from C# (and is also not a good idea). Therefore, you should turn this into a COM class, or create a C++/CLI wrapper for it.

Passing C# delegate with array reference to unmanaged code via function pointer

[ I'm writing a new question since this seems to be different enough. ]

As I said, I've already given you all of the pieces to put this together (assuming it's now clear what you're trying to do). From my comment in the other answer, you just need a .NET wrapper around a C-style array. Maybe there's a nice way to do this (SafeBuffer?), but it's easy enough to write your own:

namespace My
{
public ref class DoubleArray
{
double* m_data;
size_t m_size;

public:
DoubleArray(double* data, size_t size) : m_data(data), m_size(size) {}

property int Length { int get() { return m_size; }}
void SetValue(int index, double value) { m_data[index] = value; }
};
}

Now, you hook the two pieces together, just as before with an InvokeDelegate helper class:

class InvokeDelegate
{
msclr::gcroot<My::PopFunctionDoubleDelegate^> _delegate;

void callback(double* data, size_t size)
{
_delegate->Invoke(gcnew My::DoubleArray(data, size));
}

public:
InvokeDelegate(My::PopFunctionDoubleDelegate^ delegate) : _delegate(delegate) { }
double invoke()
{
return process_accumulate<double>([&](double* data, size_t size) { callback(data, size); });
}
};

The rest of the My namespace is:

namespace My
{
public delegate void PopFunctionDoubleDelegate(DoubleArray^);
public ref struct ProcessDoubleCLI abstract sealed
{
static double add_cli(PopFunctionDoubleDelegate^ delegate_func);
};
}

with add_cli pretty much as before:

double My::ProcessDoubleCLI::add_cli(My::PopFunctionDoubleDelegate^ delegate_func)
{
InvokeDelegate invokeDelegate(delegate_func);
return invokeDelegate.invoke();
}

Using this from C# is now:

class DoubleReader
{
public DoubleReader() { }

public void PopulateFunctionDoubleIncr(My.DoubleArray data)
{
int idx = 0;
while (idx < data.Length)
{
data.SetValue(idx, (double)idx);
++idx;
}
}

static void Main(string[] args)
{
DoubleReader dr = new DoubleReader();
My.PopFunctionDoubleDelegate func = dr.PopulateFunctionDoubleIncr;
var result = My.ProcessDoubleCLI.add_cli(func);
Console.WriteLine(result);
}
}

Edit since you seem to really want to pass the delegate as a function pointer, here's how to do that:

public delegate void PopFunctionPtrDelegate(System::IntPtr, int);

double My::ProcessDoubleCLI::add_cli(My::PopFunctionPtrDelegate^ delegate_func)
{
auto FunctionPointer = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(delegate_func);
auto func_ptr = static_cast<void(*)(double*, size_t)>(FunctionPointer.ToPointer());
return process_accumulate<double>(func_ptr);
}

But, as I said, that's hard to work with in C# because your code is

public void PopulateFunctionPtrIncr(IntPtr pData, int count)
{
}
My.PopFunctionPtrDelegate ptrFunc = dr.PopulateFunctionPtrIncr;
result = My.ProcessDoubleCLI.add_cli(ptrFunc);

Passing a C++ Array to C# Layer through Net Core

Great!, Finally I have found out the problem. In the delegate declaration, I just had to introduce parameter SizeParamIndex :

delegate void OnSpeechEndedInter(
[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] short[] untouched,
int untouchedSize,
[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] short[] audioAfterPostSpeechUntilSilenceTrigger,
int audioAfterPostSpeechUntilSilenceTriggerSize);

Because the marshaler cannot determine the size of an unmanaged array, we have to pass it in as a separate parameter considering the signature of the method and zero based indexing.

Howto call an unmanaged C++ function with a std::vector as parameter from C#?

The best solution here is to write a wrapper function in C which is limited to non-C++ classes. Non-trivial C++ classes are essentially unmarshable via the PInvoke layer [1]. Instead have the wrapper function use a more traditional C signature which is easy to PInvoke against

void findNeigborsWrapper(
Point p,
double maxDist,
Point** ppNeighbors,
size_t* pNeighborsLength)

[1] Yes there are certain cases where you can get away with it but that's the exception and not the rule.



Related Topics



Leave a reply



Submit