DLL可以将
变量、函数或C/C++类输出到其他模块。但是,在实际工作中,应该避免输出变量,因为这会删除你代码中的一个抽象层,使它更加难以维护你的DLL代码。此外,只有当使用同一个供应商提供的编译器对输入C++类的模块进行编译时们才能输出C++类。由于这个原因,也应该避免输出C++类,除非知道可执行模块的开发人员与使用的工具与DLL模块开发人员使用的工具相同。
——《Windows核心编程》
创建DLL
当创建DLL模块时,首先应该建立一个头文件,该文件包含了你想要输出的变量(类型和名字)和函数(原型和名字)。头文件还必须定义用于输出函数和变量的任何符号和数据结构。你的DLL的所有源代码模块都应该包含这个头文件。另外,必须分配该头文件,以便它能够包含在可能输入这些函数或变量的任何源代码中。拥有单个头文件,供DLL创建程序和可执行程序的使用,就可以大大简化维护工作。
需要提前补充一下的是,在visual studio中新建DLL时,vs会自动生成一个可用完备的DLL工程供你使用。
大致补充一下使用Vs新建DLL工程的整个过程,其中,除了MyLib是手动添加的之外,其他都是新建工程时,vs自动生成的。

下面的代码说明了应该如何对单个头文件进行编码,以便同事包含可执行文件和DLL的源代码文件
代码及工程
framework.h自动生成的文件 未做任何修改
#pragma once
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>
Mylib.h手动添加的头文件,用于实现导出类、全局变量、接口
#pragma once
#include "pch.h"
class MYWINAPI MyLib
{
public:
int getAge()
{
return nAge;
}
bool getGender()
{
return bGender;
}
private:
int nAge;
bool bGender;
};
MYLIBAPI int g_nAge;
MYLIBAPI int setAge(int nAge);
pch.h自动生成的文件 添加了MYLIBAPI宏定义
// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。
#ifndef PCH_H
#define PCH_H
// 添加要在此处预编译的标头
#include "framework.h"
#ifdef MYLIBAPI
#else
#define MYLIBAPI extern "C" __declspec(dllimport)
#endif // MYLIBAPI
#ifdef MYWINAPI
#else
#define MYWINAPI __declspec(dllimport)
#endif // WINAPI
#endif //PCH_H
dllmain.cpp动态库的入口函数,所有DLL入口函数基本一致,详细内容可以看这篇文章 DLL入口函数
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
MyLib.cpp手动添加的文件,接口和全局变量的定义文件
#include "pch.h"
#define MYLIBAPI extern "C" __declspec(dllexport) // 这里不是必须的。
#include "MyLib.h"
int g_nAge;
int setAge(int nAge)
{
g_nAge = nAge;
return g_nAge;
}
pch.cpp自动生成的文件,未做修改
// pch.cpp: 与预编译标头对应的源文件
#include "pch.h"
// 当使用预编译的头时,需要使用此源文件,编译才能成功。
上述DLLExportDemo工程可以正常生成DLLExportDemo.dll文件。
代码补充说明
主要补充说明一下Mylib涉及的头文件和源文件的一些操作说明。
MyLib.cpp
MyLib.h
当上述工程被编译时,在MyLib.h头文件前面使用__declspec(dllexport)对MYLIBAPI进行定义。当编译器看到负责修改变量、函数或C++类的__declspec(dllexport)时,它就知道该变量、函数或C++类是从产生的DLL模块输出的。 MYLIBAPI标志要被放在头文件中要输出的变量的定义之前和要输出的函数之前。
另外,在MyLib.cpp中, MYLIBAPI 标志并不出现在输出的变量和函数之前。 MYLIBAPI 标志在此处是不必要的,原因就是在头文件找那个已经表明了要输出哪些变量或函数。
使用dependency walker可以看到导出情况如下

extern "C"
关于 extern "C" 的作用有必要说明一下。这个修饰符只有当你在使用C++代码而不是直接编写C代码时,才能使用这个修改符。通常来说,C++编译器可能会改变函数和变量的名字,从而导致严重的链接程序问题。
例如,假设你用C++编写一个DLL,并直接用C编写一个可执行模块 当你创建DLL时函数名被改变,但是,当你创建可执行模块时,函数名没有改变。当链接程序试图链接可执行模块时,它就会报错:“可执行模块引用的符号不存在”。
但是当你使用extern "C",就可以告诉编译器不要改变变量名或函数名,这样,变量和函数就可以供使用C、C++或任何其他编程语言编写的可执行模块来访问。

本文讲述了如何在DLL开发中正确导出变量和函数,强调了避免硬编码变量的重要性,以及extern'C'修饰符的作用,以确保跨语言模块间的兼容性。
278

被折叠的 条评论
为什么被折叠?



