c++调用自己写的dll
·
c++调用自己写的dll
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
/*
一个动态库可以被多个应用程序共享
但它只被加载(映射)一次
*/
// dll程序入口
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:// 多个应用调用dll的时候触发,可能多次触发
case DLL_THREAD_DETACH:// 多个应用在注销dll使用的时候触发,可能多次触发
case DLL_PROCESS_DETACH:// 在最后销毁动态库的时候触发, 只触发一次
break;
}
return TRUE;
}




-
头文件 (Header Files) 文件夹:
- framework.h:这个头文件通常包含一些通用的包含指令和宏定义,用于简化其他头文件和源文件的编写。它可能包含 Windows 头文件和其他常用的标准库头文件。
- pch.h:这是预编译头文件。预编译头文件用于加快编译速度,将一些不常变化的头文件预先编译,这样在后续的编译过程中可以重复使用。通常包含一些大型的、不常变化的头文件,如标准库头文件或第三方库头文件。
-
源文件 (Source Files) 文件夹:
- dllmain.cpp:这是 DLL 的入口点文件。它包含
DllMain函数,该函数是 DLL 的入口点,类似于应用程序的main函数。DllMain函数在 DLL 加载和卸载时被调用,可以在这里进行一些初始化和清理工作。 - pch.cpp:这个文件通常只包含一行代码,即
#include "pch.h"。它的作用是确保预编译头文件被正确编译和使用。
- dllmain.cpp:这是 DLL 的入口点文件。它包含
-
资源文件 (Resource Files) 文件夹:
- 这个文件夹用于存放资源文件,如图标、位图、字符串表等。在 DLL 项目中,资源文件可以用来存储一些与 DLL 相关的资源数据。
-
外部依赖项 (External Dependencies) 文件夹:
- 这个文件夹显示了项目所依赖的外部头文件和库文件。这些文件通常位于系统的包含路径或库路径中,Visual Studio 会自动识别并显示在这里。
以下是 dllmain.cpp 文件的详细解释,该文件是动态链接库 (DLL) 的入口点,类似于应用程序的 main 函数。
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h" // 包含预编译头文件,通常用于加快编译速度,包含一些常用的头文件。
// DllMain 是 DLL 的入口点函数,当 DLL 被加载或卸载时,系统会调用此函数。
BOOL APIENTRY DllMain(
HMODULE hModule, // DLL 模块的句柄(类似于 DLL 的实例句柄)。
DWORD ul_reason_for_call, // 调用原因,表示 DLL 被加载、卸载或线程附加/分离。
LPVOID lpReserved // 保留参数,通常不使用。
)
{
// 根据调用原因 (ul_reason_for_call) 进行不同的处理。
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// 当 DLL 被加载到进程的地址空间时调用(例如,通过 LoadLibrary 或隐式链接)。
// 可以在这里进行一些初始化操作。
break;
case DLL_THREAD_ATTACH:
// 当进程中的新线程被创建时调用(如果 DLL 被标记为需要处理线程附加事件)。
// 通常用于线程级别的初始化。
break;
case DLL_THREAD_DETACH:
// 当线程终止时调用(如果 DLL 被标记为需要处理线程分离事件)。
// 通常用于线程级别的清理。
break;
case DLL_PROCESS_DETACH:
// 当 DLL 从进程的地址空间卸载时调用(例如,通过 FreeLibrary 或进程终止)。
// 可以在这里进行一些清理操作。
break;
}
// 返回 TRUE 表示初始化成功,FALSE 表示失败。
// 如果返回 FALSE,系统会拒绝加载 DLL 或尝试回滚加载操作。
return TRUE;
}
关键点:
-
DllMain函数:- 这是 DLL 的入口点,类似于
main函数在应用程序中的作用。 - 当 DLL 被加载或卸载时,系统会自动调用此函数。
- 这是 DLL 的入口点,类似于
-
ul_reason_for_call参数:- 用于指示调用
DllMain的原因,可以是以下四种情况之一:DLL_PROCESS_ATTACH:DLL 被加载到进程的地址空间。DLL_THREAD_ATTACH:进程中的新线程被创建(如果 DLL 需要处理线程附加事件)。DLL_THREAD_DETACH:线程终止(如果 DLL 需要处理线程分离事件)。DLL_PROCESS_DETACH:DLL 从进程的地址空间卸载。
- 用于指示调用
-
hModule参数:- 表示 DLL 模块的句柄,类似于 DLL 的实例句柄。
-
lpReserved参数:- 保留参数,通常不使用。
-
返回值:
- 返回
TRUE表示初始化成功,FALSE表示失败。 - 如果返回
FALSE,系统会拒绝加载 DLL 或尝试回滚加载操作。
- 返回
导出后的函数名会被 C++ 编译器进行名称修饰(name mangling)
#pragma once
#define DllApi _declspec(dllexport)// 导出(告诉编译器,该函数开放给外部调用)
DllApi void HelloWorld();
extern “C” 强制使用 C 语言链接规范
导出后的函数名保持原始名称
适合需要从 C 或其他语言调用的接口
#pragma once
#define DllApi extern "C" _declspec(dllexport)// 导出(告诉编译器,该函数开放给外部调用)
DllApi int Add(int,int);
class _declspec(dllexport) MyClass
{
};
头文件:mylib.h
#pragma once
#define DllApi extern "C" _declspec(dllexport)// 导出(告诉编译器,该函数开放给外部调用)
DllApi int Add(int, int);
extern "C" _declspec(dllexport) double Add2(double,double);
cpp文件:mylib.cpp
#include "pch.h"
#include "mylib.h"
#include <iostream>
#include <vector>
//加法运算
extern "C" int Add(int a, int b)
{
return a + b;
}
//加法运算
extern "C" double Add2(double a, double b)
{
return a + b;
}
调用dll


