Маршалинг возвращаемого массива из C ++ в C# - PullRequest
0 голосов
/ 26 марта 2020

У меня есть массив в моем приложении C ++, и я хочу использовать его указатель, чтобы создать из него делегат массива C# без копирования. Так что я могу заставить его работать с C# на C ++, C ++ получить доступ к тому же массиву, определенному в C#, но не может заставить его работать в обратном порядке.

C# Код:

   [DllImport("CppDll.dll",CallingConvention=CallingConvention.Cdecl)]
   [return: MarshalAs(UnmanagedType.LPArray)]
   public static extern int[] GetCppArray();

C ++ Код:

    int test_data[5] = { 12,60,55,49,26 };

    extern "C" __declspec(dllexport) int* GetCppArray()
    {
        return test_data;
    }

Использование в C#:

int[] cpparray = NativeLib.GetCppArray();

И я получаю эту ошибку:

System.Runtime.InteropServices.MarshalDirectiveException: 'Cannot marshal' возвращаемое значение ': недопустимая комбинация управляемого / неуправляемого типа.'

Я знаю, что могу использовать память писатели для записи непосредственно в память C ++ с адресом указателя массива. Он работает, если использовать тот же MarshalAs(UnmanagedType.LPArray) в параметре и передать массив C# в c ++, но почему он не работает в противоположных действиях?

Примечание: мои данные огромны, я действительно не могу использовать какие-либо скопируйте сюда.

Ответы [ 3 ]

1 голос
/ 26 марта 2020

Может быть, вы можете использовать Marshal.ReadInt32() для обработки ваших данных?

[DllImport("CppDll.dll",CallingConvention=CallingConvention.Cdecl)]
public static extern IntPtr GetCppArray();

var cpparray = NativeLib.GetCppArray();
var test_data_2 = Marshal.ReadInt32(cpparray, 2 * sizeof(int));
Console.WriteLine(test_data_2); // 55

Похоже, Span<T> может быть довольно близко к то, что ты хочешь; есть образец, который в значительной степени точен, хотя он все еще требует unsafe.

 Span<int> cpparray;
 unsafe
 {
     cpparray = new Span<int>((int*)NativeLib.GetCppArray(), 5);
 } 
 Console.WriteLine(cpparray[2]); // 55
1 голос
/ 26 марта 2020

Вы должны сначала полностью прочитать эту статью: https://docs.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-for-arrays#unmanaged -arrays

Я также нашел это соответствующие QAs: Как создать и инициализировать SAFEARRAY для двойников в C ++ для передачи в C#

Ваша GetCppArray функция возвращает только указатель - она ​​не возвращает самоописываемый «безопасный» массив, тогда как массивы в. NET включают длину и ранг (измерение ), поэтому вам определенно необходимо изменить код C ++, чтобы сделать это правильно.

Первый вариант - вернуть массив в виде безопасного массива в стиле COM * , это делается с помощью макрос SAFEARRAY( typename ) - и он должен передаваться как параметр, а не возвращаемое значение.

Существует два основных способа использования COM Safe-Arrays в C ++: использование таких функций Win32, как SafeArrayCreate - которые больно использовать правильно, или используя ATL CComSafeArray.

(Отказ от ответственности: я написал этот код, просматривая ссылки на API, я не проверил это - я даже не знаю, скомпилируется ли он).

// C++ code for SafeArrayCreate:

#include <comdef.h>
int test_data[5] = { 12, 60, 55, 49, 26 };

extern "C" __declspec(dllexport) HRESULT GetCppArray( [out] SAFEARRAY( int )** arr )
{
    SAFEARRAYBOUND bounds;
    bounds.lLbound   = 0;
    bounds.cElements = sizeof(test_data);

    *arr = SafeArrayCreate(
        VT_I4,  // element type
        1,      // dimensions
        &bounds 
    );
    if( !arr ) {
        // SafeArrayCreate failed.
        return E_UNEXPECTED;
    }

    int* arrPtr;
    HRESULT hr = SafeArrayAccessData( *arr, &arrPtr );
    if( !SUCCEEDED( hr ) ) {
         hr = SafeArrayDestroy( arr );
         // After SafeArrayDestory, if `hr != S_OK` then something is really wrong.
         return E_UNEXPECTED;
    }

    for( size_t i = 0; i < sizeof(test_data); i++ ) {
        *arrPtr[i] = test_data[i];
    }

    hr = SafeArrayUnaccessData( *arrPtr );
    if( !SUCCEEDED( hr ) ) {
         hr = SafeArrayDestroy( arr );
         return E_UNEXPECTED;
    }

    return S_OK;
}

