你可以使用自定義協議方案(Protocol Scheme)實現網頁上點擊URL后自動啟動遠程桌面連接(mstsc),參考你提供的C++代碼思路,如下實現:
第一步:注冊自定義協議
使用類似openmstsc://
協議。
注冊示例 (reg
文件形式)
Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\openmstsc]
@="URL:openmstsc Protocol"
"URL Protocol"=""[HKEY_CLASSES_ROOT\openmstsc\shell\open\command]
@="\"C:\\your-path\\open_mstsc.exe\" \"%1\""
或通過你的C++代碼自動完成注冊(代碼里已經包含該功能)。
第二步:網頁中調用協議URL
網頁端代碼(簡單HTML):
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>遠程桌面連接示例</title>
</head>
<body><a href="openmstsc://192.168.1.100:3389">連接遠程桌面 192.168.1.100</a>
</body>
</html>
注意:
- 點擊此鏈接時,瀏覽器會提示用戶是否允許調用該協議(首次使用時會詢問),確認即可。
第三步:你的C++程序實現要點(已提供,以下強調注意點)
你的C++程序中關鍵實現點(你代碼中已經包含了):
- 解析傳入的URL,提取
IP:端口
。 - 使用
ShellExecuteExW
調用mstsc.exe
并傳入/v:IP:Port
參數。
示例(摘錄):
void OpenWithMstsc(const std::wstring& serverIP) {if (serverIP.empty()) return;std::wstring mstscArgs = L"/v:" + serverIP;SHELLEXECUTEINFOW sei = { sizeof(sei) };sei.lpFile = L"mstsc.exe";sei.lpParameters = mstscArgs.c_str();sei.nShow = SW_SHOWNORMAL;sei.fMask = SEE_MASK_NOASYNC;ShellExecuteExW(&sei);
}
完整流程說明
- 網頁鏈接點擊 → 瀏覽器觸發
openmstsc://IP:Port
。 - 瀏覽器調用注冊好的協議→ 執行
open_mstsc.exe
,傳入參數。 - 程序解析URL → 調用
mstsc.exe
→ 遠程桌面客戶端打開。
這樣即可實現點擊網頁上的鏈接自動打開遠程桌面連接的功能。
// open_mstsc.cpp
// C++ 重寫版本:實現與 C# 相同功能,包括注冊自定義協議、解析 URL,并通過 WPS 打開文件,且不彈出控制臺窗口。
//語言功能 "結構化綁定" 需要編譯器標志 "/std:c++17"
//鏈接器-》系統-》子系統-》窗口模式
#include <windows.h>
#include <shlwapi.h>
#include <iostream>
#include <string>
#include <urlmon.h>
#include <shellapi.h>
#include <algorithm> // for std::transform
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "urlmon.lib")
#include <cctype>
const std::wstring PROTOCOL_NAME = L"openMstsc";
#include <windows.h>
#include <shellapi.h>bool IsRunAsAdmin() {BOOL isAdmin = FALSE;PSID adminGroup;SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,0, 0, 0, 0, 0, 0, &adminGroup)) {CheckTokenMembership(NULL, adminGroup, &isAdmin);FreeSid(adminGroup);}return isAdmin;
}void RelaunchAsAdmin() {wchar_t exePath[MAX_PATH];GetModuleFileNameW(NULL, exePath, MAX_PATH);SHELLEXECUTEINFOW sei = { sizeof(sei) };sei.lpVerb = L"runas"; // 以管理員權限運行sei.lpFile = exePath;sei.nShow = SW_SHOWNORMAL;sei.fMask = SEE_MASK_NOASYNC;if (ShellExecuteExW(&sei)) {ExitProcess(0); // 關閉當前進程}
}
// 替代 HttpUtility.UrlDecode 的簡單實現
// 使用 UrlUnescapeW API 進行解碼
static std::wstring UrlDecode(const std::wstring& encoded) {if (encoded.empty()) return L"";// 預留緩沖區存放解碼后的結果const size_t BUFFER_SIZE = 4096;wchar_t buffer[BUFFER_SIZE];wcsncpy_s(buffer, encoded.c_str(), BUFFER_SIZE);DWORD dwSize = (DWORD)BUFFER_SIZE;HRESULT hr = UrlUnescapeW(buffer, NULL, &dwSize, URL_UNESCAPE_INPLACE);if (SUCCEEDED(hr)) {return std::wstring(buffer);}else {// 如果解碼失敗,可以根據需求返回空字符串或原始值return L"";}
}static std::wstring ProcessServerIP(const std::wstring& inputUrl, const std::wstring& protocolName)
{// 1) 構造 "{protocol}://" 的小寫形式std::wstring lowerProtocol = protocolName;std::transform(lowerProtocol.begin(), lowerProtocol.end(), lowerProtocol.begin(), ::towlower);std::wstring protocolPrefix = lowerProtocol + L"://";// 2) 轉換 inputUrl 為小寫進行查找std::wstring lowerInput = inputUrl;std::transform(lowerInput.begin(), lowerInput.end(), lowerInput.begin(), ::towlower);// 3) 移除 "protocol://"std::wstring url;size_t pos = lowerInput.find(protocolPrefix);if (pos != std::wstring::npos){url = inputUrl.substr(pos + protocolPrefix.size());}else{url = inputUrl; // 原始 URL}// 4) URL 解碼(假設 UrlDecode 是可用函數)url = UrlDecode(url);// 5) 去除路徑部分,僅保留 "host:port"size_t pathPos = url.find(L'/');if (pathPos != std::wstring::npos){url = url.substr(0, pathPos);}return url;
}//-----------------------------------------------------------
// 下面是原有的函數聲明與實現
//-----------------------------------------------------------void RegisterUrlScheme(const std::wstring& protocol, const std::wstring& exePath);
std::wstring GetRegisteredPath(const std::wstring& protocol);void OpenWithMstsc(const std::wstring& fileUrl);
std::pair<std::wstring, std::wstring> GetWpsLauncherPath();
void EnsureUrlScheme(const std::wstring& protocol);int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
//int main(){if (!IsRunAsAdmin()) {RelaunchAsAdmin(); // 如果不是管理員權限,則重新以管理員權限運行return 0;}EnsureUrlScheme(PROTOCOL_NAME);int argc;LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);if (argv == NULL) return 0;if (argc < 2) {//MessageBoxW(NULL, L"?? 未檢測到 URL 參數。", L"提示", MB_OK | MB_ICONWARNING);return 0;}// 調用新版 ProcessUrlstd::wstring url = ProcessServerIP(argv[1], PROTOCOL_NAME);if (PathIsURLW(url.c_str())) {OpenWithMstsc(url);}else {}LocalFree(argv);return 0;
}void EnsureUrlScheme(const std::wstring& protocol) {wchar_t exePath[MAX_PATH];GetModuleFileNameW(NULL, exePath, MAX_PATH);std::wstring registeredPath = GetRegisteredPath(protocol);if (registeredPath.empty() || !_wcsicmp(registeredPath.c_str(), exePath) == 0) {RegisterUrlScheme(protocol, exePath);}
}std::wstring GetRegisteredPath(const std::wstring& protocol) {HKEY hKey;std::wstring regPath = L"";std::wstring keyPath = protocol + L"\\shell\\open\\command";if (RegOpenKeyExW(HKEY_CLASSES_ROOT, keyPath.c_str(), 0, KEY_READ, &hKey) == ERROR_SUCCESS) {wchar_t value[MAX_PATH];DWORD value_length = sizeof(value);if (RegQueryValueExW(hKey, NULL, NULL, NULL, (LPBYTE)value, &value_length) == ERROR_SUCCESS) {std::wstring commandLine(value);size_t firstQuoteEnd = commandLine.find(L'"', 1);if (firstQuoteEnd != std::wstring::npos) {regPath = commandLine.substr(1, firstQuoteEnd - 1);}}RegCloseKey(hKey);}return regPath;
}void RegisterUrlScheme(const std::wstring& protocol, const std::wstring& exePath) {HKEY hKey;std::wstring keyPath = protocol;if (RegCreateKeyExW(HKEY_CLASSES_ROOT, keyPath.c_str(), 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS) {RegSetValueExW(hKey, NULL, 0, REG_SZ, (const BYTE*)(L"URL:" + protocol + L" Protocol").c_str(),(DWORD)((protocol.size() + 10) * sizeof(wchar_t)));RegSetValueExW(hKey, L"URL Protocol", 0, REG_SZ, (const BYTE*)L"", sizeof(wchar_t));HKEY hCommandKey;if (RegCreateKeyExW(hKey, L"shell\\open\\command", 0, NULL, 0, KEY_WRITE, NULL, &hCommandKey, NULL) == ERROR_SUCCESS) {std::wstring command = L"\"" + exePath + L"\" \"%1\"";RegSetValueExW(hCommandKey, NULL, 0, REG_SZ, (const BYTE*)command.c_str(), (DWORD)(command.size() * sizeof(wchar_t)));RegCloseKey(hCommandKey);}RegCloseKey(hKey);MessageBoxW(NULL, L"遠程組件注冊成功。", L"成功", MB_OK | MB_ICONINFORMATION);}
}void OpenWithMstsc(const std::wstring& serverIP) {if (serverIP.empty()) {// MessageBoxW(NULL, L"? 服務器 IP 不能為空。", L"錯誤", MB_OK | MB_ICONERROR);return;}// 遠程桌面連接的完整命令行參數std::wstring mstscArgs = L"/v:" + serverIP;SHELLEXECUTEINFOW sei = { sizeof(sei) };sei.lpFile = L"mstsc.exe"; // 遠程桌面客戶端sei.lpParameters = mstscArgs.c_str();sei.nShow = SW_SHOWNORMAL;sei.fMask = SEE_MASK_NOASYNC;if (!ShellExecuteExW(&sei)) {// MessageBoxW(NULL, L"? 無法啟動遠程桌面連接。", L"錯誤", MB_OK | MB_ICONERROR);}
}
注意上面代碼要以管理員權限運行才能正確寫入注冊表,否則失敗
下面將繼續實現一下unbuntu上如何實現類似方法
sudo apt install freerdp2-x11 ?
xfreerdp /v:10.10.10.11:33389 /u:username /p:passwrod /cert-ignore /dynamic-resolution or( /w:1440 /h:900)
?