C/C++ 打開外部程序


WinExec

int main()
{
	/*  WinExec
		1.#include <Windows.h>
		2.某些 exe 如果不使用管理員權限運行 VS 則會報 740 錯誤
	*/
	WinExec("E:\\MyToolBar\\Programming\\取色器.exe", SW_SHOWNORMAL);
	cout << "取色器 GetLastError = " << GetLastError() << endl;
	WinExec("D:\\MyFiles\\WeGame\\tgp_daemon.exe", SW_SHOWNORMAL);
	cout << "tgp_daemon GetLastError = " << GetLastError() << endl;
	WinExec("C:\\Windows\\SysNative\\calc.exe", SW_SHOWNORMAL);
	cout << "calc GetLastError = " << GetLastError() << endl;

	getchar();
    return 0;
}

ShellExecute ShellExecute 在不使用管理員權限運行 VS2019 的情況下仍然可以正常打開任何程序,不報 740 錯誤。

int main()
{
	/*  ShellExecute
		不需要使用管理員權限打開 VS2019 也可以打開 WinExec 不能打開的程序
	*/
	ShellExecute(
		NULL,												// 父窗口句柄
		L"open",											// edit:編輯,open:打開,print:打印,explore:瀏覽,find:搜索
		L"E:\\MyToolBar\\Programming\\取色器.exe",			// 文件全路徑或文件夾名
		NULL,												// 程序啟動時的命令行參數
		NULL,												// 默認操作目錄為當前目錄
		SW_SHOWNORMAL										// 顯示方式 更多宏定義參考:https://docs.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-showwindow
	); cout << "取色器 GetLastError = " << GetLastError() << endl;

	ShellExecute(NULL,L"open",L"D:\\MyFiles\\WeGame\\tgp_daemon.exe",NULL,NULL,SW_SHOWNORMAL);
	cout << "tgp_daemon GetLastError = " << GetLastError() << endl;

	ShellExecute(NULL, L"open", L"C:\\Windows\\SysNative\\calc.exe", NULL, NULL, SW_SHOWNORMAL);
	cout << "calc GetLastError = " << GetLastError() << endl;

	getchar();
    return 0;
}

ShellExecuteEX 與 ShellExecute 一樣,ShellExecuteEX 也不需要管理員啟動就可以打開所有進程

int main()
{
	/*  ShellExecuteEX
		1.ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO));       // 用 0x00 初始化內存
		2.sei.cbSize = sizeof(SHELLEXECUTEINFO);			// 必須加,否則無法打開程序
	*/

	// 初始化
	SHELLEXECUTEINFO sei;
	/* 參考:https://docs.microsoft.com/zh-cn/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfoa
	
	typedef struct _SHELLEXECUTEINFOA {
	  DWORD     cbSize;				// 必須存在,可以用 sizeof(SHELLEXECUTEINFO) 賦值
	  ULONG     fMask;				// 指定結構成員的有效性
	  HWND      hwnd;				// 父窗口句柄
	  LPCSTR    lpVerb;				// edit:編輯、explore:瀏覽、find:搜索、open:打開(默認)、print:打印、properties:顯示屬性、runas:管理員運行
	  LPCSTR    lpFile;				// 目標文件
	  LPCSTR    lpParameters;		// 程序啟動參數
	  LPCSTR    lpDirectory;		// 工作目錄
	  int       nShow;				// 顯示方式
	  HINSTANCE hInstApp;			// 接收 ShellExcuteEX 的返回值
	  void      *lpIDList;			// 指向 ITEMIDLIST  對象的指針
	  LPCSTR    lpClass;			// 附加信息,可以是程序標識符、協議類型、文件后綴、注冊表路徑
	  HKEY      hkeyClass;			// 當 fMask = SEE_MASK_CLASSNAME 時使用
	  DWORD     dwHotKey;			// 與應用程序關聯的鍵盤快捷鍵
	  union {
	    HANDLE hIcon;		// 目標文件圖標句柄,fMask = SEE_MASK_ICON 時使用
	    HANDLE hMonitor;	// 文檔監視器句柄,fMask = SEE_MASK_HMONITOR 時使用
	  } DUMMYUNIONNAME;
	  HANDLE    hProcess;			// 新啟動的應用程序的句柄
	} SHELLEXECUTEINFOA, *LPSHELLEXECUTEINFOA;
	*/
	ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO));
	
	// 打開程序
	sei.cbSize = sizeof(SHELLEXECUTEINFO);
	sei.lpFile = L"E:\\MyToolBar\\Programming\\取色器.exe";
	ShellExecuteEx(&sei); 
	cout << "取色器 GetLastError = " << GetLastError() << endl;

	sei.lpFile = L"D:\\MyFiles\\WeGame\\tgp_daemon.exe";
	ShellExecuteEx(&sei);
	cout << "tgp_daemon GetLastError = " << GetLastError() << endl;

	sei.lpFile = L"C:\\Windows\\SysNative\\calc.exe";
	ShellExecuteEx(&sei);
	cout << "calc GetLastError = " << GetLastError() << endl;


	getchar();
    return 0;
}

CreateProcess 這個函數啟動外部程序可以說是一波三折,首先,先看下 ASCII 或者叫 UTF-8:

