简介:这份资源面向具备一定C++基础的开发者,聚焦VC++环境下调用MySQL数据库并访问外网Google服务的实现方案。包内提供完整的VC6工程源码,涵盖数据库连接、SQL查询执行、结果集处理等核心逻辑,同时演示了借助cURL向Google Maps Geocoding API等外网接口发起HTTP请求并解析响应的思路,可用于实时数据分析、Web服务集成等场景的学习与二次开发。资源共41个文件,以h头文件、cpp源文件、obj编译中间文件为主,另含exe可执行程序、lib静态库、dll动态库及ico、bmp等图标位图资源,压缩包约3.89MB,工程结构完整,可直接用VC打开编译调试。目前已有312人学习下载,适合想打通VC++与MySQL交互、并尝试连接外网API的中级开发者参考,帮助快速理解连接器配置、请求发送与错误处理等关键环节。
1. 从“Google.zip_vc调用mysql_连接外网Google”说起:一个老工程问题的现代拆解
第一次看到“Google.zip_vc调用mysql_连接外网Google”这个标题,很多人会以为是一个压缩包名字,其实它描述的是一类非常典型的遗留系统场景:用 VC(Visual C++ 6.0 或相近版本)写一个客户端程序,通过 MySQL 的 C API 连接数据库,同时程序里还要访问 Google 提供的某个网络服务。这个标题里三个关键词——VC、MySQL、外网 Google——分别对应了三个技术层次:本地 C++ 编译环境、数据库访问层、以及跨网络的 HTTPS 请求。它要解决的问题不是“怎么下载一个 zip”,而是“怎么让一个老 VC 工程既能连上 MySQL,又能稳定地调用外部 Google 接口”。适合谁看?如果你手里有一个维护了十年以上的 MFC 或 Win32 项目,数据库是 MySQL 5.7 或 8.0,现在需要加一个调用 Google 翻译、Google 地图或 Google 登录的功能,那这篇笔记就是写给你的。我会按“先跑通本地 MySQL 连接,再解决外网调用,最后处理编码和证书”的顺序,把每一步的命令、参数和踩坑点讲清楚。
2. VC 调用 MySQL:从环境配置到第一条查询
2.1 为什么老 VC 工程连 MySQL 总在第一步就翻车
VC 6.0 默认使用 32 位编译,而现代 MySQL 官方安装包大多只提供 64 位库。如果你直接下载 MySQL 8.0 的 Windows 安装版,在 VC 6.0 里链接libmysql.lib时会报LNK1112: module machine type 'x64' conflicts with target machine type 'x86'。这不是代码写错了,是位数不匹配。常见做法是去 MySQL 官网下载 ZIP Archive 版本,里面同时包含 32 位和 64 位的lib与include目录。以 MySQL 5.7.44 为例,解压后你会看到lib下分vs12、vs14等子目录,选vs12下的 32 位库即可兼容 VC 6.0 的链接器。另一个坑是字符集:VC 6.0 默认使用多字节字符集,而 MySQL 8.0 默认utf8mb4,如果不在连接时显式设置SET NAMES utf8mb4,中文会变成问号。我一般会在mysql_real_connect之后立刻执行一次mysql_query来设置字符集,而不是依赖配置文件。
2.2 配置 VC 6.0 的 include 与 lib 路径
打开 VC 6.0,进入Tools -> Options -> Directories,在Include files里添加 MySQL 的include目录,在Library files里添加对应 32 位库的lib目录。注意顺序:MySQL 的路径要放在系统路径之前,否则可能链接到旧版libmysql.lib。接着在工程设置Project -> Settings -> Link的Object/library modules里填入libmysql.lib。如果你用的是 MySQL 8.0,还需要额外链接libcrypto-1_1.lib和libssl-1_1.lib,因为 8.0 的客户端库依赖 OpenSSL。这两个库在 ZIP 包的lib目录下也能找到。配置完成后,写一个最小测试程序:
#include <winsock2.h> #include <mysql.h> #include <stdio.h> int main() { MYSQL *conn; MYSQL_RES *res; MYSQL_ROW row; conn = mysql_init(NULL); if (!conn) { printf("mysql_init failed\n"); return 1; } // 连接本地 MySQL,端口 3306,用户 root,密码 123456,数据库 test if (!mysql_real_connect(conn, "127.0.0.1", "root", "123456", "test", 3306, NULL, 0)) { printf("Error: %s\n", mysql_error(conn)); mysql_close(conn); return 1; } // 设置字符集,避免中文乱码 mysql_query(conn, "SET NAMES utf8mb4"); if (mysql_query(conn, "SELECT id, name FROM users LIMIT 5")) { printf("Query failed: %s\n", mysql_error(conn)); mysql_close(conn); return 1; } res = mysql_store_result(conn); while ((row = mysql_fetch_row(res)) != NULL) { printf("id=%s name=%s\n", row[0], row[1]); } mysql_free_result(res); mysql_close(conn); return 0; }这段代码的逻辑很直接:初始化句柄、建立连接、设置字符集、执行查询、遍历结果集、释放资源。参数说明:mysql_real_connect的第二个参数是主机地址,本地用127.0.0.1比localhost更可靠,因为localhost可能走命名管道而不是 TCP;第五个参数是端口,默认 3306;最后一个参数是客户端标志,填 0 表示不使用压缩协议。如果连接失败,mysql_error会返回具体原因,常见的有Can't connect to MySQL server on '127.0.0.1',这时先检查 MySQL 服务是否启动,用net start mysql确认。
2.3 用 mysql_real_query 处理带参数的 SQL
实际项目里很少执行固定 SQL,更多是根据用户输入拼接查询。VC 6.0 没有现代 C++ 的字符串格式化,我一般用sprintf拼 SQL,但必须注意转义。MySQL C API 提供了mysql_real_escape_string,用法如下:
char name[100] = "O'Reilly"; char escaped[200]; mysql_real_escape_string(conn, escaped, name, strlen(name)); char sql[512]; sprintf(sql, "SELECT id FROM users WHERE name='%s'", escaped); if (mysql_real_query(conn, sql, strlen(sql))) { printf("Query error: %s\n", mysql_error(conn)); }mysql_real_escape_string的第二个参数是输出缓冲区,第三个是输入字符串,第四个是输入长度。它会把单引号、反斜杠等特殊字符转义,防止 SQL 注入。注意:这个函数必须在连接建立之后调用,因为它依赖当前连接的字符集。如果你在连接前调用,转义结果可能不正确。另外,mysql_real_query比mysql_query多一个长度参数,适合处理包含二进制数据的 SQL,普通文本查询两者等价。
3. 连接外网 Google:VC 工程里的 HTTPS 请求怎么做
3.1 为什么 WinHTTP 比 WinINet 更适合后台调用
VC 6.0 时代没有现成的 HTTP 客户端库,常见做法有两种:WinINet 和 WinHTTP。WinINet 是给浏览器用的,会缓存 Cookie、弹登录框,适合交互式场景;WinHTTP 是给服务用的,不弹窗、不缓存,适合后台调用 Google API。我一般选 WinHTTP,因为 Google 的接口通常返回 JSON,不需要处理重定向和 Cookie。WinHTTP 在 Windows XP 及以上系统自带,VC 6.0 只需要包含winhttp.h并链接winhttp.lib。注意:VC 6.0 的 Platform SDK 可能没有最新的winhttp.h,需要安装较新的 Windows SDK,或者从 MinGW 里拷贝头文件。如果你不想折腾 SDK,也可以用LoadLibrary动态加载winhttp.dll,但那样代码量会大很多。
3.2 用 WinHTTP 发一个 GET 请求到 Google 翻译接口
下面是一个最小可运行的 WinHTTP 示例,目标是把英文翻译成中文。Google 翻译的公开接口地址是https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=zh-CN&dt=t&q=hello,返回一个 JSON 数组。代码:
#include <windows.h> #include <winhttp.h> #include <stdio.h> #pragma comment(lib, "winhttp.lib") int main() { HINTERNET hSession = WinHttpOpen(L"VCApp/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!hSession) { printf("WinHttpOpen failed: %lu\n", GetLastError()); return 1; } HINTERNET hConnect = WinHttpConnect(hSession, L"translate.googleapis.com", INTERNET_DEFAULT_HTTPS_PORT, 0); if (!hConnect) { printf("WinHttpConnect failed: %lu\n", GetLastError()); WinHttpCloseHandle(hSession); return 1; } HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/translate_a/single?client=gtx&sl=en&tl=zh-CN&dt=t&q=hello", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); if (!hRequest) { printf("WinHttpOpenRequest failed: %lu\n", GetLastError()); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 1; } if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) { printf("WinHttpSendRequest failed: %lu\n", GetLastError()); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 1; } if (!WinHttpReceiveResponse(hRequest, NULL)) { printf("WinHttpReceiveResponse failed: %lu\n", GetLastError()); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 1; } DWORD dwSize = 0; DWORD dwDownloaded = 0; char buffer[4096]; do { dwSize = 0; if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { printf("WinHttpQueryDataAvailable failed: %lu\n", GetLastError()); break; } if (dwSize == 0) break; if (dwSize > sizeof(buffer) - 1) dwSize = sizeof(buffer) - 1; ZeroMemory(buffer, sizeof(buffer)); if (!WinHttpReadData(hRequest, buffer, dwSize, &dwDownloaded)) { printf("WinHttpReadData failed: %lu\n", GetLastError()); break; } printf("%s", buffer); } while (dwSize > 0); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 0; }逻辑说明:WinHttpOpen初始化会话,第三个参数WINHTTP_ACCESS_TYPE_DEFAULT_PROXY表示使用系统代理设置,如果你的环境需要代理才能访问外网,这里会自动读取 IE 代理配置。WinHttpConnect指定主机和端口,HTTPS 默认 443。WinHttpOpenRequest的最后一个参数WINHTTP_FLAG_SECURE表示使用 TLS。WinHttpSendRequest发送请求,WinHttpReceiveResponse等待响应,然后循环读取数据。参数注意:WinHttpQueryDataAvailable返回的dwSize是本次可读的字节数,可能大于缓冲区,所以要截断。如果返回ERROR_WINHTTP_SECURE_FAILURE,说明 TLS 握手失败,通常是系统缺少根证书或 TLS 版本不匹配。
3.3 处理 Google 返回的 JSON 与中文编码
Google 翻译接口返回的 JSON 里,中文是 UTF-8 编码的。VC 6.0 的printf默认输出 ANSI,直接打印会乱码。我一般先把 UTF-8 转成宽字符再输出:
// 假设 buffer 里是 UTF-8 的 JSON int wlen = MultiByteToWideChar(CP_UTF8, 0, buffer, -1, NULL, 0); wchar_t *wbuf = (wchar_t*)malloc(wlen * sizeof(wchar_t)); MultiByteToWideChar(CP_UTF8, 0, buffer, -1, wbuf, wlen); wprintf(L"%s\n", wbuf); free(wbuf);MultiByteToWideChar的第一个参数CP_UTF8指定源编码,第二个参数 0 表示不处理非法字符。如果转换失败,检查buffer是否被截断,因为 UTF-8 的中文字符占 3 个字节,如果WinHttpReadData只读了一半,转换会出错。解决办法是先把完整响应拼接到一个动态缓冲区,再统一转换。另外,Google 接口有频率限制,短时间内大量请求会返回 429,建议在代码里加Sleep(1000)做简单限流。
4. 避坑与排查:VC+MySQL+外网调用中最容易翻车的 5 个点
4.1 现象:编译报错cannot open include file: 'mysql.h'
原因:VC 6.0 的Include files路径没有添加 MySQL 的include目录,或者添加顺序不对。解决:在Tools -> Options -> Directories里把 MySQL 的include路径移到最上面,并确认路径中没有中文和空格。如果用的是 MySQL 8.0,include目录下还有mysqlx子目录,不要误删。
4.2 现象:连接 MySQL 时返回Client does not support authentication protocol requested by server
原因:MySQL 8.0 默认使用caching_sha2_password认证插件,而 VC 6.0 链接的libmysql.lib版本较旧,不支持这种认证方式。解决:在 MySQL 命令行执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';,然后FLUSH PRIVILEGES;。如果不想改认证插件,可以升级libmysql.lib到 8.0 版本,但那样又需要链接 OpenSSL,比较麻烦。
4.3 现象:WinHTTP 请求返回ERROR_WINHTTP_SECURE_FAILURE
原因:Windows XP 或未打补丁的 Windows 7 默认只支持 TLS 1.0,而 Google 的服务器要求 TLS 1.2 以上。解决:在WinHttpOpenRequest之前调用WinHttpSetOption设置 TLS 版本:
DWORD dwProtocol = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2; WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURE_PROTOCOLS, &dwProtocol, sizeof(dwProtocol));注意:这个选项必须在WinHttpSendRequest之前设置。如果系统本身不支持 TLS 1.2,需要安装对应的系统更新。
4.4 现象:中文查询条件在 MySQL 里查不到数据
原因:连接字符集不是utf8mb4,或者表字段的排序规则是latin1。解决:连接后立即执行SET NAMES utf8mb4,并检查表定义SHOW CREATE TABLE users;,确保字段是utf8mb4。如果表已经是latin1,用ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4;转换,但注意备份数据。
4.5 现象:程序在别人电脑上运行时报找不到 libmysql.dll
原因:VC 6.0 默认动态链接libmysql.dll,但目标机器没有安装 MySQL。解决:把libmysql.dll复制到程序同目录,或者改用静态链接。静态链接需要在工程设置里忽略libmysql.lib,改为链接mysqlclient.lib,但 MySQL 官方 ZIP 包里不一定提供静态库,需要自己编译。我一般选择复制 DLL,因为最简单。
5. 进阶技巧:用连接池和异步请求把响应时间压到 200ms 以内
5.1 为什么每次请求都新建 MySQL 连接会拖慢整体速度
VC 6.0 程序如果每次查询都mysql_init+mysql_real_connect,在局域网内单次连接大约需要 20 到 50 毫秒,如果并发上来,MySQL 的max_connections很快会被打满。我一般会在程序启动时创建 5 到 10 个MYSQL*句柄放在一个数组里,用的时候取一个,用完放回。注意:MySQL C API 的MYSQL结构体不是线程安全的,如果程序是多线程的,每个线程必须用自己的连接。连接池的实现可以用一个简单的栈:
#define POOL_SIZE 10 MYSQL *pool[POOL_SIZE]; int pool_top = 0; void pool_init() { for (int i = 0; i < POOL_SIZE; i++) { pool[i] = mysql_init(NULL); mysql_real_connect(pool[i], "127.0.0.1", "root", "123456", "test", 3306, NULL, 0); mysql_query(pool[i], "SET NAMES utf8mb4"); } pool_top = POOL_SIZE; } MYSQL* pool_get() { if (pool_top > 0) return pool[--pool_top]; return NULL; } void pool_put(MYSQL *conn) { if (pool_top < POOL_SIZE) pool[pool_top++] = conn; }这个池子没有做超时重连,如果 MySQL 服务重启,池子里的连接会失效。我一般会在pool_get之后执行一次mysql_ping,如果返回非零就重新连接。mysql_ping会检查连接是否存活,但也会产生一次网络往返,所以不要每次查询都调用,可以每隔 10 次调用一次。
5.2 用 WinHTTP 异步模式避免界面卡死
VC 6.0 的 MFC 界面如果直接调用同步的WinHttpSendRequest,在等待 Google 响应时整个窗口会无响应。解决办法是用 WinHTTP 的异步模式:在WinHttpOpen时传入WINHTTP_FLAG_ASYNC,然后设置回调函数。回调函数里处理WINHTTP_CALLBACK_STATUS_READ_COMPLETE事件,把数据追加到缓冲区。异步模式的代码量比同步多一倍,但用户体验好很多。如果不想改异步,也可以把请求放到一个单独的线程里,用PostMessage通知主线程更新界面。我一般选后者,因为 VC 6.0 的线程 API 更熟悉。
5.3 验证方法:用 Wireshark 看 TLS 握手和 MySQL 协议
当你怀疑连接慢或者失败时,抓包是最直接的。用 Wireshark 过滤tcp.port == 3306可以看到 MySQL 的握手过程:服务器先发Server Greeting,客户端回Login Request,如果认证失败会看到Error包。过滤tcp.port == 443可以看到 TLS 的Client Hello和Server Hello,如果Client Hello里没有 TLS 1.2 的扩展,说明系统不支持。注意:Wireshark 只能看到加密后的内容,但握手阶段是明文的,足够判断问题出在协议版本还是证书。我习惯在代码里加一行OutputDebugString打印GetLastError,配合 DebugView 工具看错误码,比单步调试快。
5.4 一个具体技巧:把 Google 接口的域名解析结果缓存起来
每次调用 Google 接口都要做一次 DNS 解析,大约消耗 10 到 30 毫秒。如果程序频繁调用,可以把解析结果缓存起来。WinHTTP 本身有 DNS 缓存,但默认只缓存 10 分钟。我一般会在程序启动时用GetAddrInfo解析一次translate.googleapis.com,拿到 IP 后直接连接 IP,并在请求头里设置Host: translate.googleapis.com。这样省掉了 DNS 时间,但要注意 Google 的 IP 会变,缓存时间不要超过 5 分钟。另外,如果 IP 变了而程序还在用旧 IP,会返回 404 或连接超时,所以最好加一个失败重试机制:第一次用缓存 IP,失败后重新解析。
最后说一个我自己的习惯:每次改完 VC 工程配置,先编译一个最小测试程序,只连 MySQL 不调 Google,确认数据库通了再调 Google。这样出问题时能快速定位是数据库层还是网络层。希望帮到你。
本文还有配套的精品资源,点击获取