介绍

这篇文章主要实现在Simulink中可视化的工作,由于自身求解工具的可视化代码采用C++编程,同时与项目耦合过多,难以独立摘出或通过Matlab重现,因此这里采用新建桥接工程将所有需要用到的可视化代码封装并导出

封装Dll并导出C函数

首先在VS中新建空项目工程并进行配置,主要是关于项目依赖、导出Dll相关,然后将所有可能用到的代码进行简化和封装,我这里采用的类进行管理,由于跟项目相关不进行展示,新建XXXBridgeDLL.h和XXXBridgeDLL.cpp用于导出C接口供Matlab调用,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#pragma once

#ifdef XXXBRIDGEDLL_EXPORTS
#define EXPORT_API __declspec(dllexport)
#else
#define EXPORT_API __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C" {
#endif

EXPORT_API void* CreateProcessor();
EXPORT_API void DeleteProcessor(void* p);
EXPORT_API int ReadXXXDat(void* p, const char* folder, int* pNumPts);


EXPORT_API int GetPointCount(void* p); /* 返回总点数 */
EXPORT_API int CopyX_mm(void* p, double* out, int max); /* 轴向坐标 (mm) */
EXPORT_API int CopyPower(void* p, double* out, int max); /* 输出功率 (W) */
EXPORT_API int CopyGain(void* p, double* out, int max); /* 增益 (dB) */
EXPORT_API int CopyEff(void* p, double* out, int max); /* 效率 (%) */

#ifdef __cplusplus
}
#endif

这段代码是一个 C/C++ 动态链接库(DLL)的头文件(.h),用于定义向外部导出的一组函数接口。


DLL 导出宏定义部分

1
2
3
4
5
#ifdef XXXBRIDGEDLL_EXPORTS
#define EXPORT_API __declspec(dllexport)
#else
#define EXPORT_API __declspec(dllimport)
#endif
  • 作用:用于在不同编译阶段切换导出/导入。
    • 如果编译的是 DLL 本身(定义了 XXXBRIDGEDLL_EXPORTS),则 EXPORT_API 展开为 __declspec(dllexport),导出符号;
    • 如果是使用 DLL 的客户端程序,则展开为 __declspec(dllimport),用于导入符号。

extern "C"

1
2
3
4
5
6
7
#ifdef __cplusplus
extern "C" {
#endif
...
#ifdef __cplusplus
}
#endif
  • 使得这些函数按照 C语言方式 进行链接(不进行 C++ 名字改编 / name mangling),以便在 C/C++/Python/MATLAB 等跨语言调用时保持接口兼容。

导出的函数接口

1
EXPORT_API void* CreateProcessor();
  • 创建一个处理器对象,返回一个 void* 指针(通常是一个类的实例地址)。
1
EXPORT_API void DeleteProcessor(void* p);
  • 删除处理器对象,释放资源。
1
EXPORT_API int ReadXXXDat(void* p, const char* folder, int* pNumPts);
  • 从指定文件夹读取数据,p 是处理器对象指针,folder 是数据路径,pNumPts 用于返回读取到的点数。

数据访问函数(数据从内存复制出来)

1
EXPORT_API int GetPointCount(void* p);
  • 获取数据点总数。
1
EXPORT_API int CopyX_mm(void* p, double* out, int max);
  • 复制轴向坐标数据(单位:mm)到 out 指针所指向的数组,max 是允许写入的最大点数。
1
EXPORT_API int CopyPower(void* p, double* out, int max);
  • 复制输出功率数据(单位:瓦特)。
1
EXPORT_API int CopyGain(void* p, double* out, int max);
  • 复制增益数据(单位:dB)。
1
EXPORT_API int CopyEff(void* p, double* out, int max);
  • 复制电子效率数据(单位:百分比)。

这个 DLL 接口提供了一个基于文件夹读取仿真结果(XXX仿真输出),并通过一系列函数获取:

  • 坐标(X)
  • 功率(Power)
  • 增益(Gain)
  • 效率(Eff)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#define XXXBRIDGEDLL_EXPORTS

#include "XXXBridgeDLL.h"
#include "CXXXProcessor.h"
#include <algorithm>
#include <vector>

namespace {
template<typename SRC_GETTER>
int copyVector(CXXXProcessor* proc, double* out, int max, SRC_GETTER getter)
{
if (!proc || !out || max <= 0) return 0;
const auto& vec = getter(proc);
int n = std::min<int>(vec.size(), max);
std::copy(vec.begin(), vec.begin() + n, out);
return n;
}
}

