原文地址: https://blog.csdn.net/chy555chy/article/details/53021250
DLL的创建
创建步骤
文件 -> 新建 -> 项目 -> “新建项目”对话框 -> “已安装” -> 模板 -> 其他语言 -> Vistual C++ -> Win32 控制台应用程序。
在”Win32 应用程序向导”对话框中
“控制台程序类型”选择”DLL(D)”
“附加选项”勾选”导出符号(X)”
导入(导出)标记的宏定义
下列 ifdef 块是创建使从 DLL 导出更简单的宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的DLLDEMO_EXPORTS符号编译的。在使用此 DLL 的任何其他项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将DLLDEMO_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的符号视为是被导出的
#ifdef DLLDEMO_EXPORTS
#define DLLDEMO_API __declspec(dllexport)
#else
#define DLLDEMO_API __declspec(dllimport)
#endif
导出符号标识的宏定义位于:解决方案资源管理器 -> 项目属性 -> “项目属性页”对话框 -> “配置属性” -> C/C++ -> 预处理器 -> 预处理器定义
修改DLL项目
DllDemo.h
#ifdef DLLDEMO_EXPORTS
#define DLLDEMO_API __declspec(dllexport)
#else
#define DLLDEMO_API __declspec(dllimport)
#endif
// 此类是从 DllDemo.dll 导出的
class DLLDEMO_API CDllDemo {
public:
CDllDemo(void);
// TODO: 在此添加您的方法。
};
extern DLLDEMO_API int nDllDemo;
extern "C" extern DLLDEMO_API int nExternCDllDemo;
DLLDEMO_API int fnDllDemo(void);
extern "C" DLLDEMO_API int fnExternCDllDemo(void);
char DLLDEMO_API fnDefault(char, int, float);
char DLLDEMO_API __stdcall fnstdcall(char, int, float);
char DLLDEMO_API __cdecl fncdecl(char, int, float);
char DLLDEMO_API __fastcall fnfastcall(char, int, float);
dllmain.cpp
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include
#include
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
/*
获取模块文件名,如果是dll的话,获取的是调用它的可执行文件路径
WINBASEAPI
_Success_(return != 0)
_Ret_range_(1, nSize)
DWORD WINAPI GetModuleFileNameW(
_In_opt_ HMODULE hModule,
_Out_writes_to_(nSize, ((return < nSize) ? (return + 1) : nSize)) LPWSTR lpFilename,
_In_ DWORD nSize
);
*/
TCHAR lpFilename[MAX_PATH];
DWORD ret = GetModuleFileName(NULL, lpFilename, MAX_PATH);
if (ret) {
_tprintf(_T("GetModuleFileName -> lpFilename=%s\n"), lpFilename);
}
else {
printf("GetModuleFileName -> fail(%ld)", GetLastError());
}
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
printf("DLL_PROCESS_ATTACH\n");
break;
case DLL_THREAD_ATTACH:
printf("DLL_THREAD_ATTACH\n");
break;
case DLL_THREAD_DETACH:
printf("DLL_THREAD_DETACH\n");
break;
case DLL_PROCESS_DETACH:
printf("DLL_PROCESS_DETACH\n");
break;
}
return TRUE;
}
DllDemo.cpp
// DllDemo.cpp : 定义 DLL 应用程序的导出函数。
#include "stdafx.h"
#include "DllDemo.h"
// 这是导出变量的一个示例
DLLDEMO_API int nDllDemo = 1;
DLLDEMO_API int nExternCDllDemo = 2;
// 这是导出函数的一个示例。
DLLDEMO_API int fnDllDemo(void)
{
return 42;
}
DLLDEMO_API int fnExternCDllDemo(void)
{
return 142;
}
char DLLDEMO_API fnDefault(char, int, float)
{
return 'a';
}
char DLLDEMO_API __stdcall fnstdcall(char, int, float)
{
return 'b';
}
char DLLDEMO_API __cdecl fncdecl(char, int, float)
{
return 'c';
}
char DLLDEMO_API __fastcall fnfastcall(char, int, float)
{
return 'd';
}
// 这是已导出类的构造函数。
// 有关类定义的信息,请参阅 DllDemo.h
CDllDemo::CDllDemo()
{
return;
}
生成DLL有以下几种方案
解决方案资源管理器 -> “DLL项目”右键 -> 生成(U)
菜单栏 -> 生产DLL(U)(Shift + F6)
然后会在项目的根目录的Debug或Release文件夹下生成相应的文件。
dumpbin查看dll(或lib)的导出符号
dumpbin.exe 位于 :C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin
查看编译后的DLL函数名用到的只是”EXPORTS”参数而已。函数名为name列等号的左侧。
可以看到extern “C”修饰的函数名与原函数名一致。原因可以参看《函数重载与Extern “C”》一文: http://blog.csdn.net/chy555chy/article/details/53015808。另外说明一点,使用extern “C”修饰的C++函数不能使用C++的重载特性,因为导出的函数符号只是原函数名,编译器没有将参数类型信息加到导出的函数名中,因此无法区分重载函数。
DLL的调用
导入DLL
(1)当”DLL项目”和”可执行项目”属于同一个解决方案时。在”DLL项目”右键”生成”可以在项目的Debug目录下生成相应的DLL和LIB。然后在”可执行项目”右键”设为启动项目”,然后点击”本地Windows调试器”即可。
静态调用
直接运行会报无法打开LIB(不加后缀默认是LIB)或DLL
解决方案:在”可执行项目”右键 -> 添加(D) -> 引用(R)…
可以看到引用处多了相应的DLL文件
动态调用
如果是动态调用,此时可正常运行
(2)如果是外部项目的dll
静态调用(有以下2种方法)
#param comment("lib", "path\\*lib")
中指定其路径。
解决方案资源管理器 -> “可执行项目”右键”属性” -> “项目属性页”对话框 -> 配置属性 -> “链接器” -> 输入 -> 附加依赖项目 -> 编辑 -> 输入lib文件的完整路径(而不是lib的文件名)
动态调用
LoadLibrary(_T("path\\*.dll"));
中指定其路径。
DLL调用有两种方式,一种是静态调用,另外一种是动态调用
静态调用(同时需要头文件、LIB和DLL文件,缺一不可)
静态调用是一种显式的调用方式,即在编程的时候便知道了被调用的DLL中的接口函数,在编译链接的时候将DLL与工程生成的exe相关联。
#include "stdafx.h"
#include "../DllDemo/DllDemo.h"
//lib后缀可以省略,但不可以改为dll
#pragma comment(lib, "DllDemo.lib")
int main()
{
printf("%d\n", nDllDemo);
printf("%d\n", fnDllDemo());
printf("%d\n", fnExternCDllDemo());
_tsystem(_T("pause"));
return 0;
}
动态调用(仅需要DLL,不需要头文件和LIB)
动态调用是一种隐式的调用方式,即程序运行过程中装载DLL,然后获取指定函数名称的接口函数,然后再调用之。
#include "stdafx.h"
#include
int main()
{
//参考 http://blog.csdn.net/g5dsk/article/details/6680698
HMODULE hModule = LoadLibrary(_T("DllDemo.dll"));// 虽然 MSDN Library 说这里如果指定了路径,要用 backslashes (\),不要用 forward slashes (/),但其实用二者都可以。注:如果用 \,要用 \\。
if (hModule == NULL || hModule == INVALID_HANDLE_VALUE) {
return -1;
}
/*
WINBASEAPI FARPROC WINAPI GetProcAddress(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName //这个是dump /EXPORT *.dll “name”列等号前的值
);
返回的是函数或变量的地址,即函数指针或指向变量地址的指针
*/
typedef int(*TYPE_fnDllDemo) ();
typedef int(*TYPE_fnExternCDllDemo) ();
int *nDllDemo = (int *)GetProcAddress(hModule, "?nDllDemo@@3HA");
TYPE_fnDllDemo fnDllDemo = (TYPE_fnDllDemo)GetProcAddress(hModule, "?fnDllDemo@@YAHXZ");
int *nExternCDllDemo = (int *)GetProcAddress(hModule, "nExternCDllDemo");
TYPE_fnExternCDllDemo fnExternCDllDemo = (TYPE_fnExternCDllDemo)GetProcAddress(hModule, "fnExternCDllDemo");
if(nDllDemo != NULL)
printf("*nDllDemo = %d\n", *nDllDemo);
if(fnDllDemo != NULL)
printf("fnDllDemo() = %d\n", fnDllDemo());
if (nExternCDllDemo != NULL)
printf("*nExternCDllDemo = %d\n", *nExternCDllDemo);
if(fnExternCDllDemo != NULL)
printf("fnExternCDllDemo() = %d\n", fnExternCDllDemo());
_tsystem(_T("pause"));
return 0;
}
运行截图
注意:DLL_PROCESS_DETACH是在关闭命令行的时候被调用,而不是不会调用。这里被system(“pause”)暂停了,并不是说程序结束。另外即使点击关闭,基本也看不到,因为打印完窗口立马就被关闭了,这些都是一瞬间完成的。