介绍

本文主要对之前的Simulink工作进行优化,前面通过S Function一共实现了三个模块,分别为参数交互模块、调用求解模块和数据展示模块。但后续两个模块的代码中都采用了硬编码的方式来指定文件夹的路径,事实上后续两个模块的文件夹路径与第一个模块选中的文件夹是同一个路径,因此为了减少因为硬编码而导致的路径错误,我们将修改代码,把第一个模块选中的文件夹传递给后续的模块,这里将会使用Simulink的封装功能

如何传递模块参数

核心问题:传递文件夹路径

目前,sfun_callXXX.msfun_callXXXProcess.m 中的文件路径是硬编码的,这降低了灵活性。

1
2
3
4
5
% sfun_callXXX.m
paramPath = 'C:\Users\pc\Desktop\XXX_互作用-helix-非线性特征仿真实例\注波互作用1\指定输入';

% sfun_callXXXProcess.m
dir = 'C:\Users\pc\Desktop\XXX_互作用-helix-非线性特征仿真实例\注波互作用1\指定输入';

最佳的解决方案是利用 Simulink 的 Mask Parameters (模块参数) 来接收路径,然后由第一个 S-Function (sfun_callInterface) 在用户确认路径后,通过编程方式设置这些参数。

  1. 在 Simulink 模型中,右键点击 sfun_callXXX 模块,选择 Mask > Create Mask
  2. 在 Mask Editor 窗口中,进入 Parameters & Dialog 选项卡。
  3. 在左侧面板添加一个 Edit 参数。
  4. 在右侧的 Parameter Properties 中,设置:
    • Prompt: 求解器参数路径
    • Name: paramPath
    • Evaluate:取消勾选
  5. 点击 OKApply 保存 Mask。
  6. sfun_callXXXProcess 模块重复以上步骤,但将参数 Name 设置为 dataDir
  7. 再次右键点击该模块,选择 Block Parameters (S-function)
  8. 在打开的对话框中,找到一个名为 S-function parameters 的输入框。
  9. 在这个输入框里,填入你的 变量名 (paramPathdataDir)。
  10. 点击 OKApply
  11. 为另一个 S-Function 模块重复上述步骤。

image-20250827191731798

这个操作的目的是建立 Mask 和 S-Function 代码之间的连接。当你在 Mask 界面输入路径时,该路径字符串会存入名为 paramPath 的变量中。然后,通过将 paramPath填入 “S-function parameters” 字段,这个变量的值就被传递给了 S-Function 内部,代码 block.DialogPrm(1).Data 才能成功地接收到这个路径。

现在,修改这两个 S-Function,让它们从刚刚创建的 Mask Parameter 中读取路径,而不是使用硬编码的字符串。

修改 sfun_callXXX.m:

  1. setup 函数中,声明该模块有一个 Mask Parameter。

    1
    2
    3
    4
    5
    function setup(block)
    % ... (I/O 配置)
    block.NumDialogPrms = 1; % <--- 添加这一行
    % ... (SampleTime 等其他配置)
    end
  2. Outputs 函数中,用 block.DialogPrm(1).Data 替换硬编码的路径。

    1
    2
    3
    4
    5
    6
    7
    8
    function Outputs(block)
    % ...
    %— 配置区:路径 —————————
    exePath = '.\Solver\XXX.exe';
    % paramPath = 'C:\Users\pc\Desktop\...'; % <--- 删除或注释掉这一行
    paramPath = block.DialogPrm(1).Data; % <--- 添加这一行
    % ...
    end

修改 sfun_callXXXProcess.m:

  1. 同样,在 setup 函数中声明 Mask Parameter。

    1
    2
    3
    4
    5
    6
    function setup(block)
    % ...
    block.NumDialogPrms = 1; % <--- 添加这一行
    block.SampleTimes = [0.5 0];
    % ...
    end
  2. Outputs 函数中,替换硬编码的路径。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function Outputs(block)
    % ...
    if u == 2 && prevU ~= 2
    % --- 读取数据并绘图 ---
    dll = '.\bin\BWIBridgeDLL.dll';
    hdr = '.\bin\BWIBridgeDLL.h';
    % dir = 'C:\Users\pc\Desktop\...'; % <--- 删除或注释掉这一行
    dir = block.DialogPrm(1).Data; % <--- 添加这一行
    % ...
    end
    % ...
    end

最后,修改第一个 S-Function 的 GUI 确认回调函数 (confirm_action),让它在用户点击“确认”时,自动将选定的文件夹路径写入另外两个模块的 Mask Parameter。

为此,需要在 Simulink 模型中为 sfun_callXXXsfun_callXXXProcess 模块指定一个清晰的、可编程访问的名称。例如,将它们分别命名为 XXX_LauncherXXX_Processor。如图所示

image-20250827190519339

修改 sfun_callInterface.m 中的 confirm_action 函数:

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
% sfun_callInterface.m

function confirm_action(fig)
% ... (前面的代码保持不变)

% --- 新增代码:获取模型名称和设置其他模块的参数 ---
try
blockHandle = fig.UserData.blockHandle;
modelName = bdroot(blockHandle); % 获取顶层模型的名称

% 设置 sfun_callXXX 模块的路径参数
set_param([modelName, '/XXX_Launcher'], 'paramPath', folderPath);

% 设置 sfun_callXXXProcess 模块的路径参数
set_param([modelName, '/XXX_Processor'], 'dataDir', folderPath);

disp('已成功将路径参数传递到后续模块。');
catch ME
uialert(fig, ['无法设置后续模块的路径参数: ', ME.message], '参数传递错误');
% 释放处理器并返回,防止在出错时继续执行
calllib('BWIBridgeDLL', 'DeleteProcessor', proc);
return;
end
% --- 新增代码结束 ---

% ... (释放处理器、设置 confirmationFlag 和关闭 GUI 的代码保持不变)

% 释放处理器
calllib('BWIBridgeDLL', 'DeleteProcessor', proc);

% 在基础工作空间中设置确认标志为 1
assignin('base', 'confirmationFlag', 1);

% 关闭 GUI 窗口
delete(fig);
end

小结: 通过以上三步,完成了下述工作:

  1. sfun_callInterface 的 GUI 捕获用户选择的路径。
  2. 当用户点击“确认”时,该模块通过 set_param 函数,将路径动态地写入 XXX_LauncherXXX_Processor 模块的 Mask 中。
  3. 这两个模块在运行时,从自己的 Mask Parameter 中读取路径,从而实现了动态配置,完全避免了硬编码。

注意:上述代码仍会出现问题,具体来说是modelName无法被识别,后续我将附上全部修改代码,这里不做展示


加载/卸载Dll的优化

此时多个模块正在加载和卸载同一个DLL (BWIBridgeDLL.dll)。这种做法效率低下,并且可以被简化。最佳实践是在整个仿真生命周期内将DLL作为共享资源进行管理:在仿真开始时加载一次,在仿真终止时卸载一次。

1. 修改 sfun_callInterface.m

该模块应负责加载DLL,并确保它在仿真期间保持加载状态。

close_gui 函数中,删除卸载DLL的那一行代码。GUI不再需要管理DLL的卸载,这个任务将交给仿真的终止阶段来处理。

原始代码 (sfun_callInterface.m):

1
2
3
4
5
6
7
8
function close_gui(fig)
% 卸载 DLL 如果已加载
if isfield(fig.UserData, 'dllLoaded') && fig.UserData.dllLoaded && libisloaded('BWIBridgeDLL')
unloadlibrary('BWIBridgeDLL');
end
% 清理其他资源如果需要
delete(fig);
end

建议修改:

1
2
3
4
5
function close_gui(fig)
% DLL 不再在这里卸载。它将由仿真流程中最后一个S-Function模块的
% Terminate 函数来处理。
delete(fig);
end