extern "C" {
EXPORT_API void* CreateProcessor() { return new CXXXProcessor(); }
EXPORT_API void DeleteProcessor(void* p) { delete static_cast<CXXXProcessor*>(p); }

EXPORT_API int ReadXXXDat(void* p, const char* folder, int* pts) {
if (!p || !folder || !pts) return 0;
auto proc = static_cast<CXXXProcessor*>(p);
bool ok = proc->ReadXXXDat(folder);
*pts = ok ? static_cast<int>(proc->m_vdpX.size()) : 0;
return ok;
}

EXPORT_API int GetPointCount(void* p) {
return p ? static_cast<int>(static_cast<CXXXProcessor*>(p)->m_vdpX.size()) : 0;
}

EXPORT_API int CopyX_mm(void* p, double* out, int max) {
if (!p || !out || max <= 0) return 0;
auto proc = static_cast<CXXXProcessor*>(p);
int n = std::min<int>(proc->m_vdpX.size(), max);
for (int i = 0; i < n; ++i) out[i] = proc->m_vdpX[i] * 1000.0;
return n;
}
EXPORT_API int CopyPower(void* p, double* o, int m) {
return copyVector(static_cast<CXXXProcessor*>(p), o, m,
[](CXXXProcessor* q)->const std::vector<double>&{return q->PowerOut; });
}
EXPORT_API int CopyGain(void* p, double* o, int m) {
return copyVector(static_cast<CXXXProcessor*>(p), o, m,
[](CXXXProcessor* q)->const std::vector<double>&{return q->Gain; });
}
EXPORT_API int CopyEff(void* p, double* o, int m) {
return copyVector(static_cast<CXXXProcessor*>(p), o, m,
[](CXXXProcessor* q)->const std::vector<double>&{return q->Eff; });
}
}

这段代码是 XXXBridgeDLL.dll实现部分(C++源文件),与之前的头文件 XXXBridgeDLL.h 搭配使用,向外部程序提供接口来访问 XXX 仿真数据


文件头部设置

1
#define XXXBRIDGEDLL_EXPORTS
  • 定义此宏意味着当前正在编译 DLL,因此头文件中的 EXPORT_API 会变成 __declspec(dllexport),导出接口。

1
2
3
4
#include "XXXBridgeDLL.h"
#include "CXXXProcessor.h"
#include <algorithm>
#include <vector>
  • 引入头文件和 STL 库。CXXXProcessor 是处理器类,核心功能由它实现,接口只是封装。

匿名命名空间 copyVector

1
2
3
4
5
6
7
8
9
10
11
namespace {
template<typename SRC_GETTER>
int copyVector(CXXXProcessor* proc, double* out, int max, SRC_GETTER getter)
{
if (!proc || !out || max <= 0) return 0;
const auto& vec = getter(proc);
int n = std::min<int>(vec.size(), max);
std::copy(vec.begin(), vec.begin() + n, out);
return n;
}
}
  • 作用:通用的向 double* out 中复制数据的函数模板。
  • SRC_GETTER 是一个 Lambda,返回某个 vector<double> 的引用。
  • 使用匿名命名空间是为了限定作用域(防止链接冲突)。

