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

news2024/11/24 16:02:04

工程需要,最近在编一组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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/605809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hive on Spark环境搭建

Hive 引擎简介 Hive 引擎包括&#xff1a;默认 MR、tez、spark 最底层的引擎就是MR &#xff08;Mapreduce&#xff09;无需配置&#xff0c;Hive运行自带 Hive on Spark&#xff1a;Hive 既作为存储元数据又负责 SQL 的解析优化&#xff0c;语法是 HQL 语法&#xff0c;执行…

(2.54mm)TSM-120-04-S-DV-P-TR方形接线柱针脚、ADRF5545ABCPZN(通用)射频前端 SPDT

TSM-120-04-S-DV-P-TR (2.54mm) 表面安装.025"方形接线柱针脚是板对板连接器&#xff0c;有单排、双排或三排方形接线柱端子可供选择&#xff0c;带直通、直角或混合技术引脚。这些高度可靠的坚固针脚有垂直和水平两种方向&#xff0c;在混合气流 (MFG) 环境中可使用10年。…

【C语言实现简易ATM】上个C语言程序设计课,我成产品经理了?

博主简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的人。 博主主页&#xff1a;陈童学哦 所属专栏&#xff1a;C语言程序设计实验项目 如果本文对你有所帮助的话&#xff0c;还希望可以点赞&#x1f44d;收藏&#x1f4c2;支持一下…

2023年京东618满300减50是全品类吗?满减叠卷怎么用?

2023年京东618满300减50是全品类吗?满减叠卷怎么用? 京东平台上有着比较多的卖家开店&#xff0c;在对店铺进行运营的过程中&#xff0c;很多卖家都会参与平台的一些活动&#xff0c;这样能够有效的将产品推广出去&#xff0c;对于618大促活动也是属于其中活动之一&#xff0…

代码随想录第52天

1.最长递增子序列 接下来&#xff0c;我们依然用动规五部曲来详细分析一波&#xff1a; dp[i]的定义 本题中&#xff0c;正确定义dp数组的含义十分重要。 dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度 为什么一定表示 “以nums[i]结尾的最长递增子序” &…

软考A计划-电子商务设计师-专业英语

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

高压侧电流检测电路及仿真

高压侧电流检测电路仿真 电路图如下 主要设计思想&#xff0c;通过两组电阻将高压侧的共模电压降到运放&#xff08;此处也可以使用单电源运放&#xff09;的工作电压范围内。然后在进行二次放大。主要放大倍数取决于第二级放大侧电阻比值。因此如果需要减小功耗&#xff0c;可…

JAVA数组基础

目录 一、使用方式 1-动态初始化 ①先声明数组 ② 创建数组 ③分配方式 二、使用方式 2-静态初始化&#xff08;直接在声明的同时初始化{ } &#xff09; 三、数组使用注意事项和细节 四、数组两种初始化方式都是将内存空间分配到堆上面的 一、使用方式 1-动态初始化 …

助力金融科技创新 同创永益受邀参加2023 INNO CHINA中国产业创新大会

2023 INNO CHINA中国产业创新大会-金融科技创新论坛于2023年5月10日在北京大学中关村新园召开。本次金融科技创新论坛由北大创新评论主办&#xff0c;同创永益受邀参加。来自银行、保险、证券等金融行业的多位专家、学者齐聚大会现场&#xff0c;共同探讨金融科技发展新趋势、金…

复合型人才

一、为什么需要复合型人才 在社会的大环境影响下&#xff0c;不同行业所展现的交叉属性越来越强&#xff0c;因此单一型人才已经不满足当前的企业需求&#xff0c;复合型人才逐渐成为市场上的主流&#xff1a;即不仅要“精”通&#xff0c;更要“全”面。 人才需求是由市场的…

长尾词挖掘,如何选择精准的长尾词优化?

