当前位置: 首页 > article >正文

api测试工具代理配置适配

继上一篇 代理配置如果设置了以下代理绕过代理服务器libcurl需要适配。 但是上一篇代码有bug 这句代码有时没起作用curl_easy_setopt(curl, CURLOPT_NOPROXY, proxyBypass.c_str());去掉这句代码改为应用层获取哪些域名绕过代理后应用层自己判断是否使用代理。当使用代理调用 curl_easy_setopt(curl, CURLOPT_PROXY, proxyAddress.c_str()); 不使用代理则不调用这句代码。Main.cpp完整代码// 必须放在所有头文件包含之前. 预处理指令编译优化排除windows头文件windows.h中较少使用的组件 #define WIN32_LEAN_AND_MEAN // 必须放在所有头文件包含之前. 兼容性控制宏. 禁用微软对不安全函数如scanf、strcpy等的编译警告 #define _CRT_SECURE_NO_DEPRECATE #include iostream #include algorithm #include unordered_set #include unordered_map #include map #include string #include stdio.h #include stdlib.h #include string.h #include thread #include atomic #include mutex #include condition_variable #include functional #include queue #include sstream #include exception #include format #include stack #include duilib/UIlib.h #include windows.h #include objbase.h #include duilib/Core/UIContainer.h // libcurl 相关头文件 #include curl/curl.h // JSON 解析库 #include nlohmann/json.hpp // 代理API #include winhttp.h // 链接 winhttp.lib 库 #pragma comment(lib, winhttp.lib) #include HistoryManager.h #include atlbase.h #include atlstr.h #include atlconv.h #include FileLogger.h #define LOG_TO_FILE(msg) FileLogger::Instance().Log(msg) using namespace DuiLib; using json nlohmann::json; CDuiString Utf8ToDuiString(const std::string); void Log(const std::string msg) { std::string out msg \n; OutputDebugStringA(out.c_str()); } /** * brief libcurl 数据接收回调函数 * param contents 服务器返回的数据块 * param size 单个数据块字节大小 * param nmemb 数据块个数 * param s 外部传入的string用来拼接完整响应 * return 已处理的字节数 */ static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* s) { size_t newLength size * nmemb; try { // 把返回的数据追加到string中 s-append((char*)contents, newLength); } catch (std::bad_alloc e) { // 内存分配失败返回0终止请求 return 0; } return newLength; } bool MatchesBypassRule(const std::string host, const std::string rule) { // 去除首尾空白 std::string r rule; r.erase(0, r.find_first_not_of( \t)); r.erase(r.find_last_not_of( \t) 1); if (r.empty() || host.empty()) return false; // 查找第一个通配符位置 size_t star r.find(*); if (star ! std::string::npos) { // 情况1通配符在开头如 *.baidu.com → 匹配后缀 if (star 0) { std::string suffix r.substr(1); // 去掉 *, 得到 .baidu.com if (!suffix.empty() host.size() suffix.size() host.compare(host.size() - suffix.size(), suffix.size(), suffix) 0) { return true; } } // 情况2通配符在中间或末尾如 10.*.*.*、192.* → 匹配前缀忽略通配符及之后的所有内容 else { std::string prefix r.substr(0, star); if (host.compare(0, prefix.size(), prefix) 0) { return true; } } return false; } // 无通配符精确匹配或前缀匹配例如 10. 匹配 10.235... if (host r) return true; if (host.find(r) 0) return true; return false; } std::string GetHostFromURL(const std::string url) { std::string host; size_t start url.find(://); if (start std::string::npos) start 0; else start 3; size_t end url.find_first_of(:/, start); if (end std::string::npos) end url.length(); return url.substr(start, end - start); } /** * brief 获取 Windows 系统的 HTTP 代理字符串用于 libcurl * return std::string 代理字符串如 http://proxy.company.com:8080若系统未设代理或获取失败则返回空字符串 */ std::string GetProxyFromSystem(std::string outBypassList) { WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig { 0 }; // 1. 调用 Windows API 获取代理配置 if (!WinHttpGetIEProxyConfigForCurrentUser(proxyConfig)) { LOG_TO_FILE(WinHttpGetIEProxyConfigForCurrentUser failed, error: std::to_string(GetLastError())); return ; // 获取失败返回空字符串 } std::string proxyAddress; outBypassList.clear(); // 2. 检查是否配置了代理服务器 if (proxyConfig.lpszProxy ! nullptr wcslen(proxyConfig.lpszProxy) 0) { // 将宽字符串转换为多字节字符串适用于 libcurl int len WideCharToMultiByte(CP_UTF8, 0, proxyConfig.lpszProxy, -1, nullptr, 0, nullptr, nullptr); std::string proxyAddr(len, \0); WideCharToMultiByte(CP_UTF8, 0, proxyConfig.lpszProxy, -1, proxyAddr[0], len, nullptr, nullptr); // 去除末尾的 \0 if (!proxyAddr.empty() proxyAddr.back() \0) proxyAddr.pop_back(); // 构造完整的代理 URL // 注意WinHttpGetIEProxyConfigForCurrentUser 返回的可能只是 proxy.company.com:8080 // 需要加上 http:// 前缀 // 检查是否已包含协议前缀 if (proxyAddr.find(http://) 0 || proxyAddr.find(https://) 0 || proxyAddr.find(socks) 0) { proxyAddress proxyAddr; // 已有协议直接使用 } else { proxyAddress http:// proxyAddr; // 无协议添加 http:// } LOG_TO_FILE(Proxy address: proxyAddress); } // 获取绕过列表 if (proxyConfig.lpszProxyBypass ! nullptr wcslen(proxyConfig.lpszProxyBypass) 0) { int len WideCharToMultiByte(CP_UTF8, 0, proxyConfig.lpszProxyBypass, -1, nullptr, 0, nullptr, nullptr); std::string bypass(len, \0); WideCharToMultiByte(CP_UTF8, 0, proxyConfig.lpszProxyBypass, -1, bypass[0], len, nullptr, nullptr); if (!bypass.empty() bypass.back() \0) bypass.pop_back(); outBypassList bypass; LOG_TO_FILE(Proxy bypass list: outBypassList); } // 3. 清理内存防止内存泄漏 if (proxyConfig.lpszProxy ! nullptr) GlobalFree(proxyConfig.lpszProxy); if (proxyConfig.lpszProxyBypass ! nullptr) GlobalFree(proxyConfig.lpszProxyBypass); if (proxyConfig.lpszAutoConfigUrl ! nullptr) GlobalFree(proxyConfig.lpszAutoConfigUrl); return proxyAddress; // 如果未配置代理返回空字符串 } /** * brief 通用HTTP请求封装 * param url 请求地址UTF-8 * param method 请求方式 GET/POST * param body POST请求体JSON字符串可为 nullptr 或空字符串 * param headersStr 自定义请求头每行格式 key: value多条用换行分隔UTF-8可为 nullptr * return 服务器完整响应文本 */ std::string HttpRequest(const char* url, const char* method, const char* body, const char* headersStr) { Log(进入 HttpRequest 函数); LOG_TO_FILE(HttpRequest: std::string(method) std::string(url)); // 初始化curl会话 CURL* curl curl_easy_init(); if (!curl) { LOG_TO_FILE(curl_easy_init failed); return curl 初始化失败; } // 在 curl_easy_init 之后添加 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // 自定义调试回调将输出重定向到 OutputDebugString curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, [](CURL* handle, curl_infotype type, char* data, size_t size, void* userptr) - int { std::string info(data, size); OutputDebugStringA(info.c_str()); return 0; }); std::string response; // 设置请求地址 curl_easy_setopt(curl, CURLOPT_URL, url); // 自动代理设置开始。 醉过才知酒浓爱过才知情重。 std::string host GetHostFromURL(url); std::string proxyBypass; std::string proxyAddress GetProxyFromSystem(proxyBypass); // 获取系统代理字符串 Log(proxyAddress: proxyAddress); bool useProxy !proxyAddress.empty(); if (useProxy !proxyBypass.empty()) { // 按分号分隔绕过规则 std::stringstream ss(proxyBypass); std::string rule; while (std::getline(ss, rule, ;)) { if (MatchesBypassRule(host, rule)) { useProxy false; LOG_TO_FILE(Bypass proxy for host: host (rule: rule )); break; } } } if (useProxy) { LOG_TO_FILE(Setting proxy: proxyAddress); // 设置代理服务器地址 curl_easy_setopt(curl, CURLOPT_PROXY, proxyAddress.c_str()); // demo只访问HTTPHTTP代理也能正常工作这行是保险。 curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); // 已经确认使用代理就不设置CURLOPT_NOPROXY了。 //if (!proxyBypass.empty()) { // // libcurl 的 CURLOPT_NOPROXY 接受逗号分隔的主机/域列表 // std::replace(proxyBypass.begin(), proxyBypass.end(), ;, ,); // LOG_TO_FILE(Setting noproxy: proxyBypass); // curl_easy_setopt(curl, CURLOPT_NOPROXY, proxyBypass.c_str()); //} } else { LOG_TO_FILE(No proxy configured); } // 自动代理设置结束。 力拔山兮气盖世 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // 关闭证书验证 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); // 关闭主机验证 // 允许跟随重定向 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // 设置接收数据的回调函数 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); // 把response指针传给回调函数 curl_easy_setopt(curl, CURLOPT_WRITEDATA, response); // 整个 libcurl 请求允许执行的最长时间秒数包括连接建立、数据传输等所有阶段。 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // 如果是POST请求且有请求体 if (_stricmp(method, POST) 0 body strlen(body) 0) { // 启用POST方式 curl_easy_setopt(curl, CURLOPT_POST, 1L); // 设置POST提交的表单/JSON数据 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); } if (_stricmp(method, GET) ! 0 _stricmp(method, POST) ! 0) { // 如果方法不是 GET/POST可以按需要扩展 // 这里简单返回错误 curl_easy_cleanup(curl); return std::string(u8不支持的方法: ) method; } // 处理请求头。 注意跨平台换行符问题DuiLib 的 RichEdit 控件在不同系统或配置下可能返回 \r 作为换行符而 std::getline 默认以 \n 分隔导致 \r 残留在行内破坏 HTTP 协议格式。 struct curl_slist* headers nullptr; if (headersStr strlen(headersStr) 0) { std::string cleaned(headersStr); // 将所有的 \r 替换为 \n std::replace(cleaned.begin(), cleaned.end(), \r, \n); // 打印请求头,调试用 Log(Raw headersStr hex:); for (const char* p headersStr; *p; p) { char buf[10]; sprintf_s(buf, %02X , (unsigned char)*p); Log(buf); } std::istringstream stream(cleaned); std::string line; while (std::getline(stream, line)) { // 此时 line 中可能还有残留的 \r如果有 \r\n 的情况再清理一次 if (!line.empty() line.back() \r) line.pop_back(); // 去除首尾空白 line.erase(0, line.find_first_not_of( \t\r\n)); line.erase(line.find_last_not_of( \t\r\n) 1); if (line.empty()) continue; // 查找冒号分隔符 size_t colon line.find(:); if (colon ! std::string::npos) { std::string key line.substr(0, colon); std::string value line.substr(colon 1); // 去除 value 前导空白 value.erase(0, value.find_first_not_of( \t)); // 构造完整的头字段 std::string header key : value; headers curl_slist_append(headers, header.c_str()); } else { // 如果没有冒号忽略这一行 OutputDebugStringA(Ignored header line (no colon): what happened? \n心口如一犹不失为光明磊落丈夫之行也。); } } } else { headers curl_slist_append(headers, Content-Type: application/json); } if (headers) { curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); } // 执行同步HTTP请求 CURLcode res curl_easy_perform(curl); Log(HttpRequest: after curl_easy_perform, res std::to_string(res)); // 请求出错 if (res ! CURLE_OK) { //response 请求失败: std::string(curl_easy_strerror(res)); const char* errUtf8 curl_easy_strerror(res); Log(HttpRequest error: std::string(errUtf8)); response std::string(u8请求失败: ) errUtf8; // 拼接 UTF-8 中文 UTF-8 错误信息 } else { Log(HttpRequest success, response length std::to_string(response.size())); } // 清理 if (headers) { curl_slist_free_all(headers); } curl_easy_cleanup(curl); return response; } // TODO 还需要设置字符集 /* class TestWindow : public CWindowWnd, public INotifyUI { public: LPCTSTR GetWindowClassName() const override { return _T(TestWindowClass); } UINT GetClassStyle() const override { return CS_DBLCLKS; } void OnFinalMessage(HWND hWnd) override { delete this; } void Notify(TNotifyUI msg) override {} LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) override { if (uMsg WM_CLOSE) { PostQuitMessage(0); return 0; } return __super::HandleMessage(uMsg, wParam, lParam); } }; */ /* int main() { // 创建窗口 TestWindow wnd; wnd.Create(nullptr, _T(Test), UI_WNDSTYLE_FRAME, 0, 0, 500, 400); wnd.ShowModal(); } */ /* LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg WM_DESTROY) { PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, uMsg, wParam, lParam); } */ /* int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { const wchar_t CLASS_NAME[] LMyWindowClass; WNDCLASS wc {}; wc.lpfnWndProc WindowProc; wc.hInstance hInstance; wc.lpszClassName CLASS_NAME; RegisterClass(wc); HWND hwnd CreateWindowEx( 0, CLASS_NAME, L到底是天地会还是整人会啊, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hInstance, NULL ); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); MSG msg {}; while (GetMessage(msg, NULL, 0, 0)) { TranslateMessage(msg); DispatchMessage(msg); } return 0; } */ // 从WindowImplBase继承创建窗口类 /* class CTestApp : public WindowImplBase { public: virtual LPCTSTR GetWindowClassName() const { return _T(TestAppWindow); } virtual CDuiString GetSkinFile() { return _T(test1.xml); // 布局文件 } virtual void Notify(TNotifyUI msg) { if (msg.sType _T(click)) { if (msg.pSender-GetName() _T(closebtn)) { Close(); } } } virtual UILIB_RESOURCETYPE GetResourceType() const { return UILIB_FILE; // 或UILIB_ZIPRESOURCE等 } virtual CControlUI* CreateControl(LPCTSTR pstrClass) { return nullptr; // 默认返回空需根据实际需求处理 } virtual CDuiString GetSkinFolder() { return CPaintManagerUI::GetInstancePath() _T(.\\res\\); } }; */ // _tWinMain是Windows GUI应用程序的入口函数 /* int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { CPaintManagerUI::SetInstance(hInstance); // 设置实例句柄 CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath() _T(.\\res\\)); // 设置资源路径(布局文件路径) // 创建并显示窗口 CTestApp app; app.Create(NULL, _T(TestApp), UI_WNDSTYLE_FRAME, 0); app.CenterWindow(); app.ShowModal(); return 0; } */ // //const TCHAR* const kTitleControlName _T(apptitle); //const TCHAR* const kCloseButtonControlName _T(closebtn); //const TCHAR* const kMinButtonControlName _T(minbtn); //const TCHAR* const kMaxButtonControlName _T(maxbtn); //const TCHAR* const kRestoreButtonControlName _T(restorebtn); inline CDuiString Utf8ToDuiString(const std::string utf8Str) { if (utf8Str.empty()) return CDuiString(); CA2W wide(utf8Str.c_str(), CP_UTF8); return CDuiString(wide); } // 窗口实例及消息相应部分 class CFrameWindowWnd : public CWindowWnd, public INotifyUI { private: HistoryManager m_historyManager; private: void RefreshHistoryList() { CListUI* listBox static_castCListUI*(m_pm.FindControl(_T(history_list))); if (!listBox) return; listBox-RemoveAll(); const auto items m_historyManager.GetAll(); for (const auto item : items) { if (item.url.empty()) continue; std::string display [ item.method ] item.url; CA2W utf8ToWide(display.c_str(), CP_UTF8); CDuiString str(utf8ToWide); CListLabelElementUI* pItem new CListLabelElementUI; pItem-SetText(str); pItem-SetFixedHeight(24); listBox-Add(pItem); } } public: CFrameWindowWnd() {}; // 窗口唯一标识符 LPCTSTR GetWindowClassName() const { return _T(UIMainFrame); }; // 定义窗口类样式。 UI_CLASSSTYLE_FRAME窗口大小改变时触发重绘通常用于带标题栏、边框的标准窗体。 // CS_DBLCLKS‌Windows 标准样式启用窗口对鼠标双击消息的响应能力 UINT GetClassStyle() const { return UI_CLASSSTYLE_FRAME | CS_DBLCLKS; }; // 处理窗口生命周期终结 void OnFinalMessage(HWND /*hWnd*/) { delete this; }; // 核心功能控件事件分发、消息传递枢纽 void Notify(TNotifyUI msg) override { if (msg.sType _T(click)) { //if (msg.pSender-GetName() _T(I服了You)) { // 即点击的根控件root代码见下面 // Close(); // 关闭窗口 //} if (msg.pSender-GetName() _T(closebtn)) { // 点击关闭窗口 Close(); } else if (msg.pSender-GetName() _T(minbtn)) { // 最小化 //ShowWindow(m_hWnd, SW_MINIMIZE); // 不起作用 ::ShowWindow(m_hWnd, SW_MINIMIZE); // 通过全局作用域符强制调用Win32 API函数 } else if (msg.pSender-GetName() _T(maxbtn)) { // 最大化 ::ShowWindow(m_hWnd, SW_MAXIMIZE); // 隐藏最大化按钮 CButtonUI* maxBtn static_castCButtonUI*(m_pm.FindControl(_T(maxbtn))); if (maxBtn) { maxBtn-SetVisible(false); } // 显示恢复按钮 CButtonUI* restoreBtn static_castCButtonUI*(m_pm.FindControl(_T(restorebtn))); if (restoreBtn) { restoreBtn-SetVisible(true); } } else if (msg.pSender-GetName() _T(restorebtn)) { // 恢复 ::ShowWindow(m_hWnd, SW_RESTORE); CControlUI* restoreBtn static_castCControlUI*(m_pm.FindControl(_T(restorebtn))); if (restoreBtn) restoreBtn-SetVisible(false); CControlUI* maxBtn static_castCControlUI*(m_pm.FindControl(_T(maxbtn))); if (maxBtn) maxBtn-SetVisible(true); } else if (msg.pSender-GetName() _T(btn_test)) { // 测试读取输入框文本 CEditUI* editStr static_castCEditUI*(m_pm.FindControl(_T(edit_str))); if (editStr) { CDuiString strText editStr-GetText(); // 获取文本内容 CTextUI* textStr static_castCTextUI*(m_pm.FindControl(_T(text_str))); if (textStr) textStr-SetText(strText); // 刷新文本组件 // 测试显示消息盒 MessageBox(m_hWnd, L安能摧眉折腰事权贵,使我不得开心颜, L消息标题, MB_OK | MB_ICONINFORMATION); } } else if (msg.pSender-GetName() _T(send_btn)) { // 1. 获取URL输入框控件 文本 CEditUI* urlEdit static_castCEditUI*(m_pm.FindControl(_T(url_edit))); CDuiString urlW urlEdit-GetText(); // 2. 获取下拉框选中的请求方式 GET/POST CComboUI* combo static_castCComboUI*(m_pm.FindControl(_T(method_combo))); int selIdx combo-GetCurSel(); CListLabelElementUI* pItem (CListLabelElementUI*)combo-GetItemAt(selIdx); CDuiString methodW pItem ? pItem-GetText() : _T(GET); // 3. 获取请求体RichEdit输入内容 CRichEditUI* bodyEdit static_castCRichEditUI*(m_pm.FindControl(_T(body_edit))); CDuiString bodyW bodyEdit-GetText(); // 4.获取请求头 CRichEditUI* headersEdit static_castCRichEditUI*(m_pm.FindControl(_T(headers_edit))); CDuiString headersW headersEdit ? headersEdit-GetText() : _T(); // 宽字符转窄字符给libcurl使用 auto WideToMulti [](const std::wstring wstr) - std::string { if (wstr.empty()) return ; int len WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.size(), NULL, 0, NULL, NULL); std::string res(len, 0); WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.size(), res[0], len, NULL, NULL); return res; }; std::string url WideToMulti(urlW.GetData()); std::string method WideToMulti(methodW.GetData()); std::string body WideToMulti(bodyW.GetData()); std::string headers WideToMulti(headersW.GetData()); m_historyManager.Add(method, url, headers, body); // 添加历史并缓存历史记录 RefreshHistoryList(); // 刷新显示历史记录 // 4. 开子线程执行网络请求可以避免阻塞UI主线程导致窗口卡死 std::thread([, this]() // 这里的等于号意思是值捕获以“值拷贝”的方式捕获外部所有变量。 this是当前类的this指针 { // 执行HTTP请求 std::string respResult HttpRequest(url.c_str() , method.c_str() , body.c_str() , headers.empty() ? nullptr : headers.c_str()); try { // 测试下解析 JSON Log(Response raw: respResult.substr(0, 200)); json jsonStr json::parse(respResult); // 取值 int code jsonStr[code]; std::cout code : code std::endl; } catch (const std::exception e) { std::cerr 解析异常: string(e.what()) endl; } // 发送自定义消息到主线程更新界面 // 不能在子线程直接操作Duilib控件 // PostMessage前面的:: 意思是调用全局的 Win32 API 函数不调用该类里的同名函数 // 不直接传respResult respResult是局部变量出了作用域就销毁了。 ::PostMessage(m_hWnd, WM_USER 100, 0, (LPARAM)new std::string(respResult)); }).detach(); // 分离线程自动回收资源 // 先给结果框显示 请求中 提示 CRichEditUI* resultEdit static_castCRichEditUI*(m_pm.FindControl(_T(result_edit))); if (resultEdit) { resultEdit-SetText(_T(请求中请稍候...)); } } } } LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg WM_LBUTTONDBLCLK) // 双击 { // 1. 获取鼠标点击的坐标 POINT pt { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; // 2. 查找鼠标下面的控件 CControlUI* pCtrl m_pm.FindControl(pt); if (!pCtrl) return 0; // 3. 判断是不是【历史列表项】 CListLabelElementUI* pItem dynamic_castCListLabelElementUI*(pCtrl); if (!pItem) return 0; // 4. 找到历史列表 CListUI* pList static_castCListUI*(m_pm.FindControl(_T(history_list))); if (!pList) return 0; // 5. 获取选中项索引 int nIndex pList-GetCurSel(); if (nIndex 0) return 0; // 6. 拿到历史数据 const auto items m_historyManager.GetAll(); if (nIndex (int)items.size()) return 0; const HistoryItem item items[nIndex]; // 回填数据到界面 // URL CEditUI* urlEdit (CEditUI*)m_pm.FindControl(_T(url_edit)); if (urlEdit) urlEdit-SetText(Utf8ToDuiString(item.url)); // 请求方式 CComboUI* combo (CComboUI*)m_pm.FindControl(_T(method_combo)); if (combo) { for (int i 0; i combo-GetCount(); i) { CListLabelElementUI* ele (CListLabelElementUI*)combo-GetItemAt(i); if (ele ele-GetText() Utf8ToDuiString(item.method)) { combo-SelectItem(i); break; } } } // 请求头 CRichEditUI* headersEdit (CRichEditUI*)m_pm.FindControl(_T(headers_edit)); if (headersEdit) headersEdit-SetText(Utf8ToDuiString(item.headers)); // 请求体 CRichEditUI* bodyEdit (CRichEditUI*)m_pm.FindControl(_T(body_edit)); if (bodyEdit) bodyEdit-SetText(Utf8ToDuiString(item.body)); // handled return 0; } if (uMsg WM_SHOWWINDOW wParam TRUE) { RefreshHistoryList(); } // 自定义消息子线程请求完成更新响应结果到界面 if (uMsg WM_USER 100) { std::string* pResp reinterpret_caststd::string*(lParam); CRichEditUI* resultEdit static_castCRichEditUI*(m_pm.FindControl(_T(result_edit))); if (resultEdit pResp) { // 窄字符转宽字符显示 auto UTF8ToWide [](const std::string str) - std::wstring { if (str.empty()) return L; int len MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), NULL, 0); std::wstring res(len, L\0); MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), res[0], len); return res; }; std::wstring showText UTF8ToWide(*pResp); resultEdit-SetText(showText.c_str()); } // 释放堆内存 delete pResp; return 0; } if (uMsg WM_CREATE) { // 成功创建窗口后, 系统会立即发送 WM_CREATE 消息, 标志着窗口句柄已有效但尚未显示 m_pm.Init(m_hWnd); // 绑定窗口句柄初始化绘图环境 //CControlUI* root new CButtonUI; //root-SetName(_T(I服了You)); //root-SetBkColor(0xFFcccccc); //m_pm.AttachDialog(root); // 关联控件树根节点启动界面渲染. 根控件与绘图管理器关联. 这里根控件为root // 这里用xml文件作为页面布局 CDialogBuilder builder; CControlUI* root builder.Create(_T(test2.xml), 0, nullptr, m_pm); ASSERT(root Failed to parse XML); m_pm.AttachDialog(root); m_pm.AddNotifier(this); // 注册事件监听器接收控件通知如 Notify 消息 // 加载历史记录 //RefreshHistoryList(); return 0; } else if (uMsg WM_DESTROY) { // 窗口销毁 ::PostQuitMessage(0); // 0表示正常退出 } // 不想使用系统的标题栏和边框这些非客户区绘制加上下面这俩分支WM_NCACTIVATE、WM_NCCALCSIZE、WM_NCPAINT 消息的处理 else if (uMsg WM_NCACTIVATE) { // 窗口获得焦点或失去焦点时收到该消息 if (!::IsIconic(m_hWnd)) { // TRUE: 允许系统将非客户区绘制为失活状态灰色标题栏 // FALSE: 阻止系统默认的激活状态绘制可能用于实现自定义标题栏样式 return (wParam 0) ? TRUE : FALSE; } } else if (uMsg WM_NCCALCSIZE) { // 当窗口大小或位置发生变化时系统会发送该消息 return 0; // 返回0屏蔽系统默认的标题栏计算. 这使得开发者可以用客户区完全模拟自定义标题栏实现更灵活的界面设计 } else if (uMsg WM_NCPAINT) { // 用于非客户区绘制的消息,在duilib中主要用于屏蔽系统默认标题栏以实现自定义界面 return 0; } else if (uMsg WM_LBUTTONDOWN) { // 获取鼠标点击坐标 POINT pt { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; //ScreenToClient(m_hWnd, pt); // 已经是客户区坐标了不需要这一步转换 // 找到标题栏控件 CControlUI* pTitleBar m_pm.FindControl(_T(title_bar)); if (pTitleBar) { // 判断鼠标点击是否在标题栏控件范围内 RECT rcTitle pTitleBar-GetPos(); if (PtInRect(rcTitle, pt)) { CControlUI* pControl m_pm.FindControl(pt); bool isClickBtn pControl pControl-IsVisible() pControl-IsEnabled() dynamic_castCButtonUI*(pControl) ! nullptr; if (!isClickBtn) { // 发送消息让系统处理窗口移动 ::SendMessage(m_hWnd, WM_SYSCOMMAND, SC_MOVE | HTCAPTION, 0); return 0; } } } } LRESULT lRes 0; // 将 Windows 消息uMsg交由 CPaintManagerUI 处理(即交给对应控件处理)若返回 true 表示消息已被消费直接返回处理结果 if (m_pm.MessageHandler(uMsg, wParam, lParam, lRes)) return lRes; return CWindowWnd::HandleMessage(uMsg, wParam, lParam); // 基类默认处理消息的流程 } /* LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL bHandled) { }*/ public: CPaintManagerUI m_pm; // 承担界面绘制、消息处理和控件管理的核心职责 }; // 程序入口及Duilib初始化部分 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int nCmdShow) { CPaintManagerUI::SetInstance(hInstance); //CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath()); CDuiString path CPaintManagerUI::GetInstancePath(); // 设置资源路径 CPaintManagerUI::SetResourcePath(_T(skin\\)); // 相对路径 CFrameWindowWnd* pFrame new CFrameWindowWnd(); if (pFrame NULL) return 0; pFrame-Create(NULL, _T(别人笑我太疯癫我笑他人看不穿), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE); pFrame-ShowWindow(true); pFrame-CenterWindow(); // 居中显示 CPaintManagerUI::MessageLoop(); return 0; }代理配置测试日志ok.