int main()
{
	// 初始化
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    ZeroMemory(&pi, sizeof(pi));

    BOOL bRet = CreateProcess(
        NULL,           								// 不在此指定可執行文件的文件名
        "E:\\MyToolBar\\Programming\\取色器.exe",      	// 命令行參數
        NULL,           								// 默認進程安全性
        NULL,           								// 默認線程安全性
        FALSE,          								// 指定當前進程內的句柄不可以被子進程繼承
        CREATE_NEW_CONSOLE, 							// 為新進程創建一個新的控制台窗口,更多宏定義參考:https://docs.microsoft.com/zh-cn/windows/win32/procthread/process-creation-flags
        NULL,           								// 使用本進程的環境變量
        NULL,           								// 使用本進程的驅動器和目錄
        &si,											// STARTUPINFO 結構體指針
        &pi												// PROCESS_INFORMATION 結構體指針
     );if(!bRet){cout << "取色器 GetLastError = " << GetLastError() << endl; }

	getchar();
    return 0;
}

正常打開,沒有什么問題

下面再看 Unicode 編碼,仍然用上面的代碼,運行一下,發現程序在調用 CreateProcess 的時候觸發了空指針異常:

將代碼改成如下:

int main()
{
	// 初始化
	WCHAR* szCommandLine = L"E:\\MyToolBar\\Programming\\取色器.exe";
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    ZeroMemory(&pi, sizeof(pi));

    BOOL bRet = CreateProcess(
        NULL,           								// 不在此指定可執行文件的文件名
        szCommandLine ,      							// 命令行參數
        NULL,           								// 默認進程安全性
        NULL,           								// 默認線程安全性
        FALSE,          								// 指定當前進程內的句柄不可以被子進程繼承
        CREATE_NEW_CONSOLE, 							// 為新進程創建一個新的控制台窗口,更多宏定義參考:https://docs.microsoft.com/zh-cn/windows/win32/procthread/process-creation-flags
        NULL,           								// 使用本進程的環境變量
        NULL,           								// 使用本進程的驅動器和目錄
        &si,											// STARTUPINFO 結構體指針
        &pi												// PROCESS_INFORMATION 結構體指針
     );if(!bRet){cout << "取色器 GetLastError = " << GetLastError() << endl; }

	getchar();
    return 0;
}

發現異常仍然存在:

看下 MSDN 的介紹,發現 CreateProcess 的第二個參數有一句這么寫到:

也就是說 CreateProcessW 的第二個參數不能是一個常量字符串,或者是一個指向只讀地址的指針,這么也就說通了,因為L"E:\\MyToolBar\\Programming\\取色器.exe" 是常量字符串,WCHAR* szCommandLine = L"E:\\MyToolBar\\Programming\\取色器.exe"; 是一個指針,那么最后驗證一下它指向的是否是一個只讀內存就可以了。

我使用 CE 驗證的,通過勾選和不勾選 “可寫”,可以發現 WCHAR* szCommandLine 的確是只讀變量(指針):

既然這樣我們只能修改代碼:

int main()
{
	// 初始化
	WCHAR szCommandLine[] = L"E:\\MyToolBar\\Programming\\取色器.exe";
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    ZeroMemory(&pi, sizeof(pi));

    BOOL bRet = CreateProcess(
        NULL,           								// 不在此指定可執行文件的文件名
        szCommandLine ,      							// 命令行參數
        NULL,           								// 默認進程安全性
        NULL,           								// 默認線程安全性
        FALSE,          								// 指定當前進程內的句柄不可以被子進程繼承
        CREATE_NEW_CONSOLE, 							// 為新進程創建一個新的控制台窗口,更多宏定義參考:https://docs.microsoft.com/zh-cn/windows/win32/procthread/process-creation-flags
        NULL,           								// 使用本進程的環境變量
        NULL,           								// 使用本進程的驅動器和目錄
        &si,											// STARTUPINFO 結構體指針
        &pi												// PROCESS_INFORMATION 結構體指針
     );if(!bRet){cout << "取色器 GetLastError = " << GetLastError() << endl; }

	getchar();
    return 0;
}

然后就可以正常運行了:

最后介紹下這兩個結構體:

// 進程創建時的窗體信息
typedef struct _STARTUPINFO {
  DWORD  cb;				// 結構體大小
  LPWSTR lpReserved;		// 保留,NULL
  LPWSTR lpDesktop;			// 進程 窗口站/桌面 名稱
  LPWSTR lpTitle;			// 控制台進程的窗口標題
  DWORD  dwX;				// 窗口左上角 x坐標
  DWORD  dwY;				// 窗口左上角 y坐標
  DWORD  dwXSize; 			// 窗口寬
  DWORD  dwYSize;			// 窗口高
  DWORD  dwXCountChars;		// 屏幕緩沖區寬度
  DWORD  dwYCountChars;		// 屏幕緩沖區高度
  DWORD  dwFillAttribute;	// 控制台初始字體和背景色
  DWORD  dwFlags;			// 控制其他參數哪個生效的宏 (關鍵)參考:https://docs.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow
  WORD   wShowWindow;		// 
  WORD   cbReserved2;		// 保留,NULL
  LPBYTE lpReserved2;		// 保留,NULL
  HANDLE hStdInput;			// 標准輸入句柄(默認是鍵盤緩沖區)
  HANDLE hStdOutput;		// 標准輸出句柄(默認是控制台緩沖區)
  HANDLE hStdError;			// 標准錯誤句柄(默認是控制台緩沖區)
} STARTUPINFO, *LPSTARTUPINFO;

/*******************************************/

// 新進程創建時的進程和主線程信息
typedef struct _PROCESS_INFORMATION {
  HANDLE hProcess;			// 新創建進程的句柄
  HANDLE hThread;			// 新創建進程的主線程的句柄
  DWORD  dwProcessId;		// 新創建進程的 PID
  DWORD  dwThreadId;		// 新創建進程的主線程的 TID
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM