Raymond Chen 2024年01月31日
如何通过 ShellExecuteEx 或 IContextMenu 向启动的进程添加环境变量?
ShellExecuteEx
函数和 IContextMenu
接口为调用者提供了多个地方来自定义执行过程,允许调用传递一个“站点”,ShellExecuteEx
/IContextMenu
将在执行过程的各个点访问它。
今天我们将通过 ShellExecuteEx
和 IContextMenu
启动的进程来演示添加环境变量的技术。
ICreatingProcess
接口由 ShellExecuteEx
和 IContextMenu
使用,允许调用者自定义进程的创建方式。通过查询站点 SID_ExecuteCreatingProcess
并请求 ICreatingProcess
来获取扩展点。如果产生了一个,系统将调用 OnCreating
方法,并通过一个对象来定制创建过程。
今天使用的 C++ COM 库是(随机选择)C++/WinRT。
struct AddEnvironmentVariableSite :
winrt::implements<AddEnvironmentVariableSite,
::IServiceProvider,
::ICreatingProcess>
{
IFACEMETHOD(QueryService)
(REFGUID service, REFIID riid, void** ppv)
{
if (service == SID_ExecuteCreatingProcess)
{
return this->QueryInterface(riid, ppv);
}
else
{
*ppv = nullptr;
return E_NOTIMPL;
}
}
IFACEMETHOD(OnCreating)(ICreateProcessInputs* inputs)
{
return inputs->SetEnvironmentVariable(
L"EXTRAVARIABLE", L"Bonus");
}
};
这个站点通过响应 SID_ExecuteCreatingProcess
并返回它自己来实现 ICreatingProcess
,它的 OnCreating
方法设置了一个名为 EXTRAVARIABLE
的环境变量,值为 Bonus
。由于这是我们唯一做的事情,我们可以直接将 SetEnvironmentVariable()
的结果作为我们自己的返回值。如果你打算添加多个环境变量,你应该检查每个调用 SetEnvironmentVariable()
的返回值。
通常,你的自定义站点会是一个所谓的“站点链”的一部分,并且会将任何未处理的服务转发到你自己的站点,以便外部站点可以响应。
以下是一个示例,展示如何结合使用 ShellExecuteEx
和特殊的环境变量站点:
BOOL Sample() {
SHELLEXECUTEINFO sei{ sizeof(sei) };
sei.lpFile = LR"(C:\\Windows\\system32\\charmap.exe)";
sei.nShow = SW_SHOWNORMAL;
auto site = winrt::make_self<AddEnvironmentVariableSite>();
sei.hInstApp = reinterpret_cast<HINSTANCE>(site.get());
sei.fMask = SEE_MASK_FLAG_HINST_IS_SITE;
return ShellExecuteEx(&sei);
}
要传递站点给 ShellExecuteEx
,我们将其放在 hInstApp
成员中,并设置 SEE_MASK_FLAG_HINST_IS_SITE
标志,这样系统就知道在 hInstApp
中查找站点。
对于上下文菜单,我们显式地将自定义站点设置为上下文菜单的站点。以下是在托管 IContextMenu
时使用自定义站点的示例:
void OnContextMenu(HWND hwnd, HWND hwndContext, UINT xPos, UINT yPos) {
IContextMenu *pcm;
if (SUCCEEDED(GetUIObjectOfFile(hwnd, L"C:\\Windows\\clock.avi",
IID_IContextMenu, (void**)&pcm))) {
HMENU hmenu = CreatePopupMenu();
if (hmenu) {
if (SUCCEEDED(pcm->QueryContextMenu(hmenu, 0,
SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST,
CMF_NORMAL))) {
CMINVOKECOMMANDINFO info = { 0 };
info.cbSize = sizeof(info);
info.hwnd = hwnd;
info.lpVerb = "play";
auto site = winrt::make_self<AddEnvironmentVariableSite>();
IUnknown_SetSite(pcm, site.get());
pcm->InvokeCommand(&info);
IUnknown_SetSite(pcm, nullptr);
}
DestroyMenu(hmenu);
}
pcm->Release();
}
}
我忽略了
winrt::make_self
可能会抛出异常,这在上述示例代码中可能导致内存泄漏的事实。
这是一种相当笨拙的传递站点方式,但添加站点的能力是后来才加入到现有结构中的,所以我们不得不将它隐藏在一个其他情况下未使用的输入成员中。