一、DLL的生成
选择“具有导出项的(DLL)动态链接库”,vs会帮我们自动创建与项目同名的.cpp文件和.h文件,并在.h文件中定义好相关导出符号;如果选择“动态链接库(DLL)”则不会创建上述文件。
头文件中为什么要有下面的代码呢?
#ifdef YAKEDLL_EXPORTS
#define YAKEDLL_API __declspec(dllexport)
#else
#define YAKEDLL_API __declspec(dllimport)
#endif
__declspec(dllexport)意思是导出符号到dll.
__declspec(dllimport)意思是从dll导入符号.
我们生成的时候,在项目属性中系统帮我们定义了YAKEDLL_EXPORTS宏,所以生成时是将符号导出到DLL中.
而我们在使用DLL的时候,没有定义到YAKEDLL_EXPORTS宏,所以使用的时候从DLL中导入符号到.exe文件,以供.exe文件使用.
接下来再头文件和cpp文件中添加想导出为dll的函数声明及实现。
yakeDLL.h头文件
// 下列 ifdef 块是创建使从 DLL 导出更简单的
// 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 YAKEDLL_EXPORTS
// 符号编译的。在使用此 DLL 的
// 任何项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将
// YAKEDLL_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的
// 符号视为是被导出的。
#ifdef YAKEDLL_EXPORTS
#define YAKEDLL_API __declspec(dllexport)
#else
#define YAKEDLL_API __declspec(dllimport)
#endif
#include <iostream>
using namespace std;
// 此类是从 dll 导出的
class YAKEDLL_API CyakeDLL {
public:
CyakeDLL(void);
// TODO: 在此处添加其他方法。
void Output(void);
};
// 导出变量符号到DLL中
extern YAKEDLL_API int nyakeDLL;
// 导出函数符号到DLL中
YAKEDLL_API int fnyakeDLL(void);
//导出函数(使用export)
YAKEDLL_API void fnMyDLLWithDllExport(ostream& os);
YAKEDLL_API int Add(int a, int b);
// 导出函数(C语言方式)
extern "C"
{
YAKEDLL_API void fnMyDLLWithExternC(ostream&os);
}
yakeDLL.cpp文件
// yakeDLL.cpp : 定义 DLL 的导出函数。
//
#include "pch.h"
#include "framework.h"
#include "yakeDLL.h"
// 这是导出变量的一个示例
YAKEDLL_API int nyakeDLL=0;
// 这是导出函数的一个示例。
YAKEDLL_API int fnyakeDLL(void)
{
return 0;
}
YAKEDLL_API int Add(int a, int b)
{
return a + b;
}
YAKEDLL_API void fnMyDLLWithDllExport(ostream& os)
{
os << "this is my dll with dllexport." << endl;
}
YAKEDLL_API void fnMyDLLWithExternC(ostream& os)
{
os << "this is my dll with extern C." << endl;
}
// 这是已导出类的构造函数。
CyakeDLL::CyakeDLL()
{
return;
}
void CyakeDLL::Output(void)
{
std::cout << "this is CyakeDLL::Output function!" << std::endl;
return;
}
然后编译,可以看到输出已经生成了想要的dll和lib文件。

.lib文件有两种,一种是静态库,是静态编译出来的,索引和实现都在其中。
另一种是与动态编译出来的dll文件对应的lib文件,一般是一些索引信息,具体的实现在dll文件中。(本例子的情况)
从开始菜单的Visual Studio 2017的开发人员命令提示符
处启动DUMPBIN工具,执行dumpbin -exports path\to\dll
命令分析生成的dll,查看编译器产生的函数名:
VC++编译器会针对C++函数使用名称修饰,而不会修饰以C方式声明的函数(其实也修饰了,在函数名前面加了一个下划线前缀(fnMyDLLWithExternC = @ILT+305(_fnMyDLLWithExternC)),这是C语言的函数调用约定默认为__cdecl
所导致);
二、DLL的引用
调用dll有两种链接方式:隐式链接和显式链接,无论哪种方式都要求将dll和exe放在同一目录下,或者设置环境变量,让系统搜索到。比较常用的是隐式链接。
1. 隐式链接
隐式链接需要三个文件:.h文件、.lib文件 和 .dll文件。可以通过三种方式调用
- 设置头文件包含目录和链接库目录。也就是.h文件 和 .lib文件,dll文件放在exe目录下或者设置环境变量,让系统搜索到。
- 利用预编译指令#pragma comment
#include "../yakeDLL/yakeDLL.h"
#pragma comment(lib, "../Debug/yakeDLL.lib")
- 如果DLL项目和控制台项目在同一解决方案下,可以采用直接添加引用的方式。
yakeEXE_DLLTest.cpp
#include <iostream>
#include "../yakeDLL/yakeDLL.h"
int main()
{
fnMyDLLWithDllExport(std::cout);
fnMyDLLWithExternC(std::cout);
int c = Add(1, 2);
cout << c << endl;
system("PAUSE");
}
2. 显式调用
- 显式链接只需要一个文件:.dll文件。
- 所谓显式链接,就是直接调用WIN32 API函数
LoadLibrary
、GetProcAddress
和FreeLibrary
显式地装载、卸载dll。
#include <Windows.h> // 必要头文件
#include <iostream>
using namespace std;
HINSTANCE hDLL; // Handle to DLL
void (*MyDLLExport)(ostream& os);
void (*MyDLLExportExternC)(ostream& os);
int(*MyAdd)(int , int );
int main()
{
hDLL = LoadLibrary(L"yakeDLL.dll");
MyDLLExport = (void (*)(ostream&)) GetProcAddress(hDLL, "fnMyDLLWithDllExport");
MyDLLExportExternC = (void (*)(ostream&)) GetProcAddress(hDLL, "fnMyDLLWithExternC");
MyAdd = (int (*)(int , int )) GetProcAddress(hDLL, "Add");
if (MyDLLExport == NULL || MyDLLExportExternC == NULL || MyAdd == NULL)
{
std::cout << "DLL load error.\n";
return -1;
}
MyDLLExport(std::cout);
MyDLLExportExternC(std::cout);
int c = MyAdd(1, 2);
cout << c << endl;
system("PAUSE");
FreeLibrary(hDLL);
return 0;
}
2.2 C# 显式调用dll
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.InteropServices; // DLL import
namespace DLLTest_CSharpConsole
{
class Program
{
[DllImport("YAKEDLL.dll")]
internal static extern int Add(int a, int b);
static void Main(string[] args)
{
Console.WriteLine(Add(1, 2));
Console.ReadLine();
}
}
}
[DllImport("YAKEDLL.dll", EntryPoint = "Add", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl)]