Windows动态链接库的生成和使用

工程需要,最近在编一组Windows上的动态链接库给Python调用。之前做过Linux下C++动态库的编译,并提供给Python调用,Windows下的编译跟Linux还是有些差距,因此花了一点时间跑通,在这里记录一下。

为了完整对比,先后测试了Windows上静态库、动态库的编译和调用。简洁起见,本篇先记录Windows上C++静态库、动态库的编译,以及两种库在Windows上使用C++程序进行调用,后续再单独写一篇用Python调用Windows DLL的介绍。

1. 静态库的编译和使用

1.1 静态库的编译

首先创建Visual Studio静态库项目,我用的Visual Studio 2022,创建界面如下:

选择“静态库”,下一步,填写项目名称,项目位置,然后创建,如下图所示。

 创建完成后,为我们要编译的静态库添加头文件和cpp主体文件。我的demo头文件如下:

#pragma once

#ifndef __MY_LIB_TEST_H__
#define __MY_LIB_TEST_H__

#include <iostream>

using namespace std;

int add(int x, int y);
int sub(int x, int y);
void print_result(int result);

#endif

对应的cpp文件如下:

// my_lib_test.cpp : 定义静态库的函数。
//

#include "pch.h"
#include "framework.h"
#include "my_lib_test.h"


int add(int x, int y)
{
	return x + y;
}

int sub(int x, int y)
{
	return x - y;
}

void print_result(int result)
{
	cout << "Result is " << result << endl;
}

编译成功后,在当前项目的x64/Release目录下,我们可以看到生成的lib文件:

ps:我在工程中设置的是Release模式,也可以设置Debug,在不同模式下生成的库,在调用时也需要使用相应的模式。

1.2 静态库的调用

静态库的调用可以有两种方式。

第一种方式,通过#pragma comment(lib, "xxx.lib")来加载所需要的静态库。

这种方式相对简单,只需要在代码中指定要加载的静态库名称,在引用外部库比较少时相对方便一些;缺点是需要改代码并重新编译。

新建空项目,并在其中添加测试代码,test_call_lib.cpp:

#include <iostream>
#include "my_lib_test.h"

// The first method to call lib
#pragma comment(lib, "my_lib_test.lib")

int main()
{
	int sum = add(10, 20);
	int diff = sub(300, 100);

	print_result(sum);
	print_result(diff);

	return 0;
}

将在1.1步骤中生成的静态库my_lib_test.lib和它的头文件my_lib_test.h拷贝到新工程目录下,

编译通过后,执行结果如下:

第二种方式:在工程属性中指定头文件和库文件。

该方式不使用#pragma comment(lib, "xxx.lib"),二是直接在工程属性中指定依赖的头文件、库文件,设置方式如下:

在属性页“VC++目录”中,设置“包含目录”和“库目录”,由于我们已经把头文件和库文件拷贝到当前工程目录下,所以这里直接设置成当前工程目录即可。

 接下来,在属性页的“链接器”—>“输入”—>“附加依赖项”设置需要调用的lib库文件:

设置完成后,正常编写代码,编译执行即可。本人的调用代码如下,与第一种方式相比,少了#pragma comment(lib, "xxx.lib")语句。

#include <iostream>
#include "my_lib_test.h"

int main()
{
	int sum = add(10, 20);
	int diff = sub(300, 100);

	print_result(sum);
	print_result(diff);

	return 0;
}

2. 动态库的编译和使用

动态库相较于静态库的优势是,可以在程序运行时,调用到相应的库函数的时候,才会加载动态库,而不是像静态库那样在编译阶段就将库加载到程序中。由于不需要预先加载,使用动态库可以使得编译出来的可执行文件不至于过大。

2.1 动态库的编译

动态库的编译有两种方式,第一种是通过模块描述文件.def文件进行动态库的生成;第二种是通过__declspec(dllexport) 的方式。

1. 使用模块定义文件.def生成DLL

与静态库编译类似,我们首先需要建立一个动态链接库工程:

为该工程命名,并指定它的位置:

创建完成,添加代码代码。

头文件my_dll_test.h:

#pragma once
#ifndef _DLL_TEST_H_
#define _DLL_TEST_H_

#include <iostream>


void hello_world();
int add(int x, int y);
int sub(int x, int y);
void print_result(int x);

#endif

cpp文件my_dll_test.cpp:

#include "pch.h"
#include "framework.h"
#include "my_dll_test.h"

void hello_world()
{
	std::cout << "Hello world!" << std::endl;
}

int add(int x, int y)
{
	return x + y;
}

int sub(int x, int y)
{
	return x - y;
}

void print_result(int x)
{
	std::cout << "The result is " << x << std::endl;
}

 接着,我们还需要再添加一个描述模块定义的.def文件,my_dll_test.def:

内容如下:

LIBRARY my_dll_test
 
EXPORTS 
hello_world @1
add @2
sub @3
print_result @4

设置完成后,可以看到在工程属性—>“链接器”—>“输入”—>“模块定义文件”中,会自动出现我们刚才创建的my_dll_test.def文件:

编译工程,通过后,在工程目录x64/debug下面,会看到生成的dll文件和lib文件。(在工程编译时直接使用的Debug选项,也可以选择Release选项)

2. 使用__declspec(dllexport)方式生成DLL