2. 修改 sfun_callXXXProcess.m

该模块应负责在仿真结束时卸载DLL。其 Outputs 函数也应简化,直接使用已加载的库。

a. 在 Outputs 函数中:

移除 loadlibrary 和 unloadlibrary 调用。现在代码应该假设DLL已经被第一个模块加载了。为保险起见,可以添加一个 libisloaded 检查。

原始代码 (sfun_callXXXProcess.m):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Outputs(block)
% ... (前面的代码) ...
if u == 2 && prevU ~= 2
% --- 读取数据并绘图 ---
dll = '.\bin\BWIBridgeDLL.dll';
hdr = '.\bin\BWIBridgeDLL.h';
dir = block.DialogPrm(1).Data;

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

% ... (调用库的逻辑) ...

% --- 清理 ---
calllib('BWIBridgeDLL', 'DeleteProcessor', p);
unloadlibrary('BWIBridgeDLL');
end
% ... (剩余的代码) ...
end

建议修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Outputs(block)
u = block.InputPort(1).Data;
prevU = block.Dwork(1).Data;

if u == 2 && prevU ~= 2
% 为保险起见,检查库是否已加载
if ~libisloaded('BWIBridgeDLL')
fprintf('错误: BWIBridgeDLL 未被加载。仿真可能未从 sfun_callInterface 正常启动。\n');
return;
end

dir = block.DialogPrm(1).Data;

p = calllib('BWIBridgeDLL', 'CreateProcessor');
% ... (其余逻辑保持不变) ...

% --- 清理 ---
calllib('BWIBridgeDLL', 'DeleteProcessor', p);
% unloadlibrary 调用已从此函数中移除。
end

block.Dwork(1).Data = u;
end

b. 在 Terminate 函数中:

此函数在仿真停止时会执行一次。这是卸载DLL的理想位置。

原始代码 (sfun_callXXXProcess.m):

1
2
function Terminate(~)
end

建议修改:

1
2
3
4
5
6
function Terminate(~)
fprintf('[XXX] Simulation terminated. Unloading BWIBridgeDLL.\n');
if libisloaded('BWIBridgeDLL')
unloadlibrary('BWIBridgeDLL');
end
end

经过修改之后,我们的代码理论上只会在仿真开始时加载Dll,在仿真结束后卸载Dll,避免中间过程对Dll的反复调用

隐含问题

在sfun_callXXX.m的代码中,还存在着一个潜在的重要问题,我们使用了如下代码

1
2
%— 持久变量:进程句柄 —————————————
persistent procObj

这是一个持久变量,用来检测进程的状态,当我们使用了不止于一个sfun_callXXX模块时,所有模块将共享这个变量,如果我们在第一个模块中启动一个.exe进程,并将其句柄存入 procObj。紧接着,第二个模块启动另一个.exe进程,并将其句柄覆盖到同一个 procObj 变量中。此时,第一个进程的句柄就丢失了。系统将无法再轮询或终止第一个进程,从而导致逻辑错误和进程失控。简单来说,当前的设计只适用于模型中只有一个XXX_Launcher模块的情况。一旦需要多个,就会立即出错。

目前这个问题还没有解决,后续如果遇到了,会在该系列文章中提供解决方案

最后附上全部代码

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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
function sfun_callInterface(block)
% 初始化 S-Function
setup(block);
end

% ─────────────────── SETUP ────────────────────
function setup(block)
% I/O 配置(没有输入端口)
block.NumInputPorts = 0;
block.NumOutputPorts = 1;
block.OutputPort(1).Dimensions = 1;
block.OutputPort(1).DatatypeID = 0; % 输出一个常数,作为标志位

% 明确设置输出端口的采样模式(设置为样本数据)
block.OutputPort(1).SamplingMode = 'Sample'; % FRAME_NO: 样本数据

% SampleTime 配置
block.SampleTimes = [0 0]; % 立即执行,停止仿真时使用
block.SimStateCompliance = 'DefaultSimState';

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

% 设置 OpenFcn 触发 GUI
set_param(block.BlockHandle, 'OpenFcn', 'start_import_gui');
% 这里的双击回调函数并不能正常使用,如要使用,可能要把start_import_gui及相关代码作为独立的文件和函数
end

% ─── PostPropagationSetup:声明 DWork ───
function PostProp(block)
block.NumDworks = 3;

% Dwork 1: 用于存储 GUI 是否已启动的标志
block.Dwork(1).Name = 'guiLaunched';
block.Dwork(1).Dimensions = 1;
block.Dwork(1).DatatypeID = 0;
block.Dwork(1).Complexity = 'Real';
block.Dwork(1).UsedAsDiscState = true;

% Dwork 2: 用于存储确认标志
block.Dwork(2).Name = 'confirmationFlag';
block.Dwork(2).Dimensions = 1;
block.Dwork(2).DatatypeID = 0;
block.Dwork(2).Complexity = 'Real';
block.Dwork(2).UsedAsDiscState = true;

% Dwork 3: 用于存储是否已尝试启动仿真的标志(防止多次调用)
block.Dwork(3).Name = 'simStarted';
block.Dwork(3).Dimensions = 1;
block.Dwork(3).DatatypeID = 0;
block.Dwork(3).Complexity = 'Real';
block.Dwork(3).UsedAsDiscState = true;
end

% ─── InitializeConditions:初始化 DWork ───
function InitCond(block)
% 每次仿真开始时重置标志
block.Dwork(1).Data = 0; % GUI 未启动
block.Dwork(2).Data = 0; % 确认标志为 0
block.Dwork(3).Data = 0; % 未启动仿真
end

% ─── Outputs ───
function Outputs(block)
% 获取当前状态
guiLaunched = block.Dwork(1).Data;
confirmationFlag = block.Dwork(2).Data;
simStarted = block.Dwork(3).Data;

% 如果 GUI 尚未启动,则启动 GUI
if guiLaunched == 0

% --- 确保 modelName 永远是字符串 ---
rootSystem = bdroot(block.BlockHandle); % 获取顶层模型,结果可能是句柄或名称

modelName = ''; % 初始化

if isnumeric(rootSystem) && ishandle(rootSystem)
% 如果返回的是数字句柄,则通过句柄获取名称
modelName = get_param(rootSystem, 'Name');
elseif ischar(rootSystem)
% 如果返回的是字符串名称,则直接使用
modelName = rootSystem;
end

% 添加一个安全检查,如果获取名称失败则报错并停止
if isempty(modelName)
errordlg('无法获取 Simulink 模型名称,GUI 无法启动。', '严重错误');
% 在这种严重错误下,停止仿真可能更安全
set_param(bdroot(block.BlockHandle), 'SimulationCommand', 'stop');
return; % 提前退出函数
end
% --- 修复结束 ---

start_import_gui(modelName);
block.Dwork(1).Data = 1;
end

% 尝试从基础工作空间获取确认状态
try
confirmationFlag = evalin('base', 'confirmationFlag');
block.Dwork(2).Data = confirmationFlag;
catch
% 如果变量不存在,保持当前值
end

% 输出确认标志
block.OutputPort(1).Data = confirmationFlag;

% 如果确认标志是 1,且尚未启动仿真,则自动运行仿真
if confirmationFlag == 1 && simStarted == 0
set_param(bdroot(block.BlockHandle), 'SimulationCommand', 'start');
block.Dwork(3).Data = 1;
end

% 如果确认标志是 -1,表示取消,停止仿真
if confirmationFlag == -1
try
simStatus = get_param(bdroot(block.BlockHandle), 'SimulationStatus');
if strcmp(simStatus, 'running')
set_param(bdroot(block.BlockHandle), 'SimulationCommand', 'stop');
disp('仿真已取消');
end
catch
disp('未能检查到正在运行的仿真,或仿真未启动');
end
end
end

