调用 C++ DLL(第三方 DLL 与中文项目生成 DLL)
[🕒 预计 20 分钟] | 难度:进阶
LingBuilder 支持两条使用 C++ DLL 的路径,最终都以「模块」为载体被中文项目调用:
- 第三方 / 自制 C++ DLL → 封装成
.lbmod模块 → 中文命令直连 DLL 导出函数。 - 中文项目生成 DLL → 把中文项目(
.lcpp)的解决方案项目属性设为outputType: "dll",「公开」子程序自动导出为 DLL 函数 → 同样封装成模块 → 供其他项目调用。
两条路径共用同一套模块生态:安装、启用、F5 构建时自动复制 DLL 到 exe 同目录,生成的工程在 Visual Studio 中也能独立编译运行。
1. 第三方 DLL:五步封装
以一个导出 demo_add、demo_text_length、demo_distance 的 C 动态库为例:
准备双架构产物:外部模块需要为每个架构提供真实文件(内置模块的架构自动补齐不适用于外部
.lbmod):模块目录/ ├ include/demo_mathdll.h ← extern "C" 导出声明 ├ lib/Win32/demo_mathdll.lib lib/x64/demo_mathdll.lib └ bin/Win32/demo_mathdll.dll bin/x64/demo_mathdll.dll写迁移配置:一个 JSON 描述模块 ID、头文件、导入库、运行时 DLL 与每条中文命令(
runtimeName直接填 DLL 导出函数名)。生成模块:模块开发者中心选择「C++ 迁移」,或命令行
lingbuilder module migrate-cpp --config 配置.json --out 模块目录;再补齐模块说明文档与最小示例。校验打包:
lingbuilder module validate 模块目录→lingbuilder module pack 模块目录 --out .lingbuilder/module-packages/xxx.lbmod;校验会给出全部中文诊断(命令与绑定必须一一成对、路径必须安全相对路径、带 DLL 的模块不允许结构体/数组参数等)。安装启用:IDE「安装 .lbmod」→ 预览确认 → 在「配置项目所使用模块」中启用。F5 构建后日志出现「已复制模块运行时文件:xxx.dll」即表示 DLL 已就位。
NOTE
打包与安装也可以全程用模块开发者中心 UI 完成;命令行入口(module init / validate / pack / inspect / migrate-cpp)适合批处理与 AI 协作。
2. 让中文项目生成 DLL
把一个普通中文项目变成「功能库」:解决方案项目属性 buildProperties 中设置 outputType: "dll"(可配 executableName 指定产物名)。
导出口径是确定性的:
- 仅「公开」节的子程序导出;事件处理器、构造/析构、私有/保护成员一律不导出。
- 调用约定统一
__stdcall(extern "C",x64 下自动忽略);导出名单由构建自动生成在产物同目录的exports.def里,一行一个导出名,可直接查看核对。 - 英文/拼音子程序名以无装饰名进导出表(如
add_numbers,易语言等外部宿主直接按名声明即可);中文子程序名仍会导出,但在 32 位产物里只能以装饰名(_名@参数字节数)访问,构建时也会给出中文提示建议改用英文/拼音名。 - 文本参数以
const wchar_t*跨界(UTF-16 编码)、文本返回经 DLL 侧缓冲以const wchar_t*交出——消费方无论是 Debug 还是 Release 都安全。 - 动态库是纯逻辑库:不生成
wWinMain与消息循环,不会自动创建设计器窗口;首次导出调用时自动完成 COM/GDI+/通用控件初始化。
DLL 项目在 IDE 里的行为:
- F5「生成并运行」自动禁用(动态库没有运行入口):工具栏运行按钮不可点击并提示原因,命令面板与 F5 快捷键同样被拦截。
- 编译入口是「生成」:工具栏会出现专用的「生成动态库」按钮(仅 DLL 项目显示),也可以用菜单/命令面板的「生成解决方案」;两者都会在输出目录的
bin子目录产出<产物名>.dll与同名导入库.lib。 - Visual Studio 导出工程的
ConfigurationType同步为DynamicLibrary(强制 /MD),可在 VS 里独立编译。
在外部程序中调用生成的 DLL(如易语言)
生成的 DLL 是标准 Windows 动态库,任何支持 stdcall 调用的宿主都能用,易语言「DLL 命令」声明即可直接调。三条前提:
- 公开子程序用英文/拼音名:易语言按 GBK 匹配导出表字节,中文子程序名的 UTF-8 字节在易语言里无法按名找到。
- 按 32 位(Win32)构建:易语言是 32 位程序,加载不了 x64 DLL——构建属性里把平台设为 Win32(命令行构建加
--arch win32)。 - 调用约定保持默认:易语言 DLL 命令默认按 stdcall 调用,生成的 DLL 正是 stdcall,声明时不需要任何额外处理。
易语言侧声明与调用示例(声明放在 DLL 命令表中):
.版本 2
.DLL命令 加法计算, 整数型, "LingBuilderPreview.dll", "add_numbers"
.参数 被加数, 整数型
.参数 加数, 整数型类型对照表(中文项目「公开」子程序 ↔ 易语言 DLL 命令声明):
| 中文项目类型 | 实际 ABI | 易语言声明 |
|---|---|---|
| 整数型 | 4 字节 int | 整数型 |
| 长整数型 | 8 字节 long long | 长整数型 |
| 小数型 | 双精度 double | 双精度小数型(声明成「小数型」会按单精度读错值) |
| 逻辑型 | bool | 建议按整数型 0/1 声明最稳妥 |
| 字节型 | unsigned char | 字节型 |
| 文本型 | UTF-16 wchar_t* | 需转码,见下 |
WARNING
易语言的文本型是 GBK 单字节字符串,而 DLL 的文本参数是 UTF-16 宽字符——直接传中文文本会乱码。给易语言用的接口建议只出整数/小数等纯数值签名;必须传文本时,在易语言侧先把文本转成 UTF-16 编码的字节集再传入,返回的 UTF-16 文本也要按 GBK 转回后再使用。
3. 把项目 DLL 封装成模块
与第三方 DLL 完全一致:写一份与导出同名的头文件(文本参数 const wchar_t*)、填好迁移配置(runtimeName 填公开子程序名)、migrate-cpp 生成 → 校验 → 打包 → 安装。之后任何项目启用该模块,即可用中文命令直接调用另一份中文代码的能力。
4. 常见诊断对照
| 诊断 | 处理 |
|---|---|
| 公开子程序签名包含不能跨 DLL 边界的类型 | 只保留 POD 与文本签名;控件、数组、记录、句柄对象不能跨界 |
| 同名公开子程序重复导出 | 重命名其中一个,或改为私有 |
| 模块未提供兼容目标 | 补齐对应架构 target 与真实 .lib/.dll |
| 需要 MSVC 链接 .lib | 安装 Visual Studio Build Tools 后重试 |
WARNING
第三方 DLL 的公开头文件请保持「文本参数 const wchar_t*、文本返回 const wchar_t*」的形态。std::wstring 以按值或引用跨界,在 Debug 消费方 + Release DLL 组合下会被错误读取甚至堆损坏;纯 POD 参数无此问题。
5. 项目级 DLL 命令声明(免封装)
如果 DLL 只在当前项目使用,可以完全跳过模块封装:解决方案资源管理器项目下点「DLL 命令」,在声明表格里填写 DLL 名、架构文件路径(dll/Win32/、dll/x64/)与导出函数(返回类型 / 命令名 = 导出函数名 / 参数表),也可以粘贴 .h 头文件自动识别。声明保存在项目的 项目DLL命令.lcpp 文件中,随项目一起复制与分享。
声明格式示例:
DLL命令库 AdvancedMathDll
Win32 = "dll/Win32/AdvancedMathDll.dll"
x64 = "dll/x64/AdvancedMathDll.dll"
整数型 加法计算(整数型 被加数, 整数型 加数)
备注: 调用 DLL 计算两个整数之和。
整数型 取字符数(文本型 文本)
小数型 两点距离计算(小数型 横1, 小数型 纵1, 小数型 横2, 小数型 纵2)
结束DLL命令库
DLL命令库 user32
系统 = 真
整数型 提示音(整数型 类型) = MessageBeep
结束DLL命令库字段说明:
- 系统 DLL(
系统 = 真):user32、gdi32 等随 Windows 分发的库,免分发——不复制文件、直接链接系统导入库。系统 DLL 的导出名固定为英文,中文命令名必须用= 真实导出名指定别名(如提示音 = MessageBeep);构建时生成指向该真实导出名的内联转发函数完成绑定,声明的导出名在系统 DLL 中不存在时会得到中文阻断诊断。这个英文导出名还会自动登记为中文命令的补全别名:在代码里敲MessageBeep能筛出「提示音」,上屏仍是中文命令名。 - 命令备注(命令行下一行
备注: 文本)与参数备注(参数后// 文本)会同步进入补全提示。 - 传址参数(参数后加
传址):按指针传入,DLL 回填结果(仅整数/小数/逻辑/字节等 POD 类型支持,文本型不支持)。 - 命令名不能为空:空名声明无法写回声明文件(会被解析器当作无法识别的行丢弃)。「添加命令」会先给出
新命令N占位名,你可以直接改;清空命令名会被拒绝,删除整条命令请用卡片右下的「删除此命令」(折叠状态下同样可用)。 - 声明表格编辑器支持每条命令的折叠/展开、右键搜索与批量展开折叠,并按备注与参数提供补全提示。跨项目迁移用两个按钮分工完成:「复制此命令」只复制该命令自身的声明行,粘贴时并入目标项目的当前库;「复制全部声明」复制整份声明(含各库与架构路径),粘贴时按库名合并。同名命令一律跳过并在反馈区提示。
构建时框架自动完成:按实际导出表生成导入库并链接(系统 DLL 的中文别名命令生成指向真实导出名的内联转发函数)、把 DLL 复制到 exe 同目录、把声明的函数注册为中文命令(补全与新手提示同步可用,英文导出名同时作为检索别名)。缺少对应架构 DLL、系统 DLL 未导出声明的函数、DLL 未导出声明的函数时,构建会给出中文阻断诊断。
声明命令的文本 ABI 与模块一致:文本参数 const wchar_t*、文本返回经缓冲以 const wchar_t* 交出。