导出接口(extern "C"

  1. CreateProcessor
1
2
3
EXPORT_API void* CreateProcessor() {
return new CXXXProcessor();
}
  • 返回新建的处理器对象(指针以 void* 形式传出,保持 C 接口风格)。
  1. DeleteProcessor
1
2
3
EXPORT_API void DeleteProcessor(void* p) {
delete static_cast<CXXXProcessor*>(p);
}
  • 释放 CreateProcessor() 创建的对象。
  1. ReadXXXDat
1
2
3
4
5
6
7
EXPORT_API int ReadXXXDat(void* p, const char* folder, int* pts) {
if (!p || !folder || !pts) return 0;
auto proc = static_cast<CXXXProcessor*>(p);
bool ok = proc->ReadXXXDat(folder);
*pts = ok ? static_cast<int>(proc->m_vdpX.size()) : 0;
return ok;
}
  • 调用 CXXXProcessor::ReadXXXDat(folder) 加载文件夹中的数据。
  • 加载成功后将数据点数写入 pts
  • m_vdpX 是轴向坐标数组。
  1. GetPointCount
1
2
3
EXPORT_API int GetPointCount(void* p) {
return p ? static_cast<int>(static_cast<CXXXProcessor*>(p)->m_vdpX.size()) : 0;
}
  • 返回总的数据点数(坐标数量)。
  1. CopyX_mm
1
2
3
4
5
6
7
EXPORT_API int CopyX_mm(void* p, double* out, int max) {
if (!p || !out || max <= 0) return 0;
auto proc = static_cast<CXXXProcessor*>(p);
int n = std::min<int>(proc->m_vdpX.size(), max);
for (int i = 0; i < n; ++i) out[i] = proc->m_vdpX[i] * 1000.0;
return n;
}
  • 将轴向坐标 m_vdpX(单位:米)转换为毫米(乘以 1000)并复制到 out 缓冲区。
  1. CopyPower, CopyGain, CopyEff
1
2
3
4
EXPORT_API int CopyPower(void* p, double* o, int m) {
return copyVector(static_cast<CXXXProcessor*>(p), o, m,
[](CXXXProcessor* q)->const std::vector<double>&{return q->PowerOut; });
}
  • 通过 copyVector 模板复制 PowerOut 数据。
1
2
3
4
EXPORT_API int CopyGain(void* p, double* o, int m) {
return copyVector(static_cast<CXXXProcessor*>(p), o, m,
[](CXXXProcessor* q)->const std::vector<double>&{return q->Gain; });
}
  • 复制增益(Gain)数据。
1
2
3
4
EXPORT_API int CopyEff(void* p, double* o, int m) {
return copyVector(static_cast<CXXXProcessor*>(p), o, m,
[](CXXXProcessor* q)->const std::vector<double>&{return q->Eff; });
}
  • 复制效率(Eff)数据。

模块用途概览

函数名 功能描述
CreateProcessor 创建处理器对象
DeleteProcessor 释放处理器对象
ReadXXXDat 从指定目录读取 XXX 数据文件
GetPointCount 获取总数据点数
CopyX_mm 获取轴向坐标(单位 mm)
CopyPower 获取输出功率(单位 W)
CopyGain 获取增益(单位 dB)
CopyEff 获取电子效率(单位 %)

编译成功后会在指定目录生成dll文件,把dll文件和导出函数头文件放到Matlab的工作目录下供其使用

Matlab及Simulink调用Dll

在Matlab中新建m文件,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
% 0. 环境与路径
dll = 'H:\Matlab\bin\XXXBridgeDLL.dll';
hdr = 'H:\Matlab\bin\XXXBridgeDLL.h';
dir = 'C:\Users\pc\Desktop\XXX';

if ~libisloaded('XXXBridgeDLL')
loadlibrary(dll,hdr);
end

p = calllib('XXXBridgeDLL','CreateProcessor');
nPt = libpointer('int32Ptr',0);
assert(calllib('XXXBridgeDLL','ReadXXXDat',p,dir,nPt)==1,'读取失败');
N = nPt.Value;

% --- 指针缓冲区 ---
xPtr = libpointer('doublePtr', zeros(N,1));
powPtr = libpointer('doublePtr', zeros(N,1));
gainPtr= libpointer('doublePtr', zeros(N,1));
effPtr = libpointer('doublePtr', zeros(N,1));

calllib('XXXBridgeDLL','CopyX_mm', p, xPtr, N);
calllib('XXXBridgeDLL','CopyPower',p, powPtr, N);
calllib('XXXBridgeDLL','CopyGain', p, gainPtr,N);
calllib('XXXBridgeDLL','CopyEff', p, effPtr ,N);

x = xPtr.Value;
pow = powPtr.Value;
gain = gainPtr.Value;
eff = effPtr.Value;

% --- 绘图 ---
figure;
subplot(3,1,1); plot(x,pow,'-b'); title('输出功率曲线');xlabel('z / mm');ylabel('W');
subplot(3,1,2); plot(x,gain,'-b'); title('增益曲线'); xlabel('z / mm');ylabel('dB');
subplot(3,1,3); plot(x,eff ,'-b'); title('效率曲线'); xlabel('z / mm');ylabel('%');

% --- 清理 ---
calllib('XXXBridgeDLL','DeleteProcessor',p);
unloadlibrary('XXXBridgeDLL');

这段 MATLAB 代码演示了如何通过调用 C++ 动态链接库 XXXBridgeDLL.dll 来读取 XXX 仿真数据,并绘制输出功率、增益和效率随轴向位置变化的曲线


1. 加载 DLL 与设置路径

1
2
3
4
5
6
7
dll = 'H:\Matlab\bin\XXXBridgeDLL.dll';
hdr = 'H:\Matlab\bin\XXXBridgeDLL.h';
dir = 'C:\Users\pc\Desktop\XXX';

if ~libisloaded('XXXBridgeDLL')
loadlibrary(dll,hdr);
end
  • 设置 DLL 文件路径、头文件路径和数据文件夹路径。
  • 如果 DLL 尚未加载,则调用 loadlibrary 加载 DLL 并注册其接口函数。

2. 创建处理器并读取数据

1
2
3
4
p   = calllib('XXXBridgeDLL','CreateProcessor');
nPt = libpointer('int32Ptr',0);
assert(calllib('XXXBridgeDLL','ReadXXXDat',p,dir,nPt)==1,'读取失败');
N = nPt.Value;
  • 创建处理器实例 p(C++ 对象的指针)。
  • 申请一个 int32 类型的指针 nPt 用于返回数据点数量。
  • 调用 ReadXXXDatdir 文件夹读取仿真数据。
  • 将数据点个数保存在 N 中。

3. 分配输出缓冲区并复制数据

1
2
3
4
5
6
7
8
9
xPtr   = libpointer('doublePtr', zeros(N,1));
powPtr = libpointer('doublePtr', zeros(N,1));
gainPtr= libpointer('doublePtr', zeros(N,1));
effPtr = libpointer('doublePtr', zeros(N,1));

calllib('XXXBridgeDLL','CopyX_mm', p, xPtr, N);
calllib('XXXBridgeDLL','CopyPower',p, powPtr, N);
calllib('XXXBridgeDLL','CopyGain', p, gainPtr,N);
calllib('XXXBridgeDLL','CopyEff', p, effPtr ,N);
  • 为四类数据创建指针缓冲区,长度为 N
  • 通过调用 DLL 中的接口函数将数据从 C++ 内部复制到 MATLAB 缓冲区中。

4. 将指针值转换为 MATLAB 向量

1
2
3
4
x    = xPtr.Value;
pow = powPtr.Value;
gain = gainPtr.Value;
eff = effPtr.Value;
  • 将指针对象中的数据取出,转换为 MATLAB 可用的数组。

5. 绘制仿真结果曲线

1
2
3
4
figure;
subplot(3,1,1); plot(x,pow,'-b'); title('输出功率曲线');xlabel('z / mm');ylabel('W');
subplot(3,1,2); plot(x,gain,'-b'); title('增益曲线'); xlabel('z / mm');ylabel('dB');
subplot(3,1,3); plot(x,eff ,'-b'); title('效率曲线'); xlabel('z / mm');ylabel('%');
  • 打开一个新图像窗口,将三种数据绘制在三个子图中:
    • 输出功率 vs. 坐标
    • 增益 vs. 坐标
    • 效率 vs. 坐标

6. 清理资源

1
2
calllib('XXXBridgeDLL','DeleteProcessor',p);
unloadlibrary('XXXBridgeDLL');
  • 释放处理器对象 p
  • 卸载 DLL,释放系统资源。

这段代码实现了:

  1. 加载 DLL 并创建处理器。
  2. 调用 C++ 函数读取 XXX 仿真数据。
  3. 将结果复制到 MATLAB 并绘图。
  4. 使用完后清理资源,避免内存泄漏。

运行效果如下

image-20250723111646923

与求解工具链接

现在的可视化模块只是通过硬编码的方式读取指定文件,下面需要把求解工具和可视化链接,通过求解工具输出的参数来进行泛型可视化

修改现有的调用求解工具S Function代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
function sfun_callXXX(block)
setup(block);
end

% ─────────────────── SETUP ────────────────────
function setup(block)
%— I/O 配置 —————————————————————————————————————————————
block.NumInputPorts = 1;
block.NumOutputPorts = 1;
block.InputPort(1).Dimensions = 1;
block.InputPort(1).DatatypeID = 0; % double
block.InputPort(1).DirectFeedthrough = true;
block.OutputPort(1).Dimensions = 1;
block.OutputPort(1).DatatypeID = 0;

%— SampleTime 配置 ———————————————————————
block.SampleTimes = [0.5 0]; % 0.5 s 轮询
block.SimStateCompliance = 'DefaultSimState';

%— 注册方法 —————————————————————————
block.RegBlockMethod('PostPropagationSetup', @PostProp);
block.RegBlockMethod('InitializeConditions', @InitCond);
block.RegBlockMethod('Outputs', @Outputs);
block.RegBlockMethod('Terminate', @Terminate);

%— 设置仿真时间为无限,直到进程完成 —————
set_param(bdroot(block.BlockHandle), 'StopTime', 'inf');
end

% ─── PostPropagationSetup:声明 DWork ───
function PostProp(block)
block.NumDworks = 3;
names = {'prevU', 'exeState', 'pid'};
for k = 1:3
block.Dwork(k).Name = names{k};
block.Dwork(k).Dimensions = 1;
block.Dwork(k).DatatypeID = 0;
block.Dwork(k).Complexity = 'Real';
block.Dwork(k).UsedAsDiscState = true;
end
end

% ─── 初始化 ───
function InitCond(block)
block.Dwork(1).Data = 0;
block.Dwork(2).Data = 0;
block.Dwork(3).Data = 0;
end

% ─────────────────── Outputs ───────────────────
function Outputs(block)
u = block.InputPort(1).Data;
prevU = block.Dwork(1).Data;
exeState = block.Dwork(2).Data;
pid = block.Dwork(3).Data;

%— 持久变量:进程句柄 —————————————
persistent procObj
if isempty(procObj), procObj = []; end

%— 配置区:路径 —————————
exePath = '.\Solver\XXX.exe';
paramPath = 'C:\Users\pc\Desktop\XXX';

%— ① Rising-edge → 启动 EXE —————————
if u == 1 && prevU == 0 && exeState == 0
[ok, procObj, pid, exeState] = launchExe(exePath, paramPath);
if ok
fprintf('[XXX] PID=%d 已启动\n', pid);
else
exeState = -1; % 启动失败
end
end

%— ② 运行中 → 轮询进程是否退出 —————
if exeState == 1
[exeState, pid] = pollProcessExitStatus(procObj, pid);
if exeState == 2 || exeState == -1
stopSimulation(block);
end
end

%— ③ 输出 & 保存 —————————
block.OutputPort(1).Data = exeState;
block.Dwork(1).Data = u;
block.Dwork(2).Data = exeState;
block.Dwork(3).Data = pid;
end

% ─── Terminate:处理进程退出 —──
function Terminate(~)
persistent procObj
if ~isempty(procObj)
try
terminateProcess(procObj);
catch
% Ignore if the process has already been terminated
end
end
end

% ─── helper:启动进程 ───
function [ok, proc, pid, exeState] = launchExe(exePath, paramPath)
ok = false; proc = []; pid = 0; exeState = 0;
try
if exist(exePath, 'file') ~= 2
warning('[XXX] EXE 不存在: %s', exePath); return; end
if exist(paramPath, 'dir') ~= 7
warning('[XXX] 参数路径不存在: %s', paramPath); return; end

argStr = ['1|1|' paramPath]; % 启动参数
proc = System.Diagnostics.Process();
info = proc.StartInfo;
info.FileName = exePath;
info.Arguments = argStr;
info.UseShellExecute = false;
info.CreateNoWindow = true;
ok = proc.Start();
if ok
pid = double(proc.Id);
exeState = 1; % 运行中
end
catch ME
warning('[XXX] 启动 EXE 异常: %s', E.message);
end
end

% ─── helper:轮询进程退出状态 ───
function [exeState, pid] = pollProcessExitStatus(procObj, pid)
finished = false;
try
procObj.Refresh();
finished = procObj.HasExited;
if ~finished
finished = procObj.WaitForExit(50); % 等待 50 ms
end
catch
finished = ~isProcAlive(pid);
end

if finished
exitC = getExitCodeSafe(procObj);
exeState = (exitC == 0) * 2 + (exitC ~= 0) * (-1);
pid = 0;
else
exeState = 1; % Running
end
end

% ─── helper:检查 PID 是否仍存活 ───
function alive = isProcAlive(pid)
alive = false;
if pid <= 0, return; end
try
pchk = System.Diagnostics.Process.GetProcessById(pid);
alive = ~pchk.HasExited;
catch
% Process not found
end
end

% ─── helper:读取 ExitCode ───
function code = getExitCodeSafe(procObj)
code = -1;
try code = procObj.ExitCode; catch, end
end

% ─── helper:终止进程 ───
function terminateProcess(procObj)
if ~procObj.HasExited
procObj.Kill();
end
procObj.Dispose();
end

% ─── helper:停止仿真 ───
function stopSimulation(block)
set_param(bdroot(block.BlockHandle), 'SimulationCommand', 'stop');
end

这段代码是一个 Level-2 MATLAB S-Function,用于在 Simulink 中检测输入信号的 上升沿(从0变为1),然后异步启动一个外部 EXE 程序(XXX.exe),并轮询进程状态,完成后自动停止仿真

步骤 说明
输入信号从 0 → 1,触发启动 XXX.exe
启动进程,记录 PID 和运行状态
每 0.5 秒轮询一次,判断进程是否结束
若 EXE 正常退出,则输出状态为 2,仿真停止;否则输出 -1 并停止
清理进程资源,防止残留

调用工具代码解释

  1. 顶部函数入口
1
2
3
function sfun_callXXX(block)
setup(block);
end
  • S-Function 的主函数入口,调用 setup() 注册接口。
  1. setup(block):配置端口、采样、方法等
1
2
3
4
5
6
block.NumInputPorts  = 1;  % 输入1个信号(控制启动)
block.NumOutputPorts = 1; % 输出当前状态(0, 1, 2, -1)

block.SampleTimes = [0.5 0]; % 每 0.5 秒执行一次
block.RegBlockMethod(...) % 注册各阶段的回调函数
set_param(..., 'StopTime', 'inf'); % 设置仿真不主动终止
  1. PostProp:声明离散状态(DWork)
1
2
block.NumDworks = 3;
names = {'prevU', 'exeState', 'pid'};
  • prevU:上一时刻输入值(检测上升沿用)
  • exeState:执行状态(0=未启动,1=运行中,2=正常退出,-1=错误)
  • pid:外部进程 PID
  1. InitCond:初始化 DWork 数据
1
2
3
block.Dwork(1).Data = 0;  % 上一时刻输入值
block.Dwork(2).Data = 0; % exeState
block.Dwork(3).Data = 0; % pid
  1. Outputs(block):主逻辑处理区
1
u = block.InputPort(1).Data;

上升沿触发:启动外部 EXE

1
2
3
if u == 1 && prevU == 0 && exeState == 0
[ok, procObj, pid, exeState] = launchExe(...);
end

运行中:轮询状态(每次采样调用)

1
2
3
4
5
6
if exeState == 1
[exeState, pid] = pollProcessExitStatus(...);
if exeState == 2 || exeState == -1
stopSimulation(block); % 进程完成 → 停止仿真
end
end

输出当前状态并保存状态数据

1
block.OutputPort(1).Data = exeState;
  1. Terminate:终止残留进程(防挂后台)
1
2
3
if ~isempty(procObj)
terminateProcess(procObj);
end

内部 helper 函数说明

launchExe(exePath, paramPath)

  • 启动 XXX.exe
  • 参数格式是 '1|1|参数路径'
  • 返回是否成功、进程对象、PID、状态

pollProcessExitStatus(procObj, pid)

  • 检查进程是否已退出
  • 如果异常,也尝试通过 PID 判断
  • 根据 ExitCode 判断:
    • 0 → 正常结束(返回状态 2
    • 0 → 异常(状态 -1

isProcAlive(pid)

  • 尝试通过 PID 检查进程是否还活着

terminateProcess(procObj)

  • 如果进程还在运行,则强制结束并释放资源

stopSimulation(block)

  • 调用 Simulink 命令强制停止整个仿真

状态码含义总结

状态码 含义
0 初始未启动
1 正在运行 EXE
2 EXE 正常退出
-1 启动或执行异常

修改M文件

将调用dll的Matlab代码改为S Function的方式,并通过1→2来进行触发,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
function sfun_callXXXProcess(block)
setup(block);
end

% ─────────────────── SETUP ────────────────────
function setup(block)
block.NumInputPorts = 1;
block.NumOutputPorts = 0;
block.InputPort(1).Dimensions = 1;
block.InputPort(1).DatatypeID = 0; % double
block.InputPort(1).Complexity = 'Real';
block.InputPort(1).DirectFeedthrough = true;

block.SampleTimes = [0.5 0]; % 0.5 s 轮询
block.SimStateCompliance = 'DefaultSimState';

block.RegBlockMethod('PostPropagationSetup', @PostProp);
block.RegBlockMethod('InitializeConditions', @InitCond);
block.RegBlockMethod('Outputs', @Outputs);
block.RegBlockMethod('Terminate', @Terminate);
end

% ─── PostPropagationSetup:声明 DWork ───
function PostProp(block)
block.NumDworks = 1; % prevU
block.Dwork(1).Name = 'prevU';
block.Dwork(1).Dimensions = 1;
block.Dwork(1).DatatypeID = 0;
block.Dwork(1).Complexity = 'Real';
block.Dwork(1).UsedAsDiscState = true;
end

% ─── 初始化 ───
function InitCond(block)
block.Dwork(1).Data = 0;
end

% ─────────────────── Outputs ───────────────────
function Outputs(block)
u = block.InputPort(1).Data;
prevU = block.Dwork(1).Data;

% 如果脉冲信号为2,则开始执行数据读取
if u == 2 && prevU ~= 2
% --- 读取数据并绘图 ---
dll = '.\bin\XXXBridgeDLL.dll';
hdr = '.\bin\XXXBridgeDLL.h';
dir = 'C:\Users\pc\Desktop\XXX';

if ~libisloaded('XXXBridgeDLL')
loadlibrary(dll, hdr);
end

p = calllib('XXXBridgeDLL', 'CreateProcessor');
nPt = libpointer('int32Ptr', 0);
assert(calllib('XXXBridgeDLL', 'ReadXXXDat', p, dir, nPt) == 1, '读取失败');
N = nPt.Value;

% --- 指针缓冲区 ---
xPtr = libpointer('doublePtr', zeros(N, 1));
powPtr = libpointer('doublePtr', zeros(N, 1));
gainPtr= libpointer('doublePtr', zeros(N, 1));
effPtr = libpointer('doublePtr', zeros(N, 1));

calllib('XXXBridgeDLL', 'CopyX_mm', p, xPtr, N);
calllib('XXXBridgeDLL', 'CopyPower', p, powPtr, N);
calllib('XXXBridgeDLL', 'CopyGain', p, gainPtr, N);
calllib('XXXBridgeDLL', 'CopyEff', p, effPtr, N);

x = xPtr.Value;
pow = powPtr.Value;
gain = gainPtr.Value;
eff = effPtr.Value;

% --- 绘图 ---
figure;
subplot(3, 1, 1);
plot(x, pow, '-b');
title('输出功率曲线');
xlabel('z / mm');
ylabel('W');

subplot(3, 1, 2);
plot(x, gain, '-b');
title('增益曲线');
xlabel('z / mm');
ylabel('dB');

subplot(3, 1, 3);
plot(x, eff, '-b');
title('效率曲线');
xlabel('z / mm');
ylabel('%');

% --- 清理 ---
calllib('XXXBridgeDLL', 'DeleteProcessor', p);
unloadlibrary('XXXBridgeDLL');
end

% 保存当前脉冲值
block.Dwork(1).Data = u;
end

% ─── Terminate:处理退出 —──
function Terminate(~)
end

这段代码实现了一个 MATLAB Level-2 S-Function,主要功能是当输入信号为 2 时触发外部 DLL(XXXBridgeDLL.dll)的数据读取和处理,之后进行数据的可视化。

总体功能流程

  1. 输入信号:该 S-Function 监听一个输入信号 u
  2. 脉冲触发:当信号 u == 2 且前一时刻 prevU != 2 时,触发读取数据。
  3. 调用 DLL:加载外部 DLL (XXXBridgeDLL.dll),读取 XXX 仿真数据。
  4. 数据处理与绘图:读取坐标、功率、增益和效率数据,并绘制图形。
  5. 清理资源:在操作完成后,清理 DLL 资源。

代码分解

  1. sfun_callXXXProcess(block)
1
2
3
function sfun_callXXXProcess(block)
setup(block);
end
  • 这是 S-Function 的入口函数,调用 setup(block) 设置仿真模块的基本配置。
  1. setup(block): 配置端口、采样时间与方法
1
2
3
4
5
6
7
8
9
10
11
12
function setup(block)
block.NumInputPorts = 1; % 一个输入端口
block.NumOutputPorts = 0; % 无输出端口

block.SampleTimes = [0.5 0]; % 每 0.5 秒触发一次
block.SimStateCompliance = 'DefaultSimState'; % 默认的仿真状态兼容性

block.RegBlockMethod('PostPropagationSetup', @PostProp);
block.RegBlockMethod('InitializeConditions', @InitCond);
block.RegBlockMethod('Outputs', @Outputs);
block.RegBlockMethod('Terminate', @Terminate);
end
  • 配置输入端口和采样时间(每 0.5 秒触发一次)。
  • 注册方法:PostPropagationSetupInitializeConditionsOutputsTerminate
  1. PostProp(block): 声明 DWork 状态变量
1
2
3
4
5
6
7
8
function PostProp(block)
block.NumDworks = 1;
block.Dwork(1).Name = 'prevU'; % 存储上一时刻的输入信号
block.Dwork(1).Dimensions = 1;
block.Dwork(1).DatatypeID = 0; % double 类型
block.Dwork(1).Complexity = 'Real';
block.Dwork(1).UsedAsDiscState = true;
end
  • 声明一个名为 prevU 的 DWork 变量,用于保存前一时刻的输入信号值。
  1. InitCond(block): 初始化 DWork 数据
1
2
3
function InitCond(block)
block.Dwork(1).Data = 0; % 初始化 `prevU` 为 0
end
  • 初始化时,prevU 设置为 0,表示初始状态。
  1. Outputs(block): 主逻辑处理区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
function Outputs(block)
u = block.InputPort(1).Data; % 获取当前输入信号
prevU = block.Dwork(1).Data; % 获取前一时刻的输入信号

if u == 2 && prevU ~= 2
% 读取 XXX 数据
dll = '.\bin\XXXBridgeDLL.dll';
hdr = '.\bin\XXXBridgeDLL.h';
dir = 'C:\Users\pc\Desktop\XXX';

if ~libisloaded('XXXBridgeDLL')
loadlibrary(dll, hdr); % 加载 DLL
end

p = calllib('XXXBridgeDLL', 'CreateProcessor'); % 创建处理器对象
nPt = libpointer('int32Ptr', 0);
assert(calllib('XXXBridgeDLL', 'ReadXXXDat', p, dir, nPt) == 1, '读取失败'); % 读取数据
N = nPt.Value; % 获取数据点数量

% 初始化指针缓冲区
xPtr = libpointer('doublePtr', zeros(N, 1));
powPtr = libpointer('doublePtr', zeros(N, 1));
gainPtr= libpointer('doublePtr', zeros(N, 1));
effPtr = libpointer('doublePtr', zeros(N, 1));

% 从 DLL 中复制数据到指针
calllib('XXXBridgeDLL', 'CopyX_mm', p, xPtr, N);
calllib('XXXBridgeDLL', 'CopyPower', p, powPtr, N);
calllib('XXXBridgeDLL', 'CopyGain', p, gainPtr, N);
calllib('XXXBridgeDLL', 'CopyEff', p, effPtr, N);

% 获取数据
x = xPtr.Value;
pow = powPtr.Value;
gain = gainPtr.Value;
eff = effPtr.Value;

% 绘图
figure;
subplot(3, 1, 1);
plot(x, pow, '-b');
title('输出功率曲线');
xlabel('z / mm');
ylabel('W');

subplot(3, 1, 2);
plot(x, gain, '-b');
title('增益曲线');
xlabel('z / mm');
ylabel('dB');

subplot(3, 1, 3);
plot(x, eff, '-b');
title('效率曲线');
xlabel('z / mm');
ylabel('%');

% 清理资源
calllib('XXXBridgeDLL', 'DeleteProcessor', p);
unloadlibrary('XXXBridgeDLL');
end

% 保存当前脉冲值
block.Dwork(1).Data = u;
end
  • u == 2 时,调用 XXXBridgeDLL.dll 进行数据读取,获取 x(坐标)、pow(功率)、gain(增益)和 eff(效率)数据。
  • 使用 figure 绘制三张子图,显示输出功率、增益和效率随坐标的变化。
  • 在操作完成后,清理 DLL 资源。
  1. Terminate(~): 清理
1
2
function Terminate(~)
end
  • 在仿真结束时,Terminate 函数被调用,但在这个实现中没有进行额外操作。

工作流程

  1. 输入信号:当输入信号为 2 时,触发外部 EXE 进程。
  2. 调用 DLL:通过 XXXBridgeDLL.dll 读取 XXX 仿真数据。
  3. 数据处理与绘图:读取数据并绘制输出功率、增益和效率曲线。
  4. 清理:仿真结束后清理 DLL 资源。

运行示例

20250723-113309