除了使用模块定义文件的方式,还可以通过在代码中添加__declspec(dllexport)的方式来生成动态链接库。这种方式由于需要改动代码,只适合Windows开发,在代码需要跨平台时不是很方便,因此不推荐。这里简单说明一下用法。

在创建完动态链接库工程之后,添加cpp代码:

/*注意此处头文件包含顺序,iostream不能在pch.h之前,
否则会出现报错C2039 "cout"不是"std"的成员 */
#include "pch.h"
#include <iostream>


extern "C" __declspec(dllexport)
void hello_world()
{
	std::cout << "Hello world!" << std::endl;
}

extern "C" __declspec(dllexport)
int add(int x, int y)
{
	return x + y;
}

extern "C" __declspec(dllexport)
int sub(int x, int y)
{
	return x - y;
}

extern "C" __declspec(dllexport)
void print_result(int x)
{
	std::cout << "The result is " << x << std::endl;
}

代码添加完成后,直接编译即可。

2.2 动态库的调用

如果是使用.def文件生成的DLL库,调用方式有两种:隐式调用和显式调用。

1. 隐式调用

将.dll、.lib、.h文件拷贝到调用库文件的工程目录下,通过在调用代码中包含库的头文件、并通过#pragma comment(lib,"xxx.lib")加载动态链接库中的信息(注意,此处与静态库的调用不一样),实现对动态库DLL的调用,示例代码如下:

#include <iostream>
#include "my_dll_test.h"

#pragma comment(lib,"my_dll_test.lib")

using namespace std;

int main()
{
	hello_world();
	int sum = add(5, 10);
	int diff = sub(5, 10);
	print_result(sum);
	print_result(diff);

	return 0;
}

2. 显式调用

显式调用是借助Windows库的LoadLibrary来显式地加载DLL库,这种方式不需要注册.lib文件,且只在需要的地方加载DLL库即可。代价是代码略显复杂。

// explicit call
#include <iostream>
#include <windows.h>

int main()
{
	HINSTANCE hInst;
	hInst = LoadLibrary(L"my_dll_test.dll");

	// Test library function hello_world()
	typedef void(*Hello)();//函数指针
	Hello hello_world= (Hello)GetProcAddress(hInst, "hello_world");//从dll中加载函数进来
	hello_world();//运行函数

	// Test library function add()
	typedef int(*Add)(int, int);//函数指针
	Add add = (Add)GetProcAddress(hInst, "add");//从dll中加载函数进来
	int sum = add(100, 200);
	std::cout << "sum = " << sum << std::endl;

	// Test library function sub()
	typedef int(*Substract)(int, int);//函数指针
	Substract sub = (Substract)GetProcAddress(hInst, "sub");//从dll中加载函数进来
	int diff = sub(100, 200);
	std::cout << "diff = " << diff << std::endl;

	// Test library function print_result()
	typedef void(*PrintResult)(int);//函数指针
	PrintResult print_result = (PrintResult)GetProcAddress(hInst, "print_result");
	print_result(sum);
	print_result(diff);

	FreeLibrary(hInst);       //LoadLibrary后要记得FreeLibrary

	return 0;
}

 3. __declspec(dllimport)调用

如果是__declspec(dllexport)方式生成的dll,需要使用对应的__declspec(dllimport)来导入动态库函数并调用。示例代码如下:

#include <iostream>

#pragma comment(lib,"my_dll_test.lib")

extern "C" __declspec(dllimport) void hello_world();
extern "C" __declspec(dllimport) int add(int x, int y);
extern "C" __declspec(dllimport) int sub(int x, int y);
extern "C" __declspec(dllimport) void print_result(int x);

using namespace std;

int main()
{
	hello_world();
	int sum = add(5, 10);
	int diff = sub(5, 10);
	print_result(sum);
	print_result(diff);

	return 0;
}

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用Python创建动态链接库(DLL),你可以使用`ctypes`库。下面是一个简单的示例: 1. 首先,创建一个Python脚本(例如,`example.py`),并在其中编写你的函数或代码逻辑。 ```python # example.py def add_numbers(a, b): return a + b ``` 2. 使用`cython`库将Python代码转换为C代码。你可以使用以下命令安装`cython`: ``` pip install cython ``` 然后,创建一个名为`example.pyx`的文件,其中包含以下内容: ```python # example.pyx def add_numbers(a, b): return a + b ``` 3. 创建一个名为`setup.py`的文件,用于构建和编译动态链接库。在其中添加以下代码: ```python # setup.py from distutils.core import setup from Cython.Build import cythonize setup(ext_modules=cythonize("example.pyx")) ``` 4. 打开命令提示符或终端,并导航到包含上述文件的目录。然后运行以下命令来构建和编译动态链接库: ``` python setup.py build_ext --inplace ``` 这将生成一个名为`example.so`(Linux / macOS)或`example.dll`(Windows)的动态链接库文件。 5. 现在你可以在其他Python脚本中使用这个动态链接库。例如,创建一个名为`main.py`的文件,并添加以下代码: ```python # main.py from ctypes import CDLL # 加载动态链接库 example = CDLL('./example.so') # 替换为example.dll(Windows) # 调用动态链接库中的函数 result = example.add_numbers(2, 3) print(result) ``` 运行`main.py`脚本,将会输出结果 `5`。 这就是使用Python创建动态链接库的基本步骤。你可以根据自己的需求进行修改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值