相关文章:

api测试工具代理配置适配

继上一篇, 代理配置如果设置了以下代理绕过代理服务器,libcurl需要适配。 但是上一篇代码有bug, 这句代码有时没起作用: curl_easy_setopt(curl, CURLOPT_NOPROXY, proxyBypass.c_str());去掉这句代码,改为应用层获取哪…...

深度实战:Blender MMD Tools专业工作流全解析与高效技巧

深度实战:Blender MMD Tools专业工作流全解析与高效技巧 【免费下载链接】blender_mmd_tools MMD Tools is a blender addon for importing/exporting Models and Motions of MikuMikuDance. 项目地址: https://gitcode.com/gh_mirrors/bl/blender_mmd_tools …...

如何彻底解决ComfyUI节点冲突:5种策略完整指南

如何彻底解决ComfyUI节点冲突:5种策略完整指南 【免费下载链接】ComfyUI-Manager ComfyUI-Manager is an extension designed to enhance the usability of ComfyUI. It offers management functions to install, remove, disable, and enable various custom nodes…...

手把手配置NCJ29D5:基于ARM Cortex-M33的UWB测距开发避坑指南

手把手配置NCJ29D5:基于ARM Cortex-M33的UWB测距开发避坑指南 在物联网和智能汽车快速发展的今天,超宽带(UWB)技术凭借其厘米级精度的定位能力,正在重塑从数字钥匙到室内导航的各类应用场景。作为NXP专为汽车电子设计的UWB芯片,NC…...

