24 KiB
title | date | author | top | cover | toc | mathjax | summary | tags | categories | abbrlink | reprintPolicy | coverImg | img | password |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
dll生成与使用 | 2022年4月28日 | TianZD | true | true | true | false | dll是动态链接库,基于C++语言,使用不同的IDE进行测试使用 | [C++ DLL] | [C++] | fbf11e59 | cc_by | <nil> | <nil> | <nil> |
[toc]
DLL生成与使用(Clion、Visual Studio、QT Creator)
DLL概述
本节内容来自博客
它们是一些独立的文件,其中包含能被可执行程序或其他dll调用来完成某项工作的函数,只有在其他模块调用dll中的函数时,dll才发挥作用。
在实际编程中,我们可以把完成某项功能的函数放在一个动态链接库里,然后提供给其他程序调用。像Windows API中所有的函数都包含在dll中,如Kernel32.dll, User32.dll, GDI32.dll等。那么dll究竟有什么好处呢?
静态库和动态库
静态库
函数和数据被编译进一个二进制文件(扩展名通常为.lib)
在使用静态库的情况下,在编译链接可执行文件时,链接器从静态库中复制这些函数和数据,并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.exe)。当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。
动态库
在使用动态库时,往往提供两个文件:一个引入库(.lib,非必须)和一个.dll文件。
这里的引入库和静态库文件虽然扩展名都是.lib,但是有着本质上的区别,对于一个动态链接库来说,其引入库文件包含该动态库导出的函数和变量的符号名,而.dll文件包含该动态库实际的函数和数据。
使用动态链接库的好处
-
可以使用多种编程语言编写:比如我们可以用VC++编写dll,然后在VB编写的程序中调用它。
-
增强产品功能:可以通过开发新的dll取代产品原有的dll,达到增强产品性能的目的。比如我们看到很多产品踢动了界面插件功能,允许用户动态地更换程序的界面,这就可以通过更换界面dll来实现。
-
提供二次开发的平台:用户可以单独利用dll调用其中实现的功能,来完成其他应用,实现二次开发。
-
节省内存:如果多个应用程序使用同一个dll,该dll的页面只需要存入内存一次,所有的应用程序都可以共享它的页面,从而节省内存。
Clion+msvc
生成的dll可以在使用同一编译器的不同sdk之间相互调用,但是要注意导出的是x86还是amd64
生成DLL
新建项目
C++库-类型选择shared
新建文件
右键项目,新建C/C++源文件(或者直接新建一个类),并勾选创建关联头
编写c++
注意,类中的变量和函数只有public类型才可以被访问。
- 使用
__declspec(dllexport)
声明导出函数 - 使用
extern “C” __declspec(dllexport)
声明导出函数为c类型
DllDemo.h
#ifndef DLLDEMO_DLLDEMO_H
#define DLLDEMO_DLLDEMO_H
#include <string>
#include <iostream>
using namespace std;
//使用__declspec(dllexport)声明导出函数
__declspec(dllexport) void sayHello();
__declspec(dllexport) int myAdd(int a, int b);
class __declspec(dllexport) Student{
public:
void setName(string name);
string getName();
void setAge(int age);
int getAge();
private:
string name;
int age;
};
#endif //DLLDEMO_DLLDEMO_H
DllDemo.cpp
#include "DllDemo.h"
void sayHello(){
cout<<"hello!"<<endl;
}
int myAdd(int a, int b){
return a+b;
}
void Student::setName(string name) {
this->name = name;
}
string Student::getName() {
return this->name;
}
void Student::setAge(int age) {
this->age = age;
}
int Student::getAge() {
return this->age;
}
构建
在debug目录下生成了.dll、.lib文件,后续主要用到.dll、.lib和.h文件
测试
测试工具目录:
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin
win+r——cmd 进入上述目录
输入
VCVARS32.bat
然后进入dll文件所在目录
输入
dumpbin -exports Dll1.dll
使用dll
新建项目
在项目根目录新建lib文件夹
将上述生成的.dll .lib 和编写的.h文件复制到lib中
cmakelist.txt添加:
cmake_minimum_required(VERSION 3.21)
project(DllUser)
set(CMAKE_CXX_STANDARD 14)
# 包含文件夹
include_directories(
src
lib
)
# 新增;指项目根目录下的lib目录
link_directories(lib)
add_executable(${PROJECT_NAME}
src/main.cpp)
# 新增;指目标链接的dll文件
target_link_libraries(${PROJECT_NAME}
DllDemo)
运行/调试配置:
调用
方法1:lib中需要dll、lib和h三个文件
- 添加头文件
- 调用函数
#include <iostream>
//添加头文件
#include "lib/DllDemo.h"
int main() {
sayHello();
cout<<myAdd(1,2)<<endl;
Student tian;
tian.setName("TianZD");
tian.setAge(18);
cout<<"tian'name:"<<tian.getName()<<endl;
cout<<"tian'age:"<<tian.getAge()<<endl;
return 0;
}
可以看到正确输出
方法2:lib中只需要dll文件
#include <iostream>
#include <string>
using namespace std;
//#include "lib/DllDemo.h"
__declspec(dllimport) void sayHello();
__declspec(dllimport) class Student{
private:
string name;
int age;
public:
void setName(string name);
void setAge(int age);
string getName();
int getAge();
};
int main() {
sayHello();
Student tian;
tian.setName("TianZD");
tian.setAge(18);
cout<<"tian'name:"<<tian.getName()<<endl;
cout<<"tian'age:"<<tian.getAge()<<endl;
return 0;
}
多层调用
DllDemo1使用上面的
新建DllDemo2 Dll文件
将DllDemo1的dll、lib、h文件放入lib中
编写DllDemo2.h和.cpp
情况一:2的h中没有引入1的内容,cpp中引入
#ifndef DLLDEMO2_DLLDEMO2_H
#define DLLDEMO2_DLLDEMO2_H
//#include "DllDemo.h"
__declspec(dllexport) void sayHello2();
__declspec(dllexport) int myAdd2(int a, int b);
//class __declspec(dllexport) Student2: Student{
//public:
// void setAge2(int age);
// int getAge2();
//private:
// int age2;
//};
#endif //DLLDEMO2_DLLDEMO2_H
#include "DllDemo2.h"
#include "DllDemo.h"
#include <iostream>
using namespace std;
void sayHello2(){
cout<<"hello2222"<<endl;
sayHello();
Student tian;
tian.setName("dlldemo2");
cout<<tian.getName()<<endl;
}
int myAdd2(int a, int b){
return myAdd(a,b);
}
//
//void Student2::setAge2(int age) {
// this->age2 = age;
//}
//
//int Student2::getAge2() {
// return this->age2;
//}
情况二:2的h中引入了1的内容
#ifndef DLLDEMO2_DLLDEMO2_H
#define DLLDEMO2_DLLDEMO2_H
#include "DllDemo.h"
__declspec(dllexport) void sayHello2();
class __declspec(dllexport) Student2: Student{
public:
void setAge2(int age);
int getAge2();
private:
int age2;
};
#endif //DLLDEMO2_DLLDEMO2_H
#include "DllDemo2.h"
void sayHello2(){
cout<<"hello2222"<<endl;
sayHello();
}
void Student2::setAge2(int age) {
this->age2 = age;
}
int Student2::getAge2() {
return this->age2;
}
cmaklists.txt
cmake_minimum_required(VERSION 3.21)
project(DllDemo2)
set(CMAKE_CXX_STANDARD 14)
include_directories(
src
lib
)
# 新增;指项目根目录下的lib目录
link_directories(
lib
)
add_library(${PROJECT_NAME} SHARED
"src/library.cpp"
"src/DllDemo2.cpp"
"src/DllDemo2.h")
# 新增;指目标链接的dll文件
target_link_libraries(${PROJECT_NAME}
DllDemo
)
添加lib到编译器的环境变量中
使用
情况一:2的h中没有引入1的内容,cpp中引入了
只需要将1的dll和2的三个文件放入lib中
添加lib环境变量
cmake
cmake_minimum_required(VERSION 3.21)
project(DllUser2)
set(CMAKE_CXX_STANDARD 14)
include_directories(
src
lib
)
# 新增;指项目根目录下的lib目录
link_directories(lib)
add_executable(${PROJECT_NAME}
src/main.cpp)
# 新增;指目标链接的dll文件
target_link_libraries(${PROJECT_NAME}
#只需要dlldemo2
DllDemo2)
测试
#include <iostream>
#include "DllDemo2.h"
int main() {
std::cout << "Hello, World!" << std::endl;
sayHello2();
std::cout<<myAdd2(1,2);
return 0;
}
情况二:2的h中引入1
将1和2的三个文件(dll、lib、h)均放入到lib中
添加lib目录环境变量
添加cmakelists.txt
cmake_minimum_required(VERSION 3.21)
project(DllUser2)
set(CMAKE_CXX_STANDARD 14)
include_directories(
src
lib
)
# 新增;指项目根目录下的lib目录
link_directories(lib)
add_executable(${PROJECT_NAME}
src/main.cpp)
# 新增;指目标链接的dll文件
target_link_libraries(${PROJECT_NAME}
DllDemo
DllDemo2)
调用
#include <iostream>
#include "DllDemo2.h"
int main() {
std::cout << "Hello, World!" << std::endl;
Student tian;
tian.setAge(12);
cout<<tian.getAge()<<endl;
Student2 tian2;
tian2.setAge2(123);
cout<<tian2.getAge2()<<endl;
sayHello2();
return 0;
}
Visual Studio + msvc(c++)
参考博客
可以直接调用clion生成的dll
生成平台X86、X64要注意,要和要调用该DLL的平台保持一直
生成dll
把 函数 声明放在 .h 中,函数定义放在 cpp 文件中。
新建项目
选择具有导出项的DLL动态链接库
导出
声明导出
头文件
// 以c方式导出
extern "C" __declspec(dllexport) void hello();
// 以c++方式导出
__declspec(dllexport) void hello1();
//以.def方式导出
void hello2();
源文件
#include "pch.h"
#include "framework.h"
#include "Dll2.h"
#include <iostream>
void hello() {
std::cout << "hello" << std::endl;
}
void hello1() {
std::cout << "hello11" << std::endl;
}
void hello2() {
std::cout << "hello2" << std::endl;
}
然后点击 “ 生成解决方案 ” ,就可以在工程目录的 debug 目录或者 release 目录下(这取决你生成的是debug版本还是release版本)生成了动态链接库的相关文件。第三方调用时关键的文件为 .lib文件 和 .dll文件 以及工程目录下的 .h头文件 。
模板定义方式导出
在项目中定义.def 文件,该文件为模块导出文件
编写.def
LIBRARY
EXPORTS
hello2
如果是vs平台,需要在连接器中添加.def文件,如果是通过上述方式添加的.def,会自动添加
然后点击 “ 生成解决方案 ” ,就可以在工程目录的 debug 目录或者 release 目录下(这取决你生成的是debug版本还是release版本)生成了动态链接库的相关文件。第三方调用时关键的文件为 .lib文件 和 .dll文件 以及工程目录下的 .h头文件
调用
新建C++控制台应用
将dll放在debug目录下,将h和lib放在可以找到的位置
.dll文件是程序运行需要载入的动态链接库,VS中调试时可以通过 项目->属性->调试->环境 栏目添加.dll文件的 path 而成功调试,但在独立运行.exe程序是须将.dll文件放到同一目录下。
因此建议直接将 .dll文件 放入debug目录下或release目录下。.h头文件 和 .lib库文件 可以随意放置,只要是能够通过路径找到即可,为了方便管理,建议建立文件夹,放置在项目目录下。
调用
可以直接调用Clion生成的Dll,应该只与编译器有关(用的都是同一个版本的msvc)
直接添加引用
#include <iostream>
#include "Dll2.h"//通过相对路径或绝对路径添加头文件
#pragma comment (lib,"Dll2.lib") // 添加 lib 文件
int main()
{
std::cout << "Hello World!\n";
hello();
hello1();
hello2();
}
在解决方案管理面板中添加头文件和资源文件
添加一个现有项头文件,在文件夹中找到第三方库的头文件( .h ),添加进新建立的项目。 添加一个现有项资源文件,在文件夹中找到第三方库的库文件( .lib ),添加进新建立的项目。
#include <iostream>
#include "Dll2.h"//通过相对路径或绝对路径添加头文件
//#pragma comment (lib,"Dll2.lib") // 添加 lib 文件
int main()
{
std::cout << "Hello World!\n";
hello();
hello1();
hello2();
}
在 项目属性 -> 设置 中 添加 头文件 和 库文件
- 项目->属性->VC++目录->包含目录 中添加第三方库的 头文件;
- 库目录 下 添加 第三方库 的 库文件(.lib文件)。
- 项目->属性->链接器->输入->附加依赖项中输入 库文件名称。
直接在代码load动态库文件
这种方法不需要 include .h文件,不需要添加 lib库 和 lib库路径,
- 引入 windows.h(必须)
- 在 main 函数写下列语句调用 dll
因为 C++ 声明 的 动态链接库会发生 Name Mangling,导致 编译后的函数名字会发生变化,所以需要使用 工具 查看 编译编译后的 动态链接库 对应的函数名。
而 extern "C" 声明的 和 def 文件声明的,编译后的函数名不会发生变化,可以直接使用。
VS2019 自带的工具 dumpbin.exe 可以查看编译后的 动态链接库对应的 函数名。
打开命令行,输入命令 dumpbin -exports Dll2.dll
这里采用博客中的内容:
所以 addFunc 不能直接使用,只能用被 name Mangling 后的名字 ,这里 addFunc 编译后的名字是 ?addFunc@@YAHHH@Z
#include <iostream>
#include <windows.h>
// 加、减、乘 都是 int 类型
typedef int(*lpFunc)(int a, int b); //后边为参数,前面为返回值
// 除法 是 double 类型
typedef double(*lpFuncD)(double a, double b); //后边为参数,前面为返回值
int main()
{
std::cout << "Hello World!\n";
HMODULE hModule;
hModule = LoadLibrary(L"CPPDLL.dll"); //调用DLL
lpFunc lpfunc = NULL;
// GetProcAddress为获取该函数的地址
// "?addFunc@@YAHHH@Z" 这个就是 C++ Name Mangling后的 addFunc 的函数名
lpfunc = (lpFunc)GetProcAddress(hModule, "?addFunc@@YAHHH@Z");
std::cout << lpfunc(1, 2) << std::endl;
/* 使用 C extern 和 def 文件定义的动态链接库,函数名不会发生变化 */
lpfunc = (lpFunc)GetProcAddress(hModule, "subFunc");
std::cout << lpfunc(3, 4) << std::endl;
lpfunc = (lpFunc)GetProcAddress(hModule, "mulFunc");
std::cout << lpfunc(5, 6) << std::endl;
lpFuncD lpfuncd = NULL;
lpfuncd = (lpFuncD)GetProcAddress(hModule, "divFunc");
std::cout << lpfuncd(7, 8) << std::endl;
//释放
FreeLibrary(hModule);
}
使用lib和dll文件
- 把 CPPDLL.dll 文件放到 debug 目录下,
- 然后在项目中引入 CPPDLL.lib 文件。 链接器 -> 输入 -> 附加依赖项 -> 编辑
#include <iostream>
// addFunc 是使用 C++ 方式声明的,
_declspec(dllimport) int addFunc(int a, int b);
//subFunc 是 使用 extern "C" 声明的
extern "C" _declspec(dllimport) int subFunc(int a, int b);
// mulFunc 和 divFunc 是 使用 def 声明的
_declspec(dllimport) int mulFunc(int a, int b);
_declspec(dllimport) double divFunc(double a, double b);
int main()
{
std::cout << "Hello World!\n";
std::cout << addFunc(1, 2) << std::endl;
std::cout << subFunc(3, 4) << std::endl;
std::cout << mulFunc(5, 6) << std::endl;
std::cout << divFunc(7, 8) << std::endl;
return 0;
}
还可以结合 第一种方法,使用 #pragma comment (lib,"./CPPDLL.lib") //添加 lib 文件
这样就不用 手动设置 添加 lib 文件了
多层调用
只需要把多层的dll都放进去即可
VisualStudio (c#)调用c++的DLL
个人一般只用c#调用c++代码
可以直接调用Clion生成的DLL,但是只能调用extern “C”声明的导出函数
注意:一般msvc版本是X86的,要注意,使用x64位的netCore无法调用,这里我用的是netFramwork
生成DLL
使用Clion,也可以使用visualStudion,生成方法同上,这里只列出代码部分
第一层:
#ifndef DLLDEMO_DLLDEMO_H
#define DLLDEMO_DLLDEMO_H
#include <string>
#include <iostream>
using namespace std;
extern "C" __declspec(dllexport) void sayHello();
__declspec(dllexport) void sayHello1();
extern "C" __declspec(dllexport) int myAdd(int a, int b);
class Student{
public:
string name;
void studentHello();
};
#endif //DLLDEMO_DLLDEMO_H
#include "DllDemo.h"
void Student::studentHello() {
cout<<"studentHello"<<endl;
}
void sayHello(){
Student s;
s.studentHello();
cout<<"hello1111111!"<<endl;
}
void sayHello1(){
Student s;
s.studentHello();
cout<<"hello1111111!"<<endl;
}
int myAdd(int a, int b){
return a+b;
}
第二层
将1的三个文件放入lib
配置编译器lib环境变量
添加cmakelists.txt
编写调用代码
//
// Created by 12038 on 2022/4/15.
//
#ifndef DLLDEMO2_DLLDEMO2_H
#define DLLDEMO2_DLLDEMO2_H
//#include "DllDemo.h"
extern "C" __declspec(dllexport) void sayHello2();
extern "C" __declspec(dllexport) int myAdd2(int a, int b);
class Student2{
public:
void student2Hello();
};
#endif //DLLDEMO2_DLLDEMO2_H
//
// Created by 12038 on 2022/4/15.
//
#include "DllDemo2.h"
#include "DllDemo.h"
#include <iostream>
using namespace std;
void sayHello2(){
cout<<"hello2222"<<endl;
sayHello();
Student2 tian;
tian.student2Hello();
}
int myAdd2(int a, int b){
return myAdd(a,b);
}
//
void Student2::student2Hello() {
cout<<"student22222Hello"<<endl;
}
visual studio调用
建立netframework项目
将两个dll文件放到debug目录下
调用
添加using System.Runtime.InteropServices;
导入库文件[DllImport("DllDemo2.dll")]
导入函数private static extern void sayHello2();
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//添加相应的库
using System.Runtime.InteropServices;
namespace ConsoleApp3
{
class Program
{
//调用
[DllImport("DllDemo2.dll")]
private static extern void sayHello2();
[DllImport("DllDemo2.dll")]
private static extern int myAdd2(int a, int b);
static void Main(string[] args)
{
//使用
sayHello2();
Console.WriteLine(myAdd2(1, 2));
}
}
}
QT Creator + msvc
生成dll
- 新建dll项目
生成后的结构如下(这里用的的DllDemo1):
其中_global.h自带两个系统默认导出宏
#ifndef DLLDEMO1_GLOBAL_H
#define DLLDEMO1_GLOBAL_H
#include <QtCore/qglobal.h>
#if defined(DLLDEMO1_LIBRARY)
# define DLLDEMO1_EXPORT Q_DECL_EXPORT
#else
# define DLLDEMO1_EXPORT Q_DECL_IMPORT
#endif
#endif // DLLDEMO1_GLOBAL_H
- 编写导出函数
头文件:
#ifndef DLLDEMO1_H
#define DLLDEMO1_H
#include "DllDemo1_global.h"
class DLLDEMO1_EXPORT DllDemo1
{
public:
DllDemo1();
};
//声明导出的函数sayhello
DLLDEMO1_EXPORT void sayHello();
//声明导出的函数myadd
DLLDEMO1_EXPORT int myAdd(int a, int b);
#endif // DLLDEMO1_H
源文件:
#include "dlldemo1.h"
#include <iostream>
DllDemo1::DllDemo1()
{
}
//实现函数
void sayHello(){
std::cout<<"hello, dll in qt"<<std::endl;
}
int myadd(int a, int b){
std::cout<<"myadd"<<std::endl;
return a+b;
}
构建后会在debug目录下生成dll和lib文件,同时可能需要用到.h和_global.h文件
调用
新建c++控制台项目
构建项目,生成debug目录
将dll文件放到debug目录
将lib文件放到工程目录下
右键项目,添加lib库
选择外部库
选择刚才的lib文件,取消平台下的linux,mac,取消为debug版本添加d作为哦后缀,点击完成代码就被添加进call.pro
调用
- 方法1:
#include <QCoreApplication>
#include<iostream>
using namespace std;
__declspec(dllimport) void sayHello();
__declspec(dllimport) int myAdd(int a, int b);
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
cout<<myAdd(1,2)<<endl;
sayHello();
return a.exec();
}
- 方法2
将.h和_global.h放入到项目目录下
右键项目,添加两个.h文件
引入头文件
#include <QCoreApplication>
#include<iostream>
//引入头文件
#include "./dlldemo1.h"
using namespace std;
//__declspec(dllimport) void sayHello();
//__declspec(dllimport) int myAdd(int a, int b);
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
cout<<myAdd(1,2)<<endl;
sayHello();
return a.exec();
}