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

如何通过C#获取调用执行指定EXE的程序路径与进程ID

结论

可以获取调用方进程ID与文件路径,但不存在适配所有Windows版本、所有启动场景的零权限通用方案,不同场景下的实现可靠性差异较大。


常见实现方案与适用边界

1. 父进程查询法(常规场景首选,无特殊权限要求)

不管b.exe是通过ShellExecute系列API还是.NET的Process类启动a.exe,默认情况下b.exe就是a.exe的父进程,Windows内核会在新进程的基本信息结构中记录父进程PID,可通过ntdll导出的NtQueryInformationProcessAPI直接读取,拿到PID后再查询对应进程的可执行文件路径即可。

注意:这个API属于内核层长期稳定的未公开API,从Windows 2000到最新的Windows 11均保持兼容,性能远高于WMI查询。

可直接复用的实现代码:

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

public static class CallerProcessResolver
{
    [DllImport("ntdll.dll")]
    private static extern int NtQueryInformationProcess(
        IntPtr processHandle, 
        int processInformationClass, 
        IntPtr processInformation, 
        uint processInformationLength, 
        out uint returnLength);

    private const int ProcessBasicInfoClass = 0;

    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESS_BASIC_INFORMATION
    {
        public IntPtr Reserved1;
        public IntPtr PebBaseAddress;
        public IntPtr Reserved2_0;
        public IntPtr Reserved2_1;
        public IntPtr CurrentProcessId;
        public IntPtr ParentProcessId;
    }

    /// <summary>
    /// 在程序启动入口调用,获取调用方进程信息
    /// </summary>
    /// <returns>父进程PID、父进程可执行文件路径(拿不到时返回null)</returns>
    public static (int parentPid, string? parentExePath) GetCallerInfo()
    {
        using var currentProcess = Process.GetCurrentProcess();
        var infoStruct = new PROCESS_BASIC_INFORMATION();
        IntPtr structPtr = Marshal.AllocHGlobal(Marshal.SizeOf(infoStruct));
        uint returnedBytes = 0;
        try
        {
            Marshal.StructureToPtr(infoStruct, structPtr, false);
            NtQueryInformationProcess(
                currentProcess.Handle, 
                ProcessBasicInfoClass, 
                structPtr, 
                (uint)Marshal.SizeOf(infoStruct), 
                out returnedBytes);
            infoStruct = Marshal.PtrToStructure<PROCESS_BASIC_INFORMATION>(structPtr);
        }
        finally
        {
            Marshal.FreeHGlobal(structPtr);
        }

        int callerPid = infoStruct.ParentProcessId.ToInt32();
        string? callerPath = null;
        try
        {
            using var callerProcess = Process.GetProcessById(callerPid);
            callerPath = callerProcess.MainModule?.FileName;
        }
        catch
        {
            // 触发异常的常见原因:父进程已退出、权限不足无法访问父进程模块信息
        }

        return (callerPid, callerPath);
    }
}

这个方案的局限性:

  • Windows不会对父进程句柄做持有引用,如果b.exe在a.exe启动后立刻退出,对应的PID可能被系统回收复用给其他新进程,此时拿到的信息不属于真实调用方
  • 如果b.exe调用启动API时刻意通过PROC_THREAD_ATTRIBUTE_PARENT_PROCESS属性伪造父进程,拿到的是指定的虚假父进程信息
  • 如果启动过程触发UAC提权,中间会经过系统提权组件中转,此时父进程会是系统进程而非真实的b.exe
  • 如果a.exe权限远低于b.exe(比如a是普通用户权限运行,b是系统服务/管理员权限运行),会因权限不足无法读取b.exe的文件路径,只能拿到PID

2. 句柄枚举法(特殊场景兜底,需要管理员权限)

如果启动链存在UAC中转、父进程伪造这类特殊情况,可以在a.exe启动后立刻枚举系统内所有进程的打开句柄,查找持有当前a.exe进程句柄、且句柄具备进程创建相关权限的进程,即为真实调用方。
这个方案可靠性更高,但必须以管理员权限运行,且枚举句柄的性能开销大,还需要过滤系统进程的默认句柄,误判率高,仅适合对可靠性要求极高、且能保证运行权限的场景。

3. 参数传递法(100%可靠,无权限要求,优先推荐)

如果a.exe和b.exe都是你自己可控的程序,完全不需要依赖系统API查询:b.exe启动a.exe时,直接通过命令行参数把自身的PID、可执行文件路径传给a.exe即可,a.exe启动时直接解析命令行就能拿到准确信息,完全不存在前面提到的父进程退出、PID复用、UAC断链、权限不足的问题。


避坑提示
  • 不推荐使用WMI查询Win32_Process的ParentProcessId字段,性能比原生API差10~100倍,且同样存在父进程退出后信息不准的问题
  • 如果a.exe编译为32位程序在64位系统上运行,查询64位父进程路径时会触发SysWOW64文件系统重定向,要么关闭“首选32位”编译选项编译为AnyCPU,要么临时调用Wow64DisableWow64FsRedirectionAPI关闭重定向后再查询路径
  • 调用方查询必须放在a.exe的启动入口点第一时间执行,程序运行越久,父进程退出、PID复用的概率越高,结果可靠性越低

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 18:36:23