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;
}



在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  1. 头文件 (Header Files) 文件夹

    • framework.h:这个头文件通常包含一些通用的包含指令和宏定义,用于简化其他头文件和源文件的编写。它可能包含 Windows 头文件和其他常用的标准库头文件。
    • pch.h:这是预编译头文件。预编译头文件用于加快编译速度,将一些不常变化的头文件预先编译,这样在后续的编译过程中可以重复使用。通常包含一些大型的、不常变化的头文件,如标准库头文件或第三方库头文件。
  2. 源文件 (Source Files) 文件夹

    • dllmain.cpp:这是 DLL 的入口点文件。它包含 DllMain 函数,该函数是 DLL 的入口点,类似于应用程序的 main 函数。DllMain 函数在 DLL 加载和卸载时被调用,可以在这里进行一些初始化和清理工作。
    • pch.cpp:这个文件通常只包含一行代码,即 #include "pch.h"。它的作用是确保预编译头文件被正确编译和使用。
  3. 资源文件 (Resource Files) 文件夹

    • 这个文件夹用于存放资源文件,如图标、位图、字符串表等。在 DLL 项目中,资源文件可以用来存储一些与 DLL 相关的资源数据。
  4. 外部依赖项 (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;
}

关键点:

  1. DllMain 函数

    • 这是 DLL 的入口点,类似于 main 函数在应用程序中的作用。
    • 当 DLL 被加载或卸载时,系统会自动调用此函数。
  2. ul_reason_for_call 参数

    • 用于指示调用 DllMain 的原因,可以是以下四种情况之一:
      • DLL_PROCESS_ATTACH:DLL 被加载到进程的地址空间。
      • DLL_THREAD_ATTACH:进程中的新线程被创建(如果 DLL 需要处理线程附加事件)。
      • DLL_THREAD_DETACH:线程终止(如果 DLL 需要处理线程分离事件)。
      • DLL_PROCESS_DETACH:DLL 从进程的地址空间卸载。
  3. hModule 参数

    • 表示 DLL 模块的句柄,类似于 DLL 的实例句柄。
  4. lpReserved 参数

    • 保留参数,通常不使用。
  5. 返回值

    • 返回 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) 时,修饰后的符号会包含类型信息 id,确保链接时唯一性。

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 语言生态。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