什么是模块化编程
模块化编程就是我们一个复杂的项目分成很多模块,比如一个单片机项目,就可能分为:主函数模块,液晶显示和数码管显示模块,时间延时模块,温度传感器模块等。而一个程序工程包含多个源文件(.c 文件和 .h 文件),每个 .c 文件可以被称为一个模块,每一个模块都有其各自的功能,而每一个.h文件则是声明该模块,相当于功能说明书 模块化编程在嵌入式中是必须要掌握的技能。
模块化编程的好处
开发C程序时,当代码量较大功能较复杂时,单一文件程序会使得文件非常巨大,代码量非常大,成千上万行的代码在一个文件中不便于修改和维护,因此需要将不同的功能模块放在不同的文件中。
并且在团队合作开发的时候,需要模块化开发。每个人负责一部分功能的开发,而你所负责的模块,你需要将你负责的模块功能写好,封装好,之后形成一个.c与.h ,然后交付给项目组长,组长则负责整体框架(main)的编写与各个模块的组合调试,最后各个模块的组合,形成了整个工程。由此可见,模块化可以有效的提高团队开发的分工协作效率。对于整个项目开发有着很大的好处
模块化开发过程如下
- 创建.c文件
- 在.c文件内定义需要的函数和变量
- 创建.h文件,文件名要与.c文件一致
- 在.h文件中声明需要让调用方知道的函数和变量
- 在调用方文件中包含.h文件,直接调用.h文件中声明的内容即可
注意事项:
.h
头文件格式一定要按照下面这格式进行
#ifndef DEMO1_CALCULATE_H // 避免多个文件重复导入
#define DEMO1_CALCULATE_H // 定义一个预处理宏定义
//extern 声明代码
#endif //DEMO1_CALCULATE_H 表示条件命令的结束
-
导入.h文件的时候不能使用
<>
而是使用" "
因为<>
方式只会到系统的专门目录找,而这里没有我们定义的模块 ,而" "
方式编译器会先到当前的工程项目所在目录下找,找不到再到系统专门目录下找 -
定义的
x.c和x.h
要在同一个目录下不然引入x.h的时候找不到.c源码 -
.h文件
不能定义具体实现 ,只能进行声明, 而具体实现都需要在.c文件
中定义 -
函数声明时,extern可有可无,但是变量的声明必须要有,否则编译时不能识别。. (建议都写上)
-
不想让外界知道的信息,就不应该出现在
.h
头文件里,而想让外部调用的函数或变量,则一定要在头文件中声明 -
头文件(.h)命名应与源文件(.c)一致,便于清晰的查看各个头文件
-
模块内不想被外部引用的函数和全局变量需在
.c文件头
以static关键字声明, 这样这些函数和变量只会在当前.c文件中起到作用,一来可以避免函数名的重复;二来可以保护内部的实现数据,防止被破坏 ,比如:static void lEDOpen();
-
模块中想要被其他文件访问的变量,一定要是全局变量,并且在.h中声明
-
要尽量减少全局变量的使用,因为全局变量的生命周期是从程序的开始到程序的结束的,这意味着你在其他源文件中调用这个变量时,可能会产生同名变量以及变量数值错误等问题, 这个是非常不好把控的,程序一旦庞大起来,这就是灾难
模块化演示
然后我们在需要调用的地方,将头文件导进来就行了,之后就能使用头文件中声明的内容了
在上面的案例中我们只是在main.c文件中应用了模块,那么模块和模块之间能相互引用吗? 答案是可以的,直接导入就行了
代码库
代码编译过程
静态库
静态库,顾名思义,它是静态的,也就是说它不会被动态编译,它只会静态编译,节省了编译时间,提高了编译速度。同一份静态库,可以被多个程序进行编译,也就实现了代码的复用共享。
静态库的后缀一般有2种.a
(linux) , .lib
(windows) ,但是在windows下MinGW编译的静态库也是.a
的所以如果使用MinGw进行编译代码的话.a
文件能通用的
静态库命名规则: 这类库的名字一般是libxxx.a;或者 libxxx.lib; 而xxx是库的名称
动态库
动态库,就是程序应用启动的时候,动态加载的,因为它一般是在系统运行的时候就已经运行的动态库,因此其它应用可以直接使用它,并且同一个动态库可以被多个应用共享使用,在系统中对于一个动态库只会存在一份,这大大节省了内存空间,大大提升了系统的性能。
动态库的后缀一般也是2种 展名分别为:.so
( linux )或者 .dll
(windows)
动态库命名规则: 这类库的名字一般是libxxx.so 或者libxxx.dll 而xxx是库的名称
Clion库的使用
CMakeLists.txt文件使用
C语言-Cmake-CMakeLists.txt教程 可以参考这篇文章
静态库
创建静态库
先写好c代码,在demo中调试没问题了,那么我们就可以将相关的代码copy到静态库中进行打包了,下面就创建一个静态库
然后随便创建几个.c和.h文件 如下:
然后可以参考C语言-Cmake-CMakeLists.txt教程 ->打包静态或动态库CMakeLists.txt(通用)
这篇文章进行配置CMakeLists.txt ,之后打包效果如下:
注意: 打包后会自动在项目名称前加lib ,所以在创建的时候无效前缀加lib
使用静态库
然后我们将打包好的libtool.a
和头文件目录tool
都拷贝到需要的地方,然后可以参考C语言-Cmake-CMakeLists.txt教程-> 项目编译CMakeLists.txt(静态动态库通用)
这篇文章, 进行配置CMakeLists.txt, 项目编译之后在调用的地方使用#include "头文件"
就行
如果发现编译错误那么,记得刷新Clion-CMake的缓存
动态库
创建动态库
先写好c代码,在demo中调试没问题了,那么我们就可以将相关的代码copy到动态库中进行打包了,下面就创建一个动态库
然后随便创建几个.c和.h文件 如下:
然后可以参考C语言-Cmake-CMakeLists.txt教程 ->打包静态或动态库CMakeLists.txt(通用)
这篇文章进行配置CMakeLists.txt ,之后打包效果如下:
使用动态库
使用MinGW动态打包后就会出现两个文件.dll
和 .dll.a
我们只需要.dll
就行,然后我们将打包好的libtool1.dll
和头文件目录tool1
都拷贝到需要的地方,然后可以参考C语言-Cmake-CMakeLists.txt教程-> 项目编译CMakeLists.txt(静态动态库通用)
这篇文章, 进行配置CMakeLists.txt, 项目编译之后在调用的地方使用#include "头文件"
就行
上面可以看出来运行报错了,这是因为默认exe程序会去当前目录下找dll文件如果没有找到那么就会去环境变量里找,都没有找到那么就报错了具体解决办法,可以参考这篇文章C语言-Cmake-CMakeLists.txt教程 动态库导入无效,报错问题
静态库和动态库混合使用
可以参考这篇文章C语言-Cmake-CMakeLists.txt教程 打包静态或动态库CMakeLists.txt(通用)
查看动态库或者静态库
查看动态库定义的函数或变量 readelf -c xxx.a
查看动态库定义的函数或变量 readelf -A xxx.so
查看库里所有文件列表: ar -t xxx.a
查找静态库里指定的函数或变量 nm -o xxx.a | grep "yyy"
查找动态库里指定的函数或变量 nm -o xxx.so | grep "yyy"
nm 常见的符号类型:
A 该符号的值在今后的链接中将不再改变;
B 该符号放在BSS段中,通常是那些未初始化的全局变量;
D 该符号放在普通的数据段中,通常是那些已经初始化的全局变量;
T 该符号放在代码段中,通常是那些全局非静态函数;
U 该符号未定义过,需要自其他对象文件中链接进来;
W 未明确指定的弱链接符号;同链接的其他对象文件中有它的定义就用上,否则就用一个系统特别指定的默认值。
小技巧
常用的工具类我们可以使用静态库, 频繁升级的部分我们可以使用动态库
- 静态库的好处就是打包后的exe不需要依赖任何文件就能运行,只是exe文件会比较大
- 动态库的好处就是打包后的exe文件小,升级只需替换对应的dll文件就行,坏处就是dll找不到或者丢失程序就没法运行了
注意: 动态库代码和项目代码是分离的,如果更新的版本与原来的版本不兼容的话,使用动态库的新版本加载后有可能会出问题导致项目崩溃等情况。
主要还是看项目升级频繁吗?
- 如果只是单存的开发一个自己使用的工具或者以后不打算升级了,那么使用静态库是非常不错的选择, 因为移植性强,在哪里都能直接用无须安装和配置,双击就运行了
- 如果是商用项目经常需要升级扩展,那么不用想动态库就是不二的选择,升级和扩展只需要添加或者更换部分的dll就行
- 当然很多时候我们静态和动态可以混合使用, 静态库主要用于工具文件或者项目核心功能, 动态库用于项目的变化频繁的功能, (静态库和动态库都有相同的文件,内部方法也相同那么优先静态库)