1

我正在尝试将一组 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*。

我可以通过 2 种方式进行 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

4

1 回答 1

2

我看到的第一件事是,您要么需要在代码中使用wchar_t类型而不是char类型C,要么可以在 C#中使用byte而不是使用类型。在 C# 中是两个字节。在 C 代码中是 1 个字节。charSystem.Charchar

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

于 2020-12-31T15:25:22.123 回答