[英]Marshaling a return array from C++ to C#
我的 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: '无法封送'返回值':无效的托管/非托管类型组合。
我知道我可以使用内存编写器直接写入带有数组指针地址的 C++ 内存。 如果在参数中使用相同的MarshalAs(UnmanagedType.LPArray)
并将 C# 数组传递给 C++,它会起作用,但为什么它在相反的动作中不起作用?
注意:我的数据很大,我真的不能在这里使用任何副本。
您应该先完整阅读这篇文章: https : //docs.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-for-arrays#unmanaged-arrays
我还发现了这个相关的 QA: How to create and initialize SAFEARRAY of doubles in C++ to pass to C#
您的GetCppArray
函数仅返回一个指针 - 它不返回自描述的“安全”数组,而 .NET 中的数组包含长度和等级(维度)信息,因此您肯定需要修改 C++ 代码以正确执行此操作。
第一个选项是将数组作为COM 样式的安全数组返回,这是通过SAFEARRAY( typename )
宏完成的- 它必须作为参数传递,而不是返回值。
在 C++ 中使用 COM 安全数组有两种主要方法:使用 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
);
也许您可以使用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
我通过建议您使用C++/CLI
来修改我的原始答案。 你说你使用.NET Framework
,所以C++/CLI
是一个不错的选择。 通过使用它,您不需要复制C++
指针。 只需在您的C++
指针上创建一个C++/CLI
包装器类。 下面是一个例子。
C++代码
函数.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 代码
包装器.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()}");
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.