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

30,776

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);
}
Share:
30,776

Related videos on Youtube

nali
Author by

nali

Updated on July 09, 2022

Comments

  • nali
    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
      deviantfan almost 9 years
      So you want to return the vector fro the function?
    • Jashaszun
      Jashaszun almost 9 years
      How come in C++ your function returns void but in C# it returns int? 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
      nali almost 9 years
      @Jashaszun: Mistake during code simplification
    • Jashaszun
      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
      nali almost 9 years
      @Jashaszun: Here you go
    • nali
      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
      kshitij Sabale almost 9 years
      As 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
      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
      kshitij Sabale almost 9 years
      @nail, provided below. You can just return the size with the pointer.
    • Matt Arnold
      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 a std::vector instead.
    • kshitij Sabale
      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 a std::array to raw pointer + size using std::array<,>::data() and std::array<,>::size(). (In much the same way as the std::vector example below.) Careful to make sure that the pointer remains valid until ReleaseItems is called.
  • kshitij Sabale
    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
    The Vivandiere about 8 years
    @Mitch Thx. Also, I might be making an unnecessary deep copy.
  • DragonGamer
    DragonGamer about 7 years
    Thank 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
    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 Why SafeHandle?
  • user1000247
    user1000247 almost 7 years
    sometimes 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
    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
    calveeen almost 5 years
    Hey 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
    Cole Tobin over 4 years