% ─── Terminate:处理进程退出 ───
function Terminate(~)
% 清理基础工作空间中的标志变量
evalin('base', 'clear confirmationFlag');
end

function start_import_gui(modelName)
% 获取屏幕大小
screenSize = get(0, 'ScreenSize');
screenWidth = screenSize(3);
screenHeight = screenSize(4);

% 设置图形界面大小
figWidth = 800;
figHeight = 400;

% 计算居中显示的坐标
figPosX = (screenWidth - figWidth) / 2;
figPosY = (screenHeight - figHeight) / 2;

% 创建图形界面(窗口),居中显示
fig = uifigure('Name', '数据导入与显示', 'Position', [figPosX, figPosY, figWidth, figHeight], ...
'CloseRequestFcn', @(src, event) close_gui(fig));

% 存储块句柄在图形对象的 UserData 中
fig.UserData.modelName = modelName;

% 加载 DLL(在 GUI 启动时加载一次)
dll = fullfile(pwd, 'bin', 'BWIBridgeDLL.dll');
hdr = fullfile(pwd, 'bin', 'BWIBridgeDLL.h');
if ~libisloaded('BWIBridgeDLL')
loadlibrary(dll, hdr);
end
fig.UserData.dllLoaded = true; % 标记 DLL 已加载

% 创建按钮来导入数据
btnImport = uibutton(fig, 'push', 'Text', '导入数据', 'Position', [680, 360, 100, 30], ...
'ButtonPushedFcn', @(src, event) import_data(fig));

% 创建一个uitable用于显示数据
uit = uitable(fig, 'Position', [20, 60, 760, 280], ...
'ColumnName', {'变量名', '数值', '最小值', '最大值', '说明'}, ...
'Data', {}, 'ColumnEditable', [false true false false false]);
uit.Tag = 'dataTable'; % 设置标签,方便后续操作

% 用于保存数据的临时变量(移到 fig.UserData)
fig.UserData.data = [];
fig.UserData.originalData = []; % 用于存储原始数据

% 创建一个TextArea来显示文件夹路径
filePathLabel = uilabel(fig, 'Text', '选择的文件夹路径:', 'Position', [20, 360, 100, 30]);
filePathTextArea = uitextarea(fig, 'Position', [130, 360, 530, 30], 'Tag', 'filePathTextArea');
filePathTextArea.Editable = 'off'; % 设置为只读

% 创建"确认"按钮
uibutton(fig, 'push', 'Text', '确认', 'Position', [520, 20, 100, 30], ...
'ButtonPushedFcn', @(src, event) confirm_action(fig));

% 创建"取消"按钮
uibutton(fig, 'push', 'Text', '取消', 'Position', [640, 20, 100, 30], ...
'ButtonPushedFcn', @(src, event) cancel_action(fig));
end

% 导入数据的回调函数
function import_data(fig)
% 弹出文件夹选择对话框
folderPath = uigetdir('', '选择数据文件夹');

if folderPath == 0 % 用户取消选择文件夹
return;
end

% 拼接完整路径
iniFilePath = fullfile(folderPath, 'bwiparamtemp.ini');

if ~isfile(iniFilePath)
uialert(fig, 'bwiparamtemp.ini 文件不存在', '导入错误');
return;
end

% 调用 DLL 读取数据
try
% 创建处理器
proc = calllib('BWIBridgeDLL', 'CreateProcessor');

% 调用函数加载 INI 参数
calllib('BWIBridgeDLL', 'LoadParametersFromIni', proc, iniFilePath);

% 获取参数数量
paramCount = calllib('BWIBridgeDLL', 'GetGlobalParameterCount', proc);

% 获取参数的名称、数值、最小值、最大值、描述等
data = cell(paramCount, 5); % 5 列:名称、数值、最小值、最大值、描述
for i = 0:paramCount-1
name = calllib('BWIBridgeDLL', 'GetParameterName', proc, i);
value = calllib('BWIBridgeDLL', 'GetParameterValue', proc, i);
minVal = calllib('BWIBridgeDLL', 'GetParameterMin', proc, i);
maxVal = calllib('BWIBridgeDLL', 'GetParameterMax', proc, i);
desc = calllib('BWIBridgeDLL', 'GetParameterDescription', proc, i);

% 假设 value, minVal, maxVal 是 double,确保一致
data{i+1, 1} = char(name); % 变量名 (确保字符串)
data{i+1, 2} = value; % 数值 (double)
data{i+1, 3} = minVal; % 最小值 (double)
data{i+1, 4} = maxVal; % 最大值 (double)
data{i+1, 5} = char(desc); % 说明 (确保字符串)
end

% 更新表格数据
uit = findobj(fig, 'Tag', 'dataTable');
uit.Data = data; % 显示数据

% 保存数据到 fig.UserData(原始数据和当前数据)
fig.UserData.data = data;
fig.UserData.originalData = data; % 存储原始数据副本

% 更新文件夹路径显示框
filePathTextArea = findobj(fig, 'Tag', 'filePathTextArea');
if ~isempty(filePathTextArea)
filePathTextArea.Value = {folderPath}; % 显示路径 (uitextarea Value 是 cell)
else
uialert(fig, '未找到文件夹路径文本框,无法更新路径', '更新错误');
end

% 释放处理器
calllib('BWIBridgeDLL', 'DeleteProcessor', proc);

disp('数据导入成功');
catch ME
% 读取文件失败时的处理
uialert(fig, ['文件导入失败: ', ME.message], '导入错误');
end
end

% 确认按钮的回调
function confirm_action(fig)
% 获取表格数据
uit = findobj(fig, 'Tag', 'dataTable');
data = uit.Data; % 表格中的所有数据

% 获取文件夹路径作为项目路径
filePathTextArea = findobj(fig, 'Tag', 'filePathTextArea');
folderPath = filePathTextArea.Value{1}; % 获取显示的文件夹路径 (从 cell 取)

if isempty(folderPath)
uialert(fig, '文件夹路径无效', '导入错误');
return;
end

% 确保将 folderPath 转换为字符向量
folderPath = char(folderPath);

% 创建处理器
proc = calllib('BWIBridgeDLL', 'CreateProcessor');

% 获取原始数据从 fig.UserData
originalData = fig.UserData.originalData;

% 如果没有原始数据,则使用当前数据作为原始数据
if isempty(originalData)
originalData = data;
fig.UserData.originalData = data;
end

% 遍历表格中的每一行,获取参数名和新值
updated = false; % 标记是否有更新
for i = 1:size(data, 1)
paramName = data{i, 1}; % 参数名
newValue = data{i, 2}; % 修改后的新值

% 确保 paramName 转换为字符向量
paramName = char(paramName);

% 确保 newValue 是数值类型 (表格编辑可能返回 char)
if ischar(newValue) || iscell(newValue)
newValue = str2double(newValue);
end
if isnan(newValue)
uialert(fig, ['参数 ', paramName, ' 的值无效(非数值)'], '导入错误');
calllib('BWIBridgeDLL', 'DeleteProcessor', proc);
return;
end

% 获取原始值
originalValue = originalData{i, 2};

% 检查值是否发生变化
if isequal(newValue, originalValue)
disp(['参数 ', paramName, ' 未发生变化,跳过更新']);
continue; % 跳过这个参数
end

% 更新参数
result = calllib('BWIBridgeDLL', 'RewriteBWIIni_Param', proc, paramName, newValue, folderPath);

