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

源生成原生封送中ref参数是否需要特殊处理?

源生成原生封送中ref参数的AccessViolationException问题

我在试用源生成原生封送特性时,选择dbghelp.dll的SymGetModuleInfo64作为示例。编写自定义封送器后,生成的代码能运行,但在将ConvertToManaged的结果赋值给ref参数时抛出AccessViolationException。想请教ref参数是否需要特殊处理?目前我仅实例化IMAGEHELP_MODULE64并设置其SizeOfStruct后传入SymGetModuleInfo64。


生成的P/Invoke代码:

public unsafe partial class Test
{
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "7.0.10.26716")]
    [System.Runtime.CompilerServices.SkipLocalsInitAttribute]
    private static partial bool SymGetModuleInfo64(nint hProcess, long ModuleBase64, ref global::Test.Test.IMAGEHELP_MODULE64 imgHelpModule)
    {
        int __lastError;
        global::Test.Test.IMAGEHELP_MODULE64_Marshaller.Unmanaged __imgHelpModule_native;
        bool __retVal;
        int __retVal_native;
        // Marshal - Convert managed data to native data.
        __imgHelpModule_native = global::Test.Test.IMAGEHELP_MODULE64_Marshaller.ConvertToUnmanaged(imgHelpModule);
        {
            System.Runtime.InteropServices.Marshal.SetLastSystemError(0);
            __retVal_native = __PInvoke(hProcess, ModuleBase64, &__imgHelpModule_native);
            __lastError = System.Runtime.InteropServices.Marshal.GetLastSystemError();
        }

        // Unmarshal - Convert native data to managed data.
        __retVal = __retVal_native != 0;
        // 此处抛出AccessViolationException
        imgHelpModule = global::Test.Test.IMAGEHELP_MODULE64_Marshaller.ConvertToManaged(__imgHelpModule_native);
        System.Runtime.InteropServices.Marshal.SetLastPInvokeError(__lastError);
        return __retVal;
        // Local P/Invoke
        [System.Runtime.InteropServices.DllImportAttribute("dbghelp.dll", EntryPoint = "SymGetModuleInfo64", ExactSpelling = true)]
        static extern unsafe int __PInvoke(nint hProcess, long ModuleBase64, global::Test.Test.IMAGEHELP_MODULE64_Marshaller.Unmanaged* imgHelpModule);
    }
}

我尝试为参数分配内存确保所有权,但无论如何操作仍会抛出异常。


自定义封送器及结构体定义:

public unsafe partial class Test
{
    [StructLayout(LayoutKind.Sequential)]
    [NativeMarshalling(typeof(IMAGEHELP_MODULE64_Marshaller))]
    private struct IMAGEHELP_MODULE64
    {
        public int SizeOfStruct;
        public long BaseOfImage;
        public int ImageSize;
        public int TimeDateStamp;
        public int CheckSum;
        public int NumSyms;
        public SymType SymType;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string ModuleName;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
        public string ImageName;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
        public string LoadedImageName;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
        public string LoadedPdbName;

