You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Windows平台通过应用ID启动应用并获取进程ID(PID)的实现方案咨询

Windows平台通过应用ID启动应用并获取进程ID(PID)的实现方案咨询

我完全理解你现在遇到的困境:不管是用PowerShell的start命令通过shell:AppsFolder\<AppID>启动应用(无论UWP还是普通Win32程序),还是用Rust调用ShellExecuteW API,都没法直接获取到启动后进程的PID;而IApplicationActivationManager虽然能返回PID,但只支持UWP应用,没法覆盖所有场景。下面给你分PowerShell和Rust两种场景,提供针对性的解决思路:

一、PowerShell 实现方案

1. 分应用类型处理(推荐)

先通过应用ID判断是UWP还是普通Win32应用,再分别处理:

  • 普通Win32应用:从Get-StartApps里拿到应用的实际可执行文件路径,用Start-Process-PassThru参数直接获取包含PID的进程对象
  • UWP应用:调用IApplicationActivationManager COM接口启动并获取PID

示例代码:

# 1. 获取目标应用信息(替换为你的目标AppID)
$targetApp = Get-StartApps | Where-Object { $_.AppID -eq "308046B0AF4A39CB" }

# 2. 判断应用类型并启动获取PID
if ($targetApp.AppID -match '^Microsoft\.|^[0-9A-F]{16,}$') {
    # 识别为UWP应用,使用IApplicationActivationManager
    $activationManager = [Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]::Parse("{45BA127D-10A8-46EA-8AB7-56EA9078943C}")))
    $pid = $activationManager.ActivateApplication($targetApp.AppID, $null, 0, [ref]0)
    Write-Host "UWP应用启动成功,PID: $pid"
} else {
    # 识别为普通Win32应用,尝试获取可执行文件路径
    $appPath = $null
    # 先从注册表卸载项查找
    $uninstallEntry = Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" | Where-Object { $_.DisplayName -eq $targetApp.Name }
    if ($uninstallEntry) {
        $appPath = Join-Path $uninstallEntry.InstallLocation "$($targetApp.Name).exe"
    }
    # 注册表找不到就从开始菜单快捷方式解析
    if (-not $appPath) {
        $shortcutPath = Get-ChildItem -Path "$env:APPDATA\Microsoft\Windows\Start Menu\Programs" -Recurse -Filter "*.lnk" | Where-Object { $_.Name -like "*$($targetApp.Name)*" } | Select-Object -First 1 -ExpandProperty FullName
        $shell = New-Object -ComObject WScript.Shell
        $appPath = $shell.CreateShortcut($shortcutPath).TargetPath
    }
    # 启动并获取PID
    $process = Start-Process -FilePath $appPath -PassThru
    Write-Host "Win32应用启动成功,PID: $($process.Id)"
}

2. 通用 fallback 方案(通过进程名/窗口查找)

如果类型判断失效,还可以在启动应用后,通过进程名或者窗口标题匹配查找PID(注意:如果存在多个同名进程,结果可能不准确):

# 启动目标应用
start 'shell:AppsFolder\<你的AppID>'
# 等待1秒确保进程启动完成
Start-Sleep -Seconds 1
# 通过进程名查找(替换为目标应用的进程名,比如Firefox)
$pid = (Get-Process -Name "firefox" | Select-Object -Last 1).Id
Write-Host "找到PID: $pid"

二、Rust 实现方案

1. 分场景调用不同API

和PowerShell思路一致,分普通Win32应用和UWP应用分别处理:

  • 普通Win32应用:放弃ShellExecuteW,改用CreateProcessW API,它能直接返回包含PID的PROCESS_INFORMATION结构体
  • UWP应用:继续使用IApplicationActivationManager COM接口,获取启动后的PID

处理普通Win32应用的Rust代码示例

use std::ptr;
use windows::core::{PCWSTR, Result};
use windows::Win32::Foundation::{BOOL, STARTF_USESHOWWINDOW};
use windows::Win32::System::Threading::{CreateProcessW, PROCESS_INFORMATION, STARTUPINFOW};
use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL;

