目錄
1、需求描述
2、選擇URI Scheme實現
3、何為URI Scheme?
4、將自定義的URL Scheme信息寫入注冊表的C++源碼實現
5、如何實現最開始的3種需求
6、后續需要考慮的細節問題
? ? ? ?之前陸續收到一些從Web頁面上啟動我們C++客戶端軟件的需求,希望我們能提供一些技術上的支持與協助,支持從Web網頁上將我們的C++客戶端軟件啟動起來。于是我大概地研究了相關的實現方法,下面把研究的過程與結果在此做一個分享,希望能給大家提供一個借鑒或參考。
C++軟件異常排查從入門到精通系列教程(核心精品專欄,訂閱量已達10000多個,歡迎訂閱,持續更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++實戰專欄(重點專欄,專欄文章已更新500多篇,訂閱量已達8000多個,歡迎訂閱,持續更新中...)
https://blog.csdn.net/chenlycly/article/details/140824370C++ 軟件開發從入門到實戰(重點專欄,專欄文章已更新300多篇,歡迎訂閱,持續更新中...)
https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能開發匯總(專欄文章列表,歡迎訂閱,持續更新...)
https://blog.csdn.net/chenlycly/article/details/124272585C++軟件分析工具從入門到精通案例集錦(專欄文章,持續更新中...)
https://blog.csdn.net/chenlycly/article/details/131405795開源組件及數據庫技術(專欄文章,持續更新中...)
https://blog.csdn.net/chenlycly/category_12458859.html網絡編程與網絡問題分享(專欄文章,持續更新中...)
https://blog.csdn.net/chenlycly/category_2276111.html
1、需求描述
? ? ? ?最近多個第三方開發廠商為了快速集成我們的軟件及系統(把我們的軟件系統作為子系統,融入到他們的大型業務系統中),不想基于我們軟件SDK做費時費力的二次開發,而是直接從Web網頁上啟動我們客戶端軟件。簡單地歸納了一下,類似的需求可以分以下幾類:
1)僅僅是從Web網頁上將C++客戶端軟件啟動起來,即將軟件調起來就行了,沒有后續自動操作。用戶根據自己的需要,手動操作我們的軟件。
2)從Web網頁上將C++客戶端軟件啟動起來,并給軟件傳遞服務器地址、用戶名和密碼,讓軟件自動發起登陸,登錄成功后顯示登錄后的界面。
3)從Web網頁上將C++客戶端軟件啟動起來,并且啟動時傳遞一些信息,讓軟件執行指定的一些操作。比如給軟件傳遞服務器地址、用戶名和密碼等信息,讓軟件自動發起登錄,并加入到指定的會議中。
? ? ? ?其實上述需求可以簡單的歸結為,將C++客戶端軟件啟動起來,并給C++客戶端軟件傳遞一些命令行參數,C++客戶端軟件解析出參數,執行指定的操作。
? ? ? 以瀏覽器打開騰訊會議的會議鏈接為例,比如在IM軟件中分享的騰訊會議的會議鏈接如下所示:
點擊上面的會議鏈接,系統用系統中安裝的瀏覽器打開鏈接(或者手動將上述會議鏈接拷貝到瀏覽器中打開),如下所示:
點擊加入會議按鈕,會彈出是否要打開本地安裝的騰訊會議程序的提示框:
?點擊打開騰訊會議,將本地安裝的騰訊會議啟動起來,并自動加入到指定的會議中:
2、選擇URI Scheme實現
? ? ? ?如果是在C++程序中啟動一個C++軟件,會比較簡單,只要獲取一下目標軟件的安裝路徑(可以從注冊表中讀取,安裝程序時會將軟件的安裝路徑寫到注冊表中),就能直接通過軟件的全路徑,直接將軟件啟動起來了。
? ? ? ?現在越來越多的系統都轉向B/S架構,用戶可以隨處隨地訪問到系統中去,只要有網絡有電腦就行了,不用再安裝各種客戶端軟件了。就像我們上面提到的一些客戶一樣,因為某些業務場景的需要,可能需要從Web網頁上啟動系統中安裝的基于C/S架構的客戶端軟件。
? ? ? ?Web網頁一般都是在瀏覽器中打開的,出于安全的原因,Web瀏覽器既不能直接讀寫注冊表,即無法通過訪問注冊表獲取要啟動軟件的安裝路徑,所以無法像C++程序那樣直接啟動二進制文件,所以在Web網頁中想啟動本地的應用程序似乎遇到了問題。其實這并不是問題,我們使用URI Scheme技術與規范就能實現這樣的需求。
3、何為URI Scheme?
? ? ? ?URI,全稱是Uniform Resource Identifier,統一資源標志符。在Web開發領域,其表示的是Web上每一種可用的資源,如HTML文檔、圖片、視頻等。URI Scheme,我們稱之為URI方案,是一種技術規范,其中的URI是個更寬泛的概念,它可以是一個本地的文件,也可以是一個網絡上的視頻。
? ? ? ?從Web網頁中啟動本地應用程序的URI Scheme規范中,需要將本地應用程序的信息通過寫注冊表的方式注冊到系統中,然后在網頁中使用“SchemeName://”就可以只在啟動本地程序了。具體的做法是,在注冊表的HKEY_CLASSES_ROOT下創建一個自定義的SchemeName注冊表節點,然后再在該節點下創建多個節點,并在給相關節點設置注冊表鍵值。
? ? ? ?以QQ內嵌的QQGame為例,添加注冊表信息的步驟如下:
1)在HKEY_CLASSES_ROOT下創建QQGameProtocol節點
? ? ? QQGameProtocol就是對應的Scheme方案名稱,也是Web頁面上啟動對應程序的URL的前綴名稱,即QQGameProtocol://。然后給該節點添加一個URL Protocol名稱的鍵值,將其Value設置為本地應用程序的完整路徑。對于當前的QQGameProtocol,就是C:\Users\Public\Documents\Tencent\QQGameMicro\QQGwp.exe,如上圖所示。
2)在QQGameProtocol根節點下創建DefaultIcon節點
? ? ? ?給DefaultIcon節點設置默認的字符串鍵值(REG_SZ類型),其Value的格式為“應用程
序全路徑,圖標索引”的形式,該鍵值是用來指定該URI方案使用的圖標。本例中的Value
為:C:\Users\Public\Documents\Tencent\QQGameMicro\QQGwp.exe,1,如上圖所示。
3)在QQGameProtocol下創建shell節點
? ? ? ?先在QQGameProtocol下創建shell節點,然后在shell節點下創建open節點,然后在open節點創建command節點。shell節點和open節點不需要設置鍵值,command節點需要設置鍵值,其鍵值用來指定啟動目標應用程序時是否給目標程序傳遞命令行參數。
? ? ? ?一般只需要設置傳遞一個參數即可,比如當前Scheme下的"C:\Users\Public\Documents\Tencent\QQGameMicro\QQGwp.exe" "%1"。如果要傳遞多個參數,可以自定義一個組合格式,命令行只用一個參數即可。比如我們要給目標程序傳遞服務器地址、用戶名和密碼,可以采用這樣的組合格式:
#serveraddr=192.168.72.135#username=admin1#password=123456
即將要傳遞的多個參數按指定的格式組合起來生成一個命令行字符串參數即可。
? ? ? ?當在Web頁面上點擊“SchemeName://”鏈接時,就會到系統注冊表的HKEY_CLASSES_ROOT節點下查找SchemeName節點項,找到后取出目標應用程序的全路徑,并查找傳遞的命令行參數個數,這樣就能把本地的目標應用程序啟動起來了。
? ? ? ?如果要給目標程序傳遞參數,則使用“SchemeName://參數”的形式。經測試發現,如果在command節點中設置了%1傳遞參數的標識,則Web網頁中設置的URL必須要帶參數,即“SchemeName://參數”。如果使用不帶參數的URL:“SchemeName://”,則無法啟動目標程序。
? ? ? ?那如何既要支持不傳參數啟動,也要支持傳參數啟動呢?難道要在注冊表中創建兩個不同的SchemeName節點?其實不用這么麻煩,使用一個帶參數的SchemeName節點就夠了,對于直接啟動目標程序不帶啟動參數的,也可以攜帶一個標識參數,在程序中約定不傳參數的標識符,比如noparam,當程序中解析出noparam,則表示是不帶參數啟動的,直接啟動程序即可,不用做后續的操作。
?? ? ? ?在這里,給大家重點推薦一下我的幾個熱門暢銷專欄,歡迎訂閱:(博客主頁還有其他專欄,可以去查看)
專欄1:【C++軟件異常與異常排查從入門到精通系列教程】(該精品技術專欄的訂閱量已達到10000多個,專欄中包含大量項目實戰分析案例,有很強的實戰參考價值,廣受好評!專欄文章持續更新中,已經更新到200篇以上!歡迎訂閱!)
C++軟件調試與異常排查從入門到精通系列文章匯總https://blog.csdn.net/chenlycly/article/details/125529931
本專欄根據多年C++軟件異常排查的項目實踐,系統地總結了引發C++軟件異常的常見原因以及排查C++軟件異常的常用思路與方法,詳細講述了C++軟件的調試方法與手段,詳細介紹分析C++軟件問題的常用分析工具,以圖文并茂的方式給出具體的項目問題實戰分析實例(詳細講述分析排查過程,很有實戰參考價值),帶領大家逐步掌握C++軟件調試與異常排查的相關技術,適合基礎進階和想做技術提升的相關C++開發人員!
考察一個開發人員的水平,一是看其編碼及設計能力,二是要看其軟件調試能力!所以軟件調試能力(排查軟件異常的能力)很重要,必須重視起來!能解決一般人解決不了的問題,既能提升個人能力及價值,也能體現對團隊及公司的貢獻!
專欄中的文章都是通過項目實戰總結出來的,包含大量項目問題實戰分析案例,有很強的實戰參考價值!專欄文章還在持續更新中,預計文章篇數能更新到200篇以上!
專欄2:【C/C++實戰進階】(該專欄涵蓋了C++多方面的內容,是當前重點打造的專欄,訂閱量已達8000多個,專欄文章已經更新到500多篇,持續更新中...)
C/C++實戰進階(專欄文章,持續更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的開發實戰為基礎,總結并講解一些的C/C++基礎與項目實戰進階內容,以圖文并茂的方式對相關知識點進行詳細地展開與闡述!專欄涉及了C/C++領域多個方面的內容,包括C++基礎及編程要點(模版泛型編程、STL容器及算法函數的使用等)、數據結構與算法、C++11及以上新特性(開源代碼中可能會用到很多新特性(比如WebRTC開源庫),日常編碼中也會用到部分新特性,面試時也會頻繁地涉及到,學習新特性很有必要)、常用C++開源庫的介紹與使用(比如SQLite、libcurl、libwebsockets、libevent、jsoncpp/RapidJson、Redis、RabbitMQ、MongoDB、MQTT、ZooKeeper、OpenCV、FFmpeg、SDL、GStreamer、Live555、ReactOS等)、代碼分享(調用系統API、使用開源庫)、常用編程技術(動態庫、多線程、多進程、數據庫及網絡編程等)、軟件UI編程(Win32/duilib/QT/MFC)、C++軟件調試技術(引發C++軟件異常的常見原因分析與總結、排查C++軟件異常的手段與方法、分析C++軟件異常的基礎知識、使用常用軟件分析工具分析C++軟件問題、多個項目實戰問題分析案例分享等)、設計模式(單例模式、工廠模式、觀察者模式、狀態模式等)、網絡基礎知識與網絡問題分析進階內容(實戰問題分析實例分享)等。本專欄的內容都是建立在項目實踐的基礎上,來源于項目實戰,服務于項目實戰,很有實戰參考價值!
專欄3:【分析C++軟件問題的實用軟件與高效工具實戰案例集錦】
C++常用軟件分析工具從入門到精通案例集錦匯總(專欄文章,持續更新中...)https://blog.csdn.net/chenlycly/article/details/131405795
常用的C++軟件輔助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro以及內存泄漏檢測工具等,本專欄詳細介紹如何使用這些工具去巧妙地分析和解決日常工作中遇到的問題,很有實戰參考價值!
專欄4:【VC++常用功能代碼封裝】
VC++常用功能開發匯總(專欄文章,持續更新中...)https://blog.csdn.net/chenlycly/article/details/124272585
將10多年C++開發實踐中常用的功能,以高質量的代碼展現出來。這些常用的高質量規范代碼,可以直接拿到項目中使用,能有效地解決軟件開發過程中遇到的問題。
專欄5:【C/C++軟件開發從入門到實戰】(本專欄涵蓋了C++多方面的內容,是當前重點打造的專欄,專欄文章已經更新到300多篇,持續更新中!歡迎訂閱!)?
C++ 軟件開發從入門到精通(專欄文章,持續更新中...)https://blog.csdn.net/chenlycly/category_12695902.html
根據多年C++軟件開發實踐,詳細地總結了C/C++軟件開發相關技術實現細節,分享了大量的實戰案例,很有實戰參考價值。
4、將自定義的URL Scheme信息寫入注冊表的C++源碼實現
? ? ? ?下面給出將自定義的URL Scheme信息寫入注冊表的C++源碼實現:
BOOL WriteURISchemaReg()
{// exe程序的完整路徑CString strExePath = m_strInstallPath + _T("xyzlink.exe");// URI Scheme名稱CString strProtocolName = _T("XyzlinkProtocol");HKEY hRootKey = NULL;DWORD dwKeyValue = 0;DWORD dwDisposition = 0;UCHAR szBuf[MAX_PATH] = { 0 };// 1、在HKEY_CLASSES_ROOT下創建URI Schema相關注冊表的根節點RootNodelong lRet = ::RegCreateKeyEx(HKEY_CLASSES_ROOT, ProtocalNodeName, 0, NULL, 0,KEY_ALL_ACCESS, NULL, &hRootKey, &dwDisposition);if (lRet != ERROR_SUCCESS){return FALSE;}// 給根節點RootNode設置值1lRet = ::RegSetValueEx(hRootKey, NULL, 0, REG_SZ, (LPBYTE)(LPCTSTR)strProtocolName,strProtocolName.GetLength() * sizeof(TCHAR));if (lRet != ERROR_SUCCESS){RegCloseKey(hRootKey);return FALSE;}// 給根節點RootNode設置值2CString strKey = _T("URL Protocol");lRet = RegSetValueEx(hRootKey, strKey.GetBuffer(0), 0, REG_SZ, (LPBYTE)(LPCTSTR)strExePath,strExePath.GetLength() * sizeof(TCHAR));if (lRet != ERROR_SUCCESS){RegCloseKey(hRootKey);return FALSE;}// 2、在根節點RootNode下創建DefaultIcon節點strKey = _T("DefaultIcon");HKEY hDefaultIconKey = NULL;lRet = RegCreateKeyEx(hRootKey, strKey, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hDefaultIconKey, &dwDisposition);if (lRet != ERROR_SUCCESS){RegCloseKey(hRootKey);return FALSE;}// 給RootNode\DefaultIcon節點設置值CString strExePathPlus = strExePath + _T(",1");lRet = RegSetValueEx(hDefaultIconKey, NULL, 0, REG_SZ, (LPBYTE)(LPCTSTR)strExePathPlus,strExePathPlus.GetLength() * sizeof(TCHAR));if (lRet != ERROR_SUCCESS){RegCloseKey(hDefaultIconKey);RegCloseKey(hRootKey);return FALSE;}// 3、在RootNode\DefaultIcon節點下創建子節點shellstrKey = _T("shell");HKEY hShellKey = NULL;lRet = RegCreateKeyEx(hDefaultIconKey, strKey, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hShellKey, &dwDisposition);if (lRet != ERROR_SUCCESS){RegCloseKey(hDefaultIconKey);RegCloseKey(hRootKey);return FALSE;}// 4、在RootNode\DefaultIcon\shell節點下創建子節點openstrKey = _T("open");HKEY hOpenKey = NULL;lRet = RegCreateKeyEx(hShellKey, strKey, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hOpenKey, &dwDisposition);if (lRet != ERROR_SUCCESS){RegCloseKey(hDefaultIconKey);RegCloseKey(hRootKey);return FALSE;}// 5、在RootNode\DefaultIcon\shell\open節點下創建子節點commandstrKey = _T("command");HKEY hCommandKey = NULL;lRet = RegCreateKeyEx(hOpenKey, strKey, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hCommandKey, &dwDisposition);if (lRet != ERROR_SUCCESS){RegCloseKey(hOpenKey);RegCloseKey(hDefaultIconKey);RegCloseKey(hRootKey);return FALSE;}// 給command節點設置值(命令行參數)CString strCmdParam;strCmdParam.Format(_T("\"%s\" \"%%1\""), strExePath);lRet = RegSetValueEx(hCommandKey, NULL, 0, REG_SZ, (LPBYTE)(LPCTSTR)strCmdParam,strCmdParam.GetLength() * sizeof(TCHAR));if (lRet != ERROR_SUCCESS){RegCloseKey(hCommandKey);RegCloseKey(hOpenKey);RegCloseKey(hDefaultIconKey);RegCloseKey(hRootKey);return FALSE;}RegCloseKey(hCommandKey);RegCloseKey(hOpenKey);RegCloseKey(hDefaultIconKey);RegCloseKey(hRootKey);return TRUE;
}
5、如何實現最開始的3種需求
? ? ? ?搞清楚了使用URI Scheme規范實現從Web頁面中啟動本地應用程序的方法,下面我們再回到最開始提出的3個需求,看看如何去實現。
? ? ? ?第一種需求不需要傳遞參數,后面兩種需求則需要傳遞參數,我們使用一個帶參數傳遞的Scheme節點即可。我們可以定義一個啟動type類型標識launchtype,對于直接啟動的,type為noparam。對于啟動后發起自動登錄的,type為autologin;對于啟動后需要執行具體操作的,可以根據具體的業務,定義具體的type類型,這樣更靈活。
? ? ? ?對于目標應用程序,則可以根據不同的type類型,解析對應的參數數據,并對參數的合法性進行校驗。
? ? ? ?下面把Web網頁的測試代碼給出來,保存成.html文件,用瀏覽器打開即可:
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Start exe demo</title>
</head>
<body>
<a href="SchemeName://">打開目標程序</a>
</body >
</html >
6、后續需要考慮的細節問題
? ? ? ?上面大概說了一下問題的解決辦法和思路,其實還有很多細節需要去考慮。比如下面的幾種場景:
1)程序可能沒有安裝
? ? ? ?如果目標應用程序沒有安裝,肯定是啟動不起來的,是不是要檢測啟動失敗的原因,然后自動跳轉到安裝程序的下載頁面。
2)僅將目標程序啟動起來,但目標程序已經運行
? ? ? ?一般情況下,很多程序都是單實例運行的,即只允許運行一個實例。假定目標程序是單實例運行的,點擊Web頁面中的啟動程序的鏈接時,已經有個進程在運行了,目標程序中要彈出程序已經運行的提示,并將已經啟動的程序拉到前端顯示。
3)啟動程序后需要有后續操作,但目標程序已經運行
? ? ? ?啟動程序后需要有后續操作,比如自動發起登錄,但此時目標應用程序已經運行。如果已啟動的進程還沒登錄,是要自動發起登錄?還是擱置不管?如果已啟動的進程已經登錄,則提示已經啟動,并將已啟動的主窗口拉到最前顯示。如果目標程序已經啟動且已經登錄成功,則需要將命令行參數發給已啟動的進程,讓該進程執行要執行的操作,比如加入會議。
PS. 微軟官方說明連接:
Registering an Application to a URI Scheme