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

VC++ 非 MFC DLL 開發全攻略:靜態/動態連結、導出語法、DEF 檔、跨語言調用

     編輯:關於C語言

前言

DLL(動態連結庫)是 Windows 程式設計核心技術。本文系統總結 VC++ 中非 MFC DLL的完整開發流程:從專案建立、導出語法、兩種調用方式、DEF 檔應用,到跨語言(VB/C#)呼叫慣例與進階主題。

一、 核心概念:LIB vs DLL

類型關鍵字連結時機體積影響更新部署
靜態庫無特殊修飾連結期代碼複製入 EXE需重新編譯 EXE
動態庫__declspec(dllexport/dllimport)載入期/運行期共享單一副本替換 DLL 即可(介面相容前提)

二、 導出函式/變數/類別的三種語法

1. __declspec(dllexport) —— 推薦,最直觀

// MyLib.h
#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif

extern "C" MYLIB_API int add(int x, int y);      // C 連結慣例,無名稱修飾
MYLIB_API int g_var = 10;                        // 導出變數
class MYLIB_API MyClass { public: void foo(); }; // 導出整個類別

2. 模組定義檔 —— 精確控制導出名稱/序數

; MyLib.def
LIBRARY "MyLib"
EXPORTS
    add @1          ; 序數 1,可由 GetProcAddress(MAKEINTRESOURCE(1)) 調用
    g_var DATA      ; 導出變數需加 DATA
    ?foo@MyClass@@QAEXXZ  ; C++ 修飾名(複雜,不推薦手寫)

3. /EXPORT 連結器選項

命令列:link /EXPORT:add /EXPORT:g_var ...,較少直接使用。

三、 完整實戰範例:建立與調用 DLL

步驟 1:建立 DLL 專案

  • Visual Studio → 新專案 → **Dynamic-Link Library (DLL)**
  • 勾選「匯出符號」產生範本宏 MYLIB_API

步驟 2:編寫導出介面

// MyLib.h
#pragma once
#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif

extern "C" MYLIB_API int add(int x, int y);
MYLIB_API int g_value = 42;

class MYLIB_API Calculator {
public:
    Calculator();
    int multiply(int a, int b);
private:
    int m_factor;
};
// MyLib.cpp
#include "MyLib.h"
#include <stdio.h>

extern "C" MYLIB_API int add(int x, int y) { return x + y; }

Calculator::Calculator() : m_factor(2) {}
int Calculator::multiply(int a, int b) { return a * b * m_factor; }

步驟 3:建置產出

  • MyLib.dll、MyLib.lib(導入庫)、MyLib.h

步驟 4:靜態連結調用(隱式連結)

// Client.cpp
#include "MyLib.h"
#pragma comment(lib, "MyLib.lib")  // 指定導入庫

int main() {
    printf("add(2,3)=%d\n", add(2,3));
    printf("g_value=%d\n", g_value);
    Calculator calc;
    printf("calc.multiply(3,4)=%d\n", calc.multiply(3,4));
    return 0;
}

步驟 5:動態連結調用(顯式連結)

// ClientDyn.cpp
#include <windows.h>
#include <stdio.h>

typedef int (*PFN_ADD)(int, int);

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

    PFN_ADD pAdd = (PFN_ADD)GetProcAddress(hDll, "add");  // 或 MAKEINTRESOURCE(1)
    if (pAdd) printf("add(5,6)=%d\n", pAdd(5,6));
    else printf("GetProcAddress failed\n");

    FreeLibrary(hDll);
    return 0;
}

四、 跨語言調用:_stdcall 慣例

VB/VBA/C#/Python 等預設使用 __stdcall(參數右→左入棧、被呼叫者清棧)。C/C++ 預設 __cdecl(呼叫者清棧)。

// 導出 __stdcall 函式供 VB/C# 呼叫
extern "C" __declspec(dllexport) int __stdcall add_stdcall(int x, int y) {
    return x + y;
}

// DEF 檔指定序數
EXPORTS
    add_stdcall @1

C# 呼叫範例:

[DllImport("MyLib.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "add_stdcall")]
public static extern int AddStdCall(int x, int y);

五、 進階主題

1. DllMain 入口點

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason, LPVOID lpReserved) {
    switch (ul_reason) {
        case DLL_PROCESS_ATTACH:  /* 單次初始化 */ break;
        case DLL_THREAD_ATTACH:   /* 執行緒附加 */ break;
        case DLL_THREAD_DETACH:   /* 執行緒分離 */ break;
        case DLL_PROCESS_DETACH:  /* 清理資源 */ break;
    }
    return TRUE;
}

禁忌:DllMain 內**不可**呼叫 LoadLibrary、CreateThread、同步物件、CRT 函式,否則極易死結。

2. 共享資料區

// 所有進程共享同一份 g_shared
#pragma data_seg(".SHARED")
MYLIB_API int g_shared = 0;
#pragma data_seg()
#pragma comment(linker, "/SECTION:.SHARED,RWS")
  • 需**初始化**(未初始化會進 BSS 區,不共享)
  • 多執行緒/進程並發存取需自行同步(Interlocked API / Mutex)

3. 執行緒區域儲存

// 每個執行緒擁有獨立副本
__declspec(thread) int tls_var = 0;

六、 總結:最佳實踐清單

  1. 公開介面用 extern "C" __declspec(dllexport) + MYLIB_API 宏
  2. 提供 .h + .lib 供靜態連結;保留 LoadLibrary 彈性
  3. 跨語言/跨編譯器:**強制 __stdcall + 純 C 介面**
  4. DEF 檔僅在需精確控制序數/名稱時使用
  5. DllMain 保持極簡;共享資料加同步;避免 C++ 類別跨 DLL 邊界(記憶體管理、異常、RTTI 風險)

本文整理自原博客技術筆記,經結構化重寫發布

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved