C# UnmanagedCallersOnly方法使用数组的报错及解决问询
解决UnmanagedCallersOnly返回数组的CS8894错误
错误原因
UnmanagedCallersOnly特性要求方法的参数和返回值必须是非托管兼容的 blittable 类型,托管数组(如Item[])属于托管引用类型,不符合非托管交互的类型要求,因此触发CS8894错误。
解决方案思路
不能直接返回托管数组,需要手动完成托管数组到非托管内存的拷贝,通过一个包含数组指针和长度的结构体将数组信息传递给C++,同时提供内存释放方法避免泄漏。
步骤1:定义数组信息结构体
这个结构体用于传递非托管数组的指针和元素个数,C++端可以直接解析:
[StructLayout(LayoutKind.Sequential)] public struct ItemArrayInfo { public IntPtr ItemsPtr; // 非托管数组的指针 public int Length; // 数组元素个数 }
步骤2:修改GetAll方法,返回数组信息
手动分配非托管内存,将托管数组的元素拷贝到非托管内存中,再返回包含指针和长度的结构体:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct Item { public int Value; [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] public static Item GetSingle() { return new Item(); } [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] public static ItemArrayInfo GetAll() { // 模拟生成托管数组 var managedItems = new[] { new Item { Value = 10 }, new Item { Value = 20 }, new Item { Value = 30 } }; if (managedItems.Length == 0) { return new ItemArrayInfo { ItemsPtr = IntPtr.Zero, Length = 0 }; } // 计算单个元素大小和总内存大小 int elementSize = Marshal.SizeOf<Item>(); int totalSize = elementSize * managedItems.Length; // 分配非托管内存 IntPtr unmanagedPtr = Marshal.AllocHGlobal(totalSize); // 将每个托管结构体拷贝到非托管内存 for (int i = 0; i < managedItems.Length; i++) { IntPtr elementAddr = IntPtr.Add(unmanagedPtr, i * elementSize); Marshal.StructureToPtr(managedItems[i], elementAddr, false); } return new ItemArrayInfo { ItemsPtr = unmanagedPtr, Length = managedItems.Length }; } // 提供释放非托管内存的方法,C++调用后释放内存 [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] public static void FreeItemArray(IntPtr arrayPtr) { if (arrayPtr != IntPtr.Zero) { Marshal.FreeHGlobal(arrayPtr); } } }
步骤3:C++端调用示例
在C++中定义对应的结构体,导入DLL函数后即可调用,记得最后释放内存:
#include <cstdio> #include <cstdint> // 与C#端Item结构体一一对应 struct Item { int Value; }; // 与C#端ItemArrayInfo结构体一一对应 struct ItemArrayInfo { Item* ItemsPtr; int Length; }; // 导入DLL中的函数 extern "C" { __declspec(dllimport) ItemArrayInfo GetAll(); __declspec(dllimport) void FreeItemArray(Item* arrayPtr); } int main() { ItemArrayInfo arrayInfo = GetAll(); // 遍历输出数组元素 for (int i = 0; i < arrayInfo.Length; i++) { printf("Item %d: Value = %d\n", i, arrayInfo.ItemsPtr[i].Value); } // 必须调用释放函数,避免内存泄漏 FreeItemArray(arrayInfo.ItemsPtr); return 0; }
注意事项
- 确保
Item是blittable类型:即结构体的托管和非托管内存布局一致,比如仅包含int、float等简单值类型,无引用类型。如果包含非blittable类型,需要额外处理封送。 - 必须调用
FreeItemArray:Marshal.AllocHGlobal分配的是非托管内存,不会被.NET GC自动回收,C++端使用完后必须调用释放函数。 - 空数组处理:如果返回空数组,直接返回指针为
IntPtr.Zero、长度为0的结构体即可,避免无效内存操作。
内容的提问来源于stack exchange,提问作者Jonas K
相关产品推荐
相关产品推荐

