说明

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相关的头文件。需设置如下:

ncnn 编译动态库 编译生成动态库_#define


我手动创建了lib文件夹,用于存放dll和lib文件,将dll和lib文件拷贝到该路径,并进行设置。

ncnn 编译动态库 编译生成动态库_#include_02


设置“附加依赖项”。

ncnn 编译动态库 编译生成动态库_c++_03


写一个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所在路径下。这个问题待修改。

最后输出如下:

ncnn 编译动态库 编译生成动态库_ncnn 编译动态库_04

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;
}