这是我的 Raymond Chen's code 版本,它通过 C++ 异常添加了错误处理。
首先,我们声明一些帮助函数,用于从HRESULT 抛出std::system_error 异常,将 GUID 转换为字符串和用于 COM 初始化的 RAII 包装器。
#include <windows.h>
#include <shldisp.h>
#include <shlobj.h>
#include <exdisp.h>
#include <atlbase.h>
#include <stdlib.h>
#include <iostream>
#include <system_error>
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
if( FAILED( hr ) )
throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}
template< typename ResultT = std::string >
ResultT to_string( REFIID riid )
{
LPOLESTR pstr = nullptr;
if( SUCCEEDED( ::StringFromCLSID( riid, &pstr ) ) )
{
// Use iterator arguments to cast from wchar_t to char if element type of ResultT is char.
ResultT result{ pstr, pstr + wcslen( pstr ) };
::CoTaskMemFree( pstr ); pstr = nullptr;
return result;
}
return {};
}
struct ComInit
{
ComInit() { ThrowIfFailed( ::CoInitialize( nullptr ), "Could not initialize COM" ); }
~ComInit() { ::CoUninitialize(); }
ComInit( ComInit const& ) = delete;
ComInit& operator=( ComInit const& ) = delete;
};
接下来是执行实际工作的函数。这与Remy Lebeau's answer 中的代码基本相同,但增加了错误处理(以及我个人的格式化风格)。
void FindDesktopFolderView( REFIID riid, void **ppv )
{
CComPtr<IShellWindows> spShellWindows;
ThrowIfFailed(
spShellWindows.CoCreateInstance( CLSID_ShellWindows ),
"Could not create instance of IShellWindows" );
CComVariant vtLoc{ CSIDL_DESKTOP };
CComVariant vtEmpty;
long lhwnd = 0;
CComPtr<IDispatch> spdisp;
ThrowIfFailed(
spShellWindows->FindWindowSW(
&vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp ),
"Could not find desktop shell window" );
CComQIPtr<IServiceProvider> spProv{ spdisp };
if( ! spProv )
ThrowIfFailed( E_NOINTERFACE, "Could not query interface IServiceProvider" );
CComPtr<IShellBrowser> spBrowser;
ThrowIfFailed(
spProv->QueryService( SID_STopLevelBrowser, IID_PPV_ARGS(&spBrowser) ),
"Could not query service IShellBrowser" );
CComPtr<IShellView> spView;
ThrowIfFailed(
spBrowser->QueryActiveShellView( &spView ),
"Could not query active IShellView" );
ThrowIfFailed(
spView->QueryInterface( riid, ppv ),
"Could not query interface " + to_string( riid ) + " from IShellView" );
}
void GetDesktopAutomationObject( REFIID riid, void **ppv )
{
CComPtr<IShellView> spsv;
FindDesktopFolderView( IID_PPV_ARGS(&spsv) );
CComPtr<IDispatch> spdispView;
ThrowIfFailed(
spsv->GetItemObject( SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView) ),
"Could not get item object SVGIO_BACKGROUND from IShellView" );
ThrowIfFailed(
spdispView->QueryInterface( riid, ppv ),
"Could not query interface " + to_string( riid ) + " from ShellFolderView" );
}
void ShellExecuteFromExplorer(
PCWSTR pszFile,
PCWSTR pszParameters = nullptr,
PCWSTR pszDirectory = nullptr,
PCWSTR pszOperation = nullptr,
int nShowCmd = SW_SHOWNORMAL)
{
CComPtr<IShellFolderViewDual> spFolderView;
GetDesktopAutomationObject( IID_PPV_ARGS(&spFolderView) );
CComPtr<IDispatch> spdispShell;
ThrowIfFailed(
spFolderView->get_Application( &spdispShell ),
"Could not get application object from IShellFolderViewDual" );
CComQIPtr<IShellDispatch2> spdispShell2{ spdispShell };
if( !spdispShell2 )
ThrowIfFailed( E_NOINTERFACE, "Could not query interface IShellDispatch2" );
ThrowIfFailed(
spdispShell2->ShellExecute(
CComBSTR{ pszFile },
CComVariant{ pszParameters ? pszParameters : L"" },
CComVariant{ pszDirectory ? pszDirectory : L"" },
CComVariant{ pszOperation ? pszOperation : L"" },
CComVariant{ nShowCmd } ),
"ShellExecute failed" );
}
用法示例:
int main()
{
try
{
ComInit init;
ShellExecuteFromExplorer( L"http://www.stackoverflow.com" );
}
catch( std::system_error& e )
{
std::cout << "ERROR: " << e.what() << "\n"
<< "Error code: " << e.code() << std::endl;
}
}
补充说明:
使用此方法时,您可能会注意到已启动应用程序的窗口并不总是出现在前台,尤其是在它已经在运行的情况下。我的解决方法是在调用ShellExecuteFromExplorer() 之前调用AllowSetForegroundWindow( ASFW_ANY ) 以使进程能够将自身置于前台(我们无法指定进程ID,因为我们不知道将要预先启动的进程)。