说明
windows版本,vs2019
创建一个动态库
新建一c++项目,创建一个dll类型项目。
在头文件中添加一个mylib.h文件:
·
#pragma once
#ifndef MYLIB_H
#define MYLIB_H
extern "C" __declspec(dllexport) void Hello();
extern "C" __declspec(dllexport) int Add(int a, int b);
template<typename T>
__declspec(dllexport) T __stdcall Jiafa(T a, T b) {
return a + b;
}
template __declspec(dllexport) int __stdcall Jiafa<int>(int a, int b);
template __declspec(dllexport) double __stdcall Jiafa<double>(double a, double b);
#endif
在该文件之中,声明了两个需要导入到dll中的普通函数。
同时,声明和实现了一个模板函数,并将模板函数进行了实例化。
在源文件中添加一个c++文件
// mylib.cpp
#include "pch.h"
#include <iostream>
// mylib.cpp
#include "pch.h"
#include <iostream>
#include "mylib.h"
using namespace std;
void Hello() {
cout << "Hello, world!" << endl;
}
int Add(int a, int b) {
return a + b;
}
编译生成dll文件。注意,生成的.dll文件和.lib文件和头文件,是需要提供给其他项目工程(调用dll中的函数)的。
问题:为什么要引用pch.h文件?
预编译使用,并且需要放在引用文件的首行。
问题:extern “C” 的作用有哪些?
extern “C” 是一个 C++ 语言的特性,用于指示编译器将某段代码按照 C 语言的规则进行编译和链接。在编写动态库时,使用 extern “C” 可以确保动态库中的函数能够按照 C 语言的规则进行导出和调用,从而提高动态库的可移植性和互操作性。
extern “C” 的作用包括:
消除 C++ 名字修饰
在 C++ 中,函数名会被编译器进行修饰,以便支持函数重载等特性。这种修饰会将函数名变得很长,不方便在 C 语言中进行链接。使用 extern “C” 可以告诉编译器不要对函数名进行修饰,从而使函数名变得简短、易于理解,并且能够在 C 语言中进行链接。
例如,在 C++ 中定义一个函数:
int Add(int a, int b) {
return a + b;
}
编译器会将函数名修饰为 _Z3Addii,而在使用 extern “C” 修饰后,编译器会将函数名保持不变,即为 Add。
指定函数调用约定
在不同的操作系统和编译器中,函数调用的约定可能不同。使用 extern “C” 可以指定函数调用的约定为 C 语言的约定,从而保证函数能够正确地被调用。
例如,在 Windows 操作系统中,使用 __stdcall 约定传递函数参数和返回值。在使用 extern “C” __stdcall 修饰后,编译器会生成按照 __stdcall 约定进行传递参数和返回值的代码。
支持动态链接
动态链接库中的函数需要按照 C 语言的规则进行导出,以便其他程序能够正确地链接和调用这些函数。使用 extern “C” 修饰动态链接库中的函数可以确保它们按照 C 语言的规则进行导出,并且能够被其他程序正确地链接和调用。
在使用 extern “C” 修饰动态链接库时,需要注意的是,在 C++ 中,函数参数和返回值的类型和数量可能会影响函数的名字,因此需要确保函数参数和返回值的类型和数量与函数声明中的一致,否则可能导致链接错误。
问题:模板类、模板函数的导出,需要注意什么呢?
注意,需要将模板函数实例化。
使用dll
创建一个新项目,用于测试和调用dll中的函数。
我手动创建了一个include文件夹,用于存放dll相关的头文件。需设置如下:
我手动创建了lib文件夹,用于存放dll和lib文件,将dll和lib文件拷贝到该路径,并进行设置。
设置“附加依赖项”。
写一个cpp文件,调用dll中的函数。
// main.cpp
#include <iostream>
#include <Windows.h>
#include "mylib.h"
using namespace std;
extern __declspec(dllimport) int __stdcall Jiafa<int>(int a, int b);
extern __declspec(dllimport) int Add(int a, int b);
int main() {
int result1 = Add(1, 2);
cout << "result = " << result1 << endl;
HINSTANCE hDLL = LoadLibrary(TEXT("mydll.dll"));
if (hDLL == NULL) {
cout << "Error: " << GetLastError() << endl;
return 1;
}
typedef void (*HelloFunc)();
HelloFunc helloFunc = (HelloFunc)GetProcAddress(hDLL, "Hello");
if (helloFunc == NULL) {
cout << "Error: " << GetLastError() << endl;
return 2;
}
helloFunc();
typedef int(*pAdd)(int, int);
pAdd Add = (pAdd)GetProcAddress(hDLL, "Add");
if (Add != NULL) {
int result = Add(1, 2);
cout << "result = " << result << endl;
}
int result = Jiafa<int>(3, 4);
cout << "result = " << result << endl;
FreeLibrary(hDLL);
return 0;
}
问题:设置正确仍然无法链接到dll。
将dll拷贝到了exe所在路径下。这个问题待修改。
最后输出如下:
OK,所有的函数都调用到了。
模板类导出到DLL中,并测试调用
具体工程的设置,类同上文,这里不具体描述,直接给出代码。
DLL中的头文件和源文件
//头文件,tmpclss.h
#pragma once
#ifndef TMPCLSS_H
#define TMPCLSS_H
#ifdef MYTEMCLASS_EXPORTS
#define TMPCLSS_API __declspec(dllexport)
#define TMPCLSS_TEMPLATE __declspec(dllexport)
#else
#define TMPCLSS_API __declspec(dllimport)
#define TMPCLSS_TEMPLATE __declspec(dllimport)
#endif
template <typename T>
class TMPCLSS_TEMPLATE Tmpclss
{
public:
Tmpclss() {};
T Add(T a, T b);
};
extern "C" template class TMPCLSS_TEMPLATE Tmpclss<int>;
extern "C" template class TMPCLSS_TEMPLATE Tmpclss<double>;
class TMPCLSS_API EasyClss
{
public:
EasyClss() {};
int Add(int a, int b);
};
#endif
源文件tmpclss.cpp
#include "pch.h"
#include "tmpclss.h"
using namespace std;
template <typename T>
T Tmpclss<T>::Add(T a, T b)
{
return a + b;
}
int EasyClss::Add(int a, int b)
{
return a + b;
}
测试调用代码如下:
#include <iostream>
#include "tmpclss.h"
using namespace std;
int main()
{
cout << "Hello World!" << endl;
Tmpclss<int> hisclass;
int result = hisclass.Add(7, 8);
cout << "result = " << result << endl;
EasyClss easyobject;
int result1 = easyobject.Add(7, 8);
cout << "result1 = " << result1 << endl;
return 0;
}