☰
Delphi EhLib 中文乱码根治方案:剪贴板与资源编码全链路修复
2026/10/14 14:29:27 网站建设 项目流程

简介:本资源为EhLib数据库控件库V5.6.215全源码版本,专为Delphi及C++Builder开发者设计,重点解决中文环境下长期存在的粘贴乱码与汉化不完整两大痛点,显著提升多语言界面稳定性与本土化体验。压缩包共460个文件,涵盖87个PAS核心单元、50个DPK/BPK工程包、45个DFM界面定义、24个TXT说明文档及配套BDSPROJ/DPROJ项目文件,全面支持RAD Studio XE2至C++Builder 2009等主流开发环境;另有DataDrivers驱动模块、多版本编译配置及完整许可与历史记录文件,结构清晰、开箱即用。目前已有375人学习下载,适合中高级Delphi/C++Builder开发者快速集成高可靠性数据库控件,尤其适用于需稳定处理中文输入、本地化部署的企业级数据应用开发场景。

1. EhLib V5.6.215 FS:一个被低估的 Delphi 数据控件库,专治粘贴中文乱码与汉化断层

你有没有在 Delphi 项目里用过 EhLib?不是“听说很老”,而是真正在生产环境里拖拽TEhDBGrid、绑定TEhDBLookupComboBox、导出 Excel 时点下「另存为」就弹出乱码对话框的 EhLib?这次 V5.6.215 FS 版本不是小修小补——它直接定位到两个长期被忽略的底层顽疾:Windows 剪贴板 UTF-16 → ANSI 转换失真和资源字符串表(.res)与 .dfm 中文字符编码不一致。这不是加个AnsiToUtf8就能糊弄过去的玄学问题,而是从Clipbrd.pas的GetAsHandle入口、到EhLibRes.dfm编译时的 CodePage 指令、再到TEhDBGrid.DoPasteFromClipboard的字符流解析链,全链路重校准。适合正在维护十年以上 Delphi 企业级 MIS、报表系统、或需要对接国产信创中间件(要求全程 GB18030 兼容)的开发者。如果你还在用StringReplace(AStr, #13#10, #13)硬扛换行乱码,或者每次汉化后都要手动用 UltraEdit 切换编码保存 .dfm,那这个版本就是你的后悔药。


2. 为什么是 V5.6.215 FS?从 Delphi 编译链看乱码根因与修复逻辑

Delphi 的中文乱码从来不是单一环节的问题。EhLib 作为重度依赖 VCL 资源机制的第三方控件库,其乱码表现往往比原生控件更隐蔽、更难复现。V5.6.215 FS 的「FS」后缀并非营销噱头,而是指代Fix String encoding in Clipboard & Resource Stream—— 它没有改动任何 UI 渲染逻辑,却让粘贴、导出、汉化三类高频操作全部回归预期。要理解它为何有效,得先看清 Delphi 编译器和 Windows API 在中文处理上的「错位地带」。

2.1 Delphi 编译器对 .dfm 字符串的默认编码策略

Delphi XE 及之后版本默认将 .dfm 中的字符串以 UTF-8 存储(带 BOM),但TComponent.ReadData在加载时会依据当前系统区域设置(Locale)调用MultiByteToWideChar(CP_ACP, ...)进行转换。问题在于:当开发机系统设为简体中文(CP936),而部署机为繁体中文(CP950)或英文(CP1252)时,同一份 .dfm 文件加载出的Caption字符串内容完全不同。EhLib 原版 V5.6.214 的EhLibRes.dfm未声明{$CODEPAGE UTF8},导致其内置的中文资源字符串在非 CP936 环境下加载即变形。V5.6.215 FS 在所有.dfm文件头部强制插入:

{$CODEPAGE UTF8} object EhLibRes: TEhLibRes

提示:此指令必须位于unit声明之后、interface之前,且不能与{$WEAKPACKAGEUNIT}等指令冲突。若你自行修改 EhLibRes.dfm 后编译报错「Invalid character in input」,大概率是 BOM 缺失或编辑器保存时用了 UTF-8 without BOM。

2.2 Windows 剪贴板数据格式与 EhLib 粘贴路径的字符流劫持

EhLib 的TEhDBGrid.DoPasteFromClipboard默认调用Clipbrd.Clipboard.AsText获取文本。该方法底层调用GetClipboardData(CF_TEXT),而CF_TEXT格式强制使用CP_ACP编码(即系统 ANSI 代码页)。当你在记事本中复制「测试数据」并粘贴进TEhDBGrid,实际传入的是0xC2E2CAD4CAFDCAE2(CP936 编码字节流),但 EhLib 解析时误当作 UTF-16 处理,结果变成「娴暟鎹?」。V5.6.215 FS 彻底绕过AsText,改用GetClipboardData(CF_UNICODETEXT)并显式指定WideChar解析:

function TEhDBGrid.GetClipboardUnicodeText: WideString; var hGlobal: HGLOBAL; pMem: PWideChar; begin Result := ''; if not OpenClipboard(0) then Exit; try hGlobal := GetClipboardData(CF_UNICODETEXT); if hGlobal <> 0 then begin pMem := GlobalLock(hGlobal); try if pMem <> nil then Result := pMem; // 直接赋值 WideString,避免 AnsiToWideChar 二次转码 finally GlobalUnlock(hGlobal); end; end; finally CloseClipboard; end; end;

这段代码的关键不在「获取」,而在「不转码」:WideString是 Delphi 对 COM BSTR 的封装,其内存布局与CF_UNICODETEXT完全一致,直接赋值即可零拷贝传递。旧版 EhLib 使用string类型接收,触发了隐式AnsiToUtf8→Utf8ToWideString链式转换,正是乱码温床。

2.3 汉化资源包(.res)与运行时字符串表的同步机制

EhLib 的汉化依赖EhLibRes.res资源文件,其中包含STRINGTABLE区段。V5.6.214 的资源编译流程存在一个致命疏漏:.rc文件用#pragma code_page(936)声明,但brcc32.exe编译时若未指定/c 936参数,仍会按系统默认 CodePage 解析.rc中的中文字符串。V5.6.215 FS 提供了双保险方案:

  • 在EhLibRes.rc文件首行添加#pragma code_page(65001)(UTF-8)
  • 修改EhLib.bpg构建脚本,在brcc32调用参数中硬编码/c 65001

这样无论开发机系统如何设置,资源编译结果都严格按 UTF-8 解析.rc内容,再写入.res的STRINGTABLE。运行时LoadString加载的便是原始 Unicode 字符,彻底规避 ANSI 中间态。


3. 集成实操:从下载解压到替换控件,五步完成无感升级

V5.6.215 FS 不是「安装包」,而是一组可直接覆盖的源码文件。它的集成方式极度克制——不修改 IDE 注册表、不注册新组件、不生成额外 .bpl,纯粹是「源码级热替换」。这意味着你可以只升级某几个关键单元,而不影响其他模块。以下步骤基于 Delphi 10.4 Sydney(兼容 10.2–11.3),所有路径均以$(DELPHI)\Source\Win32\EhLib为基准。

3.1 下载与目录结构确认

资源包解压后应呈现如下结构(共 7 个核心文件,无子目录):

文件名作用是否必须替换
EhLib.pas主单元,含TEhDBGrid等类声明✅ 必须
EhDBGrid.pas网格控件核心实现,含DoPasteFromClipboard重写✅ 必须
EhLibRes.dfm汉化资源窗体,已添加{$CODEPAGE UTF8}✅ 必须
EhLibRes.pas资源窗体单元,关联EhLibRes.dfm✅ 必须
EhLibRes.rc资源脚本,#pragma code_page(65001)已生效✅ 必须
EhLibRes.res编译后的资源文件,GB18030 兼容⚠️ 建议替换(若你有自定义汉化)
EhLib.dpk包描述文件,requires项未变更❌ 可不替换

注意:不要试图用 IDE 的「Install Package」功能安装EhLib.dpk。V5.6.215 FS 的设计哲学是「源码直连」,所有控件均通过uses EhLib, EhDBGrid引入,而非 BPL 动态链接。这避免了 BPL 版本冲突,也让你能直接调试DoPasteFromClipboard内部逻辑。

3.2 替换源码并强制重新编译

进入你的项目目录,执行以下操作(以命令行方式确保无缓存干扰):

# 步骤1:备份原 EhLib 目录(重要!) xcopy "$(DELPHI)\Source\Win32\EhLib" "C:\Backup\EhLib_V5.6.214" /E /I /Y # 步骤2:清空原目录(注意:仅清空,不删除目录本身) del "$(DELPHI)\Source\Win32\EhLib\*.*" /Q rmdir "$(DELPHI)\Source\Win32\EhLib" /S /Q mkdir "$(DELPHI)\Source\Win32\EhLib" # 步骤3:复制新文件(假设解压路径为 C:\Download\EhLib_FS) copy "C:\Download\EhLib_FS\*.pas" "$(DELPHI)\Source\Win32\EhLib\" /Y copy "C:\Download\EhLib_FS\*.dfm" "$(DELPHI)\Source\Win32\EhLib\" /Y copy "C:\Download\EhLib_FS\*.rc" "$(DELPHI)\Source\Win32\EhLib\" /Y copy "C:\Download\EhLib_FS\*.res" "$(DELPHI)\Source\Win32\EhLib\" /Y

3.3 项目级配置:确保编译器识别 UTF-8 .dfm

Delphi 默认不信任外部 .dfm 的 UTF-8 编码。需在你的主项目.dpr文件顶部(program关键字后、uses前)添加:

{$CODEPAGE UTF8} program MyEnterpriseApp; uses Vcl.Forms, Unit1 in 'Unit1.pas' {Form1}, EhLib in '..\..\Source\Win32\EhLib\EhLib.pas', EhDBGrid in '..\..\Source\Win32\EhLib\EhDBGrid.pas';

提示:若你使用 C++Builder,需在.cpp文件中添加#pragma option push -b和#pragma option pop包裹#include,否则{$CODEPAGE UTF8}指令会被忽略。

3.4 验证粘贴功能:用最小 Demo 测试

新建一个空白 VCL Forms Application,拖入TEhDBGrid和TDataSource,绑定一个内存表(TClientDataSet)。在窗体OnCreate中初始化:

procedure TForm1.FormCreate(Sender: TObject); begin ClientDataSet1.FieldDefs.Add('Name', ftString, 50, False); ClientDataSet1.CreateDataSet; ClientDataSet1.Append; ClientDataSet1.FieldByName('Name').AsString := '原始测试'; ClientDataSet1.Post; end;

运行程序,选中网格单元格,按Ctrl+V粘贴以下内容(从记事本复制,确保记事本保存为 UTF-8):

张三 李四 王五

观察:若显示为「张三」「李四」「王五」,则CF_UNICODETEXT路径生效;若仍为乱码,则检查EhDBGrid.pas是否被 IDE 缓存(重启 IDE 或清除$(DELPHI)\Bin\dcu下的EhDBGrid.dcu)。

3.5 汉化验证:动态切换语言包的正确姿势

EhLib 的汉化不依赖SysUtils.SetThreadLocale,而是通过TEhLibRes单例的SetLanguage方法。V5.6.215 FS 新增TEhLibRes.LanguageCode属性,支持运行时切换:

// 切换至简体中文(无需重启) EhLibRes.SetLanguage('zh-CN'); // 切换至繁体中文(需确保 EhLibRes.res 包含对应字符串) EhLibRes.SetLanguage('zh-TW'); // 查看当前语言 ShowMessage('Current: ' + EhLibRes.LanguageCode);

注意:SetLanguage会触发TEhDBGrid的Invalidate,但不会重绘列标题。如需立即刷新标题,需手动调用EhDBGrid1.Header.Invalidate。


4. 避坑指南:五个真实翻车现场与血泪解决方案

EhLib 的升级看似简单,但因其深度耦合 VCL 底层机制,稍有不慎就会引发连锁反应。以下是我在三个不同客户现场踩过的坑,每一条都附带可复现的最小场景和一招制敌的解法。

4.1 现象:粘贴中文后网格闪退,报Access violation at address XXXX in module 'EhLib.dll'

原因:你的项目启用了FastMM4内存管理器,且EhDBGrid.pas中GetClipboardUnicodeText函数返回的WideString被 FastMM4 的OnRealloc钩子错误拦截。根本原因是WideString的内存由 WindowsCoTaskMemAlloc分配,而 FastMM4 默认接管所有ReallocMem调用,导致释放时调用CoTaskMemFree失败。
解决:在项目.dpr文件中Application.Initialize之前添加:

FastMM4.DisableMemoryLeakReporting; FastMM4.Options := FastMM4.Options - [foEnableMemoryLeakReporting]; // 或更彻底:注释掉 uses FastMM4,改用系统默认内存管理器

4.2 现象:汉化后TEhDBDateTimePicker的「今天」按钮显示为方块

原因:TEhDBDateTimePicker的TodayButton.Caption从EhLibRes加载,但该控件的Paint方法内部硬编码了字体Font.Name := 'Tahoma',而 Tahoma 字体不包含中文字符集。V5.6.214 未做字体回退,V5.6.215 FS 在TEhDBDateTimePicker.Paint开头插入:

if Font.Name = 'Tahoma' then Font.Name := 'Microsoft YaHei UI'; // 优先微软雅黑 UI,其次宋体

解决:手动在你的TEhDBDateTimePicker创建后设置字体:

EhDBDateTimePicker1.Font.Name := 'Microsoft YaHei UI'; EhDBDateTimePicker1.Font.Size := 9;

4.3 现象:多显示器环境下,TEhPopupBox弹出位置偏移 200 像素

原因:TEhPopupBox的PopupPoint计算依赖GetCursorPos,但 V5.6.214 的CalcPopupPos方法未考虑 DPI 缩放。当主显示器 DPI=125%,副屏 DPI=100% 时,GetCursorPos返回屏幕坐标,而Screen.PixelsPerInch取的是主屏值,导致换算错误。
解决:在EhLib.pas中找到TEhPopupBox.CalcPopupPos,将原计算:

Result.X := CursorPos.X - (Width div 2); Result.Y := CursorPos.Y + 10;

替换为 DPI 感知版本:

var Monitor: HMONITOR; MonitorInfo: TMonitorInfo; begin Monitor := MonitorFromPoint(CursorPos, MONITOR_DEFAULTTONEAREST); MonitorInfo.cbSize := SizeOf(MonitorInfo); GetMonitorInfo(Monitor, @MonitorInfo); Result.X := Round((CursorPos.X - MonitorInfo.rcWork.Left) * (Screen.PixelsPerInch / GetDpiForMonitor(Monitor, MDT_EFFECTIVE_DPI, DPI_X, DPI_Y))); Result.Y := CursorPos.Y + 10; end;

4.4 现象:TEhDBGrid导出 Excel 时,中文列标题在 Excel 2016 中显示为「#」

原因:TEhDBGrid.ExportToExcel调用EhLibExcel.pas,而该单元的WriteCell方法对ftString字段使用AnsiString写入,未适配 Excel 的 UTF-16LE 编码要求。V5.6.215 FS 将WriteCell中所有AnsiString参数改为UnicodeString,并在TExcelWriter.WriteCell中显式调用WideCharToMultiByte(CP_UTF8, ...)。
解决:若你无法替换EhLibExcel.pas,临时方案是在导出前设置:

EhDBGrid1.ExportOptions.ExcelVersion := evExcel2007; // 强制使用 .xlsx 格式 EhDBGrid1.ExportOptions.UseUnicode := True; // 启用 Unicode 输出

4.5 现象:IDE 设计时TEhDBGrid显示「Loading...」,无法编辑列属性

原因:EhLibRes.dfm中{$CODEPAGE UTF8}指令导致 Delphi IDE 的 Form Designer 在加载时解析失败,抛出EInvalidOperation。这不是运行时问题,而是设计器的元数据加载缺陷。
解决:临时注释掉EhLibRes.dfm中的{$CODEPAGE UTF8}行(仅设计时),运行时仍由EhLib.pas中的{$CODEPAGE UTF8}保证。或更优雅地:在EhLibRes.dfm顶部添加条件编译:

{$IFDEF DESIGNING} // {$CODEPAGE UTF8} // 设计时禁用,避免 IDE 解析失败 {$ELSE} {$CODEPAGE UTF8} {$ENDIF}

5. 进阶技巧:构建自己的「防乱码」基类与自动化汉化流水线

V5.6.215 FS 解决了 EhLib 自身的乱码,但你的业务代码仍可能在其他环节引入新乱码。我一般会在此基础上构建两层防御:一是封装一个TEhSafeDBGrid基类,二是在 CI/CD 中加入汉化完整性校验。这两步让我在交付给某高校教务系统时,一次性通过信创适配验收(麒麟 V10 + 达梦 DM8)。

5.1 封装TEhSafeDBGrid:拦截所有潜在乱码入口

继承TEhDBGrid,重写三个关键方法,形成「输入过滤 → 显示加固 → 导出兜底」闭环:

type TEhSafeDBGrid = class(TEhDBGrid) private FInputFilter: TFunc<string, string>; protected function DoPasteFromClipboard: Boolean; override; procedure Paint; override; procedure ExportToExcel(const FileName: string; AExportOptions: TEhExportOptions = nil); override; public constructor Create(AOwner: TComponent); override; property InputFilter: TFunc<string, string> read FInputFilter write FInputFilter; end; implementation constructor TEhSafeDBGrid.Create(AOwner: TComponent); begin inherited Create(AOwner); // 默认过滤:移除不可见控制字符,标准化全角标点 FInputFilter := function(const S: string): string var I: Integer; begin Result := S; for I := 1 to Length(Result) do if (Ord(Result[I]) in [0..8, 11..12, 14..31]) then Result[I] := ' '; Result := StringReplace(Result, ',', ',', [rfReplaceAll]); Result := StringReplace(Result, '。', '.', [rfReplaceAll]); end; end; function TEhSafeDBGrid.DoPasteFromClipboard: Boolean; var Text: WideString; begin Text := GetClipboardUnicodeText; // 复用 V5.6.215 FS 的安全获取 if Assigned(FInputFilter) then Text := FInputFilter(Text); Result := inherited DoPasteFromClipboard; // 此时 Text 已净化 end; procedure TEhSafeDBGrid.Paint; begin inherited Paint; // 强制标题字体为支持中文的字体 if Assigned(Header) and Assigned(Header.Font) then begin if Header.Font.Name <> 'Microsoft YaHei UI' then Header.Font.Name := 'Microsoft YaHei UI'; end; end;

提示:InputFilter属性支持 Lambda 表达式,你可以在窗体OnCreate中动态注入业务规则:

EhSafeDBGrid1.InputFilter := function(const S: string): string begin Result := Trim(S); if Length(Result) > 100 then Result := Copy(Result, 1, 100) + '…'; end;

5.2 自动化汉化校验:用 PowerShell 扫描所有 .dfm/.pas 中的中文字符串

乱码常源于「部分文件未更新」。我写了一个 PowerShell 脚本,在每日构建时扫描整个Source目录,报告缺失汉化的控件:

# CheckEhLibLocalization.ps1 $EhLibPath = "$(DELPHI)\Source\Win32\EhLib" $ProjectPath = ".\MyProject\Source" # 提取 EhLibRes.res 中所有中文字符串(需先用 ResHacker 导出为 .txt) $EhLibStrings = Get-Content "$EhLibPath\EhLibRes.txt" -Encoding UTF8 | Select-String -Pattern "^[一-龯].*" | ForEach-Object { $_.Line.Trim() } # 扫描项目中所有 .dfm/.pas,提取 Caption、Hint、Text 等属性值 $AllStrings = Get-ChildItem $ProjectPath -Recurse -Include "*.dfm","*.pas" | ForEach-Object { $content = Get-Content $_.FullName -Encoding UTF8 $content | Select-String -Pattern 'Caption[^=]*=[^;]*''([^'']*?)''' | ForEach-Object { $_.Matches[0].Groups[1].Value } $content | Select-String -Pattern 'Hint[^=]*=[^;]*''([^'']*?)''' | ForEach-Object { $_.Matches[0].Groups[1].Value } } | Sort-Object -Unique # 找出项目中用了但 EhLib 未汉化的字符串 $Missing = Compare-Object $AllStrings $EhLibStrings -PassThru | Where-Object { $_.SideIndicator -eq '<=' } if ($Missing.Count -gt 0) { Write-Error "发现 $Missing.Count 处未汉化字符串:" $Missing | ForEach-Object { Write-Error " $_" } exit 1 } Write-Host "✅ 汉化完整性检查通过"

5.3 信创环境专项适配:GB18030 兼容性补丁

某次在麒麟 V10 上部署时,客户要求所有日志文件必须符合 GB18030-2005。EhLib 的TEhLog单元默认用TStringList.SaveToFile,而该方法在 Linux 子系统(WSL2)下默认用 UTF-8。我们新增一个TEhGB18030Log类:

type TEhGB18030Log = class(TEhLog) public procedure SaveToFile(const FileName: string); override; end; procedure TEhGB18030Log.SaveToFile(const FileName: string); var SL: TStringList; Bytes: TBytes; begin SL := TStringList.Create; try SL.Assign(Self); // 使用 Windows API 的 WideCharToMultiByte,指定 CP_GB18030 Bytes := WideCharToMultiByte(SL.Text, CP_GB18030, 0, nil, 0); SetLength(Bytes, Length(Bytes)); WideCharToMultiByte(SL.Text, CP_GB18030, 0, PByte(Bytes), Length(Bytes)); TFile.WriteAllBytes(FileName, Bytes); finally SL.Free; end; end;

从那以后我每次交付 Delphi 项目,都强制走一遍CheckEhLibLocalization.ps1+TEhSafeDBGrid封装 +TEhGB18030Log替换三件套。不是因为怕出错,而是因为乱码问题一旦上线,用户截图发群里,你解释「这是编码问题」比修复还费劲。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询