从 hello.c 到 libss.a:Simulink 使用 rtwmakecfg.m 链接预编译静态库

Simulink 代码生成中使用 rtwmakecfg.m 链接预编译静态库

最近在整理 Simulink 自定义模块代码生成流程时,遇到一个比较实际的问题:
有些底层 C 代码希望封装起来,不直接暴露源码,但又希望别人可以正常使用接口,并且能完成代码生成和编译。

最直接的方式当然是把 .c/.h 文件都放到工程里,让 Simulink 代码生成时一起参与编译。这样简单直接,但是源码也会完整暴露出来。对于普通测试没有什么问题,但如果是底层驱动、通信协议、算法模块,或者已经调好的平台接口,就不太合适。

所以这里采用另一种方式:
把具体实现提前编译成静态库,例如 libss.a,对外只提供头文件和库文件。用户可以正常调用接口,但是看不到具体实现。

这篇文章主要记录一下如何通过 rtwmakecfg.m,在 Simulink/RTW 代码生成过程中自动链接预编译好的 .a 静态库。

1. 基本思路

整体流程如下:

hello.c / hello.h
        ↓
使用目标编译器生成 hello.o
        ↓
使用 ar 工具打包成 libss.a
        ↓
Simulink 生成的代码调用 hello_add()
        ↓
rtwmakecfg.m 把 libss.a 加入链接
        ↓
最终生成 elf / hex / s19

这里要注意一点:
.a 文件不是拿来重新编译的,而是拿来链接的。

也就是说,代码生成阶段仍然会正常生成 .c 文件,编译器也会把这些 .c 文件编译成 .o。到了最后链接阶段,链接器发现代码里调用了 hello_add(),就会到 libss.a 里面寻找这个符号。如果库里存在这个函数,最终就可以正常链接通过。

2. 示例代码

先准备一个简单的函数。

hello.h

#ifndef HELLO_H
#define HELLO_H

int hello_add(int a, int b);

#endif

hello.c

#include "hello.h"

int hello_add(int a, int b)
{
    return a + b;
}

实际项目中,hello.c 可以换成更复杂的底层代码,比如驱动接口、协议栈、标定读写、算法模块等。
对外只暴露 hello.h,具体实现被编译进静态库中。

3. 编译生成 .a 静态库

以 PowerPC 工具链为例,假设工具链路径为:

C:\powerpc-eabivle-4_9_e200

可以写一个简单的 build_lib.bat

@echo off
setlocal

set TOOLCHAIN=C:\powerpc-eabivle-4_9_e200
set GCC=%TOOLCHAIN%\bin\powerpc-eabivle-gcc.exe
set AR=%TOOLCHAIN%\bin\powerpc-eabivle-ar.exe
set NM=%TOOLCHAIN%\bin\powerpc-eabivle-nm.exe

if not exist build mkdir build

"%GCC%" -c hello.c ^
  -o build\hello.o ^
  -mcpu=e200z4 ^
  -O2 ^
  -ffunction-sections ^
  -fdata-sections ^
  -I.

if errorlevel 1 goto error

"%AR%" rcs build\libss.a build\hello.o

if errorlevel 1 goto error

echo.
echo ===== libss.a content =====
"%AR%" t build\libss.a

echo.
echo ===== symbols =====
"%NM%" build\libss.a

echo.
echo OK: build\libss.a generated.
goto end

:error
echo Build failed.
exit /b 1

:end
endlocal

执行后会生成:

build\libss.a

可以用 nm 检查库里的符号:

C:\powerpc-eabivle-4_9_e200\bin\powerpc-eabivle-nm.exe build\libss.a

如果能看到类似下面的内容,说明函数已经被打进库里:

hello.o:
00000000 T hello_add

其中 T hello_add 表示 hello_add 是一个可以被外部链接的代码符号。

4. 使用 rtwmakecfg.m 添加静态库

rtwmakecfg.m 的作用可以理解为:
在 RTW 构建过程中,告诉代码生成器当前模块还依赖哪些头文件路径、源文件路径、库文件或者目标文件。

这里我们不希望重新编译 hello.c,而是希望直接链接已经生成好的 libss.a,所以使用:

makeInfo.linkLibsObjs

假设目录结构如下:

模型目录/
├─ Test.slx
├─ hello.h
├─ libss.a
├─ xxx.mexw64
├─ xxx.tlc
└─ rtwmakecfg.m

其中 libss.a 是目标平台使用的静态库,hello.h 是对外暴露的头文件。

rtwmakecfg.m 可以这样写:

function makeInfo = rtwmakecfg()

thisDir = fileparts(mfilename('fullpath'));

makeInfo.includePath = {thisDir};
makeInfo.sourcePath  = {};
makeInfo.sources     = {};
makeInfo.linkLibsObjs = {};
makeInfo.precompile  = 1;

libFile = fullfile(thisDir, 'libss.a');

if exist(libFile, 'file')
    makeInfo.linkLibsObjs{end+1} = libFile;
else
    warning('rtwmakecfg: libss.a not found: %s', libFile);
end

disp('===== rtwmakecfg is running =====');
disp(makeInfo.linkLibsObjs);

end

这里推荐使用:

thisDir = fileparts(mfilename('fullpath'));

而不是直接使用 pwd
因为 pwd 代表 MATLAB 当前工作目录,它可能会变化;而 mfilename('fullpath') 可以稳定获取当前 rtwmakecfg.m 文件所在目录。

5. linkLibsObjs 和 sourcePath 的区别

这个地方容易搞混。

如果写到 sourcePathsources,意思更偏向于让构建系统去编译源文件。
而这里我们已经提前把 hello.c 编译成了 libss.a,不希望它再次参与编译,所以不需要把 hello.c 加进去。

本例真正需要的是:

makeInfo.includePath = {thisDir};
makeInfo.linkLibsObjs = {fullfile(thisDir, 'libss.a')};

includePath 用来让生成代码找到 hello.h
linkLibsObjs 用来让最终链接阶段找到 libss.a

这样,源码实现不需要出现在最终工程里,只需要提供头文件和库文件。

6. 如何判断 rtwmakecfg.m 是否生效

最简单的方法是在 rtwmakecfg.m 里加一行打印:

disp('===== rtwmakecfg is running =====');

构建模型时,如果 MATLAB 命令行里能看到这行输出,说明 rtwmakecfg.m 已经被调用。

然后再到生成目录里搜索:

libss.a

如果在生成的 makefile 或者编译日志中可以看到 libss.a 出现在链接命令里,说明静态库已经进入最终链接流程。

如果 rtwmakecfg.m 已经执行,但是最终链接命令里没有出现 libss.a,那就需要检查当前目标的 .tmf 文件是否正确展开了这些库信息。尤其是自定义 target 或 template makefile 方式下,这一步比较容易出问题。

7. 文件名有没有要求

如果使用的是完整路径方式:

makeInfo.linkLibsObjs{1} = fullfile(thisDir, 'libss.a');

那么 .a 文件名没有特别严格的要求,只要路径和文件名写对即可。

不过从 GNU 工具链习惯来看,静态库一般命名为:

libxxx.a

比如:

libss.a
libhello.a
libdriver.a

这样以后如果改成 -L-l 的链接方式,也更符合常规习惯。例如 -lss 默认会寻找 libss.a

8. 需要注意的平台一致性

静态库必须使用目标平台对应的编译器生成。

比如目标是 PowerPC,就应该使用:

powerpc-eabivle-gcc
powerpc-eabivle-ar

如果目标是 ARM Cortex-M,就应该使用:

arm-none-eabi-gcc
arm-none-eabi-ar

不能拿 Windows 下普通 MinGW 或 MSVC 编出来的库去链接嵌入式目标工程。
否则很容易出现文件格式不识别、ABI 不匹配、符号找不到等问题。

9. 小结

这个方法的核心价值是:
把实现代码提前编译成静态库,对外只提供头文件和库文件。

这样用户可以正常使用接口,也可以正常完成 Simulink 代码生成和目标编译,但不会直接看到底层 .c 源码。

对于自定义工具箱、底层驱动封装、算法模块交付,这种方式还是比较实用的。
rtwmakecfg.m 本身不复杂,关键是要分清楚:哪些文件需要参与编译,哪些文件只需要参与链接。

posted @ 2026-06-12 15:55  顾率  阅读(6)  评论(0)    收藏  举报