让本地代码对.NET托管对象进行操作

后端开发 2026-07-08

假设我想在我的自定义图形引擎中为TTF字体添加支持。我把目光投向了FreeType,这是在C 领域广泛使用、专为处理TTF字体而设计的库。

要在FreeType中加载字体,你需要调用 FT_Open_Face:

    [LibraryImport("freetype")]
    internal static partial int FT_Open_Face(nint library, FT_Open_Args* args,
                                             CLong face_index, nint* aface);

FT_Open_Args 是一个C 结构体,其布局如下:

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FT_Open_Args
{
    public uint flags;
    public byte* memory_base;
    public CLong memory_size;
    public byte* pathname;
    public FT_Stream* stream;
    public nint driver;
    public int num_params;
    public FT_Parameter* params;
}

FT_Stream 进一步来看,它的样子如下:

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FT_Stream
{
    public byte* base;
    public CULong size;
    public CULong pos;

    public nint descriptor;
    public nint pathname;
    public delegate* unmanaged[Cdecl]<FT_Stream*, CULong,
        byte*, CULong, CULong> read;
    public delegate* unmanaged[Cdecl]<FT_Stream*, void> close;

    public nint memory;
    public nint cursor;
    public nint limit;
}

我的图形引擎提供一种方法来访问已编译的游戏资源,该方法返回 System.IO.Stream。为了让FreeType在 C语言中使用自定义流,你需要把 descriptor 指向你的自定义流对象,然后把 readclose 指向你自己的读取回调和关闭回调。

那么,我该如何让FreeType使用.NET的 Stream 对象来加载我的字体呢?

解决方案

你可以将 GCHandle 传递给未托管的库,并在回调中检索它。

在从.NET流创建 FT_Stream 时,你需要对该流进行包装:

    internal static unsafe FT_Stream* ToFTStream(this Stream stream)
    {
        // FreeType requires ftStream to be in unmanaged memory
        FT_Stream* ftStream = NativeMemory.AllocZeroed((nuint)sizeof(FT_Stream));
        GCHandle h = GCHandle.Alloc(stream);

        ftStream->size = new CULong((nuint)stream.Length);
        ftStream->descriptor = (nint)h;
        ftStream->read = &_FTRead;
        ftStream->close = &_FTClose;

        return ftStream;
    }

在FreeType使用的回调中,对它进行解封装:

    [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
    private unsafe static CULong _FTRead(FT_Stream* ftStream, CULong offset,
                                         byte* buffer, CULong count)
    {
        GCHandle h = (GCHandle)(ftStream->descriptor);
        Stream stream = (Stream)(h.Target)!;

        // rest of your code...
    }
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章