感知默认的浏览器 为了确定机器的默认浏览器,你需要指定一个.htm文件到FindExecutable()函数,一个自包含例程可以快速建立空文件,调用FindExecutable()函数,然后删除这个空文件: void GetDefaultBrowser(LPTSTR szBrowserName) { HFILE h =_lcreat("dummy.htm", 0); _lclose(h); FindExecutable("dummy.htm", NULL, szBrowserName); DeleteFile("dummy.htm"); } 当然,要感觉默认浏览器是IE还是Netscape,你还可以直接检查注册表HKEY_CLASSES_ROOT\.htm键下的Default实体值。如果是htmlfile则浏览器是IE,如果是NetscapeMarkup,则浏览器是Netscape。每个浏览器都在注册表子树中设置自己的不同值。只要改变了这个值,默认浏览器就改变了。
连接到URL 如果你需要知道浏览器名以便连接到远程URL,或查看HTML文件,有一个快速方案:ShellExecute()。 ShellExecute(NULL, NULL, __TEXT(""), NULL, NULL, SW_SHOW); 这个函数本身就执行恢复和导出浏览器的操作(如果有一个安装的)。在文件名有一个前缀‘http’时,ShellExecute()函数在HKEY_CLASSES_ROOT\http\shell\open\command下进行搜索。
发送Email消息 要编程发送e-mail消息,你可以有几个选择:有协作数据对象,消息API,或依赖于其它应用提供的服务如微软的OutLook。我们总是在HTML页上采用这个任务的简化形式,这只需要一个特定的‘mailto’协议连接:
<A href=mailto:desposito@infomedia.it>Dino Esposito</A>
这里,采用ShellExecute(),同样便捷的操作对于Windows程序也是可用的:
ShellExecute(NULL, NULL, __TEXT(""), NULL, NULL, SW_SHOW); 再次需要注册表中的键: HKEY_CLASSES_ROOT \mailto \shell \open \command http 和 mailto是可插入协议的例子—构建于一个进程内COM服务器中,客户URL协议通过访问这个资源过程指导浏览器操作。使用ShellExecute(),你就可以通过任何注册的协议唤醒资源,甚至是象res这样的客户协议。
打印文档 只要允许使用命令行打印某种文档的程序存在,你就可以输出如下命令:
ShellExecute(NULL, __TEXT("print"), szDocName, NULL, NULL, SW_SHOW);
一般的习惯是允许在命令行上使用 /p 选项打印文档,但也有一种习惯—不使用任何选项来表示打印操作。
查找文件和文件夹 如果需要从特定文件夹开始运行‘查找’对话框,这样调用比较容易:
ShellExecute(NULL, __TEXT("find"), szDirName, NULL, NULL, SW_SHOW);
如果指定了一个NULL或空串作为文件夹名,这个对话框将显示准备在C驱动器上开始工作。如果传递一个非零的串,指向一个不存在文件夹,函数将返回错误。
ShellExecute()对CreateProcess() 为了与CreateProcess()函数比较,我们已经足够详细地解释了ShellExecute()。哪一个函数比较好这一点,使用哪一个函数建立进程,这一点是无法确定的(它们有相当多的不同,二者都非常有用)。 {dy}个要说明的是,ShellExecute()内部调用了CreateProcess(),所以ShellExecute()必然是CreateProcess()函数的一个更小和用法更简单的封装。反之,ShellExecute()在打开和打印文档方面是足够灵活的。没有涉及可用文档类的特定动词。除非你需要使用CreateProcess()建立探索高级属性的进程(Debug模式,环境设置,启动信息等),否则我们建议你总应该选择ShellExecute(),它有一个更简单的文法规则。
为什么应该使用ShellExecute()来运行程序 另一个使天平向ShellExecute()倾斜的论点来自微软新需求方案的建议—这是一本在建立微软Windows98和WindowsNT兼容的应用时需要参考的手册。 微软建议你使用ShellExecute()来运行外部应用,是因为它能确保系统管理员采用的任何策略限制被仔细地检查。系统策略允许管理者确定哪些应用能或不能从Windows启动。ShellExecute()采用黑名单执行这个过程,而CreateProcess()没有。
策略 策略简单地是相关设置集,它们一般保存在系统注册表中。其中最有趣的一个是Shell约束策略,其中所包含的注册表实体使你可以控制‘开始’菜单和探测器的功能。 其中你可以做的是防止Shell在开始菜单中显示‘运行’或‘查找’项。同样,你已可以禁止使用‘控制板’或任务条‘属性’对话框改变设置。下面就让我们看一看怎样做这些设置。
Shell约束策略 Shell约束策略涉及的注册表键是: HKEY_CURRENT_USER \Software \Microsoft \Windows \CurrentVersion \Policies \Explorer 为了说明其轮廓,你需要建立某些不存在的新实体,它们应有默认设置0或1: 实体
要使更新发生,所有实体都必须是DWORD。在以这种方式删除命令时,改变立即发生,然而用户界面在重新引导之前并不更新。此时如果试着使用一个命令,将会得到下面的信息框:
关于实现策略的注册键信息来源可以在MSDN库的平台SDK范围找到。
扩展ShellExecute() 尽管支持策略设置,ShellExecute()还是有一个难于使用的重大障碍:它不能返回或使你知道新建进程的Handle。也就是说,你不能导出程序并在继续执行之前等待它终止。换句话说ShellExecute()受到了它的16位血统的损害,它仅仅发掘了新的和更有威力的函数CreateProcess()的一个特征子集—WinExec()也支持的子集。 然而在4.0以后版本中引进了一个新函数:ShellExecuteEx()。它有一个Shell函数典型的原型,支持多标志,以及上述所有功能,通过提供对进程同步和PIDLs的支持扩展了ShellExecute()。
ShellExecuteEx()函数 ShellExecuteEx()函数明确地取代了ShellExecute()。它在shellapi.h中声明:
BOOL ShellExecuteEx(LPSHELLEXECUTEINFO lpExecInfo);
SHELLEXECUTEINFO定义如下: typedef struct _SHELLEXECUTEINFO { DWORD cbSize; ULONG fMask; HWND hwnd; LPCTSTR lpVerb; LPCTSTR lpFile; LPCTSTR lpParameters; LPCTSTR lpDirectory; int nShow; HINSTANCE hInstApp; // 可选的成员 LPVOID lpIDList; LPCSTR lpClass; HKEY hkeyClass; DWORD dwHotKey; HANDLE hIcon; HANDLE hProcess; } SHELLEXECUTEINFO, FAR *LPSHELLEXECUTEINFO;
在使用这个结构之前,我们极力建议你把它充填为0,并设置cbSize到结构的实际长度,操作如下:
SHELLEXECUTEINFO sei; ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO)); sei.cbSize = sizeof(SHELLEXECUTEINFO);
正如声明中的注释所说,结构的成员分成了两组。实际上,头一组使ShellExecuteEx()的功能等价于ShellExecute()。而选项成员组使函数更有力,这正是‘Ex’后缀的由来。
hwnd, lpVerb, lpFile, lpParameters, lpDirectory 和 nShow成员等价于ShellExecute()的参数,这是我们已经看到的。而hInstApp成员则是一个输出缓冲,这将由ShellExecute()的返回值填写。 nShow成员总是表示建立窗口的风格,即使lpFile是一个应用程序,它也仅仅说明应用应该怎样显示。无论lpFile是应用程序还是文档文件,nShow必须总是赋值为SW_型常量,你是知道的,如果设置为0将获得隐藏窗口。 下面是调用ShellExecuteEx()的最简单方法: SHELLEXECUTEINFO sei; ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO)); sei.cbSize = sizeof(SHELLEXECUTEINFO); sei.lpFile = __TEXT("explorer.exe"); sei.nShow = SW_SHOW; sei.lpVerb = __TEXT("open"); ShellExecuteEx(&sei);
可选成员 没有在ShellExecute()中对应参数的成员之一是fMask。它可以是一个或多个下面值的组合:
标志
附加的特征 可选字段适用于某些超出ShellExecute()的附加功能。{dy}点,也是最重要的一点,可以使用PIDLs来运行应用和打开文件夹。下面是打开‘打印机’文件夹的代码: LPITEMIDLIST pidl; SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &pidl); SHELLEXECUTEINFO sei; ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO)); sei.cbSize = sizeof(SHELLEXECUTEINFO); sei.nShow = SW_SHOW; sei.lpIDList = pidl; sei.fMask = SEE_MASK_INVOKEIDLIST; sei.lpVerb = __TEXT("open"); ShellExecuteEx(&sei); 如果指定了SEE_MASK_DOENVSUBST标志,则可以在lpFile和lpDirectory中使用任何环境变量。例如,要打开Windows目录,可以表示为%WINDIR%。 {zh1},我们获得了由ShellExecuteEx()导出的应用的同步能力。在设置了SEE_MASK_NOCLOSEPROCESS位到fMask成员后,新进程的handle将由hProcess成员返回,因此这一行代码: WaitForSingleObject(sei.hProcess, INFINITE);
将导致调用的应用阻塞,等待另一个应用终止。
显示文件属性对话框 SEE_MASK_INVOKEIDLIST标志是一个重要标志,因为这是ShellExecuteEx()另一个优于ShellExecute()的亮点:它允许函数象执行静态动词那样唤醒动态动词。前面解释过,动态动词是运行时由Shell扩展的关联菜单添加的。其工作方法是:如果ShellExecuteEx()不能在静态动词列表中找到这个动词,它就试图寻找给定文件的关联菜单。这个搜索引出IContextMenu接口指针。然后通过接口暴露的方法唤醒动态动词。 作为这个操作的结论,我们可以很容易地显示文件的属性对话框—与右击文件,然后选择属性显示的对话框相同。这里是一个简单的例子函数: void ShowFileProperties(LPCTSTR szPathName) { SHELLEXECUTEINFO sei; ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO)); sei.cbSize = sizeof(SHELLEXECUTEINFO); sei.lpFile = szPathName; sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_INVOKEIDLIST; sei.lpVerb = __TEXT("properties"); ShellExecuteEx(&sei); }
ShellExecuteEx()函数的返回值 这个函数返回一个布尔值用来描述调用是否成功:TRUE,成功,而FALSE,则失败。GetLastError()和hInstApp中的返回值可以用来获得调用时发生事件的更多信息。
例子:程序执行器 下面的截图显示了一个示例程序Execute的界面,它允许你测试动词,它是一个基于对话框的应用程序。
实现这个应用的功能,实际上就是提供这四个按钮的处理器操作代码。OnBrowse()是最容易的,代码如下: void OnBrowse(HWND hDlg) { TCHAR szFile[MAX_PATH] = {0}; TCHAR szWinDir[MAX_PATH] = {0}; GetWindowsDirectory(szWinDir, MAX_PATH); OPENFILENAME ofn; ZeroMemory(&ofn, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.lpstrFilter = __TEXT("All files\0*.*\0"); ofn.nMaxFile = MAX_PATH; ofn.lpstrInitialDir = szWinDir; ofn.lpstrFile = szFile; if(!GetOpenFileName(&ofn)) return; else SetDlgItemText(hDlg, IDC_FILENAME, ofn.lpstrFile); } 其次是OnShellExecute(),简单地从对话框中抽取文件名和操作,生成ShellExecute()函数的调用,以及显示返回值: void OnShellExecute(HWND hDlg) { TCHAR sFile[MAX_PATH] = {0}; TCHAR sOp[MAX_PATH] = {0}; TCHAR sRC[MAX_PATH] = {0}; GetDlgItemText(hDlg, IDC_FILENAME, sFile, MAX_PATH); GetDlgItemText(hDlg, IDC_OPERATION, sOp, MAX_PATH); HINSTANCE h = ShellExecute(NULL, sOp, sFile, NULL, NULL, SW_SHOW); wsprintf(sRC, __TEXT("%ld"), h); SetDlgItemText(hDlg, IDC_RETVAL, sRC); return; } 第三步是OnShellExecuteEx(),这与上项恰好相同,只是使用了SHELLEXECUTEINFO结构: void OnShellExecuteEx(HWND hDlg) { TCHAR sFile[MAX_PATH] = {0}; TCHAR sOp[MAX_PATH] = {0}; TCHAR sRC[MAX_PATH] = {0}; GetDlgItemText(hDlg, IDC_FILENAME, sFile, MAX_PATH); GetDlgItemText(hDlg, IDC_OPERATION, sOp, MAX_PATH); SHELLEXECUTEINFO sei; ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO)); sei.cbSize = sizeof(SHELLEXECUTEINFO); sei.lpFile = sFile; sei.nShow = SW_SHOW; sei.fMask = SEE_MASK_DOENVSUBST | SEE_MASK_INVOKEIDLIST; sei.lpVerb = sOp; DWORD rc = ShellExecuteEx(&sei); wsprintf(sRC, __TEXT("%ld"), rc); SetDlgItemText(hDlg, IDC_RETVAL, sRC); return; } {zh1}是OnFindExec(),使用FindExecutableEx()函数,把前面给出的代码在这里汇集到一起: void OnFindExec(HWND hDlg) { TCHAR sFile[MAX_PATH] = {0}; TCHAR sPrg[MAX_PATH] = {0}; TCHAR sRC[MAX_PATH] = {0}; GetDlgItemText(hDlg, IDC_FILENAME, sFile, MAX_PATH); HINSTANCE h = FindExecutableEx(sFile, NULL, sPrg); wsprintf(sRC, __TEXT("%ld"), h); SetDlgItemText(hDlg, IDC_RETVAL, sRC); SetDlgItemText(hDlg, IDC_EXE, sPrg); return; } 添加#includes shlobj.h, commdlg.h 和resource.h到源程序的顶部,并保证连接到comdlg32.h文件,就可以编译和连接这个应用了。下面的截图显示了它所取得的一个GIF文件的‘属性’对话框:
多监视器支持 作为结束关于ShellExecute()和ShellExecuteEx()函数的讨论,我们给出一个Windows98下的一个新特征—多监视器支持。这是一种编程使输出能够扩张到多个监视器的能力。在Windows98下有一令人惊异的函数MonitorFromPoint(),到目前为止我们还不甚了解这个函数。多监视器支持与ShellExecute()之间有什么关系呢?正好,Windows98 版的这个函数支持多监视器。这就是说,任何子进程都与父进程显示在同一个监视器上。然而,这仅仅是默认行为。如果指定了hwnd参数,你就可以重定向新窗口到有这个hwnd参数窗口所在的监视器上。
ShellExecute()函数上的钩子 你有没有听说过IShellExecuteHook接口?就象它的名字一样,其逻辑遵循传统的Windows钩子模型,而实现则要求你写一个进程内COM服务器。接口方法由ShellExecute()和 ShellExecuteEx()两个函数调用,以使用户获得对启动进程这个过程的更多控制。通过使用IShellExecuteHook接口,模块可以解析(客户方法解析)被执行的命令行,和导出正确的程序。 例如,在MS-DOS下,我们有时写一个小的批处理过程,使用短的或容易键入的名字,以此种方法,我们可以快速并容易地运行程序和执行重复的任务。现在,IShellExecuteHook接口给了我们在Windows下做差不多相同工作的能力。实现了IShellExecuteHook接口的模块在ShellExecute()或 ShellExecuteEx()执行文件动词操作时,无论文件类型如何都被唤醒。这个模块夹在操作中间,可以作任何适合的操作,比如: 跟踪(记录到文件)所有由Shell启动的应用 防止非授权访问某些程序或文件夹 实现命名对象—即,映射到特殊程序或活动的关键字 实现IShellExecuteHook接口实际上是相当容易的。不幸的是没有任何资料说明怎样使Shell知道你已经实现了它,关于这一点,我们将在下一节中讨论。
注册IShellExecuteHook处理器 {dy},IShellExecuteHook处理器是一个COM服务器,它必须在下面的路径上适当地注册: HKEY_CLASSES_ROOT \CLSID 当然只有这些还远远不够,Windows Shell 必须知道这个处理器存在,以及它放在哪儿。由于IShellExecuteHook处理器与浏览器辅助对象没有多少差别,因此我们认为它们的注册方式应该类似,事实证明我们是正确的。辅助对象和Shell执行钩子二者都必须注册在下面的位置: HKEY_LOCAL_MACHINE \Software \Microsoft \Windows \CurrentVersion \Explorer 辅助对象在‘Browser Helper Objects’键名下,而执行钩子在‘ShellExecuteHooks’键名下:
如上图所示,每一个键下可以包含一个CLSID串集合。Shell遍历这个列表,并装入这些服务器。
IShellExecuteHook接口 IShellExecuteHook是我们目前为止所看到的最简单的COM接口之一。它只由一个函数Execute()组成。下面是这个函数的声明:
HRESULT Execute(LPSHELLEXECUTEINFO pei);
SHELLEXECUTEINFO与我们在ShellExecuteEx()中介绍的结构相同。这个函数由系统在新应用或文档被打开之前通过Shell接口唤醒。换言之,这个钩子在运行新应用,或以下面方法执行一个文档的动词时被唤醒: 编程,由ShellExecute()或ShellExecuteEx()。 通过‘运行’对话框。 从探测器双击。 如果通过CreateProcess()或WinExec()运行其它的程序,这个钩子模块不能得到通知。同样,如果通过DOS运行程序或打开文档,或使用任何其它底层技术,也不能唤醒钩子。 利用作为变量传递的结构,Execute()方法接受动词,文件名,变量,目录,以及任何用户传递给ShellExecute()或其它关联函数的数据。 前面已经提到,ShellExecute()和ShellExecuteEx()二者都最终调用CreateProcess()。然而,它们除了取得和传递命令行到CreateProcess()外还要做更多其它的操作,而一开始,它们就处理策略和支持这个钩子。
从钩子返回 钩子返回S_FALSE,则Shell通常可以继续,和建立需要的进程。然而,如果不能继续要求的操作—即,钩子不想要Shell启动进程—则应该返回S_OK。这是因为钩子检查了某些条件,想要防止当前连接的用户运行程序或文档,另一种可能性是钩子代码想要自己运行这个文档。给出一个非标准的优先线程。这要求你自己处理CreateProcess()调用。更重要的是,如果返回S_OK到Shell,我们还需要适当地设置SHELLEXECUTEINFO结构中的hInstApp成员。 “适当设置hInstApp成员”,就是说给它赋一个值,向Shell表示我们的处理成功或失败(使用相关的错误消息)。如果我们自己运行这个应用,则这个值应是新进程的HINSTANCE。如果我们截断处理,则这个值可以是任何大于32的值,以防止Shell显示任何错误消息框。 下面是一个例子,假设我们确定阻塞任何新进程: HRESULT Execute(LPSHELLEXECUTEINFO lpsei) { return S_OK } 无论我们接收的参数如何,都立即返回S_OK。此时Shell发现hInstApp成员地值是0,解释返回值为错误码。然后适当地显示一个消息框,如下图所示:
写IShellExecuteHook处理器 写COM服务器,活动模版库(ATL)是重要的资源。运行ATL COM应用大师,生成一个Hook名的COM 服务器框架,选择‘简单对象’后,我们就可以在其中添加新类了:
新类名为CShowHook,应该从IShellExecuteHook导出。这个接口要求代码包含shlobj.h文件。由于比直接从IShellExecuteHook导出要好,我们定义一个普通的实现类(以习惯的ATL风格)命名为IShellExecuteHookImpl: // IShellExecuteHookImpl.h // ////////////////////////////////////////////////////////////////////// #include <AtlCom.h> #include <ShlObj.h> class ATL_NO_VTABLE IShellExecuteHookImpl : public IShellExecuteHook { public: // IUnknown STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) = 0; _ATL_DEBUG_ADDREF_RELEASE_IMPL(IShellExecuteHookImpl) // IShellExecuteHook STDMETHOD(Execute)(LPSHELLEXECUTEINFO lpsei) { return S_FALSE; } }; 实际的CShowHook类声明如下: #include "resource.h" #include "comdef.h" #include "IShellExecuteHookImpl.h" /////////////////////////////////////////////////////////////////////////// // CShowHook class ATL_NO_VTABLE CShowHook : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CShowHook, &CLSID_ShowHook>, public IShellExecuteHookImpl, public IDispatchImpl<IShowHook, &IID_IShowHook, &LIBID_SHOWLib> { public: CShowHook() { } STDMETHOD(Execute)(LPSHELLEXECUTEINFO lpsei); DECLARE_REGISTRY_RESOURCEID(IDR_SHOWHOOK) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CShowHook) COM_INTERFACE_ENTRY(IShowHook) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(IShellExecuteHook) END_COM_MAP() // IShowHook public: }; 现在还缺少这个钩子的实现。我们开始时提出了三个钩子的作用:跟踪,授权和命名。现在让我们看一看这三方面必要的代码: HRESULT CShowHook::Execute(LPSHELLEXECUTEINFO lpsei) { // 跟踪程序/文件的打开操作 TCHAR szTime[50] = {0}; GetTimeFormat(LOCALE_SYSTEM_DEFAULT, 0, NULL, NULL, szTime, 50); TCHAR szText[1024] = {0}; wsprintf(szText, __TEXT("%s: %s at %s"),lpsei->lpVerb, lpsei->lpFile, szTime); FILE *f; f = fopen(__TEXT("c:\\ShowHook.txt"), __TEXT("a+t")); fseek(f, 0, SEEK_END); fprintf(f, __TEXT("%s: %s at %s\n\r"),lpsei->lpVerb, lpsei->lpFile, szTime); fclose(f); // 检查快捷方式列表和运行程序 TCHAR szFileName[MAX_PATH] = {0}; GetPrivateProfileString(__TEXT("GoldList"), lpsei->lpFile, "", szFileName, MAX_PATH, __TEXT("c:\\showhook.ini")); if(lstrlen(szFileName)) { lpsei->hInstApp = reinterpret_cast<HINSTANCE>(WinExec(szFileName, SW_SHOW)); return S_OK; } // 如果名字包含DEBUG,则禁止做任何操作 strlwr(const_cast<LPTSTR>(lpsei->lpFile)); if(strstr(lpsei->lpFile, __TEXT("debug"))) { lpsei->hInstApp = reinterpret_cast<HINSTANCE>(42); return S_OK; } // 让Shell继续 return S_FALSE; }
编辑注册表脚本 在分析上面代码之前,需要多做一点事情,以使服务器成为xx自注册服务器。这涉及到对大师给出的注册表脚本代码的补充。加入使Shell执行钩子的特殊信息,把这些信息放在文件的尾部: HKLM { SOFTWARE { Microsoft { Windows { CurrentVersion { Explorer { ShellExecuteHooks { val {4F43D133-2951-11D2-BC00-7CA506C10000} = s '' } } } } } } } 这个钩子包含在ShowHook.dll中,服务器的注册是自动的。这里我们已经做的是正确地在ShellExecuteHooks键下注册钩子的CLSID。
钩子怎样工作 跟踪就是把使用的动词,活动的文件名,和调用时间保存到磁盘。下面的图中显示了跟踪的结果。注意,在记录中还包含了一个重启动活动的跟踪(SysTray.exe的实例)。
上面的代码还涉及到命名—识别键名列表,然后把它们转换成应用。这个列表保存在根目录下的一个.ini文件中,其典型内容为: [GoldList] reg=regedit.exe tt=notepad.exe AddNewHardware=control.exe sysdm.cpl,Add New Hardware 左边的字是由钩子识别的并把它转换成右边的命令行。这就允许我们键入AddNewHardware到‘运行’框中。 {zh1}是Execute()函数的授权部分,防止名字中包含‘debug’串的任何文件夹或文件被打开。注意,如果忘记了返回值要大于32,则可能有一个讨厌的错误消息框出现。这正好说明了ShellExecute()实际上是经常由探测器调用的,所以应该在钩子代码中仔细地考量尺寸和效率。
小结 在这一章中我们讨论了大量的基础知识,并且指出了所用方法的一些缺陷。我们从讨论WinExec()函数开始,随后是CreateProcess()函数,{zh1}讨论了ShellExecute()函数。在讨论这些函数的特征和bugs后,我们论及了FindExecutable()函数,它也有几个瑕疵。 总体上,ShellExecuteEx()似乎组合了CreateProcess()和ShellExecute()函数的功能。支持PIDLs和策略,以及钩子能力使ShellExecuteEx()成为‘{zh0}的Windows程序执行器’。这与我们的观点相同,我们解释了: ShellExecute()和FindExecutable()的特征与Bugs。 为什么Windows98资料推荐使用ShellExecute()/ShellExecuteEx()而不 是CreateProcess(), ShellExecuteEx()在那些方面扩展了ShellExecute()的功能。 怎样使用钩子扩展ShellExecuteEx()。
本文来自CSDN博客,转载请标明出处: |