[英]How to return array of struct from C++ dll to 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#-side:
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編組器知道(因為我們給它的信息)第二個參數是out ResultOfStrategy[] ros
的長度(參見SizeParamIndex
?),因此它可以創建一個.NET數組並從C分配的數組中復制數據。 請注意,在C代碼中,我使用了::CoTaskMemAlloc
來分配內存。 .NET 希望使用該分配器分配內存,因為它隨后釋放它。 如果你使用malloc
/ new
/ ??? 分配ResultOfStrategy[]
內存,會發生不好的事情。
案例2:
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#-side(函數指針)接收一個分配器。 此分配器將分配length
元素,然后必須記住分配的內存的地址。 這里的訣竅是C# ResultOfStrategy[]
我們分配一個C所需大小的ResultOfStrategy[]
,然后直接用於C-side。 如果ResultOfStrategy
不是blittable(這個術語意味着你只能使用ResultOfStrategy
一些類型,主要是數字類型,沒有string
,沒有char
,沒有bool
,請參見此處 ),這將會破壞。 代碼非常先進,因為除此之外,它必須使用GCHandle
來固定.NET數組,以便它不會被移動。 處理這個GCHandle
非常復雜,所以我必須創建一個IDisposable
的ResultOfStrategyContainer
。 在這個類中,我甚至保存對創建的數組的引用( 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();
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.