[C++] 使用Visual Studio创建及使用静态链接库和动态链接库

1. 静态链接库和动态链接库的区别

静态链接库文件扩展名为 .lib, 相当于多个**.obj** 文件链接而成的一个库文件。使用时,需要对应的 .h 头文件和 .lib。 优点是,使用步骤少,简单方便。缺点是生成的 .exe 较大,而且每次修改 .lib, .exe 都需要重新编译。

动态链接库文件扩展名为.dll, 可以多个进程可以共享一个同一个动态链接库,能够节省内存空间。仅在运行时需要,如果修改动态链接库,不一定需要重新编译 .exe, 版本升级方便。

和动态链接库相比较,静态链接库生成和使用都更简单。

2. 静态链接库的创建及使用

我使用的VS版本是免费的 Visual Studio Community 2017, 和动态链接库相比,静态链接库的概念,创建和使用都极其简单。

2.1 .lib 的创建

文件 > 新建 > 项目 > 空项目

头文件和源文件里分别添加 add.hadd.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.libmy_dll.dll

3.2 .dll 的使用

必要的文件:add_dll.h, main.cpp, my_dll.lib, my_dll.dll

add_dll.h (这个文件和创建时 dlladd.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.hadd_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 a typedef 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

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Visual Studio中,你可以使用静态链接动态链接链接库文件。 对于静态链接,你需要将静态库的文件名添加到项目属性的链接器输入的附加依赖项中。这可以通过右键点击项目,选择属性,然后在链接器选项下的输入中添加静态库的文件名来完成。对于动态链接库也是同样的操作。这样在编译时,编译器会将静态库或动态库的代码和你的项目代码合并在一起。 对于动态链接,你还需要在项目属性中添加包含目录。这可以通过右键点击项目,选择属性,然后在C/C++选项下的常规中添加包含目录来完成。这些包含目录应该包含库文件的头文件,以便编译器能够正确地找到库的函数和数据定义。在配置相对路径时,最好使用Visual Studio提供的宏来配置,这样可以更方便地管理路径。例如,使用boost库时,可以配置环境变量并重启电脑,这样Visual Studio就会生成相应的宏。 总结起来,使用Visual Studio进行静态链接动态链接的步骤如下: 1. 对于静态链接,将静态库的文件名添加到项目属性的链接器输入的附加依赖项中。 2. 对于动态链接,将静态库和动态库的文件名添加到项目属性的链接器输入的附加依赖项中。 3. 对于动态链接,添加包含库文件的头文件的目录到项目属性的C/C++选项的常规中的附加包含目录中。 希望这些信息对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值