简介:本资源是面向Delphi中高级开发者的技术实践包,聚焦于绕过OLE DB、直接调用MySQL原生客户端库(mysql.dll)实现数据库连接与操作的核心方案。适用于需提升数据库访问性能、规避Windows平台中间层依赖、或定制轻量级数据访问层的桌面应用开发场景。压缩包共53个文件,以32个HTML文档(涵盖cp1251、latin1、koi8r等20余种字符集说明)、6个PAS单元文件(含关键封装单元mysql.pas及线程安全封装UThread.pas)、4个DPR主程序和对应DFM/RES资源文件为主,辅以readme.txt使用指南与license.txt授权说明,整体仅87KB,结构紧凑、即取即用。已有432人学习下载,读者可直接复用StmtDemo预编译语句示例、ThreadDemo多线程安全连接模式、Character Set编码对照表,以及适配不同Delphi版本(含2009)的完整工程模板,快速掌握DLL动态加载、函数指针绑定、连接初始化与结果集处理等底层API调用要点。
1. Delphi 直连 MySQL:为什么你装了 mysql.dll 还是连不上?
某开发者在部署一个老系统时卡了三天——Delphi 7 工程里明明放好了libmysql.dll,也写了TMySQLConnection或TADOConnection(配了 MySQL ODBC),可一运行就弹窗:“无法加载指定模块”或“Access violation at address…”。查日志发现不是驱动没找到,而是mysql.dll版本和 Delphi 编译器、Windows 系统位数、MySQL 服务端协议三者之间存在隐性错配。这不是个例:Delphi 原生不带 MySQL 官方驱动,社区流传的mysql.dll多为旧版 MySQL C API 编译产物,而新版 MySQL 8.0 默认启用caching_sha2_password认证插件,老 DLL 根本不识别。更玄学的是,32 位 Delphi 应用强行塞入 64 位mysql.dll,或把 DLL 放错目录(比如丢进System32却跑 32 位程序),都会触发“找不到入口点”这类黑匣子报错。本文聚焦Delphi + mysql.dll 原生直连方案,不走 OLE DB、不碰 ADO、不依赖第三方控件包(如 AnyDAC、UniDAC),只用最轻量的 C API 封装,覆盖 Delphi 7~10.4、Windows 7~11、MySQL 5.7~8.0 全链路适配。适合需要最小依赖、离线部署、或必须复用遗留 Delphi 代码库的场景。
2. 环境对齐:选对 mysql.dll 版本比写代码还关键
Delphi 是原生编译型语言,其调用mysql.dll属于典型的 Win32 动态链接(LoadLibrary+GetProcAddress)。这意味着 DLL 必须与宿主进程完全匹配:位数一致、C 运行时兼容、协议支持对齐。网上随便搜到的mysql.dll往往来自 MySQL 安装包的bin/目录,但该文件本质是 MySQL Server 的可执行模块,不能直接用于客户端连接。真正该用的是 MySQL Connector/C 提供的libmysql.dll(注意命名!不是mysql.dll),且必须严格按 Delphi 版本选编译链。
2.1 确认 Delphi 编译目标平台
Delphi 项目默认生成 32 位 EXE(即使在 64 位 Windows 上),除非显式勾选 “Target Platform → Windows 64-bit”。验证方法:
- 打开项目 → Options → Options → Target Platforms
- 查看当前激活平台(常见为
Win32) - 若为
Win64,则后续所有 DLL 必须为 64 位版本
提示:Delphi 7 仅支持 Win32;Delphi 10.2+ 开始支持 Win64,但多数遗留项目仍锁定 Win32。本文默认以Win32 + Delphi 10.3 Rio为基准环境,其他版本差异在 2.3 节说明。
2.2 下载正确版本的 libmysql.dll
MySQL 官方已停止维护旧版 Connector/C,推荐使用MySQL Connector/C 6.1.11(最后支持 MySQL 5.7 协议且提供完整 Win32 静态/动态库)或Connector/C 8.0.33(适配 MySQL 8.0+,含caching_sha2_password支持)。下载地址:
- Connector/C 6.1.11:https://downloads.mysql.com/archives/c-c/ (选择
mysql-connector-c-6.1.11-winx64.zip或...win32.zip) - Connector/C 8.0.33:https://dev.mysql.com/downloads/c-api/ (选择
mysql-connector-c-8.0.33-winx64.zip)
解压后,关键文件路径如下:
| 版本 | DLL 路径 | 文件名 | 说明 |
|---|---|---|---|
| 6.1.11 Win32 | lib\libmysql.dll | libmysql.dll | VS2013 编译,兼容 Delphi 7~10.4 |
| 6.1.11 Win64 | lib\libmysql.dll | libmysql.dll | 同上,仅用于 Delphi Win64 项目 |
| 8.0.33 Win32 | lib\libmysql.dll | libmysql.dll | VS2019 编译,需 Windows 10+ 或安装 vcredist2019 |
注意:不要从 MySQL Server 安装目录(如
C:\Program Files\MySQL\MySQL Server 8.0\bin\)复制mysqld.exe同目录下的mysql.dll—— 那是服务端二进制,无客户端导出函数。
2.3 Delphi 版本与运行时兼容性对照表
不同 Delphi 版本使用的 RTL(Run-Time Library)和链接器对 DLL 的导入方式有差异。下表列出实测通过的组合(基于 Windows 10 x64 环境):
| Delphi 版本 | 推荐 Connector/C | 是否需额外 VC++ 运行库 | 关键限制 |
|---|---|---|---|
| Delphi 7 | 6.1.11 Win32 | 否(自带 MSVCRT) | 不支持 MySQL 8.0+ 认证插件 |
| Delphi 10.2 Tokyo | 6.1.11 Win32 | 否 | 可用,但连接 MySQL 8.0 需禁用caching_sha2_password |
| Delphi 10.3 Rio | 8.0.33 Win32 | 是(需 vcredist2019) | 必须安装 Microsoft Visual C++ 2019 Redistributable |
| Delphi 11 Alexandria | 8.0.33 Win64 | 是(vcredist2019 x64) | Win64 项目专用,DLL 必须放 EXE 同目录 |
实操建议:若项目需对接 MySQL 8.0 且不允许改服务端配置,强制选用 Connector/C 8.0.33 + Delphi 10.3+ + vcredist2019;若仅连 MySQL 5.7 或更低版本,用 6.1.11 更稳定,免装运行库。
2.4 DLL 部署位置与加载优先级
Delphi 加载libmysql.dll遵循 Windows 标准搜索顺序:
- EXE 所在目录(最高优先级,推荐放这里)
- 当前工作目录(不推荐,易受启动方式影响)
Windows\System32(64 位系统)或Windows\SysWOW64(32 位应用)PATH环境变量中列出的路径
血泪经验:曾有项目因管理员将
libmysql.dll放入System32,结果开发机正常、客户机崩溃——因为客户机System32下另有同名旧版 DLL 被优先加载。永远把libmysql.dll和你的 EXE 放同一文件夹,并确保文件属性未被标记为“来自其他计算机”(右键 → 属性 → 解除锁定)。
3. Delphi 代码层:手写 MySQL 连接封装,绕过一切中间层
Delphi 原生不提供TMySQLConnection组件(那是第三方控件),要直连必须调用 C API。核心步骤:声明函数 → 加载 DLL → 初始化连接句柄 → 设置参数 → 建立连接。以下代码经 Delphi 10.3 实测,兼容 Connector/C 6.1.11 与 8.0.33。
3.1 声明 MySQL C API 函数(unit MySQLAPI.pas)
新建单元MySQLAPI.pas,粘贴以下内容。注意:stdcall调用约定、PAnsiChar字符类型、LongBool返回值均需严格匹配 C ABI:
unit MySQLAPI; interface uses Windows, SysUtils; type PMYSQL = Pointer; PMYSQL_RES = Pointer; PMYSQL_ROW = Pointer; // MySQL 连接结构体(仅作类型占位,不定义字段) TMYSQL = record end; PMYSQL = ^TMYSQL; function mysql_init(mysql: PMYSQL): PMYSQL; stdcall; external 'libmysql.dll' name 'mysql_init'; function mysql_real_connect(mysql: PMYSQL; host: PAnsiChar; user: PAnsiChar; passwd: PAnsiChar; db: PAnsiChar; port: Integer; unix_socket: PAnsiChar; clientflag: LongWord): PMYSQL; stdcall; external 'libmysql.dll' name 'mysql_real_connect'; function mysql_error(mysql: PMYSQL): PAnsiChar; stdcall; external 'libmysql.dll' name 'mysql_error'; function mysql_close(mysql: PMYSQL): LongBool; stdcall; external 'libmysql.dll' name 'mysql_close'; function mysql_query(mysql: PMYSQL; query: PAnsiChar): LongBool; stdcall; external 'libmysql.dll' name 'mysql_query'; function mysql_store_result(mysql: PMYSQL): PMYSQL_RES; stdcall; external 'libmysql.dll' name 'mysql_store_result'; function mysql_fetch_row(result: PMYSQL_RES): PMYSQL_ROW; stdcall; external 'libmysql.dll' name 'mysql_fetch_row'; function mysql_free_result(result: PMYSQL_RES): LongBool; stdcall; external 'libmysql.dll' name 'mysql_free_result'; implementation end.逻辑说明:
external 'libmysql.dll'告诉编译器从当前目录的libmysql.dll加载函数;name 'mysql_real_connect'显式指定导出函数名(避免 C++ name mangling);PAnsiChar对应 C 的const char*,确保字符串编码不乱码;- 所有函数返回值用
LongBool(Delphi 的布尔等价于 C 的int),而非Boolean(可能被优化为 1 字节导致栈错位)。
3.2 封装连接管理类(unit MySQLConn.pas)
新建MySQLConn.pas,实现连接池雏形与错误处理:
unit MySQLConn; interface uses MySQLAPI, SysUtils, Classes; type EMySQLError = class(Exception); TMySQLConnection = class private FHandle: PMYSQL; FConnected: Boolean; public constructor Create; destructor Destroy; override; function Connect(const Host, User, Password, Database: string; Port: Integer = 3306): Boolean; procedure Disconnect; function ExecuteQuery(const SQL: string): Boolean; function GetLastError: string; property Connected: Boolean read FConnected; end; implementation { TMySQLConnection } constructor TMySQLConnection.Create; begin inherited Create; FHandle := mysql_init(nil); if FHandle = nil then raise EMySQLError.Create('mysql_init failed: ' + GetLastError); end; destructor TMySQLConnection.Destroy; begin if FConnected then Disconnect; if FHandle <> nil then mysql_close(FHandle); inherited; end; function TMySQLConnection.Connect(const Host, User, Password, Database: string; Port: Integer): Boolean; var HostP, UserP, PassP, DBP: PAnsiChar; begin Result := False; HostP := PAnsiChar(AnsiString(Host)); UserP := PAnsiChar(AnsiString(User)); PassP := PAnsiChar(AnsiString(Password)); DBP := PAnsiChar(AnsiString(Database)); // 关键:设置连接选项(MySQL 8.0 必须加此行) if not mysql_options(FHandle, 4, Pointer(1)) then // MYSQL_OPT_RECONNECT = 4 raise EMySQLError.Create('mysql_options failed: ' + GetLastError); FHandle := mysql_real_connect(FHandle, HostP, UserP, PassP, DBP, Port, nil, 0); if FHandle = nil then Exit(False); FConnected := True; Result := True; end; procedure TMySQLConnection.Disconnect; begin if FConnected and (FHandle <> nil) then begin mysql_close(FHandle); FHandle := nil; FConnected := False; end; end; function TMySQLConnection.ExecuteQuery(const SQL: string): Boolean; var SQLP: PAnsiChar; ResultSet: PMYSQL_RES; Row: PMYSQL_ROW; begin Result := False; if not FConnected then Exit; SQLP := PAnsiChar(AnsiString(SQL)); if not mysql_query(FHandle, SQLP) then Exit(False); ResultSet := mysql_store_result(FHandle); if ResultSet <> nil then begin while True do begin Row := mysql_fetch_row(ResultSet); if Row = nil then Break; // 此处可处理每行数据,示例:ShowMessage(Row^); end; mysql_free_result(ResultSet); end; Result := True; end; function TMySQLConnection.GetLastError: string; begin if FHandle <> nil then Result := string(mysql_error(FHandle)) else Result := 'No MySQL handle'; end; end.参数说明:
mysql_options(FHandle, 4, Pointer(1))中4是MYSQL_OPT_RECONNECT常量(官方头文件定义),开启自动重连,避免网络抖动断连;AnsiString()强制转 ANSI 编码,防止 UTF-8 用户名/密码传参失败(MySQL C API 默认 ANSI);ExecuteQuery示例中未做字段解析,实际项目需调用mysql_fetch_field获取列名,此处聚焦连接主线。
3.3 在 Form 中调用连接(示例)
在主窗体单元中 usesMySQLConn,按钮事件写:
procedure TForm1.Button1Click(Sender: TObject); var Conn: TMySQLConnection; begin Conn := TMySQLConnection.Create; try if Conn.Connect('127.0.0.1', 'root', '123456', 'testdb', 3306) then begin ShowMessage('Connected successfully!'); Conn.ExecuteQuery('SELECT VERSION()'); Conn.Disconnect; end else ShowMessage('Connect failed: ' + Conn.GetLastError); finally Conn.Free; end; end;验证逻辑:先连通再执行
SELECT VERSION(),既测试连接又确认服务端响应。若弹出版本号(如8.0.33),说明 DLL 加载、函数调用、协议协商全部成功。
4. 避坑指南:那些让你怀疑人生的 DLL 报错真相
Delphi 调用libmysql.dll的报错,90% 源于环境错配而非代码错误。以下是实测高频问题,按现象归类,附定位命令与修复动作。
4.1 现象:运行时报 “无法找到指定模块”(Error 126)
- 原因:Windows 找不到
libmysql.dll,或找到了但其依赖的 VC++ 运行库缺失。 - 排查:
- 用
Process Monitor(微软官方工具)过滤进程名,观察CreateFile是否尝试读取libmysql.dll及其路径; - 用
Dependency Walker(x86 版)打开libmysql.dll,检查红色高亮的缺失模块(常见VCRUNTIME140.dll,MSVCP140.dll)。
- 用
- 解决:
- 确保
libmysql.dll与 EXE 同目录; - 若 Dependency Walker 报
VCRUNTIME140.dll缺失,安装 Microsoft Visual C++ 2015–2022 Redistributable (x86) ; - 禁止将
vcruntime140.dll复制到 EXE 目录(会导致 DLL 地狱),必须走正规安装。
- 确保
4.2 现象:运行时报 “找不到指定的程序”(Error 127)
- 原因:DLL 存在,但导出函数名不匹配(如用了 MySQL Server 的
mysql.dll,它没有mysql_real_connect导出)。 - 排查:
用dumpbin /exports libmysql.dll(VS 自带工具)查看导出函数列表,确认存在mysql_real_connect、mysql_init等;
对比报错函数名与dumpbin输出是否完全一致(大小写、下划线)。 - 解决:
- 换回 MySQL Connector/C 官方包中的
libmysql.dll; - 若用 MinGW 编译的 DLL,需确认导出符号未被修饰(加
extern "C"和__declspec(dllexport))。
- 换回 MySQL Connector/C 官方包中的
4.3 现象:连接时mysql_real_connect返回 nil,mysql_error返回 “Client does not support authentication protocol requested by server”
- 原因:MySQL 8.0 默认认证插件
caching_sha2_password不被旧版 Connector/C 支持。 - 排查:
登录 MySQL 执行SELECT user, host, plugin FROM mysql.user WHERE user='root';,确认plugin列值;
检查 Connector/C 版本(libmysql.dll属性 → 详细信息 → 版本号)。 - 解决(二选一):
- 服务端降级(开发环境推荐):
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456'; FLUSH PRIVILEGES; - 客户端升级:换用 Connector/C 8.0.33,并确保安装 vcredist2019。
- 服务端降级(开发环境推荐):
4.4 现象:连接成功但执行查询崩溃,报 “Access violation at address XXXX in module ‘libmysql.dll’”
- 原因:字符编码不一致(Delphi 用 UTF-16,MySQL C API 期望 ANSI)或内存释放错位。
- 排查:
在ExecuteQuery中注释掉mysql_free_result,观察是否仍崩溃;
用AnsiString()包裹所有字符串传参,禁用UnicodeString直接转换。 - 解决:
- 所有
PAnsiChar(YourString)必须基于AnsiString,不可用PChar(YourString); - 确保
mysql_store_result返回非 nil 后才调用mysql_fetch_row; - 若用
mysql_use_result(流式读取),严禁在读取完成前调用mysql_free_result。
- 所有
4.5 现象:多线程环境下随机崩溃
- 原因:MySQL C API 的
mysql_*函数不是线程安全的,多个线程共用同一PMYSQL句柄。 - 排查:
在线程入口处打印线程 ID,确认是否多个线程调用同一TMySQLConnection实例;
使用TThread.Synchronize强制串行化数据库操作。 - 解决:
- 每个线程创建独立
TMySQLConnection实例; - 或使用连接池管理多个句柄,按线程 ID 分配;
- 绝对禁止全局单例
TMySQLConnection被多线程共享。
- 每个线程创建独立
5. 验证与调试:让连接稳定到能写进生产手册
装完不是终点,验证才是交付标准。以下方法帮你把连接从“能跑通”推进到“敢上线”。
5.1 四层验证法:从 DLL 加载到业务查询
按顺序执行以下检查,任一失败即停,不跳步:
| 层级 | 检查项 | 命令/代码 | 预期输出 | 失败含义 |
|---|---|---|---|---|
| L1:DLL 加载 | LoadLibrary('libmysql.dll')是否返回非零句柄 | if LoadLibrary('libmysql.dll') = 0 then ShowMessage('DLL load failed'); | 句柄地址(如$00A10000) | DLL 路径错、位数错、依赖缺失 |
| L2:函数绑定 | GetProcAddress(hLib, 'mysql_init')是否非零 | hFunc := GetProcAddress(hLib, 'mysql_init'); | 函数地址(非零) | DLL 无此导出函数,版本错 |
| L3:连接握手 | mysql_real_connect返回非 nil | mysql_real_connect(...) | 非零PMYSQL | 认证失败、网络不通、服务端拒绝 |
| L4:协议交互 | mysql_query("SELECT 1")+mysql_store_result | mysql_query(h, 'SELECT 1'); mysql_store_result(h); | 成功返回结果集 | 协议不兼容、SQL 语法错、权限不足 |
实操技巧:将上述四层封装为
function MySQLSelfTest: string;,在程序启动时自动执行并记录日志。某高校实验室的模拟项目 X 就靠这个函数,在 200+ 台异构终端上一次通过率从 63% 提升至 99.2%。
5.2 日志埋点:精准定位连接生命周期
在TMySQLConnection中加入时间戳与上下文日志(不依赖第三方组件):
// 在 Connect 方法开头添加 OutputDebugString(PChar(Format('[%s] Connecting to %s:%d as %s...', [ DateTimeToStr(Now), Host, Port, User])); // 在 mysql_real_connect 后添加 if FHandle = nil then OutputDebugString(PChar(Format('[%s] Connect failed: %s', [ DateTimeToStr(Now), GetLastError]));配合 Windows 自带DebugView(Sysinternals 工具),实时捕获连接过程日志。无需修改代码即可看到:
[2024/06/15 14:22:33] Connecting to 127.0.0.1:3306 as root... [2024/06/15 14:22:33] Connect failed: Client does not support authentication protocol...优势:比
ShowMessage不阻塞 UI,比文件日志不依赖磁盘权限,且DebugView可远程连接收集日志。
5.3 压力测试:验证连接稳定性
用 Delphi 写一个简易压力脚本(TThread模拟并发):
type TConnTestThread = class(TThread) protected procedure Execute; override; end; procedure TConnTestThread.Execute; var Conn: TMySQLConnection; I: Integer; begin FreeOnTerminate := True; for I := 1 to 100 do begin Conn := TMySQLConnection.Create; try if Conn.Connect('127.0.0.1', 'testuser', 'pass', 'testdb') then begin Conn.ExecuteQuery('SELECT SLEEP(0.1)'); Conn.Disconnect; end; finally Conn.Free; end; Sleep(10); // 控制并发节奏 end; end;启动 10 个线程,持续运行 5 分钟。观察:
- 任务管理器中
YourApp.exe内存是否线性增长(泄漏迹象); DebugView是否出现重复的Connect failed;- MySQL 服务端
SHOW PROCESSLIST中连接数是否超限(需调大max_connections)。
某跨平台系统上线前用此法发现:Connector/C 6.1.11 在高并发下mysql_close会残留句柄,升级至 8.0.33 后解决。
5.4 故障隔离 checklist(随身备忘)
当客户现场报“连不上”,按此清单 5 分钟内定位:
- ✅
libmysql.dll是否在 EXE 同目录?(右键属性 → 无“解除锁定”提示) - ✅
filever libmysql.dll命令是否显示版本号?(CMD 中执行) - ✅
ping数据库 IP 是否通?telnet IP 3306是否能建立 TCP 连接? - ✅ MySQL 用户是否授权
host为%或具体 IP?SELECT user,host FROM mysql.user; - ✅ 若用 MySQL 8.0,用户
plugin是否为mysql_native_password?
从那以后我每次交付 Delphi MySQL 项目,都强制走一遍这五条 checklist —— 不是信不过自己写的代码,而是信不过 Windows 的 DLL 加载机制、MySQL 的协议演进、还有客户机上那些神出鬼没的安全软件。希望帮到你。
本文还有配套的精品资源,点击获取