24

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

11
  • So you want to return the vector fro the function? Commented Jul 14, 2015 at 21:33
  • 1
    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). Commented Jul 14, 2015 at 21:35
  • @Jashaszun: Mistake during code simplification Commented Jul 14, 2015 at 21:39
  • 1
    @nali Then please fix your question. As it is, there are problems that can confuse potential answerers (such as me). Commented Jul 14, 2015 at 21:39
  • 1
    @nail, provided below. You can just return the size with the pointer. Commented Jul 14, 2015 at 23:10

3 Answers 3

31

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
Sign up to request clarification or add additional context in comments.

2 Comments

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"?
@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?
14

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]);
        }
    }
}

2 Comments

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()
8

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

4 Comments

+1, but I would reiterate that the returned array must be allocated with CoTaskMemAlloc to avoid corruption or leaks.
@Mitch Thx. Also, I might be making an unnecessary deep copy.
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?
@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/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.