本指南总结了在 Sysplorer 中调用外部 C++ DLL 的完整流程、关键配置与常见排错方法,适用于 C++ 与 Modelica 的混合编程场景。
一、核心成功公式
正确的 C++ 导出代码 + 一致的工具链(MSVC + 64 位) + 准确的库路径 = 成功调用二、环境要求
| 项目 | 要求 | 说明 |
|---|---|---|
| 操作系统 | Windows | Sysplorer 与 MSVC 均运行于 Windows |
| C++ 编译器 | Visual Studio 2017(或兼容版本) | 必须与 Sysplorer 的编译器一致 |
| 平台位数 | x64(64 位) | DLL、LIB、Sysplorer 仿真平台必须一致 |
| Sysplorer 编译器设置 | VC(Visual Studio) | 不可使用默认 GCC/MinGW |
三、完整实施流程
第一阶段:准备 C++ 动态库(VS2017)
1. 创建 DLL 项目
- 打开 Visual Studio 2017。
- 选择文件 → 新建 → 项目。
- 选择动态链接库(DLL)模板。
- 配置项目名称(例如
2026_10_23_Dll1),点击确定。
2. 编写导出函数
在头文件和源文件中编写要导出的函数。必须使用extern "C"和__declspec(dllexport):
// MyDll.h#pragmaonce#ifdefBUILD_MYDLL#defineAPI_SYMBOL__declspec(dllexport)#else#defineAPI_SYMBOL__declspec(dllimport)#endifextern"C"API_SYMBOLintadd(inta,intb);// MyDll.cpp#include"pch.h"#defineBUILD_MYDLL#include"Mydll.h"#include<iostream>#include<windows.h>usingnamespacestd;intadd(inta,intb){// 程序执行到这里,会被强行暂停(阻塞),直到你点击弹窗的“确定”MessageBoxA(NULL,"程序已暂停,快去 VS 附加进程!","抓住我",MB_OK);intc=a+b;// 记得在这一行按 F9 打上红点断点returnc;}原因说明:
__declspec(dllexport):将函数符号写入 DLL 导出表,使外部程序可以调用。extern "C":禁用 C++ 名字修饰(Name Mangling),确保导出的函数名保持为Add、MultiplyArray等原始名称。否则函数名会变成类似?Add@@YAHHH@Z的乱码,Modelica 无法正确链接。
3. 切换平台为 x64
- 在 VS 工具栏中,将解决方案平台从默认的
x86切换为x64。 - 右键项目 →属性→C/C++→代码生成→运行库:
- Debug 模式选择:多线程调试 DLL(
/MDd) - Release 模式选择:多线程 DLL(
/MD) - 如需避免依赖 VC++ 运行库分发,可选择
/MT或/MTd(静态链接运行库)。
- Debug 模式选择:多线程调试 DLL(
原因说明:Sysplorer 通常运行在 64 位环境下,32 位 DLL 无法被 64 位进程加载。平台位数必须完全一致。
4. 生成 DLL
按Ctrl+Shift+B或右键项目选择生成。输出目录将生成两个关键文件:
| 文件 | 作用 | 是否必须 |
|---|---|---|
2026_10_23_Dll1.dll | 运行时加载的动态库 | 仿真运行时必须 |
2026_10_23_Dll1.lib | 编译链接时的导入库 | 编译 Modelica 时必须 |
原因说明:.lib是导入库(Import Library),只包含函数在 DLL 中的位置信息,不包含实际代码。编译器用它来生成正确的导入表;运行时系统根据导入表去加载.dll。.pdb包含调试相关信息
5. 部署文件
* 打开你的 VS 输出目录(`C:\MyVSCode\...\x64\Debug`)。 * 将最新的 **`.dll`** 和 **`.pdb`** (如果是隐式调用,加上 **`.lib`**)**复制并覆盖**到你的 Sysplorer 工作目录(`C:\Users\TR\Documents\MWORKS\ElectricalValve\Demo`)。 * *注意:DLL 和 PDB 必须是同一次编译生成的,且修改时间戳一致。*第二阶段:编写 Modelica 包装层
在 Sysplorer 中定义外部函数包装层,建立 C++ 函数与 Modelica 之间的映射。
1. 参数类型对应关系
| C++ 类型 | Modelica 类型 | 说明 |
|---|---|---|
int | Integer | 整数 |
double | Real | 双精度浮点数 |
double* | Real[:] | 数组,按引用传递 |
const char* | String | 字符串(谨慎使用,涉及内存管理) |
bool | Boolean | 布尔值 |
2. 外部函数定义示例
function myAdd input Integer a; input Integer b; output Integer result; external "C" result = add(a, b) annotation ( Library="2026_10_23_Dll1", // 动态库 和当前 .mo 文件在同一个目录下 LibraryDirectory="." ); end myAdd;关键说明:
external "C":声明按 C 语言调用约定进行链接。Library = "2026_10_23_Dll1":只写库名,不要加.lib或.dll后缀。LibraryDirectory:指定.lib文件所在目录。推荐使用modelica://包名/Resources/Library这种相对路径,便于项目迁移。
第三阶段:配置 Sysplorer 编译环境
1. 切换 C 编译器为 VC
- 打开 Sysplorer。
- 进入仿真设置 → 编译。
- 将 C 编译器从默认的 GCC (mingw) 切换为 VC (Visual Studio)。
原因说明:GCC/MinGW 的库格式与 MSVC 生成的.lib不兼容。若使用 GCC 编译器链接 MSVC 生成的导入库,会报cannot find -lxxx或链接错误。
2. 统一平台位数为 64 位
- 在仿真设置 → 编译中,确认仿真平台为 64 位(x64)。
- 确保此设置与 VS 中生成 DLL 的平台一致。
原因说明:32 位与 64 位的目标计算机类型不同,混用会导致LNK4272错误。
第四阶段:在模型中调用
在 Modelica 模型的equation或algorithm区域中调用包装函数:
model System_Demo "系统示例" annotation(__MWORKS(version = "26.3.0",ContinueSimConfig(SaveContinueFile="false",SaveBeforeStop="false",NumberBeforeStop=1,FixedContinueInterval="false",ContinueIntervalLength=100,ContinueTimeVector)), experiment(Algorithm=Dassl,InlineIntegrator=false,InlineStepSize=false,NumberOfIntervals=500,StartTime=0,StopTime=100,StoreEventValue=0,Tolerance=0.0001)); Controller.PID_Controller pID_Controller annotation(Placement(transformation(origin = {-108, 22}, extent = {{-10, -10}, {10, 10}}))); Valve.ElectTrical electTrical annotation(Placement(transformation(origin = {-60, 7.4}, extent = {{-10, -10}, {10, 10}}))); Valve.Valve valve annotation(Placement(transformation(origin = {-60, -30.6}, extent = {{-10, -10}, {10, 10}}))); Modelica.Blocks.Sources.Constant const(k = 5) annotation(Placement(transformation(origin = {-196, 27.6}, extent = {{-10, -10}, {10, 10}}))); Modelica.Blocks.Math.Add add(k2 = -1) annotation(Placement(transformation(origin = {-148, 21.6}, extent = {{-10, -10}, {10, 10}}))); Modelica.Blocks.Tables.CombiTable1Ds combiTable1Ds(table = {{0.0, 0}, {2 * 3.14, 50}}) annotation(Placement(transformation(origin = {22, -37.2}, extent = {{-10, -10}, {10, 10}}))); // ================== 新增部分开始 ================== // 1. 定义一个变量来接收 C++ 函数的计算结果 Integer testDllResult; // 2. 定义外部函数的包装 function myAdd input Integer a; input Integer b; output Integer result; external "C" result = add(a, b) annotation ( Library="2026_10_23_Dll1", // 假设 动态库 和当前 .mo 文件在同一个目录下 LibraryDirectory="." ); end myAdd; // ================== 新增部分结束 ================== equation connect(electTrical.p, valve.n) annotation(Line(origin = {-65.2, -11.6}, points = {{0.4, 8}, {0.4, -8}}, color = {0, 0, 255})); connect(electTrical.n, valve.p) annotation(Line(origin = {-55.2, -11.6}, points = {{0.2, 8}, {0.2, -8}, {-0.6, -8}}, color = {0, 0, 255}), __MWORKS(BlockSystem(NamedSignal))); connect(const.y, add.u1) annotation(Line(origin = {-172, 28}, points = {{-13, -0.4}, {12, -0.4}}, color = {0, 0, 127})); connect(add.y, pID_Controller.u) annotation(Line(origin = {-128, 22}, points = {{-9, -0.4}, {9, -0.4}}, color = {0, 0, 127})); connect(pID_Controller.y, electTrical.v) annotation(Line(origin = {-79, 21}, points = {{-18, 0.6}, {18.6, 0.6}, {18.6, -1.4}}, color = {0, 0, 127})); connect(valve.phi, combiTable1Ds.u) annotation(Line(origin = {-19, -37}, points = {{-30, -0.2}, {29, -0.2}}, color = {0, 0, 127})); connect(combiTable1Ds.y[1], add.u2) annotation(Line(origin = {-65, -20}, points = {{98, -17.2}, {101, -17.2}, {101, -36}, {-101, -36}, {-101, 35.6}, {-95, 35.6}}, color = {0, 0, 127})); // ================== 新增部分开始 ================== // 3. 在方程区调用你的 DLL 函数(测试 80 + 20) testDllResult = myAdd(200, 20); // ================== 新增部分结束 ================== end System_Demo;运行仿真后,在结果查看器中观察testDllResult的值。
四、常见错误与排错
| 报错信息 | 根本原因 | 解决办法 |
|---|---|---|
cannot find -lxxx (ld.exe) | 编译器不匹配。Sysplorer 使用 GCC,而 DLL 是 MSVC 生成。 | 在仿真设置中将编译器切换为 VC。 |
LNK1181: 无法打开输入文件 "xxx.lib" | LibraryDirectory路径错误,未指向.lib的真实目录。 | 使用 DLL 的绝对路径,或规范的modelica://包名/Resources/Library相对路径。 |
LNK4272: 库计算机类型 "x86" 与目标计算机类型 "x64" 冲突 | 位数不匹配。使用了 32 位库链接 64 位目标。 | 回 VS 切换为 x64 平台,重新编译 DLL 和 LIB。 |
LNK2019: 无法解析的外部符号 | 库已找到,但函数名不匹配;或 C++ 未加extern "C"导致名字被修饰。 | 确保 C++ 代码使用extern "C";确认 LIB/DLL 为 x64 版本;可用dumpbin /exports 2026_10_23_Dll1.dll检查导出函数名。 |
| 运行时提示“找不到 2026_10_23_Dll1.dll” | .dll未放入可执行文件搜索路径。 | 将.dll复制到 Sysplorer 工作目录、模型目录,或系统PATH路径中。 |
| 调用后结果异常或崩溃 | 参数类型/调用约定不匹配;数组越界;内存未初始化。 | 检查 C++ 与 Modelica 参数类型一一对应;确认数组长度一致;避免在 C++ 中返回局部变量指针。 |
五、进阶建议与最佳实践
1. 规范化资源路径
避免使用LibraryDirectory = "."这类相对路径,否则项目迁移后容易失效。推荐目录结构:
MyPackage/ ├── package.mo ├── Resources/ │ ├── Library/ │ │ ├── win64/ │ │ │ ├── MyDll.lib │ │ │ └── MyDll.dll │ └── Include/ │ └── MyDll.hModelica 中路径写为:
LibraryDirectory = "modelica://MyPackage/Resources/Library/win64"2. 处理 C++ 运行库依赖
简单函数(如Add)通常无额外依赖。
一旦使用std::string、std::vector、文件流等,DLL 会依赖msvcp140.dll、vcruntime140.dll等 VC++ 运行库。
发布给别人使用时,方案有两种:
- 静态链接运行库:在 VS 中将运行库改为
/MT(Release)或/MTd(Debug)。 - 附带运行库安装包:让对方安装对应版本的 Visual C++ Redistributable。
3. 调试 C++ 代码
Sysplorer 仿真时无法直接看到 C++ 内部状态,可采用以下方式调试:
printf输出:在 C++ 函数中加入printf或写日志文件,观察执行流程。- 附加到进程:在 VS 中选择调试 → 附加到进程,选择 Sysplorer 进程,然后在 C++ 代码中下断点运行仿真。
- 独立测试:先用一个最小的 C++ 控制台程序验证 DLL 导出函数是否正确,再集成到 Sysplorer。
4. 接口设计原则
- 保持 C++ 导出函数为纯 C 风格接口(
extern "C"、基本类型、指针)。 - 避免直接传递 C++ 类、STL 容器、异常等跨 DLL 边界。
- 数组长度建议在 Modelica 侧固定或在参数中显式传递,避免越界。
5. 版本与平台管理
- DLL、LIB、Sysplorer 仿真平台三者必须一致:同为 x64,同为 MSVC 工具链。
- 建议为 Debug 和 Release 分别生成对应的 DLL/LIB,避免混用。
- 文件命名建议带平台后缀,例如
MyDll_x64.lib、MyDll_x64.dll,便于管理。
六、快速检查清单
在点击仿真前,逐项确认:
- C++ 代码中导出的函数使用了
extern "C"和__declspec(dllexport) - VS 解决方案平台为 x64
- 已生成对应的
.dll和.lib文件 - Sysplorer 仿真设置中编译器为 VC
- Sysplorer 仿真平台为 64 位
Library只写库名,不带后缀LibraryDirectory指向.lib文件所在目录.dll文件位于 Sysplorer 可搜索的路径中(模型目录、工作目录或PATH)- Modelica 包装函数的参数类型与 C++ 函数一一对应
七、总结
在 Sysplorer 中调用外部 C++ DLL,本质上是在 Modelica 层通过external "C"建立一座桥梁,让 Sysplorer 的 64 位 MSVC 编译环境能够找到并加载由 VS 生成的 64 位 DLL。
只要牢牢把握住以下三点,就能稳定复现成功:
- C++ 导出规范:
extern "C"+__declspec(dllexport)。 - 工具链一致:MSVC 编译器 + x64 平台。
- 路径准确:
.lib给链接器,.dll给运行时的 Windows 加载器。