web-dev-qa-db-ja.com

アンマネージC ++からC#へのベクトル/配列の受け渡し

アンマネージC++からC#に約100〜10,000ポイントを渡したいです。

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

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

Std :: vectorをアンマネージC++からすべてのコンテンツとともにC#に渡すには、コードを変更する必要がありますか?

16
nali

マネージコードがベクトルのサイズを変更しない限り、バッファにアクセスし、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;
}

発信者:

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
25
Mitch

これはC++ CLIラッパーを使用して実装しました。 C++ CLIは、C++ C#相互運用機能の3つの可能なアプローチの1つです。他の2つのアプローチは、P/InvokeとCOMです。 (私は他のアプローチよりもC++ CLIを使用することをお勧めするいくつかの良い人々を見てきました)

ネイティブコードからマネージコードに情報をマーシャリングするには、まずネイティブコードをC++ CLIマネージクラス内にラップする必要があります。ネイティブコードとそのC++ CLIラッパーを含む新しいプロジェクトを作成します。このプロジェクトの/clrコンパイラスイッチを必ず有効にしてください。このプロジェクトをDLLにビルドします。このライブラリを使用するには、C#内にその参照を追加し、それに対して呼び出しを行うだけです。両方のプロジェクトが同じソリューションにある場合、これを行うことができます。

これは、ネイティブコードからC#マネージコードにstd::vector<double>をマーシャリングするための簡単なプログラムのソースファイルです。

1)プロジェクト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; }
};

ファイル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
}

ファイル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
    };

}

ファイル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)プロジェクト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]);
        }
    }
}
11
The Vivandiere

複数のオプションを考えることもできますが、いずれにしても、配列のデータをコピーすることが含まれます。 [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);
}

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