% 检查更新是否成功
if result
disp(['参数 ', paramName, ' 修改成功']);
% 更新原始数据
originalData{i, 2} = newValue;
updated = true;
else
uialert(fig, ['参数 ', paramName, ' 修改失败'], '导入错误');
calllib('BWIBridgeDLL', 'DeleteProcessor', proc);
return;
end
end

% 更新原始数据
fig.UserData.originalData = originalData;

try
% 从 UserData 获取模型名称字符串
modelName = fig.UserData.modelName; % <--- 修改点

% 检查 modelName 是否有效 (例如,不是空的)
if isempty(modelName) || ~ischar(modelName)
error('未能获取有效的模型名称。'); % 抛出一个错误
end

% 设置 sfun_callXXX 模块的路径参数
set_param([modelName, '/XXX_Launcher'], 'paramPath', folderPath);

% 设置 sfun_callXXXProcess 模块的路径参数
set_param([modelName, '/XXX_Processor'], 'dataDir', folderPath);

disp('已成功将路径参数传递到后续模块。');

catch ME
uialert(fig, ['无法设置后续模块的路径参数: ', ME.message], '参数传递错误');
% ... (释放处理器并返回)
return;
end

% 释放处理器
calllib('BWIBridgeDLL', 'DeleteProcessor', proc);

% 在基础工作空间中设置确认标志为 1
assignin('base', 'confirmationFlag', 1);

% 关闭 GUI 窗口
delete(fig);
end

% 取消按钮的回调
function cancel_action(fig)
% 在基础工作空间中设置确认标志为 -1
assignin('base', 'confirmationFlag', -1);

% 关闭 GUI 窗口
delete(fig);
end

% GUI 关闭回调函数
function close_gui(fig)
% 卸载 DLL 如果已加载
% if isfield(fig.UserData, 'dllLoaded') && fig.UserData.dllLoaded && libisloaded('BWIBridgeDLL')
% unloadlibrary('BWIBridgeDLL');
% end
% 清理其他资源如果需要
delete(fig);
end
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
180
181
182
183
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;

block.NumDialogPrms = 1;

%— 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_互作用-helix-非线性特征仿真实例\注波互作用1\指定输入';
paramPath = block.DialogPrm(1).Data;

%— ① 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
fprintf('[XXX] PID=%d 已退出\n', block.Dwork(3).Data)
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
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
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.NumDialogPrms = 1;

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
% 为保险起见,检查库是否已加载
if ~libisloaded('BWIBridgeDLL')
fprintf('错误: BWIBridgeDLL 未被加载。仿真可能未从 sfun_callInterface 正常启动。\n');
return;
end

% --- 读取数据并绘图 ---
% dll = '.\bin\BWIBridgeDLL.dll';
% hdr = '.\bin\BWIBridgeDLL.h';
% dir = 'C:\Users\pc\Desktop\XXX_互作用-helix-非线性特征仿真实例\注波互作用1\指定输入';
dir = block.DialogPrm(1).Data;

p = calllib('BWIBridgeDLL', 'CreateProcessor');
nPt = libpointer('int32Ptr', 0);
result = calllib('BWIBridgeDLL', 'ReadXXXDat', p, dir, nPt);
if ~result
fprintf('读取失败')
calllib('BWIBridgeDLL', 'DeleteProcessor', p);
unloadlibrary('BWIBridgeDLL')
end;

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('BWIBridgeDLL', 'CopyX_mm', p, xPtr, N);
calllib('BWIBridgeDLL', 'CopyPower', p, powPtr, N);
calllib('BWIBridgeDLL', 'CopyGain', p, gainPtr, N);
calllib('BWIBridgeDLL', '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('BWIBridgeDLL', 'DeleteProcessor', p);
% unloadlibrary('BWIBridgeDLL');
end

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

% ─── Terminate:处理退出 —──
function Terminate(~)
fprintf('[XXX] Simulation 终止. 卸载 BWIBridgeDLL.\n');
if libisloaded('BWIBridgeDLL')
unloadlibrary('BWIBridgeDLL');
end
end