ADS EM仿真选Momemtum还是FEM?看完这篇对比和实战配置,别再纠结了

ADS EM仿真选Momentum还是FEM?核心原理与实战决策指南 在射频与微波电路设计中,电磁场仿真工具的选择往往直接决定设计效率与结果可靠性。作为业界标准平台之一,ADS(Advanced Design System)提供了Momentum和FEM两种主…...

告别懵圈!手把手教你用C语言和USB HID协议实现自定义键盘宏按键(附完整报告描述符解析)

从零构建USB HID设备:C语言实战自定义键盘宏按键开发指南 当你在游戏激战中需要快速执行复杂连招,或是办公时频繁重复输入特定文本序列,物理按键的局限性总会让人感到掣肘。传统解决方案往往依赖软件层面的宏录制,但这存在兼容性差…...

UDS诊断实战:手把手教你用0x3D服务(WriteMemoryByAddress)刷写ECU标定数据

UDS诊断实战:手把手教你用0x3D服务(WriteMemoryByAddress)刷写ECU标定数据 在汽车电子诊断领域,ECU标定数据的修改是工程师们经常需要面对的任务。想象一下这样的场景:台架测试中某个燃油喷射参数需要微调,…...

保姆级教程:GD32F470的DMA+PWM配置详解(从寄存器到固件库,以Timer7为例)

