将矢量/数组从非托管C++传递到C#

nal*_*ali 13 c# c++ interop vector marshalling

我想从非托管C++传递大约100 - 10,000点到C#.

C++端看起来像这样:

__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#
}
Run Code Online (Sandbox Code Playgroud)

现在我的C#端看起来像这样:

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 */ );
    }
}
Run Code Online (Sandbox Code Playgroud)

我如何改变我的代码,以便将std :: vector从非托管C++传递给C#?

Mit*_*tch 23

只要托管代码没有调整向量的大小,就可以访问缓冲区并将其作为指针传递给vector.data()(对于C++ 0x)或&vector[0].这导致零拷贝系统.

示例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;
}
Run Code Online (Sandbox Code Playgroud)

呼叫者:

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
Run Code Online (Sandbox Code Playgroud)

  • @DragonGamer,手头的问题是您的非托管代码正在分配 .Net GC 不知道的内存。`SafeHandle` 允许 GC 工作,即使面对可能会泄漏的错误代码。请参阅[为什么是`SafeHandle`?](https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safehandle(v=vs.110).aspx#Remarks) (2认同)

The*_*ere 9

我使用C++ CLI包装器实现了这一点.C++ CLI是C++ C#interop的三种可能方法之一.另外两种方法是P/Invoke和COM.(我见过一些好人建议使用C++ CLI而不是其他方法)

为了将信息从本机代码编组到托管代码,您需要首先将本机代码包装在C++ CLI托管类中.创建一个新项目以包含本机代码及其C++ CLI包装器.确保/clr为此项目启用编译器开关.将此项目构建到dll.要使用此库,只需在C#中添加其引用并对其进行调用.如果两个项目都在同一个解决方案中,您可以执行此操作.

以下是我的源文件,用于将std::vector<double>本机代码编组为C#托管代码的简单程序.

1)Project EntityLib(C++ CLI dll)(带有包装器的本机代码)

文件NativeEntity.h

#pragma once

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

文件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
}
Run Code Online (Sandbox Code Playgroud)

File ManagedEntity.h (包装类)

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

}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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]);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 嘿,当您将“^”运算符放在 array&lt;double&gt;^ Managed::Entity::GetVec() 函数中时,我可以知道它的含义吗?并且也在 array&lt;double&gt; ^GetVec() (2认同)

Nik*_*lay 7

我可以想到多个选项,但所有选项都包括复制数组的数据.使用[out]参数,您可以尝试:

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);
}
Run Code Online (Sandbox Code Playgroud)

C#代码

[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);
}
Run Code Online (Sandbox Code Playgroud)

  • +1,但我要重申,返回的数组 [必须用`CoTaskMemAlloc` 分配](https://msdn.microsoft.com/en-us/library/z6cfh6e6(v=vs.110).aspx) 以避免损坏或泄漏。 (2认同)
  • 有时我的计数返回的数字与数组不同.我不是使用双打,而是自定义结构.2的价值来自哪里? (2认同)