適用環境:Windows?10/11 + Visual?Studio?2022 + CMake ≥?3.20
目標讀者:希望在 C++?項目中輕松調用 HTTPS(GET/POST/PUT/DELETE),又被 LNK20xx?鏈接錯誤困擾的開發者
目錄
- 為什么選 vcpkg 與 libcurl
- 用 vcpkg 安裝帶 SSL 的 libcurl
- CMake 最小工程模板
- HTTPS GET/POST/PUT/DELETE 封裝示例
- 鏈接錯誤排查:
__imp_CertOpenStore
?等無法解析 - LNK4098 運行庫沖突的本質與解決
- 小結與最佳實踐
1. 為什么選 vcpkg 與 libcurl
- libcurl?跨平臺、成熟,支持 HTTP2、HTTP3、FTP、SFTP…;只想做簡單 REST 調用也完全夠用。
- vcpkg?由微軟維護,直接給 VS / CMake 注入正確的頭文件、庫路徑、依賴鏈,省去?“到處找 *.lib” 的痛苦。
- 對 Windows 而言,HTTPS 有兩條后端:OpenSSL(跨平臺一致)或 Schannel(調用系統 SSL)。文中使用 openssl 變體,切換只需改個 triplet 選項。
2. 用 vcpkg 安裝帶 SSL 的 libcurl
# ① 克隆 vcpkg(已有可跳過)
git clone https://github.com/microsoft/vcpkg
.\vcpkg\bootstrap-vcpkg.bat# ② 安裝 libcurl + openssl
.\vcpkg\vcpkg install curl[openssl] # 默認動態庫 /MD# ③ 將 vcpkg 集成到 VS/MSBuild(只需一次)
.\vcpkg\vcpkg integrate install
若偏好系統 Schannel:vcpkg install curl[schannel]
。
3. CMake 最小工程模板
your-project/├─ CMakeLists.txt└─ main.cpp
cmake_minimum_required(VERSION 3.20)
project(http_demo LANGUAGES CXX)# 若沒做 integrate,可取消注釋下一行
# set(CMAKE_TOOLCHAIN_FILE "路徑到/vcpkg/scripts/buildsystems/vcpkg.cmake")find_package(CURL CONFIG REQUIRED)add_executable(http_demo main.cpp)
target_link_libraries(http_demo PRIVATE CURL::libcurl)set_target_properties(http_demo PROPERTIESCXX_STANDARD 17CXX_STANDARD_REQUIRED ON)
4. HTTPS GET/POST/PUT/DELETE 封裝示例
#include <curl/curl.h>
#include <iostream>
#include <vector>
#include <string>static size_t write_cb(char* p, size_t s, size_t n, void* ud)
{auto* buf = static_cast<std::string*>(ud);buf->append(p, s * n);return s * n;
}// 通用請求
CURLcode request(const std::string& url, const std::string& verb,const std::string& body,const std::vector<std::string>& headers,bool ignore_cert, std::string& out, long& status)
{CURL* h = curl_easy_init();if (!h) return CURLE_FAILED_INIT;struct curl_slist* hs = nullptr;for (auto& it : headers) hs = curl_slist_append(hs, it.c_str());curl_easy_setopt(h, CURLOPT_URL, url.c_str());curl_easy_setopt(h, CURLOPT_CUSTOMREQUEST, verb.c_str());curl_easy_setopt(h, CURLOPT_HTTPHEADER, hs);curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_cb);curl_easy_setopt(h, CURLOPT_WRITEDATA, &out);if (verb == "POST" || verb == "PUT")curl_easy_setopt(h, CURLOPT_POSTFIELDS, body.c_str());if (ignore_cert) {curl_easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0L);curl_easy_setopt(h, CURLOPT_SSL_VERIFYHOST, 0L);}curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L);auto rc = curl_easy_perform(h);curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &status);curl_slist_free_all(hs);curl_easy_cleanup(h);return rc;
}// 便捷包裝
#define DECL(NAME, VERB) \std::string NAME(const std::string& url, const std::string& body = "",\const std::vector<std::string>& hdr = {}, bool nocert = false)\{ std::string r; long st; request(url, VERB, body, hdr, nocert, r, st);\std::cout << '[' << VERB << "] " << st << '\n'; return r; }DECL(get_, "GET")
DECL(del_, "DELETE")
DECL(post_, "POST")
DECL(put_, "PUT")int main()
{curl_global_init(CURL_GLOBAL_DEFAULT);std::cout << get_("https://self-signed.badssl.com/", "", {}, true).substr(0, 80) << "...\n";std::vector<std::string> h = { "Content-Type: application/json" };post_("https://httpbin.org/post", R"({"k":"v"})", h, true);curl_global_cleanup();
}
5. 鏈接錯誤排查
癥狀
error LNK2019: 無法解析的外部符號 __imp_CertOpenStore
warning LNK4098: 默認庫 "LIBCMT" 與其他庫的使用沖突
根因
- 靜態版 libcurl / openssl 依賴 Crypt32.lib / Secur32.lib / Ws2_32.lib …,未加入鏈接器。
- 你的工程用
/MT
靜態 CRT,而 vcpkg 發行包是/MD
動態 CRT,導致 LIBCMT 與 MSVCRT 沖突。
解決
首選辦法——交給 vcpkg & CMake:
find_package(CURL CONFIG REQUIRED)
target_link_libraries(<exe> PRIVATE CURL::libcurl)
vcpkg 自動把系統庫也傳給鏈接器;且其 triplet 與你的運行庫設置匹配,無需手工維護依賴表。
若手動指定庫:
libcurl.lib;libssl.lib;libcrypto.lib;zlib.lib;
ws2_32.lib;wldap32.lib;crypt32.lib;secur32.lib;normaliz.lib;bcrypt.lib;
advapi32.lib;version.lib
統一運行庫:
VS ? 項目屬性 ? C/C++ ? 代碼生成 ? 運行庫 → /MD
(Debug 選 /MDd
)。
保持全部第三方庫一致即可消除 LNK4098。
6. CRT 沖突深挖(可選閱讀)
/MD
?鏈接到vcruntime140.dll
,ucrtbase.dll
,執行時動態加載;/MT
?把 CRT 代碼打包進目標程序。
二者 ABI 完全一樣,但把兩套實現塞進同一個進程會讓鏈接器難以解析同名符號,最終觸發 LNK4098。
一條規則:所有目標 + 第三方庫保持同一運行庫選項。
7. 小結與最佳實踐
- 使用?
vcpkg integrate install
+CURL::libcurl
,可省去繁瑣的手動依賴管理。 - HTTPS 后端任選 openssl / schannel,只需更改 vcpkg feature。
- 遇到
__imp_Cert*
未解析 ? 缺?Crypt32.lib?等系統庫;
遇到 LNK4098 ? 運行庫/MD
vs/MT
不一致。 - 示例中的封裝函數已支持 GET/POST/PUT/DELETE;
生產環境請提供 CA 鏈而非ignore_cert=true
。 - 建議把 vcpkg triplet 與項目運行庫 寫進
vcpkg.json
+CMakePresets.json
以保持團隊一致性。
至此,你就擁有了一套可直接在 Windows 上進行 HTTPS 調用的干凈工程,并掌握了排雷思路。祝編碼愉快!
附錄:
更靠譜的類封裝
?
class CurlEasy {
public:
?? ?CurlEasy() {
?? ??? ?handle_ = curl_easy_init();
?? ??? ?if (!handle_) throw std::runtime_error("curl_easy_init failed");
?? ??? ?// 通用選項 ?
?? ??? ?curl_easy_setopt(handle_, CURLOPT_FOLLOWLOCATION, 1L);
?? ??? ?curl_easy_setopt(handle_, CURLOPT_ACCEPT_ENCODING, ""); ? // 自動解壓 gzip/deflate ?
?? ??? ?curl_easy_setopt(handle_, CURLOPT_NOPROGRESS, 1L);
?? ?}
?? ?~CurlEasy() { if (handle_) curl_easy_cleanup(handle_); }
?? ?// 禁止拷貝,允許移動 ?
?? ?CurlEasy(const CurlEasy&) = delete;
?? ?CurlEasy& operator=(const CurlEasy&) = delete;
?? ?CurlEasy(CurlEasy&&) = default;
?? ?CurlEasy& operator=(CurlEasy&&) = default;
?? ?struct Response {
?? ??? ?long ?status{};
?? ??? ?std::vector<byte> body; ? ? // 原始字節,不會被空字符截斷 ?
?? ??? ?std::string ? ? ? ? ? ?headers; ?// 原樣拼接的請求頭,可自行解析 ?
?? ?};
?? ?Response request(const std::string& url,
?? ??? ?const std::string& verb = "GET",
?? ??? ?const std::vector<byte>& body = {},
?? ??? ?const std::vector<std::string>& hdr = {},
?? ??? ?bool ignore_cert = false)
?? ?{
?? ??? ?set_basic(url, verb, body, hdr, ignore_cert);
?? ??? ?resp_.body.clear();
?? ??? ?resp_.headers.clear();
?? ??? ?curl_easy_setopt(handle_, CURLOPT_WRITEDATA, &resp_.body);
?? ??? ?curl_easy_setopt(handle_, CURLOPT_WRITEFUNCTION, &CurlEasy::write_body);
?? ??? ?curl_easy_setopt(handle_, CURLOPT_HEADERDATA, &resp_.headers);
?? ??? ?curl_easy_setopt(handle_, CURLOPT_HEADERFUNCTION, &CurlEasy::write_header);
?? ??? ?auto code = curl_easy_perform(handle_);
?? ??? ?if (code != CURLE_OK)
?? ??? ??? ?throw std::runtime_error(curl_easy_strerror(code));
?? ??? ?curl_easy_getinfo(handle_, CURLINFO_RESPONSE_CODE, &resp_.status);
?? ??? ?return resp_;
?? ?}
private:
?? ?static size_t write_body(char* p, size_t sz, size_t nm, void* ud) {
?? ??? ?auto* vec = static_cast<std::vector<byte>*>(ud);
?? ??? ?vec->insert(vec->end(),
?? ??? ??? ?reinterpret_cast<byte*>(p),
?? ??? ??? ?reinterpret_cast<byte*>(p) + sz * nm);
?? ??? ?return sz * nm;
?? ?}
?? ?static size_t write_header(char* p, size_t sz, size_t nm, void* ud) {
?? ??? ?auto* s = static_cast<std::string*>(ud);
?? ??? ?s->append(p, sz * nm);
?? ??? ?return sz * nm;
?? ?}
?? ?void set_basic(const std::string& url, const std::string& verb,
?? ??? ?const std::vector<byte>& body,
?? ??? ?const std::vector<std::string>& hdr,
?? ??? ?bool ignore_cert)
?? ?{
?? ??? ?curl_easy_setopt(handle_, CURLOPT_URL, url.c_str());
?? ??? ?if (verb == "GET")
?? ??? ??? ?curl_easy_setopt(handle_, CURLOPT_HTTPGET, 1L);
?? ??? ?else if (verb == "POST" || verb == "PUT") {
?? ??? ??? ?curl_easy_setopt(handle_, CURLOPT_CUSTOMREQUEST, verb.c_str());
?? ??? ??? ?curl_easy_setopt(handle_, CURLOPT_POSTFIELDS, body.data());
?? ??? ??? ?curl_easy_setopt(handle_, CURLOPT_POSTFIELDSIZE, body.size());
?? ??? ?}
?? ??? ?else
?? ??? ??? ?curl_easy_setopt(handle_, CURLOPT_CUSTOMREQUEST, verb.c_str());
?? ??? ?// 頭 ?
?? ??? ?struct curl_slist* list = nullptr;
?? ??? ?for (auto& h : hdr) list = curl_slist_append(list, h.c_str());
?? ??? ?curl_easy_setopt(handle_, CURLOPT_HTTPHEADER, list);
?? ??? ?// 證書 ?
?? ??? ?if (ignore_cert) {
?? ??? ??? ?curl_easy_setopt(handle_, CURLOPT_SSL_VERIFYPEER, 0L);
?? ??? ??? ?curl_easy_setopt(handle_, CURLOPT_SSL_VERIFYHOST, 0L);
?? ??? ?}
?? ?}
?? ?CURL* handle_{};
?? ?Response resp_{};
};
int main() { curl_global_init(CURL_GLOBAL_DEFAULT); try { CurlEasy curl; auto res = curl.request("https://httpbin.org/gzip"); // gzip 壓縮 JSON std::cout << "HTTP " << res.status << " , body bytes = " << res.body.size() << '\n'; // 若確定是文本,可安全構造字符串 std::string text(reinterpret_cast<char*>(res.body.data()), res.body.size()); std::cout << text.substr(0, 120) << "...\n"; } catch (const std::exception& e) { std::cerr << "ERROR: " << e.what() << '\n'; } curl_global_cleanup(); }