GD32F470 DMAPWM深度配置实战:从寄存器操作到固件库封装 在嵌入式开发中,精确控制PWM波形输出是电机驱动、电源管理等应用的核心需求。GD32F470系列凭借其丰富的外设资源和高性能定时器,成为许多工业级应用的理想选择。本文将深入剖析如何利用…...

PotPlayer字幕翻译插件:5分钟实现视频实时双语字幕

PotPlayer字幕翻译插件:5分钟实现视频实时双语字幕 【免费下载链接】PotPlayer_Subtitle_Translate_Baidu PotPlayer 字幕在线翻译插件 - 百度平台 项目地址: https://gitcode.com/gh_mirrors/po/PotPlayer_Subtitle_Translate_Baidu 还在为外语视频没有中文…...

Windows系统printui.dll文件丢失无法启动程序解决

在使用电脑系统时经常会出现丢失找不到某些文件的情况,由于很多常用软件都是采用 Microsoft Visual Studio 编写的,所以这类软件的运行需要依赖微软Visual C运行库,比如像 QQ、迅雷、Adobe 软件等等,如果没有安装VC运行库或者安装…...

Windows系统propsys.dll文件丢失无法启动程序解决

在使用电脑系统时经常会出现丢失找不到某些文件的情况,由于很多常用软件都是采用 Microsoft Visual Studio 编写的,所以这类软件的运行需要依赖微软Visual C运行库,比如像 QQ、迅雷、Adobe 软件等等,如果没有安装VC运行库或者安装…...

别再混淆了!一文搞懂PCB设计中的‘特征阻抗’与‘直流电阻’到底有啥区别

别再混淆了!一文搞懂PCB设计中的‘特征阻抗’与‘直流电阻’到底有啥区别 刚接触高速PCB设计的工程师,常常会对"特征阻抗50Ω"和万用表测得的"走线电阻0.1Ω"产生困惑——为什么同一个铜箔走线会有两个完全不同的"阻抗"值…...

SpringBoot配置中的变量引用技巧

在SpringBoot应用中,配置文件的灵活性是其一大优势。我们经常需要在配置文件中引用其他属性的值来动态生成新的配置项。本文将通过一个实例讲解如何在SpringBoot的application.yml文件中使用变量引用技术,特别是如何将一个变量的值作为Map的键名。 背景 假设我们有一个Spri…...

Hitboxer终极指南:免费解决游戏按键冲突的专业SOCD重映射工具

Hitboxer终极指南:免费解决游戏按键冲突的专业SOCD重映射工具 【免费下载链接】socd Key remapper for epic gamers 项目地址: https://gitcode.com/gh_mirrors/so/socd 你是否曾在激烈的格斗游戏中,因为同时按下左右方向键而无法准确释放必杀技&…...

MATLAB 中的矩阵转换与性能优化

在 MATLAB 编程中,处理和转换矩阵数据是一个常见的任务。尤其当我们需要将多个二维矩阵合并为一个大的二维矩阵时,如何有效地进行数据处理不仅仅影响程序的执行效率,还关系到数据的准确性和程序的可维护性。本文将通过一个实际的例子,展示如何将多个二维矩阵转换为一个统一…...

魔兽争霸3终极优化指南:免费开源工具WarcraftHelper让你的经典游戏焕发新生

魔兽争霸3终极优化指南:免费开源工具WarcraftHelper让你的经典游戏焕发新生 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为魔兽争霸…...

解密C语言中的动态数组

在编程语言中,数组是非常基础的数据结构。C语言中,数组的大小在编译时通常是固定的。然而,随着需求的增加,程序员们也开始希望能在运行时动态地定义数组大小。本文将结合实例讨论在C语言中如何处理动态数组,特别是针对游戏编程中的常见需求——4-in-a-row游戏的棋盘初始化…...

如何免费解锁原神60帧限制?2025终极教程让游戏体验翻倍

如何免费解锁原神60帧限制?2025终极教程让游戏体验翻倍 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock 想让你的原神世界从60帧的束缚中解放出来吗?genshin-fps-u…...

英雄联盟智能助手Seraphine:5分钟掌握LCU API驱动的战绩查询与自动BP工具

