将结构数组从 c 编组到 c#

问题描述

我正在尝试将一个 c 结构数组编组到 C#(使用 Unity)中,但是,无论我使用哪种方法,我总是遇到异常或崩溃。

我正在加载符合(或应该……)Libretro API 的 dll(libretro 内核),c/c++ 端对我不可用(更准确地说,不允许我修改),这意味着我必须处理我从那个 dll 中返回的数据,无论它是如何布局的。

C API 结构定义如下(RETRO_NUM_CORE_OPTION_VALUES_MAX 是一个值为 128 的常量):

struct retro_core_option_value
{
   const char *value;
   const char *label;
};

struct retro_core_option_deFinition
{
   const char *key;
   const char *desc;
   const char *info;
   struct retro_core_option_value values[RETRO_NUM_CORE_OPTION_VALUES_MAX];
   const char *default_value;
};

struct retro_core_options_intl
{
   struct retro_core_option_deFinition *us;
   struct retro_core_option_deFinition *local;
};

我的 C# 映射目前看起来像这样:

[StructLayout(LayoutKind.Sequential,CharSet = CharSet.Ansi)]
public struct retro_core_option_value
{
    public char* value;
    public char* label;
}

[StructLayout(LayoutKind.Sequential,CharSet = CharSet.Ansi)]
public struct retro_core_option_deFinition
{
    public char* key;
    public char* desc;
    public char* info;
    [MarshalAs(UnmanagedType.ByValArray,SizeConst = RETRO_NUM_CORE_OPTION_VALUES_MAX)]
    public retro_core_option_value[] values;
    public char* default_value;
}

[StructLayout(LayoutKind.Sequential)]
public struct retro_core_options_intl
{
    public IntPtr us;
    public IntPtr local;
}

回调函数在C#端有如下签名:

public unsafe bool Callback(retro_environment cmd,void* data)

retro_environment 是一个转换为枚举的无符号整数,对其执行切换,然后指示如何适当处理 void* 数据指针。这里的数据是一个 retro_core_options_intl*。

我可以通过两种方式进行 void* 转换:

retro_core_options_intl intl = Marshal.PtrToStructure<retro_core_options_intl>((IntPtr)data);

retro_core_options_intl* intl = (retro_core_options_intl*)data;

我用两种方法都得到了一个可读地址(第一种方法是 intl.us,第二种方法是 intl->us),在我的特殊情况下,“本地”部分是空的,但“我们”部分被定义为强制性的API。 intl->us 指向一个可变长度的retro_core_option_deFinition 数组。

我遇到的问题是试图读取这个强制构造中的值。

我现在正在尝试加载的数组可以在这里看到:https://github.com/visualboyadvance-m/visualboyadvance-m/blob/master/src/libretro/libretro_core_options.h 在第 51 行。

API 为“struct retro_core_option_value values[RETRO_NUM_CORE_OPTION_VALUES_MAX]”结构成员定义了一个固定大小,但是进来的代码几乎总是定义为一个数组,其中最后一个元素是“{ NULL,NULL }”以表示结束,所以它们并不总是(几乎从不)包含 128 个值。

我试过了:

retro_core_options_intl intl = Marshal.PtrToStructure<retro_core_options_intl>((IntPtr)data);
retro_core_option_deFinition us = Marshal.PtrToStructure<retro_core_option_deFinition>(intl.us);

这给出了一个 NullReferenceException。

retro_core_options_intl intl = Marshal.PtrToStructure<retro_core_options_intl>((IntPtr)data);
retro_core_option_deFinition[] us = Marshal.PtrToStructure<retro_core_option_deFinition[]>(intl.us);

这给出了一个长度为 0 的 retro_core_option_deFinition 数组。

retro_core_options_intl intl = Marshal.PtrToStructure<retro_core_options_intl>((IntPtr)data);
retro_core_option_deFinition us = new retro_core_option_deFinition();
Marshal.PtrToStructure(intl.us,us);

这给出了“目标是一个装箱值”。

这基本上就是我所在的地方... 任何帮助将不胜感激:)

可以在此处找到整个代码库:https://github.com/Skurdt/LibretroUnityFE

解决方法

我看到的第一件事是您需要在 wchar_t 代码中使用 char 类型而不是 C 类型,或者您可以使用 byte 而不是 {{ 1}} 在 C# 中。 C# 中的 char 是两个字节。 C 代码中的 System.Char 为 1 个字节。

您还可以在 C# 代码中使用 char 并使用 System.String 属性对其进行注释,以告诉它传入的是什么类型的字符数据,例如 Ansi 或 Unicode C 字符串。