        public int CVSig;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 780)]
        public string CVData;

        public int PdbSig;
        public GUID PdbSig70;
        public int PdbAge;
        public bool PdbUnmatched;
        public bool DbgUnmatched;
        public bool LineNumbers;
        public bool GlobalSymbols;
        public bool TypeInfo;
        public bool SourceIndexed;
        public bool Publics;
        public int MachineType;
        public int Reserved;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct GUID
    {
        public int Data1;
        public ushort Data2;
        public ushort Data3;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[] data4;
    }

    [Flags]
    private enum SymType : uint
    {
        SymNone,
        SymCoff,
        SymCv,
        SymPdb,
        SymExport,
        SymDeferred,
        SymSym,
        SymDia,
        SymVirtual,
    }

    [CustomMarshaller(typeof(IMAGEHELP_MODULE64), MarshalMode.Default, typeof(IMAGEHELP_MODULE64_Marshaller))]
    private static unsafe class IMAGEHELP_MODULE64_Marshaller
    {
        [StructLayout(LayoutKind.Explicit)]
        public ref struct Unmanaged
        {
            [FieldOffset(0)] public int SizeOfStruct;
            [FieldOffset(580)] public fixed byte LoadedPdbName[256];
        }

        public static Unmanaged ConvertToUnmanaged(IMAGEHELP_MODULE64 managed)
        {
            return new Unmanaged
            {
                SizeOfStruct = managed.SizeOfStruct
            };
        }

        public static IMAGEHELP_MODULE64 ConvertToManaged(Unmanaged unmanaged)
        {
            return new IMAGEHELP_MODULE64
            {
                LoadedPdbName = AnsiStringMarshaller.ConvertToManaged(unmanaged.LoadedPdbName)
            };
        }
    }

    [LibraryImport("dbghelp.dll", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static partial bool SymGetModuleInfo64(nint hProcess, long ModuleBase64,
        ref IMAGEHELP_MODULE64 imgHelpModule);
}

调用代码:

var moduleInfo = new IMAGEHELP_MODULE64();
moduleInfo.SizeOfStruct = Marshal.SizeOf(moduleInfo);
if (!SymGetModuleInfo64(_processPtr, _baseOfDll, ref moduleInfo))
    throw new Win32Exception(Marshal.GetLastWin32Error());

问题分析与解决

核心问题

  1. 内存越界写入:你的Unmanaged ref struct仅定义了SizeOfStruct和LoadedPdbName,但SymGetModuleInfo64会向传入的结构体指针写入完整的原生IMAGEHLP_MODULE64数据,导致写入超出Unmanaged结构体内存范围的数据,触发内存损坏,最终在赋值时抛出异常。
  2. 结构体大小不匹配:Marshal.SizeOf(moduleInfo)计算的是托管结构体的大小,包含字符串等托管类型的布局开销,和原生结构体实际大小不符,导致SizeOfStruct设置错误,原生函数写入逻辑混乱。
  3. 字符串处理风险:直接使用AnsiStringMarshaller.ConvertToManaged处理fixed byte数组,若未正确处理字符串终止符,可能读取超出数组范围的内存。

修复步骤

  1. 匹配原生结构体内存布局
    必须让Unmanaged结构体的大小和布局与Windows SDK中的IMAGEHLP_MODULE64完全一致,即使你只关心个别字段。可以直接定义完整的原生结构体,或通过Size属性强制匹配大小:

    [StructLayout(LayoutKind.Sequential, Size = 1048)] // 对应原生IMAGEHLP_MODULE64的标准大小
    public ref struct Unmanaged
    {
        public int SizeOfStruct;
        public long BaseOfImage;
        public int ImageSize;
        public int TimeDateStamp;
        public int CheckSum;
        public int NumSyms;
        public uint SymType;
        public fixed byte ModuleName[32];
        public fixed byte ImageName[256];
        public fixed byte LoadedImageName[256];
        public fixed byte LoadedPdbName[256];
        // 其余成员可省略,但Size属性必须保证总大小正确
    }
    
  2. 改用in-place更新逻辑
    对于ref参数,自定义封送器应直接更新传入的托管实例,而非返回新对象,避免内存赋值时的异常:

    public static void ConvertToManaged(Unmanaged unmanaged, ref IMAGEHELP_MODULE64 managed)
    {
        // 仅更新关心的字段,保留原有字段值
        fixed (byte* p = unmanaged.LoadedPdbName)
        {
            managed.LoadedPdbName = Marshal.PtrToStringAnsi((nint)p) ?? string.Empty;
        }
    }
    

    同时调整封送器的MarshalMode为ManagedToUnmanagedIn和UnmanagedToManagedOut,让源生成器自动生成in-place更新的代码。

  3. 修正SizeOfStruct赋值
    直接使用原生结构体的标准大小赋值,避免托管结构体大小的干扰:

    moduleInfo.SizeOfStruct = 1048; // 替换为Windows SDK中定义的sizeof(IMAGEHLP_MODULE64)值
    

简化替代方案

如果仅关心LoadedPdbName,可以直接使用标准P/Invoke,无需自定义封送器,避免手动处理内存布局:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Size = 1048)]
private struct IMAGEHLP_MODULE64
{
    public int SizeOfStruct;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string LoadedPdbName;
}

[DllImport("dbghelp.dll", CharSet = CharSet.Ansi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SymGetModuleInfo64(nint hProcess, long ModuleBase64, ref IMAGEHLP_MODULE64 imgHelpModule);

内容的提问来源于stack exchange,提问作者Alex Malcoci

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 12:17:02