首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC++

VC++ DLL 編程深度解析(二):非 MFC DLL 完整實戰指南

     編輯:VC++

涵蓋:導出函數/變量/類、.def 文件、動態/靜態加載、__stdcall 約定、DllMain 生命週期

1. 創建非 MFC DLL 專案

New Project → Win32 Dynamic-Link Library → 名稱 SimpleDLL → Application Settings → DLL, Empty Project。

2. 導出函數:兩種聲明方式

方式一:__declspec(dllexport)(推薦,直觀)


// SimpleDLL.h
#ifndef SIMPLEDLL_H
#define SIMPLEDLL_H

#ifdef SIMPLEDLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

extern "C" DLL_API int Add(int a, int b); // C 鏈接 + 導出/導入宏
#endif

// SimpleDLL.cpp
#include "SimpleDLL.h"
int Add(int a, int b) { return a + b; }

專案屬性 → C/C++ → Preprocessor → Preprocessor Definitions 添加 SIMPLEDLL_EXPORTS。

方式二:模塊定義文件 .def(無裝飾名,精確控制序號)


; SimpleDLL.def
LIBRARY "SimpleDLL"
EXPORTS
    Add @1           ; 序號 1
    Sub @2 NONAME    ; 僅按序號導出,無名稱
    ; 變量導出
    g_Counter DATA

屬性 → Linker → Input → Module Definition File 填 SimpleDLL.def。

3. 應用程序調用:動態加載 vs 靜態加載

動態加載(運行時決定,靈活)


#include 
#include 

typedef int (__stdcall *PFN_ADD)(int, int); // 必須匹配調用約定

int main() {
    HMODULE hDll = LoadLibrary(_T("SimpleDLL.dll"));
    if (!hDll) { printf("LoadLibrary 失敗: %d\n", GetLastError()); return 1; }

    // 方式 A:按名稱獲取
    PFN_ADD pAdd = (PFN_ADD)GetProcAddress(hDll, "Add");
    // 方式 B:按序號獲取(需 .def 指定 @n)
    // PFN_ADD pAdd = (PFN_ADD)GetProcAddress(hDll, MAKEINTRESOURCEA(1));

    if (pAdd) printf("2+3=%d\n", pAdd(2,3));
    else printf("GetProcAddress 失敗: %d\n", GetLastError());

    FreeLibrary(hDll);
    return 0;
}

靜態加載(編譯時綁定,簡潔)


#pragma comment(lib, "SimpleDLL.lib") // 鏈接導入庫
extern "C" __declspec(dllimport) int Add(int, int); // 聲明導入

int main() {
    printf("%d\n", Add(2,3)); // 直接調用,像本地函數
    return 0;
}

原理:.lib 為導入庫,僅含導出符號重定位信息與 DLL 文件名。鏈接器將 DLL 文件名寫入 EXE 導入表,載入器啟動時自動加載 DLL 並解析地址。

4. DllMain:DLL 進程/線程附加分離入口


BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason, LPVOID lpReserved) {
    switch (ul_reason) {
        case DLL_PROCESS_ATTACH:
            // 進程加載 DLL:初始化全局資源、禁用線程附加通知優化
            DisableThreadLibraryCalls(hModule); 
            printf("[DLL] Process Attach\n");
            break;
        case DLL_PROCESS_DETACH:
            // 進程卸載 DLL:清理資源
            printf("[DLL] Process Detach\n");
            break;
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
            // 線程創建/退出時調用(若未 DisableThreadLibraryCalls)
            break;
    }
    return TRUE; // FALSE 會導致加載失敗
}

驗證調用時機:動態加載時,輸出順序為 Process Attach → 業務調用 → Process Detach。

5. __stdcall 調用約定:跨語言互操作關鍵

約定參數入棧棧清理修飾名稱適用場景
__cdecl (默認)右→左調用者_FuncNameC/C++ 內部、可變參數
__stdcall (WINAPI)右→左被調用者 (ret n)_FuncName@nWin32 API、COM、跨語言
__fastcall前 2 個進 ECX/EDX被調用者@FuncName@n高性能內部調用

錯誤示範:DLL 導出 __stdcall,EXE 用 __cdecl 函數指針調用 → 運行時棧破壞,彈出「Run-Time Check Failure #0」。


// 正確:雙方統一
// DLL.h
extern "C" __declspec(dllexport) int __stdcall Add(int, int);

// EXE
typedef int (__stdcall *PFN_ADD)(int, int); // 注意 __stdcall

6. 導出全局變量


// DLL.h
extern int g_Counter; // 聲明

// DLL.cpp
int g_Counter = 0; // 定義
// .def 中導出: g_Counter DATA

// EXE 使用(靜態加載)
#pragma comment(lib, "SimpleDLL.lib")
extern __declspec(dllimport) int g_Counter; // 直接用變量名

int main() {
    printf("%d\n", g_Counter); // 讀
    g_Counter = 100;           // 寫,同步到 DLL 數據段
    return 0;
}

注意:若用 extern int g_Counter 無 dllimport,得到的是地址,需 *(int*)&g_Counter 訪問,極易出錯。強烈建議統一使用 __declspec(dllimport)。

7. 導出 C++ 類


// Point.h
#ifndef POINT_H
#define POINT_H

#ifdef SIMPLEDLL_EXPORTS
#define DLL_CLASS __declspec(dllexport)
#else
#define DLL_CLASS __declspec(dllimport)
#endif

class DLL_CLASS Point {
public:
    float x, y;
    Point(float _x=0, float _y=0) : x(_x), y(_y) {}
    float Distance() const;
};

#endif

// Point.cpp
#include "Point.h"
float Point::Distance() const { return sqrt(x*x + y*y); }

應用程序 #include "Point.h" + #pragma comment(lib,...) 即可直接構建對象、調用成員函數,完全透明化。

總結:非 MFC DLL 最佳實踐清單

  1. 統一用 DLL_API 宏切換 dllexport/dllimport。
  2. 對外接口全部 extern "C" __stdcall,避免 C++ 名稱修飾與調用約定坑。
  3. 導出變量/類時務必在頭文件加 __declspec(dllimport)。
  4. 提供 .def 文件精確控制導出序號,支援按序號獲取。
  5. DllMain 中只做極簡初始化/清理,嚴禁調用 LoadLibrary/同步操作/複雜邏輯。
  6. 發布時:Release 版 DLL + .lib + .h + .def(可選) + 版本資源。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved