[C++] 使用Visual Studio创建及使用静态链接库和动态链接库
文章目录
1. 静态链接库和动态链接库的区别
静态链接库文件扩展名为 .lib, 相当于多个**.obj** 文件链接而成的一个库文件。使用时,需要对应的 .h 头文件和 .lib。 优点是,使用步骤少,简单方便。缺点是生成的 .exe 较大,而且每次修改 .lib, .exe 都需要重新编译。
动态链接库文件扩展名为.dll, 可以多个进程可以共享一个同一个动态链接库,能够节省内存空间。仅在运行时需要,如果修改动态链接库,不一定需要重新编译 .exe, 版本升级方便。
和动态链接库相比较,静态链接库生成和使用都更简单。
2. 静态链接库的创建及使用
我使用的VS版本是免费的 Visual Studio Community 2017, 和动态链接库相比,静态链接库的概念,创建和使用都极其简单。
2.1 .lib 的创建
文件 > 新建 > 项目 > 空项目:
头文件和源文件里分别添加 add.h
和 add.cpp
, 代码分别为:
add.h
#ifndef ADD_H
#define ADD_H
int add(int a, int b);
#endif // ADD_H
add.cpp
#include "add.h"
int add(int a, int b) {
return a + b;
}
修改项目属性:
属性 > 配置属性 > 常规 > 配置类型 , 应用程序(.exe) 改为静态库(.lib)
然后选择 生成,就会生成 .lib 文件。
2.2 .lib 的使用
使用静态库需要 .lib 文件和对应的 .h 文件。
可以在解决方案中添加新的项目,然后解决方案属性中设置一下项目依赖项,就是指定多个项目生成先后顺序,否则可能出问题。这里当然是 .lib 要先生成。
应用 .lib 的项目,需要包含头文件,然后属性 > 链接器 > 输入 > 附加依赖项,指定 .lib 的路径,就可以了。
或者也可以将别人给的 .lib 拖放进项目资源里(如果没有源代码的话),然后只需要包含头文件就可以了,更简单。
3. 动态链接库的创建及使用
3.1 .dll 的创建
类似于静态链接库的创建,不同的是需要将应用程序 (.exe) 改为动态库 (.dll) 。
头文件和源文件分别为:
add.h
#ifndef ADD_H
#define ADD_H
int __declspec(dllexport) add(int a, int b);
#endif // ADD_H
add.cpp
#include "add.h"
int add(int a, int b) {
return a + b;
}
选择”生成“,将生成 my_dll.lib 和 my_dll.dll
3.2 .dll 的使用
必要的文件:add_dll.h, main.cpp, my_dll.lib, my_dll.dll
add_dll.h (这个文件和创建时 dll 的 add.h 是不一样的,一个文件里是dllexport
,另一个是dllimport
)
#ifndef ADD_H
#define ADD_H
int __declspec(dllimport) add(int a, int b);
#endif // ADD_H
main.cpp
#include <stdio.h>
#include "add.h"
//extern int __declspec(dllimport) add(int a, int b);
int main() {
int a = 2;
int b = 1;
printf("a=%d, b=%d\n", a,b);
printf("add: %d\n", add(a,b));
getchar();
return 0;
}
如果是第三方提供的动态链接库,没有源代码,my_dll.lib 可以直接拖放到项目资源文件里,也可以在 属性 > 链接器 > 输入 > 附加依赖 (Additional Dependencies)里指定 my_dll.lib 的路径。然后生成。如果执行,my_dll.dll 需要和 .exe 放在同一个路径下。
3.3 修改头文件使其通用
上面的例子,3.1 和 3.2 中的 add.h 和 add_dll.h内容不一样,为了简化使用,可以把两个合并成一个,使其通用:
add_default.h
#ifndef ADD_H
#define ADD_H
#ifdef BUILD_DLL
#define PORT_DLL __declspec(dllexport)
#else
#define PORT_DLL __declspec(dllimport)
#endif
int PORT_DLL add(int a, int b);
#endif // ADD_H
创建 dll 时使用这个,使用 dll 时也可以使用这个,使用 dll 的用户就不需要额外再自己写一个头文件了。
不同之处在于:创建 dll 时,需要修改属性: 属性 > 配置属性 > C/C++ > 命令行 > /D “BUILD_DLL”, /D
表示 define
, 使用 dll 时不需要这一步。
4. 动态库的显式和隐式链接(explicit and implicit linking)
显式链接要使用三个函数LoadLibrary
, GetProcAddress
, FreeLibrary
, 又麻烦又好出错,一般情况下,要尽量避免使用。使用隐式链接相对简单得多。
From what I understand, implicit dynamic linking is the fact of saying that your program needs the library in order to run, by adding the library in the dependency section of your program. If the library isn’t found at the start of the program, the program simply won’t be executed.
Explicit dynamic linking is using a function like “LoadLibrary” (windows) or “dlopen” (Linux) in order to load a library at runtime. It’s exactly what a plugin is, and how you can code it.
Now, doing an explicit dynamic linking is going to add work and complexity, and I don’t see any reason for it to be more efficient than an implicit dynamic linking. You use explicit dynamic linking only when you cannot do otherwise, like loading a library depending on some runtime value.
4.1 显式链接
To use a DLL by explicit linking, applications must make a function call to explicitly load the DLL at run time. To explicitly link to a DLL, an application must:
-
Call
LoadLibrary
,LoadLibraryEx
, or a similar function to load the DLL and obtain a module handle. -
Call
GetProcAddress
to obtain a function pointer to each exported function that the application calls. Because applications call the DLL functions through a pointer, the compiler does not generate external references, so there is no need to link with an import library. However, you must have atypedef
or using statement that defines the call signature of the exported functions that you call. -
Call
FreeLibrary
when done with the DLL.
For example, this sample function calls LoadLibrary
to load a DLL named “MyDLL”, calls GetProcAddress
to obtain a pointer to a function named “DLLFunc1”, calls the function and saves the result, and then calls FreeLibrary to unload the DLL.
例子:
#include "windows.h"
typedef HRESULT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT*);
HRESULT LoadAndCallSomeFunction(DWORD dwParam1, UINT * puParam2)
{
HINSTANCE hDLL; // Handle to DLL
LPFNDLLFUNC1 lpfnDllFunc1; // Function pointer
HRESULT hrReturnVal;
hDLL = LoadLibrary("MyDLL");
if (NULL != hDLL)
{
lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1");
if (NULL != lpfnDllFunc1)
{
// call the function
hrReturnVal = lpfnDllFunc1(dwParam1, puParam2);
}
else
{
// report the error
hrReturnVal = ERROR_DELAY_LOAD_FAILED;
}
FreeLibrary(hDLL);
}
else
{
hrReturnVal = ERROR_DELAY_LOAD_FAILED;
}
return hrReturnVal;
}
4.2 隐式链接
包含 .lib 文件的方法叫做隐式链接,上例中的 3.2 中的链接方法使用的就是隐式动态链接。
Including the lib file is called implicit linking, as the machine code is known at compile time but not actually included into the actual application. (Static libraries include the machine code directly into the application, where as dynamic code load it in at run time.)
4.3 DEF的创建及使用
DEF modulation definition file 模块定义文件。
这个文件是要手写的,手动创建然后添加到项目源文件中。然后生成dll时,编译器会根据这个文件生成一个 .lib。这个 .lib 可用于隐式链接。MFC DLL Wizard 可以自动生成 .def 文件。
如果不手写,也可以自己写个脚本,或者使用别的工具生成 DEF 文件,在项目中添加 .def 文件到源文件。
一个最简单的.DEF 文件的格式:
LIBRARY my_test_dll // 测试library名称大小写没关系
EXPORTS
add @1 // add 是简单的一个函数名
函数名后可以加序号: @Number, 序号范围: 1 ~ N, 但是不写也可以,会自动分配序号。
如果添加了这个文件,生成 dll 项目时就会生成一个 .lib,然后可以使用隐式链接。也可以使用显式链接。
测试发现,如果 dll 项目的头文件如果是一般的头文件,没有使用 __declspec(dllexport)
, 则必须添加 .def, 否则虽然可以成功生成 .dll,但生成的 .dll 文件无法使用,GetProcAddress
会失败,如果加了 .def, 则生成 dll 项目的同时还会生成一个 .lib, 此文件可用于隐式链接。
测试例子:
mydll: add.h
#pragma once
//int add(int x, int y);
```C++
mydll: ***add.cpp***
mydll: add.cpp
#include "add.h"
int add(int x, int y) {
return x + y;
}
mydll: mdll.def
LIBRARY MYDLL
EXPORTS
add
main.cpp
#include <iostream>
#include <Windows.h>
using namespace std;
typedef int(*LPGETNUMBER)(int, int);
int main() {
LPGETNUMBER lp;
int result = -99999;
HINSTANCE hInstance = LoadLibrary("mydll.dll");
if (NULL != hInstance) {
lp = (LPGETNUMBER)GetProcAddress(hInstance, "add");
if (NULL != lp) {
result = lp(4, 6);
std::cout << result << std::endl;
}
else std::cout << " GetProcAddress failed!" << endl;
FreeLibrary(hInstance);
}
else std::cout << "LoadLibrary failed!" << std::endl;
std::cout << "Test end " << std::endl;
system("pause");
return 0;
}
下面是输出:
10
Test end
请按任意键继续. . .
测试发现如果此时使用上面的显式动态链接,上面的头文件 add.h 没有写也没关系,但是如果改成隐式动态链接,则 add.h 必须是正确的。
Use the *.def file when you build the DLL, to specify what function names the DLL is supposed to export.
After the DLL is built, use dumpbin /exports to verify that the functions are indeed exported from the DLL.
After you have verified that the DLL is exporting functions, you can either link to them at run-time using LoadLibray/GetProcAddress, and/or you can link to them at build-time by passing the DLL’s *.lib file (which was created when you built the DLL using its *.def file) as an argument to your application’s linker.
上面的一段话的大意是说,使用DEF文件时,应该在函数声明上加上 __stdcall, __declspec(dllexport) 以及 extern “C”, 原因没看明白。加上 extern “C” 的意思是,这个函数C/C++代码都是可以使用的。然后有人说使用 DEF 是过时的做法。
[1] Microsoft Visual C++ Static and Dynamic Libraries
[2] How to use the .def file for explicit linking?
[3] Link an executable to a DLL
[4] implicit dynamic linking vs explicit dynamic linking - which is more effective?
[5] Link an executable to a DLL
[6] .def files C/C++ DLLs
[7] Exporting from a DLL Using DEF Files