c# - 如何将结构数组从 C++ dll 返回到 C#
问题描述
我需要在 dll 中调用一个函数并返回一个结构数组。我事先不知道数组的大小。如何才能做到这一点?错误can not marshal 'returns value' invalid managed / unmanaged
C# 中的代码:
[DllImport("CppDll"]
public static extern ResultOfStrategy[] MyCppFunc(int countO, Data[] dataO, int countF, Data[] dataF);
在 C++ 中:
extern "C" _declspec(dllexport) ResultOfStrategy* WINAPI MyCppFunc(int countO, MYDATA * dataO, int countF, MYDATA * dataF)
{
return Optimization(countO, dataO, countF, dataF);
}
返回结构数组:
struct ResultOfStrategy
{
bool isGood;
double allProfit;
double CAGR;
double DD;
int countDeals;
double allProfitF;
double CAGRF;
double DDF;
int countDealsF;
Param Fast;
Param Slow;
Param Stop;
Param Tp;
newStop stloss;
};
解决方案
我给你两个回应。第一个是一种非常基本的方法。第二个是相当先进的。
鉴于:
C端:
struct ResultOfStrategy
{
//bool isGood;
double allProfit;
double CAGR;
double DD;
int countDeals;
double allProfitF;
double CAGRF;
double DDF;
int countDealsF;
ResultOfStrategy *ptr;
};
C#端:
public struct ResultOfStrategy
{
//[MarshalAs(UnmanagedType.I1)]
//public bool isGood;
public double allProfit;
public double CAGR;
public double DD;
public int countDeals;
public double allProfitF;
public double CAGRF;
public double DDF;
public int countDealsF;
public IntPtr ptr;
}
请注意,我已经删除了bool
,因为它在案例 2 中存在一些问题(但它适用于案例 1)......现在......
情况 1 非常基本,它会导致 .NET 封送拆收器将 C 中内置的数组复制到 C# 数组中。
我写的案例 2 是相当先进的,它试图绕过这个 marshal-by-copy 并让 C 和 .NET 可以共享相同的内存。
为了检查差异,我编写了一个方法:
static void CheckIfMarshaled(ResultOfStrategy[] ros)
{
GCHandle h = default(GCHandle);
try
{
try
{
}
finally
{
h = GCHandle.Alloc(ros, GCHandleType.Pinned);
}
Console.WriteLine("ros was {0}", ros[0].ptr == h.AddrOfPinnedObject() ? "marshaled in place" : "marshaled by copy");
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
}
并且我在 中添加了一个ptr
字段,该字段struct
包含struct
(C 端)的原始地址,以查看它是否已被复制或是否是原始struct
.
情况1:
C端:
__declspec(dllexport) void MyCppFunc(ResultOfStrategy** ros, int* length)
{
*ros = (ResultOfStrategy*)::CoTaskMemAlloc(sizeof(ResultOfStrategy) * 2);
::memset(*ros, 0, sizeof(ResultOfStrategy) * 2);
(*ros)[0].ptr = *ros;
(*ros)[0].allProfit = 100;
(*ros)[1].ptr = *ros + 1;
(*ros)[1].allProfit = 200;
*length = 2;
}
和 C# 端:
public static extern void MyCppFunc(
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 1)] out ResultOfStrategy[] ros,
out int length
);
接着:
ResultOfStrategy[] ros;
int length;
MyCppFunc(out ros, out length);
Console.Write("Case 1: ");
CheckIfMarshaled(ros);
ResultOfStrategy[] ros2;
.NET marshaler 知道(因为我们给了它信息)第二个参数是 的长度out ResultOfStrategy[] ros
(见SizeParamIndex
?),所以它可以创建一个 .NET 数组并从 C 分配的数组中复制数据。请注意,在 C 代码中,我使用::CoTaskMemAlloc
来分配内存。.NET希望使用该分配器分配内存,因为它随后会释放它。如果你使用malloc
/ new
/???分配ResultOfStrategy[]
内存,不好的事情就会发生。
案例二:
C面:
__declspec(dllexport) void MyCppFunc2(ResultOfStrategy* (*allocator)(size_t length))
{
ResultOfStrategy *ros = allocator(2);
ros[0].ptr = ros;
ros[1].ptr = ros + 1;
ros[0].allProfit = 100;
ros[1].allProfit = 200;
}
C#端:
// Allocator of T[] that pins the memory (and handles unpinning)
public sealed class PinnedArray<T> : IDisposable where T : struct
{
private GCHandle handle;
public T[] Array { get; private set; }
public IntPtr CreateArray(int length)
{
FreeHandle();
Array = new T[length];
// try... finally trick to be sure that the code isn't interrupted by asynchronous exceptions
try
{
}
finally
{
handle = GCHandle.Alloc(Array, GCHandleType.Pinned);
}
return handle.AddrOfPinnedObject();
}
// Some overloads to handle various possible length types
// Note that normally size_t is IntPtr
public IntPtr CreateArray(IntPtr length)
{
return CreateArray((int)length);
}
public IntPtr CreateArray(long length)
{
return CreateArray((int)length);
}
public void Dispose()
{
FreeHandle();
}
~PinnedArray()
{
FreeHandle();
}
private void FreeHandle()
{
if (handle.IsAllocated)
{
handle.Free();
}
}
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr AllocateResultOfStrategyArray(IntPtr length);
[DllImport("CplusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void MyCppFunc2(
AllocateResultOfStrategyArray allocator
);
接着
ResultOfStrategy[] ros;
using (var pa = new PinnedArray<ResultOfStrategy>())
{
MyCppFunc2(pa.CreateArray);
ros = pa.Array;
// Don't do anything inside of here! We have a
// pinned object here, the .NET GC doesn't like
// to have pinned objects around!
Console.Write("Case 2: ");
CheckIfMarshaled(ros);
}
// Do the work with ros here!
现在这个很有趣...... C 函数从 C# 端接收一个分配器(一个函数指针)。该分配器将分配length
元素,然后必须记住已分配内存的地址。这里的技巧是,我们在 C# 端分配了ResultOfStrategy[]
C 所需大小的 a,然后直接在 C 端使用。如果不是 blittable,这将严重ResultOfStrategy
破坏(一个术语意味着您只能在内部使用某些类型ResultOfStrategy
,主要是数字类型, no string
, no char
, no bool
,请参见此处)。该代码非常先进,因为除此之外,它还必须使用GCHandle
.NET 数组来固定,这样它就不会被移动。处理这个GCHandle
非常复杂,所以我必须创建ResultOfStrategyContainer
一个IDisposable
. 在这个类中,我什至保存了对创建的数组(的ResultOfStrategy[] ResultOfStrategy
)的引用。注意使用using
. 这是使用类的正确方法。
bool
和案例2
正如我所说,在bool
使用案例 1 时,它们不适用于案例 2……但我们可以作弊:
C端:
struct ResultOfStrategy
{
bool isGood;
C#端:
public struct ResultOfStrategy
{
private byte isGoodInternal;
public bool isGood
{
get => isGoodInternal != 0;
set => isGoodInternal = value ? (byte)1 : (byte)0;
}
这有效:
C端:
extern "C"
{
struct ResultOfStrategy
{
bool isGood;
double allProfit;
double CAGR;
double DD;
int countDeals;
double allProfitF;
double CAGRF;
double DDF;
int countDealsF;
ResultOfStrategy *ptr;
};
int num = 0;
int size = 10;
__declspec(dllexport) void MyCppFunc2(ResultOfStrategy* (*allocator)(size_t length))
{
ResultOfStrategy *ros = allocator(size);
for (int i = 0; i < size; i++)
{
ros[i].isGood = i & 1;
ros[i].allProfit = num++;
ros[i].CAGR = num++;
ros[i].DD = num++;
ros[i].countDeals = num++;
ros[i].allProfitF = num++;
ros[i].CAGRF = num++;
ros[i].DDF = num++;
ros[i].countDealsF = num++;
ros[i].ptr = ros + i;
}
size--;
}
}
C#端:
[StructLayout(LayoutKind.Sequential)]
public struct ResultOfStrategy
{
private byte isGoodInternal;
public bool isGood
{
get => isGoodInternal != 0;
set => isGoodInternal = value ? (byte)1 : (byte)0;
}
public double allProfit;
public double CAGR;
public double DD;
public int countDeals;
public double allProfitF;
public double CAGRF;
public double DDF;
public int countDealsF;
public IntPtr ptr;
}
接着
ResultOfStrategy[] ros;
for (int i = 0; i < 10; i++)
{
using (var pa = new PinnedArray<ResultOfStrategy>())
{
MyCppFunc2(pa.CreateArray);
ros = pa.Array;
// Don't do anything inside of here! We have a
// pinned object here, the .NET GC doesn't like
// to have pinned objects around!
}
for (int j = 0; j < ros.Length; j++)
{
Console.WriteLine($"row {j}: isGood: {ros[j].isGood}, allProfit: {ros[j].allProfit}, CAGR: {ros[j].CAGR}, DD: {ros[j].DD}, countDeals: {ros[j].countDeals}, allProfitF: {ros[j].allProfitF}, CAGRF: {ros[j].CAGRF}, DDF: {ros[j].DDF}, countDealsF: {ros[j].countDealsF}");
}
Console.WriteLine();
}
推荐阅读
- python - 在数据框中的列中的某个位置添加字符串
- sql-server - 为每条更新的记录更新主表并在日志表中插入
- angular - 正确预处理 Angular 组件和模板
- java - UIManager SetLookAndFeel 的 Java 鼠标侦听器错误
- rabbitmq - RabbitMQ - 在事务中分组消息
- android - CollapsingToolbarLayout 未按预期工作
- jenkins - Jenkins delcarative管道中用于屏蔽参数值的密码参数
- reactjs - 如何在下一个 js 中抽象公共代码(包含返回)
- visual-studio-code - 有没有办法在vscode的查找和替换中禁用ctrl+enter的默认行为?
- javascript - Vue.js - 将模板传递给孙子