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

C# 过早访问进程MainModule触发Win32Exception的最优解决方案问询

问题根因

你遇到的异常本质是.NET的Process.MainModule属性在访问时会直接尝试读取进程的模块信息,如果进程还未完成PE头加载、主模块未完成映射,该属性的内部实现就会直接抛出Win32Exception,你写的if (game.MainModule != null)判断本身就触发了属性的访问逻辑,所以即使是判空操作也会抛异常,这就是测试人员遇到报错的原因。

最优实现方案(无异常、无需反复挂起恢复进程)

核心思路是启动进程时直接将其创建为挂起状态,避免游戏逻辑提前执行,再通过Win32 API循环查询模块加载状态,全程通过返回值判断是否就绪,不会触发异常。

第一步:Win32 API 声明

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public static class Win32Api
{
    [StructLayout(LayoutKind.Sequential)]
    private struct STARTUPINFO
    {
        public int cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public int dwX;
        public int dwY;
        public int dwXSize;
        public int dwYSize;
        public int dwXCountChars;
        public int dwYCountChars;
        public int dwFillAttribute;
        public int dwFlags;
        public short wShowWindow;
        public short cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    }

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern bool CreateProcess(
        string lpApplicationName,
        string lpCommandLine,
        IntPtr lpProcessAttributes,
        IntPtr lpThreadAttributes,
        bool bInheritHandles,
        uint dwCreationFlags,
        IntPtr lpEnvironment,
        string lpCurrentDirectory,
        [In] ref STARTUPINFO lpStartupInfo,
        out PROCESS_INFORMATION lpProcessInformation);

    [DllImport("psapi.dll", SetLastError = true)]
    private static extern bool EnumProcessModules(
        IntPtr hProcess,
        [Out] IntPtr[] lphModule,
        int cb,
        out int lpcbNeeded);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern uint ResumeThread(IntPtr hThread);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hObject);

    private const uint CREATE_SUSPENDED = 0x00000004;
}

第二步:核心实现逻辑

public static (int processId, IntPtr baseAddress, IntPtr hProcess, IntPtr hMainThread) LaunchGameAndGetBaseAddress(string gamePath, string launchArgs)
{
    var si = new STARTUPINFO();
    si.cb = Marshal.SizeOf(si);
    var pi = new PROCESS_INFORMATION();

    // 直接创建挂起状态的进程,完全避免游戏逻辑提前执行
    bool createSuccess = CreateProcess(
        lpApplicationName: gamePath,
        lpCommandLine: $"{gamePath} {launchArgs}",
        lpProcessAttributes: IntPtr.Zero,
        lpThreadAttributes: IntPtr.Zero,
        bInheritHandles: false,
        dwCreationFlags: CREATE_SUSPENDED,
        lpEnvironment: IntPtr.Zero,
        lpCurrentDirectory: System.IO.Path.GetDirectoryName(gamePath),
        lpStartupInfo: ref si,
        lpProcessInformation: out pi);

    if (!createSuccess)
    {
        throw new InvalidOperationException($"进程启动失败,错误码:{Marshal.GetLastWin32Error()}");
    }

    IntPtr[] moduleHandles = new IntPtr[1];
    int bytesNeeded = 0;
    int retryCount = 0;
    const int maxRetry = 500; // 最多等待500ms,可根据需求调整

    // 循环查询模块加载状态,无异常,通过返回值判断
    while (retryCount < maxRetry)
    {
        if (EnumProcessModules(pi.hProcess, moduleHandles, Marshal.SizeOf<IntPtr>(), out bytesNeeded) && bytesNeeded >= Marshal.SizeOf<IntPtr>())
        {
            // 第一个模块就是主模块,直接拿到BaseAddress
            return (pi.dwProcessId, moduleHandles[0], pi.hProcess, pi.hThread);
        }

        // 加载未完成,等待1ms再试
        System.Threading.Thread.Sleep(1);
        retryCount++;
    }

    // 超时处理逻辑
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
    throw new TimeoutException("等待进程主模块加载超时");
}

后续使用说明

你拿到返回的主线程句柄hMainThread后,可以先打完补丁再调用ResumeThread(hMainThread)让游戏继续运行即可,全程不需要反复挂起恢复进程,也不会触发非预期异常。

轻量优化方案(不想引入原生API)

如果你不想引入太多Win32 API,可以优化原有逻辑,避免递归,限制重试次数,把异常控制在有限范围内,比原有递归方案更稳定:

Process game = Process.Start(Path.Combine(installPath, "game.exe"), launchCommands);
IntPtr baseAddress = IntPtr.Zero;
int retryCount = 0;
const int maxRetry = 500;

while (retryCount < maxRetry)
{
    try
    {
        if (game.MainModule != null)
        {
            game.Suspend();
            baseAddress = game.MainModule.BaseAddress;
            break;
        }
    }
    catch (Win32Exception)
    {
        // 忽略加载未完成的异常,继续重试
    }
    System.Threading.Thread.Sleep(1);
    retryCount++;
}

if (baseAddress == IntPtr.Zero)
{
    throw new TimeoutException("等待主模块加载超时");
}
int processId = game.Id;

注意事项

  • 请确保你的工具架构和游戏架构匹配(32位工具只能访问32位进程,64位工具可以访问32/64位进程),建议将工具编译为Any CPU并勾选「优先32位」,或者直接对应游戏架构编译。
  • 如果游戏有反作弊机制,该方案可能会被拦截,需要单独适配。

内容的提问来源于stack exchange,提问作者Krisan Thyme

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 07:06:01