C动态库的编写及调用
C动态库的编写及调用
GCC常用编译选项
1、总体编译选项
选项名称 | 作用 |
---|---|
-c | 只是编译不链接,生成目标文件.o |
-S | 只是编译不汇编,生成汇编代码 |
-E | 只进行预编译,不做其他处理 |
-g | 在可执行程序中包含标准调试信息 |
-o file | 把输出文件输出到file里 |
-v | 打印出编译器内部编译各过程的命令行信息和编译器的版本 |
-I dir | 在头文件的搜索路径列表中添加dir目录 |
-L dir | 在库文件的搜索路径列表中添加dir目录 |
-static | 链接静态库 |
-llibrary | 连接名为library的库文件 |
2、其他常用编译选项
选项名称 | 作用 |
---|---|
-ansi | 支持符合ANSI标准的C程序 |
-pedantic | 允许发出ANSI C标准所列的全部警告信息 |
-pedantic-error | 允许发出ANSI C标准所列的全部错误信息 |
-Wall | 允许发出gcc提供的所有有用的报警信息 |
-Werror | 把所有的告警信息转化为错误信息,并在告警发生时终止编译过程 |
-O | 主要进行线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化 |
-O2 | 除了完成所有“-O1”级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等 |
-O3 | 还包括循环展开和其他一些与处理器特性相关的优化工作 |
-static | 指示链接器构建一个完全链接的可执行程序,即链接静态库而不链接动态库 |
-fPIC | 指示链接器创建一个共享的目标文件,即so文件 |
-shared | 生成动态库,一般和上面的-fPIC一起使用 |
编写DLL
extern “c”
__stdcall的调用方式就会在原来函数名上加符号,而extern “c” __cdecl则不会附加额外的符号。
如果导出函数使用了extern”C” __cdecl,那么就不需要再重命名了,这个时候dll里的名字就是原始名字;如果使用了extern”C” __stdcall,这时候dll中的函数名被修饰了,就需要重命名。重命名的方式有两种,要么使用*.def文件,在文件外修正,要么使用#pragma,在代码里给函数别名。
__declspec(dllexport)和__declspec(dllimport)的作用
__declspec还有另外的用途,这里只讨论跟dll相关的使用。正如括号里的关键字一样,导出和导入。__declspec(dllexport)用在dll上,用于说明这是导出的函数。而__declspec(dllimport)用在调用dll的程序中,用于说明这是从dll中导入的函数。
因为dll中必须说明函数要用于导出,所以__declspec(dllexport)很有必要。但是可以换一种方式,可以使用def文件来说明哪些函数用于导出,同时def文件里边还有函数的编号。
而使用__declspec(dllimport)却不是必须的,但是建议这么做。因为如果不用__declspec(dllimport)来说明该函数是从dll导入的,那么编译器就不知道这个函数到底在哪里,生成的exe里会有一个call XX的指令,这个XX是一个常数地址,XX地址处是一个jmp dword ptr[XXX]的指令,跳转到该函数的函数体处,显然这样就无缘无故多了一次中间的跳转。如果使用了__declspec(dllimport)来说明,那么就直接产生call dword ptr[XXX],这样就不会有多余的跳转了。
__stdcall带来的影响
这是一种函数的调用约定方式,压栈方式和最后的清栈的方式不同,__stdcall
是函数自己处理栈
,__cdecl是函数调用者清理
。默认情况下VC使用的是__cdecl的函数调用方式,如果产生的dll只会给C/C++程序使用,那么就没必要定义为__stdcall调用方式,如果要给Win32汇编使用(或者其他的__stdcall调用方式的程序),那么就可以使用__stdcall。这个可能不是很重要,因为可以自己在调用函数的时候设置函数调用的规则。像VC就可以设置函数的调用方式,所以可以方便的使用win32汇编产生的dll。不过__stdcall这调用约定会Name-Mangling,所以我觉得用VC默认的调用约定简便些。但是,如果既要__stdcall调用约定,又要函数名不给修饰,那可以使用*.def文件,或者在代码里#pragma的方式给函数提供别名(这种方式需要知道修饰后的函数名是什么)。
举例:
extern “C” __declspec(dllexport) bool __stdcall cswuyg();
extern “C” __declspec(dllimport) bool __stdcall cswuyg();
#pragma comment(linker,"/export:cswuyg=_cswuyg@0")
*.def文件
指定导出函数,并告知编译器不要以修饰后的函数名作为导出函数名,而以指定的函数名导出函数(比如有函数func,让编译器处理后函数名仍为func)。这样,就可以避免由于microsoft VC++编译器的独特处理方式而引起的链接错误。
也就是说,使用了def文件,那就不需要extern “C”了,也可以不需要__declspec(dllexport)了(不过,dll的制造者除了提供dll之外,还要提供头文件,需要在头文件里加上这extern”C”和调用约定,因为使用者需要跟制造者遵守同样的规则)。
举例def文件格式:
LIBRARY XX(dll名称这个并不是必须的,但必须确保跟生成的dll名称一样)
EXPORTS
[函数名] @ [函数序号]
编写好之后加入到VC的项目中,就可以了。
另外,要注意的是,如果要使用__stdcall,那么就必须在代码里使用上__stdcall,因为*.def文件只负责修改函数名称,不负责调用约定。
也就是说,def文件只管函数名,不管函数平衡堆栈的方式。
如何把*.def文件加入到工程之后,链接的时候并没有自动把它加进去。那么可以这样做:
手动的在link添加:
1)工程的properties Configuration Properties Linker Command Line在“Additional options”里加上:/def:[完整文件名].def
2)工程的properties Configuration Properties Linker Input Module Definition File里加上[完整文件名].def
DllMain函数
每一个动态链接库都会有一个DllMain函数。如果在编程的时候没有定义DllMain函数,那么编译器会给你加上去。
DllMain函数格式:
1 | BOOL APIENTRY DllMain( HANDLE hModule, |
编写dll可以使用.def文件对导出的函数名进行命名。
实例DLL
C
1.头文件 test.h
1 |
|
2.实现文件 test.cpp
1 |
|
3.调用dllmain.h
1 |
|
C++
1.头文件 testClass.h
1 |
|
2.实现文件 testClass.cpp
1 |
|
3.调用dllmain.h
1 |
|
Linux 动态库和静态库的生成
参考:http://www.latelee.org/programming-under-linux/library-on-linux.html
1.示例代码
1 | /* lib.h */ |
1 | /*lib.c*/ |
1 | /*main.c*/ |
2.生成静态库
1 | 生成静态库 |
1 | 使用 |
3.生成动态库
1 | 生成动态库 |
1 | 使用 |
4.库环境的配置
在/etc/profile中添加库的路径
1 | export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH |