C,C++动态链接库 静态库
导言
首先要了解编译-链接的原理,如下图所示:
编译就是将某种语言写成的源代码,转换成目标文件。目标文件包含着机器代码及代码在运行时使用的数据。编译器就是干这个事情的,比如cl.exe,gcc,g++.
链接就是将多个目标文件以及库文件生成可执行的文件或者静态库或者动态库的过程。常见的连接器有link.exe, ld
在编译的时候,因为有可能要用到其他函数,所以就需要头文件来告诉编译器。
库文件不仅仅是对源代码进行保密,还可以减少重复编译的时间。
如何生成静态库及调用静态库
通过win32项目:
默认情况下,一些常见的配置选项如下:
先新建两个文件,如下所示,后面是头文件和源文件的内容,目前先这样。
编译生成如下:
注意这里有位是静态库,没有使用_declspec等,extern "C"也没有写。
然后通过一个win32项目控制台应用程序来测试这个lib库。
首先肯定是要引用个头文件,可以使用相当路径,注意..就是当前路径的上级目录
编译报错如下:
因为找不到实际的lib函数,
这里有几种方法
第一种就是在配置属性->链接器->附加库目录输入lib所在的目录,附加依赖项,这种方式生成的exe其实是链接了lib库到里面的,单独的exe也是可以运行的。
第二种方式就是使用#pragma, 这种方式跟第一种其实内部实现是一样的。
如何生成非MFC动态库及如何调用
也是通过生成win32项目:
配置属性里面如下:
同样的,我们先生成一个h,一个cpp文件,如下所示:
这个时候编译,可以生成dll,但是导入到Dependency发现没有导出函数可以给别人调用
头文件中比如改成这样才行:
表示这个函数是以C的方式导出给别人使用。
如果改成这样:
导出的函数签名是这样:
如果上面没有写"C",但是在配置里面修改为如下所示:
那么就跟写了extern "C"是一样的,结果就是:
同样的,这里要注意调用约定啊。
如果在头文件中写了extern "C",但是配置里面选择的是编译为c++,那么结果是什么呢?
还是以头文件中的为准。但是要注意不能同时在头文件中设置extern "C",编译为C。会报个错:
最终生成了这样几个文件:
调用方式,如下,先新建一个win32控制台应用程序
第一种方法:静态调用,也就是最普通的方法:
头文件必须要有:
链接库设置(跟写#pragma一样):
生成即可。
这里的头文件的作用是告诉应用程序有什么函数可用
链接器里面是导入lib的目的是:包含导出函数的符号名及序号,但不含实际代码。
这个时候如果把lib文件删除,照样可以运行exe(因为已经链接到里面了),如果把dll删除,就运行不了了,因为实际的代码在dll中了,
这点跟有实际代码的lib不一样。
第二种方法:通过#pragma方法链接lib导入库,然后通过头文件引入或者重新声明函数原型也可以:
注意这里还没有用到dllimport???,为什么呢?因为当使用dll的函数时,往往不需要显示的导入函数,编译器可自动完成,
但如果你显示的导入函数,编译器会产生质量更好的代码,由于编译器确切的知道了一个函数是否在一个dll中,它就可以产生
更好的代码,不再需要间接的调用转接。
第三种方法:显示调用动态链接库,修改了类的头文件。
这里通过先申明一个函数指针类型来存获得的函数地址,后面就可以通过这个函数指针来调用函数,另外全局变量获得的也是地址(这种方式不推荐,
其实在第二种或者第一种方法里面就可以直接使用了),所以需要这样来转换,
但是类不知道怎么通过这种方式来调用,可能不行吧。注意这里使用HINSTANCE这些需要Windows.h头文件,另外要注意函数指针的用法。
这种方式不需要头文件,也不需要lib文件,直接通过dll来进行加载,但是前提的先知道这个函数的签名(这里全部是C方式导出,所以函数签名没有改变)
可能C#调用C也是同样的原理吧。
之前的动态链接库的头文件很简单,现在改造如下:
因为头文件没有定义过,就定义这个,就包含进来这些声明,如果下次再被包含,那么因为定义过了,所以不会重复包含这些声明,
,另外USEDLL的意思是如果区分导出还是导入。
这样再在客户端调用的时候就简单了,如果是第一种第二种方式,就不需要单独申明函数了,直接用头文件即可,因为头文件直接就帮我们做了,是一回事。
还有一种导出dll函数的方法,就是使用def文件,下面新建一个dll项目,两个函数,添加def文件,如下所示:
可以看出这里其实不需要头文件也是可以的,如果有就更好。声明函数原型。
调用如下:
可以看出,函数签名没有改变,跟extern "C"是一样的。
注意extern "C"虽然可以用到类的修饰上,但是编译器会忽略掉,最后产生的还是C++修饰符。
还有一点就是关于调用约定的概念
假设函数这样定义:
那么客户端在调用的时候就要相对应:
关于调用约定的说明
stdcall:
1. 参数从右向左压入堆栈。
2.函数自身修改堆栈。
3.函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸
对于C函数,C++函数,其导出后的名称是不一样的,C++导出时的名称很怪的,推荐使用这种方式
cdecl调用约定,C语言缺省的调用约定(VS默认)
1. 参数从右向左压入堆栈。
2.函数不自身修改堆栈。
3.函数名自动加前导的下划线
C#如何调用DLL
平台调用几大要素
1. 调用约定,stdcall,cdecl,winapi等,一般win32的函数都是stdcall,
windows平台一般是stdcall,所以对于win32函数调用,我们一般不需
要制定调用约定,c库的函数一般都是cdecl的,所以需要明确来制定
2. EntryPoint,这个是指定入口点,如果没有指定这个就要名称一致
3. 指定字符集,CharSet. 在处理字符串的时候,C#都是Unicode的,如果
指定Ansi,那么在封送托管字符串的时候,就把字符串从托管Unicode封送
到非托管Ansi。如果制定Unicode,就是复制字符串格式。默认是Ansi
C#调用不需要头文件的形式,应该类似于动态库动态调用一样,只能调用dll。至于具体的跨平台调用内容太多,就不在这里
一一讲述了。
总结下来是这样的:
静态库 | 动态库 | |
生成方法 | 通过VS向导,选择静态库 | 通过VS向导,选择DLL |
函数导出方法 | 直接在头文件中声明函数原型即可,这里不需要dllexport | 1)在头文件中声明函数原型,并且加上dllexport 2)通过def文件 |
客户端调用方法 | 1) 头文件声明函数原型(也可以自己写)。 2) 静态库要么通过链接器直接输入,要么通过#pragma指令 | 第一种:头文件申明原型(也可以自己写,dllimport可以不要)+静态库链接(可以在配置里面输入,也可以使用#pragma指令) 第二种:动态调用动态链接库 |
生成的类型 | 生成lib文件,lib文件包含实际的代码 | 生成dll,lib文件, Dll是实际执行的代码,lib是找到入口点等 |
注意事项 | 不能包含其他动态链接库或者静态库?? C库函数都是这种静态库 静态库的指令直接包含在最终的exe文件中 | 注意头文件中条件编译的意义 注意动态调用的时候,全局变量得到的是地址 注意调用约定的意思 |
代码见(命名不好):
https://files.cnblogs.com/files/monkeyZhong/StaticLibDemo.zip