英雄联盟智能助手Seraphine:5分钟掌握LCU API驱动的战绩查询与自动BP工具 【免费下载链接】Seraphine 英雄联盟战绩查询工具 项目地址: https://gitcode.com/gh_mirrors/se/Seraphine 在英雄联盟的竞技对局中,BP阶段的决策效率直接影响着游戏胜负…...

如何快速掌握WechatDecrypt:微信聊天记录解密的完整指南

如何快速掌握WechatDecrypt:微信聊天记录解密的完整指南 【免费下载链接】WechatDecrypt 微信消息解密工具 项目地址: https://gitcode.com/gh_mirrors/we/WechatDecrypt 你是否曾因更换手机而丢失珍贵的微信聊天记录?或者不小心删除了重要的商务…...

如何在 SvelteKit 中为动态加载的图片正确实现悬停显示覆盖层

本文详解如何在 SvelteKit 中优雅、响应式地实现图片悬停时显示信息覆盖层,避免直接操作 DOM,推荐使用 class: 指令与局部状态管理,兼顾可维护性、作用域样式支持和编译器兼容性。 本文详解如何在 sveltekit 中优雅、响应式地实现图片悬…...

PUBG绝地求生压枪脚本终极指南:5步实现罗技鼠标精准射击

PUBG绝地求生压枪脚本终极指南:5步实现罗技鼠标精准射击 【免费下载链接】logitech-pubg PUBG no recoil script for Logitech gaming mouse / 绝地求生 罗技 鼠标宏 项目地址: https://gitcode.com/gh_mirrors/lo/logitech-pubg 在《绝地求生》这款硬核射击…...