fn launch_win32_app(app_path: &str) -> Result<u32> {
    let app_path_wide: Vec<u16> = app_path.encode_utf16().chain(Some(0)).collect();
    let mut startup_info = STARTUPINFOW::default();
    startup_info.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
    startup_info.dwFlags = STARTF_USESHOWWINDOW.0;
    startup_info.wShowWindow = SW_SHOWNORMAL.0;
    let mut process_info = PROCESS_INFORMATION::default();

    let success = unsafe {
        CreateProcessW(
            PCWSTR::null(),
            PCWSTR::from_raw(app_path_wide.as_ptr()),
            ptr::null_mut(),
            ptr::null_mut(),
            BOOL(0),
            0,
            ptr::null_mut(),
            PCWSTR::null(),
            &startup_info,
            &mut process_info,
        )
    };

    if success.as_bool() {
        // 关闭不需要的句柄,避免资源泄漏
        unsafe {
            windows::Win32::System::Threading::CloseHandle(process_info.hProcess);
            windows::Win32::System::Threading::CloseHandle(process_info.hThread);
        }
        Ok(process_info.dwProcessId)
    } else {
        Err(windows::core::Error::from_win32())
    }
}

处理UWP应用的Rust代码示例(基于IApplicationActivationManager)

use std::ptr;
use windows::core::{ComInterface, PCWSTR, Result};
use windows::Win32::System::Com::{CoInitializeEx, COINIT_APARTMENTTHREADED};

#[windows::interface("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
#[allow(non_snake_case)]
trait IApplicationActivationManager: ComInterface {
    fn ActivateApplication(
        &self,
        appUserModelId: &PCWSTR,
        arguments: &PCWSTR,
        options: u32,
        processId: *mut u32,
    ) -> windows::core::HRESULT;
}

fn launch_uwp_app(app_id: &str) -> Result<u32> {
    // 初始化COM环境
    unsafe {
        CoInitializeEx(ptr::null(), COINIT_APARTMENTTHREADED)?;
    }

    // 创建IApplicationActivationManager实例
    let activation_manager: IApplicationActivationManager =
        windows::core::factory::<IApplicationActivationManager>()?.create_instance()?;
    
    let app_id_wide: Vec<u16> = app_id.encode_utf16().chain(Some(0)).collect();
    let mut pid = 0;

    // 启动应用并获取PID
    unsafe {
        activation_manager.ActivateApplication(
            PCWSTR::from_raw(app_id_wide.as_ptr()),
            PCWSTR::null(),
            0,
            &mut pid,
        )?;
    }

    Ok(pid)
}

2. 通用 fallback:启动后通过窗口查找PID

如果无法提前区分应用类型,也可以在启动后通过窗口标题匹配获取PID,Rust里可以用EnumWindows API遍历窗口,再通过GetWindowThreadProcessId获取对应PID:

use std::ptr;
use windows::core::Result;
use windows::Win32::Foundation::{HWND, LRESULT, WPARAM};
use windows::Win32::UI::WindowsAndMessaging::{EnumWindows, GetWindowTextW, GetWindowThreadProcessId};

fn find_pid_by_window_title(title_substring: &str) -> Option<u32> {
    let title_wide: Vec<u16> = title_substring.encode_utf16().chain(Some(0)).collect();
    let mut found_pid = None;

    unsafe {
        EnumWindows(
            Some(enum_windows_callback),
            WPARAM(
                (&mut found_pid, title_wide.as_ptr()) as *mut (Option<u32>, *const u16) as usize,
            ),
        );
    }

    found_pid
}

unsafe extern "system" fn enum_windows_callback(hwnd: HWND, lparam: WPARAM) -> i32 {
    let (found_pid, target_title_ptr) = *(lparam.0 as *mut (Option<u32>, *const u16));
    if found_pid.is_some() {
        return 0; // 已找到目标,停止枚举
    }

    // 获取当前窗口标题
    let mut window_title = [0u16; 256];
    let title_len = GetWindowTextW(hwnd, &mut window_title);
    if title_len == 0 {
        return 1; // 无标题窗口,跳过
    }

    // 转换为字符串并匹配
    let target_title = String::from_utf16_lossy(std::slice::from_raw_parts(target_title_ptr, 256));
    let window_title_str = String::from_utf16_lossy(&window_title[..title_len as usize]);

    if window_title_str.contains(&target_title) {
        let mut pid = 0;
        GetWindowThreadProcessId(hwnd, Some(&mut pid));
        *found_pid.as_mut().unwrap() = Some(pid);
        return 0;
    }

    1 // 继续枚举下一个窗口
}

关键注意事项

  • shell:AppsFolder\<AppID>是Shell层的高层启动方式,会由Shell进程代为处理,因此无法直接获取子进程的PID,必须换用能直接控制进程创建的API(如CreateProcessW)或UWP专用的COM接口
  • UWP应用的AppID通常带有Microsoft.前缀或为一串长GUID,普通Win32应用的AppID一般与可执行文件名或路径相关,可通过此特征提前区分应用类型
  • 启动后建议等待1-2秒再执行PID查找操作,避免进程尚未完全启动导致查找失败

内容来源于stack exchange

火山引擎 最新活动