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

DLL 開發核心知識:調用約定與名稱修飾深度解析(一)

     編輯:VC++

理解 __cdecl/__stdcall/__fastcall 的棧操作差異,掌握 C++ 名稱修飾規則,實現跨語言無縫互操作

1. 什麼是調用約定?

調用約定規定了函數調用時的參數入棧順序、棧清理責任方、返回值傳遞方式、名稱修飾規則。不同語言/編譯器默認約定不同,跨模塊調用必須統一。

2. 三大主流約定汇编級解析

測試代碼:


int __cdecl Add_cdecl(int a, int b);
int __stdcall Add_stdcall(int a, int b);
int __fastcall Add_fastcall(int a, int b, int c, int d); // 前兩個進寄存器

2.1 __cdecl (C/C++ 默認)


; 調用端
push 2          ; 參數右→左入棧
push 1
call Add_cdecl
add esp, 8      ; 調用者清理棧 (2 參數 × 4 字節)

; 函數體 (簡化)
Add_cdecl:
    push ebp
    mov ebp, esp
    mov eax, [ebp+8]  ; a
    add eax, [ebp+12] ; b
    pop ebp
    ret               ; 僅返回,不清理棧

特點:支持可變參數 (printf)、調用者清棧、修飾名 _FuncName。

2.2 __stdcall (Win32 API / COM 標準)


; 調用端
push 2
push 1
call Add_stdcall
; 無 add esp, 8  — 由被調用者清理

; 函數體
Add_stdcall:
    push ebp
    mov ebp, esp
    mov eax, [ebp+8]
    add eax, [ebp+12]
    pop ebp
    ret 8             ; 返回時清理 8 字節參數棧

特點:固定參數數量、被調用者清棧 (ret n)、修飾名 _FuncName@n (n=參數字節數)。

2.3 __fastcall (高性能內部調用)


; 調用端 Add_fastcall(1, 2.0, 3, 4)
push 4              ; 第 4 參數入棧
mov edx, 3          ; 第 3 參數 → EDX
push 40000000h      ; 第 2 參數 (double 2.0) 高 32 位
push 0              ; 第 2 參數低 32 位
mov ecx, 1          ; 第 1 參數 → ECX
call Add_fastcall

; 函數體
Add_fastcall:
    push ebp
    mov ebp, esp
    push ecx              ; 保存第 1 參數
    mov [ebp-8], edx      ; 保存第 3 參數
    ; 第 2 參數已在棧上 [ebp+8]/[ebp+12] (double 佔 8 字節)
    ; 計算...
    pop ecx
    mov esp, ebp
    pop ebp
    ret 0Ch               ; 清理棧上 12 字節 (參數 3,4)

特點:前兩個 ≤4 字節參數進 ECX/EDX、其餘進棧、被調用者清棧、修飾名 @FuncName@n。

3. C++ 名稱修飾

為支持重載、命名空間、類成員,C++ 編譯器將函數簽名編碼進符號名。MSVC 修飾規則(簡化):


// 例子
void Func(int);           // ?Func@@YAXH@Z
void Func(int, double);   // ?Func@@YAXHN@Z
class A { void Func(); }; // ?Func@A@@QAEXXZ
namespace N { void Func(); } // ?Func@N@@YAXXZ

關鍵點:

  • 修飾名包含:函數名、類/命名空間作用域、參數類型、調用約定、const/volatile、返回類型(部分)。
  • extern "C" 禁用 C++ 修飾,生成 C 風格名 (_Func@n 或 Func),跨語言導出必加。
  • 不同編譯器 (GCC/MSVC/Clang) 修飾算法完全不同,二進制不兼容。

4. 實戰:導出函數聲明最佳模式


// MyDLL.h
#ifdef MYDLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

// 導出 C 接口:extern "C" + __stdcall + DLL_API
extern "C" DLL_API int __stdcall Add(int a, int b);
extern "C" DLL_API int __stdcall Sub(int a, int b);

// 導出 C++ 類:類級別 dllexport/dllimport
#ifdef MYDLL_EXPORTS
#define DLL_CLASS __declspec(dllexport)
#else
#define DLL_CLASS __declspec(dllimport)
#endif

class DLL_CLASS Math {
public:
    static int Mul(int a, int b); // 靜態成員也需類導出
    int AddMember(int a, int b);
};

5. 常見陷阱與排查

現象原因解決
Run-Time Check Failure #0 (棧指針損壞)調用約定不匹配 (__cdecl vs __stdcall)雙方統一聲明 __stdcall,函數指針類型含 __stdcall
Linker LNK2019: 無法解析的外部符號 _Func@12調用方期望 __stdcall,DLL 導出 __cdeclDLL 加 __stdcall,或調用方改 __cdecl
GetProcAddress 返回 NULL名稱修飾導致符號不匹配用 extern "C" + .def 文件固定導出名
C# DllImport EntryPointNotFoundC# 默認 CallingConvention.StdCall,C++ 導出 __cdeclC++ 加 __stdcall,或 C# 指定 CallingConvention.Cdecl

6. 驗證導出符號工具

  • dumpbin /exports MyDLL.dll (VS 命令提示符)
  • Depends.exe 圖形化查看
  • undname 解碼修飾名:undname ?Func@@YAXH@Z

下一節將講解:.def 文件高級用法、導出變量/類的二進制佈局、跨編譯器 ABI 兼容策略。

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