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

VC++ DLL 導出 C++ 類別完整指南 (__declspec 與 .def 比較)

     編輯:VC 教學

前言

從 DLL 導出 C++ 類別供其他模組使用,需解決編譯時導出/導入符號切換與名稱修飾問題。本文比較 __declspec(dllexport/dllimport) 與模組定義檔 (.def) 兩種主流做法,並提供最佳實務巨集模式。

方法一:__declspec(dllexport) 直接標記類別


// MyClass.h
class __declspec(dllexport) CMyClass { ... };

缺點:標頭檔同時被 DLL 專案與使用者專案包含。使用者專案編譯時同樣看到 dllexport,導致連結錯誤 (嘗試導出而非導入)。

方法二:巨集切換導出/導入 (推薦)

標頭檔 (MyClass.h):


#ifdef MYDLL_EXPORTS
    #define MYDLL_API __declspec(dllexport)
#else
    #define MYDLL_API __declspec(dllimport)
#endif

class MYDLL_API CMyClass
{
public:
    CMyClass();
    void DoSomething();
    // ...
};

DLL 專案設定:專案屬性 → C/C++ → Preprocessor → Preprocessor Definitions 加入 MYDLL_EXPORTS。

使用者專案:不定義 MYDLL_EXPORTS,自動變為 dllimport。

方法三:模組定義檔 (.def) 導出

MyClass.def:


LIBRARY "MyDll"
EXPORTS
    ?DoSomething@CMyClass@@QAEXXZ  ; 修飾名稱 (需依編譯器輸出)
    ??0CMyClass@@QAE@XZ            ; 建構函數
    ??1CMyClass@@UAE@XZ            ; 解構函數

缺點:

  • 需手動維護修飾名稱 (Name Mangling),跨編譯器版本不相容。
  • 無法導出類別整體 (需逐一列出成員函數)。
  • 不支援類別模板、內聯函數導出。

優點:完全隱藏導出細節於標頭檔外部,標頭檔無需 __declspec 汙染。

最佳實務:結合巨集與 .def (可選)

標頭檔使用巨集 MYDLL_API 宣告類別,.def 僅用於導出純 C 函數或特定進入點。類別導出完全依賴 __declspec 機制。

完整範例專案結構


MyDll/
├── MyDll.h          // 巨集定義 + 類別宣告
├── MyDll.cpp        // 類別實作
├── MyDll.def        // (可選) 導出 C 風格函數
└── MyDll.vcxproj    // 定義 MYDLL_EXPORTS

MyDll.h


#pragma once
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif

class MYDLL_API CInDLL
{
public:
    CInDLL();
    ~CInDLL();
    void ProcessData(int nValue);
    static int GetVersion(); // 靜態成員函數亦可導出
};

MyDll.cpp


#include "MyDll.h"
#include 

CInDLL::CInDLL() { std::cout << "CInDLL constructed\n"; }
CInDLL::~CInDLL() { std::cout << "CInDLL destroyed\n"; }

void CInDLL::ProcessData(int n) { std::cout << "Processing " << n << "\n"; }
int CInDLL::GetVersion() { return 100; }

使用者專案呼叫


// Client.cpp
#include "MyDll.h"
#pragma comment(lib, "MyDll.lib") // 連結匯入庫

int main()
{
    CInDLL obj; // 建構函數自動從 DLL 載入
    obj.ProcessData(42);
    std::cout << "Version: " << CInDLL::GetVersion() << std::endl;
    return 0;
}

關鍵注意事項

C++/CLI 或 COM 替代方案

  • C++/CLI:產生 .NET 組件,跨語言 (C#, VB.NET) 直接參考,無修飾名稱問題。
  • COM:定義介面 (IDL),實作 IUnknown,透過 CoCreateInstance 建立,支援跨語言、跨進程、版本獨立。

結語

導出 C++ 類別的標準做法是標頭檔巨集切換 __declspec(dllexport/dllimport),配合專案定義 MYDLL_EXPORTS。.def 檔僅適合導出 C 風格函數。嚴守 CRT 一致、標頭檔同步、編譯器版本鎖定三大鐵律,確保二進位相容與穩定運行。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
議題說明
名稱修飾C++ 類別成員函數導出依賴編譯器特定修飾名稱,僅限同編譯器版本互操作 (MSVC ↔ MSVC)。跨編譯器需用 C 風格介面。
類別佈局相容DLL 與使用者專案必須使用相同結構對齊、相同標頭檔版本,否則物件大小、vtable 位置不一致導致崩潰。
CRT 版本一致DLL 與 EXE 必須使用相同 CRT 版本 (MD/MT/MDd/MTd),否則 new/delete、std::string 等跨模組記憶體管理會出錯。
匯入庫建置 DLL 產生 .lib 匯入庫,使用者專案需連結此 .lib。
循環相依避免 DLL 之間互相導出類別造成循環載入,改用介面類別 (純虛函數) 打破循環。