Затем необходимо обновить C# код, чтобы объявить, что он возвращает SafeArray:

// HRESULT is best represented as a UInt32 instead of Int32.

[DllImport( "CppDll.dll", CallingConvention = CallingConvention.Cdecl )]
public static extern UInt32 GetCppArray(
    [MarshalAs( UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I4 )] out Int32[] arr
);
0 голосов
/ 26 марта 2020

Я изменил свой первоначальный ответ, предложив вам использовать C++/CLI. Вы сказали, что используете .NET Framework, поэтому C++/CLI - хороший вариант. При его использовании вам не нужно копировать указатель C++. Просто создайте класс-оболочку C++/CLI поверх указателя C++. Ниже приведен пример.

код C ++

functions.h

#pragma once
#ifdef LIBRARY_EXPORTS
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C"
{
#endif

    API int* GetCppArray();
    API int GetCppArraySize();
    API void DeleteCppArray(int* vec);

#ifdef __cplusplus
}
#endif

функций. cpp

#include "pch.h"
#include "functions.h"

const int vecSize = 10000;

int* GetCppArray()
{
    int* vec = new int[vecSize];
    for (int i = 0; i < vecSize; ++i)
    {
        vec[i] = i * 2;
    }

    return vec;
}

int GetCppArraySize()
{
    return vecSize;
}

void DeleteCppArray(int* vec)
{
    delete[] vec;
}

C ++ / код CLI

Wrapper.h

#pragma once

using namespace System;

namespace Wrapper 
{
    public ref class ArrayWrapper : public IDisposable
    {
    private:
        int* values;
        int size;
        bool isDisposed;

    public:
        ArrayWrapper();
        ~ArrayWrapper();
        !ArrayWrapper();

        property int Size
        {
            int get();
        }

        property int default[int]
        {
            int get(int index);
            void set(int index, int value);
        }
    };
}

Упаковщик. cpp

#include "pch.h"
#include "Wrapper.h"

#include "../Library/functions.h"

namespace Wrapper
{
    ArrayWrapper::ArrayWrapper()
    {
        this->values = GetCppArray();
        this->size = GetCppArraySize();
        this->isDisposed = false;
    }

    ArrayWrapper::~ArrayWrapper()
    {
        if (this->isDisposed == true)
        {
            return;
        }

        this->!ArrayWrapper();
        this->isDisposed = true;
    }

    ArrayWrapper::!ArrayWrapper()
    {
        DeleteCppArray(this->values);
        this->values = nullptr;
        this->size = 0;
    }

    int ArrayWrapper::Size::get()
    {
        return this->size;
    }

    int ArrayWrapper::default::get(int index)
    {
        if (index < 0 || index >= this->size)
        {
            throw  gcnew Exception("Invalid index");
        }
        return this->values[index];
    }

    void ArrayWrapper::default::set(int index, int value)
    {
        if (index < 0 || index >= this->size)
        {
            throw  gcnew Exception("Invalid index");
        }

        this->values[index] = value;
    }
}

C# код

using System;
using Wrapper;

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            ArrayWrapper vector = new ArrayWrapper();

            for (int i = 0; i < vector.Size; i++)
            {
                Console.Write($"{vector[i].ToString()} ");
            }
            Console.WriteLine();
            Console.WriteLine("Done");

            int index = vector.Size / 2;
            Console.WriteLine($"vector[{index.ToString()}]={vector[index].ToString()}");
            vector[index] *= 2;
            Console.WriteLine($"vector[{index.ToString()}]={vector[index].ToString()}");
        }
    }
}
...