You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 15:01:11