一致性哈希终极指南:分布式系统设计的核心算法解析

一致性哈希终极指南:分布式系统设计的核心算法解析 【免费下载链接】system-design-resources These are the best resources for System Design on the Internet 项目地址: https://gitcode.com/gh_mirrors/sy/system-design-resources 一致性哈希是分布式系…...

* Spring AI 的Tool Calling 工具调用

Function Calling:让大模型拥有“动手能力”: https://blog.csdn.net/weixin_55772633/article/details/160636233?spm1011.2415.3001.5331 官网地址:https://docs.spring.io/spring-ai/reference/api/tools.html 一、什么是 Tool Calling&…...

哔哩下载姬Downkyi实战指南:B站视频高效下载与内容管理解决方案

哔哩下载姬Downkyi实战指南:B站视频高效下载与内容管理解决方案 【免费下载链接】downkyi 哔哩下载姬downkyi,哔哩哔哩网站视频下载工具,支持批量下载,支持8K、HDR、杜比视界,提供工具箱(音视频提取、去水印…...

TensorFlow自动微分终极指南:从基础概念到实战应用的完整教程

TensorFlow自动微分终极指南:从基础概念到实战应用的完整教程 【免费下载链接】TensorFlow-Course :satellite: Simple and ready-to-use tutorials for TensorFlow 项目地址: https://gitcode.com/gh_mirrors/te/TensorFlow-Course TensorFlow自动微分技术…...

定时执行:按时间自动触发AI任务

定时执行:按时间自动触发AI任务📝 本章学习目标:本章介绍流程编排,让AI Agent执行更加规范可控。通过本章学习,你将全面掌握"定时执行:按时间自动触发AI任务"这一核心主题。一、引言:…...

本地大模型赋能命令行:tlm工具实现离线AI助手与Shell工作流融合

1. 项目概述:当命令行遇上本地大模型 如果你和我一样,每天有超过一半的时间是在终端里度过的,那你肯定也经历过这样的时刻:面对一个复杂的文件处理需求,脑子里知道要做什么,但就是敲不出那条完美的命令组合…...

为Godot引擎深度集成Lua:模块编译、特性解析与开发实践

1. 项目概述:为Godot引擎注入Lua灵魂如果你和我一样,既是Godot引擎的忠实拥趸,又对Lua脚本语言那简洁、高效和易于嵌入的特性情有独钟,那么你肯定也想过一个问题:为什么Godot不能原生支持Lua呢?GDScript固然…...

如何构建高效TensorFlow数据流水线:数据集生成器完整指南

如何构建高效TensorFlow数据流水线:数据集生成器完整指南 【免费下载链接】TensorFlow-Course :satellite: Simple and ready-to-use tutorials for TensorFlow 项目地址: https://gitcode.com/gh_mirrors/te/TensorFlow-Course TensorFlow-Course数据集生成…...