#include <iostream>
#include "../MyDll/mylib.h"
int main()
{
// int a = Add(100, 200);
double b = Add2(100.1, 200);
std::cout << b;
}

C++ 编译器对导出后的函数名进行名称修饰(Name Mangling)的核心目的是解决符号唯一性冲突,支持语言高级特性,并兼容底层链接机制。以下是具体原因分析:
1. 支持函数重载(Function Overloading)
- C++ 允许同名函数通过参数类型、数量或顺序区分(如
int foo(int)和double foo(double))。 - 底层链接器(如 Linux 的
ld或 Windows 的link.exe)需要唯一符号名来定位函数。名称修饰通过将参数类型信息编码到符号名中,生成如_Z3fooi和_Z3food的唯一标识,避免冲突。
2. 区分类成员函数与命名空间
- C++ 的类成员函数和命名空间允许不同上下文中定义同名符号,但链接器需要全局唯一的标识。
- 名称修饰将类名、成员函数名及访问权限(如
public/private)编码到符号中。例如:- 私有成员函数
void CTest::Function(int)可能被修饰为?Function@CTest@@AAEXH@Z,其中@@AAE表示私有访问。 - 公有成员函数
long CTest::DrawText(...)可能被修饰为?DrawText@CTest@@QAEJPAUHDC__@@JPBDUtagRGBQUAD@@E_N@Z,包含类名、返回值类型及参数列表。
- 私有成员函数
3. 支持模板实例化
- 模板生成的函数和变量需通过名称修饰区分不同实例。例如:
- 模板函数
template<typename T> T add(T a, T b)在实例化为int add(int, int)和double add(double, double)时,修饰后的符号会包含类型信息i和d,确保链接时唯一性。
- 模板函数
4. 兼容调用约定与返回值类型
- C++ 支持多种调用约定(如
__cdecl、__stdcall、__fastcall),名称修饰会编码调用方式。例如:__stdcall函数int Function1(char*, unsigned long)可能被修饰为?Function1@@YGHPADK@Z,其中@@YG表示调用约定。
- 返回值类型也参与修饰,确保链接器能匹配函数签名。
5. 解决 C 与 C++ 混合编程的符号冲突
- C 语言函数名在编译后通常保持不变(如
foo),而 C++ 会进行修饰。若直接混合使用,链接器会因符号名不一致报错。 - 通过
extern "C"禁用名称修饰,强制按 C 风格编译函数名(如extern "C" void foo(int)),确保 C 代码能正确调用 C++ 函数。
6. 历史与技术演进
- 早期 C++ 编译器通过将代码转换为 C 再编译,需遵守 C 标识符规则,名称修饰成为必要手段。
- 现代编译器虽能直接生成机器码,但链接器仍依赖修饰后的符号名区分不同语言特性,名称修饰因此保留。
示例对比
- C 语言:函数
void foo(int)编译后符号名为_foo(可能加下划线前缀)。 - C++ 语言:同名函数可能被修饰为
_Z3fooi(包含函数名长度和参数类型i表示int)。 - 重载函数:
double foo(double)可能被修饰为_Z3food,确保链接时唯一。
名称修饰是 C++ 编译器为支持函数重载、类成员、模板等高级特性而设计的机制,通过编码函数签名信息生成唯一符号名,解决链接阶段的冲突问题。这一机制虽增加了符号名的复杂性,但确保了 C++ 程序的灵活性和可扩展性,同时通过 extern "C" 兼容 C 语言生态。
更多推荐

所有评论(0)