编译预处理(对源文件 .h .cpp等进行预处理,主要处理一些伪指令,即#定义的命令或语句(宏定义#define、头文件包括指令#include、条件编译指令#ifdef等)和特殊符号__LINE__(被解释为当前行号)、FILE(被解释为当前被编译的C源程序的名称)等,生成.i 文件)
#include
using namespace std;
#line 100
int main(){
cout << "__LINE__: " << __LINE__ << endl; //#line 使用之前是当前行数 8 #line 100 之后是108
cout << "__FILE__: " << __FILE__ << endl; //当前文件名
return 0;
}
编译(对预处理后的.i文件进行编译,主要进行词法分析、语法分析、语义分析等,生成.s的汇编文件)
汇编(将对应的汇编指令翻译成机器指令,生成可重定位的二进制目标文件 .o)
链接(.a /.lib .so/.dll)
潜在缺点:应用程序不是自包含的。它依赖于一个独立的DLL模块的存在——在安装过程中必须亲自部署或验证的模块
创建DLL项目,在项目下分别新建MathLibrary.h和MathLibrary.cpp,然后编译即可生成DLL文件
#pragma once
/*
* __declspec(dllexport) 和 __declspec(dllimport) 是特定于C 和 C++语言的扩展。可以使用它们从DLL中导出或向其中导入函数、数据和对象
* grammar:__declspec(dllimport) declarator; __declspec(dllexport) declarator;
*/
#ifdef MATHLIBRARY_EXPORTS
#define MATHLIBRARY_API __declspec(dllexport)
#else
#define MATHLIBRARY_API __declspec(dllimport)
#endif
extern "C" MATHLIBRARY_API void fibonacci_init(const unsigned long long a, const unsigned long long b);
extern "C" MATHLIBRARY_API bool fibonacci_next();
extern "C" MATHLIBRARY_API unsigned long long fibonacci_current();
extern "C" MATHLIBRARY_API unsigned fibonacci_index();
//MathLibrary.cpp
#include "pch.h"
#include
#include
#include "MathLibrary.h"
//DLL内部状态变量
static unsigned long long previous_;
static unsigned long long current_;
static unsigned index_;
//这个函数必须比其他函数先被调用
void fibonacci_init(const unsigned long long a, const unsigned long long b) {
index_ = 0;
current_ = a;
previous_ = b;
}
//生成序列中的下一个值。成功返回true,溢出返回false
bool fibonacci_next() {
if ((ULLONG_MAX - previous_ < current_) || (UINT_MAX == index_)) return false;
if (index_ > 0) previous_ += current_;
std::swap(current_, previous_);
++index_;
return true;
}
//获取序列中的当前值
unsigned long long fibonacci_current() {
return current_;
}
//获取序列中当前索引位置
unsigned fibonacci_index() {
return index_;
}
创建新的C++控制台项目MathClient充当客户端。新建MathClient.cpp。设置属性:
//MathClient.cpp
#include
#include "MathLibrary.h"
int main()
{
fibonacci_init(1, 1);
do {
std::cout << fibonacci_index() << ": " << fibonacci_current() << std::endl;
} while (fibonacci_next());
std::cout << fibonacci_index() + 1 << " Fibonacci sequence values fit in an unsigned 64-bit integer." << std::endl;
}
编译,运行可以看到结果
大多数应用程序会使用隐式链接,因为这是可使用的最简单的连接方法。
应用程序必须在运行时进行函数调用以显式加载DLL,若要显式链接到DLL,应用程序必须:
例,以下示例调用 LoadLibrary 以加载名为 “MyDLL” 的DLL,调用 GetProcAddress 以获取指向名唯 “DLLFunc1”的函数的指针,调用该函数并保存结果,然后调用 FreeLibrary 以卸载DLL
#include
#include
using namespace std;
typedef HRESULT(CALLBACK* LPFNDLLFUNC1)(DWORD, UINT*);
HRESULT LoadAndCallSomFunction(DWORD dwParam1, UINT* puParam2) {
HINSTANCE hDLL; //Handle to DLL
LPFNDLLFUNC1 lpfnDLLFunc1; //Function Pointer
HRESULT hrReturnVal;
//利用TEXT宏使其自动选择正确的字符集。或使用LoadLibraryA
//因为LoadLibrary在定义了UNICODE的情况下使用的是LoadLibraryW,需要的是UNICODE字符串也就是宽字符
hDLL = LoadLibrary(TEXT("D:\_fyyFolder\_fyy\work\cppProject\MathLibrary\Debug\MathLibrary.dll"));
if (NULL != hDLL) {
lpfnDLLFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1");
if (NULL != lpfnDLLFunc1) hrReturnVal = lpfnDLLFunc1(dwParam1, puParam2);
else hrReturnVal = ERROR_DELAY_LOAD_FAILED;
FreeLibrary(hDLL);
}
else {
hrReturnVal = ERROR_DELAY_LOAD_FAILED;
}
return hrReturnVal;
}
DllMian必须具有DLL入口点所需的签名。默认入口点函数_DllMainCRTStartup使用Windows传递的相同参数调用DllMain。默认如果未提供DllMain函数,VisualStudio将为你提供一个函数并链接它,以便_DllmainCRTStartup始终可以调用某些内容。
用于DllMain的签名:
#include
extern "C" BOOL WINAPI DllMain(
HINSTANCE const instance; //32bit unsigned long.Handle. struct HINSTANCE__{int unused;}
DWORD const reason; // 32bit unsigned long
LPVOID const reserved; // void *
);
DLL源代码必须包含名唯DllMain的函数,以下代码提供基本框架:
#incldue <windows.h>
extern "C" BOOL WINAPI DllMain(HINSTANCE const instance, DWORD const reason, LPVOID const reserved){
switch(reason){
case DLL_PROCESS_ATTACH:
//对每个新的线程进行初始化。如果加载DLL失败返回FALSE
break;
case DLL_THREAD_ATTACH:
//执行特定于线程的初始化
break;
case D__THREAD_DETACH:
//执行特定于线程的清理
break;
case DLL_PROCESS_DETACH:
//执行任何必要的清理
break;
}
return TRUE; //DLL_PROCESS_ATTACH成功
}
显式链接DLL的进程会调用GetProcAddress以获取DLL中导出函数的地址
可使用返回的函数指针调用DLL函数
采用句柄作为参数(LoadLibrary、AfxLoadLibrary、GetModuleHandle的返回值),并采用要调用的函数的名称或函数的导出序号
因为通过指针调用DLL函数,没有编译时类型检查,所以要确保函数的参数正确
通常使用 查看导出函数的函数原型并为函数指针创建匹配的typedef 来提供类型安全
typedef UINT (CALLBACK* LPFNDLLFUNC1)(DWORD, UINT);
...
HINSTANCE hDLL;
LPFNDLLFUNC1 lpfnDllFunc1;
DWORD dParam1;
UINT uParam2, uReturnVal;
hDLL = LoadLibrary("MathLibrary.dll");
if(NULL != hDLL){
lpfnDllFunc1 = (LPFNDLLFUNC1) GetProcAddress(hDLL, "DLLFunc1");
if(!lpfnDllFunc1){
FreeLibrary(hDLL);
return SOME_ERROR_CODE;
} else {
uReturnVal = lpfnDllFunc1(dwParam1, uParam2);
}
}
仅当要链接到DLL使用模块定义(.def)文件生成,并且序号随函数在DLL.def文件的EXPORTS节中列出时,才能获取导出序号。
如果DLL具有许多导出函数,则与函数名称相比,使用导出需要调用GetProcAddress会稍微快一些,因为到处序号充当DLL导出表中的索引。使用导出序号,GetProcAddress可以直接查找函数,而不是将指定名称与DLL导出表中的函数名进行比较
但仅当可控制将序号分配给.def文件中的导出函数时,才应使用导出序号调用
全文为MSDN — DLL的学习笔记