长尾词的挖掘也是一门大学问&#xff0c;它存在多种不同的方法。最常用的方法是把关键词直接放搜索引擎的搜索框搜索和使用长尾词挖掘工具这两种。 以运动水壶为例。 关键词直接放搜索引擎的搜索框搜索&#xff0c;结果如下&#xff1a; 使用长尾词挖掘工具&#xff0c;…

【C++笔记总结】面向对象编程——封装 |C++

文章目录 前言一、类的封装1.1、公有&#xff0c;私有&#xff0c;保护1.2、类的定义和类的实现相分离1.3、构造函数&#xff0c;析构函数&#xff0c;拷贝构造函数1.4、静态数据成员和静态成员函数1.5、友元函数&#xff0c;友元类 二、类的实现——对象2.1、对象的静态分配&a…

【SpringCloud——Elasticsearch(中)】

一、DSL查询语法以及整合JAVA代码使用 以下操作案例均基于上篇的hotel索引库及其数据进行。 1、查询基本语法 GET /indexName/_search {"query": {"查询类型":{"查询条件":"条件值"}} } 2、查询所有 2.1、DSL语句 #查询所有 GET …

Java并发编程面试题——线程池

目录 1.什么是线程池&#xff1f;有什么优缺点&#xff1f;2.创建线程池的方式有哪些&#xff1f;2.1.通过 Executor 框架的工具类 Executors 来创建不同类型的线程池2.2.使用 ThreadPoolExecutor 类自定义线程池2.3.注意事项 3.自定义线程池时有哪些参数&#xff1f;它们各有说…

笔试强训 Day 7

选择题&#xff1a; 1.在&#xff08;&#xff09;情况下适宜采用 inline 定义内联函数 A 函数体含有循环语句 B 函数体含有递归语句C 函数代码少、频繁调用 D 函数代码多&#xff0c;不常调用 复习一下内联函数 在编译阶段&#xff0c;会将内联函数展开 —— 将函数调用替换成…

四、若依(前后端分离)项目构建docker 镜像

若依(前后端分离&#xff09;项目构建docker 镜像 1. 构建好ruoyi-admin.jar包&#xff0c;上传到服务器项目目录下 2. 创建conf目录将若依项目&#xff08;Spring boot &#xff09;配置文件修改好&#xff0c;上传存入conf目录 注意&#xff1a;这里的地址不能写127.0.0.1和…

ur5在gazebo中仿真的官方源码浅析

一 复现 好久之前初学rosgazebo机械臂仿真的时候总有些懵&#xff0c;用的是ur5机械臂&#xff0c;现在回过头来看好像看懂了一些&#xff0c;故重新理清了一下功能包的逻辑&#xff0c;方便查阅。 官方源码 本文参考 ubuntu16.04安装UR3/UR5/UR10机械臂的ROS驱动并实现gazebo…

chatgpt赋能python:Python列表从后往前遍历

Python列表从后往前遍历 作为一门广泛应用于数据科学、机器学习和Web开发的高级编程语言&#xff0c;Python为开发人员和科学家们提供了很多便利。在Python里面&#xff0c;列表是一种非常常见的数据结构&#xff0c;它允许开发人员存储和处理多个元素。但是&#xff0c;有时候…

5款提高工作效率的无广告软件

今天推荐一些可以大幅度提升办公效率的小软件&#xff0c;安全无毒&#xff0c;下载简单&#xff0c;最重要的是没有广告&#xff01; 1.照片处理——Darktable Darktable是一款用于处理和管理数码照片的工具。它可以让你对RAW格式的照片进行非破坏性的编辑,并提供多种模块和…

Nginx 启动成功无法访问网页

查看是否有Nginx进程 ps -ef | grep nginx 如下图有三个进程就是启动成功了 端口 因为Nginx我配置的是80端口&#xff0c;所以只要检查80端口是否开放即可 netstat -lnt | grep 80tcp: 这表示所显示的连接是基于TCP协议的。0.0.0.0:80: 这是本地监听的IP地址和端口号。在这…