C++ dll動態程式庫建置架構筆記(CMake build) -1 dll程式寫法

這篇介紹如何建置一個基本的 C++ 動態程式庫。
我的系統環境:
  • Win10
  • CMake 3.18.4
  • VisualStudio 2019

一、程式寫法差異

1.一般的寫法

my_math.h
int Add(int a, int b);

class math_class
{
public:
    int Add(int a, int b);
};

my_math.cpp
int Add(int a, int b)
{
    return a+b;
}

int math_class::Add(int a, int b)
{
    return a+b;
}

2.dll的寫法

在需要提供給外部使用的 function 或是 class 加上 __declspec(dllexport) ,告訴編譯器這是能提供給外部使用的 API 介面。

my_math.h
extern "C" __declspec(dllexport) int Add(int a, int b);

class __declspec(dllexport) math_class
{
public:
    int Add(int a, int b);
};

my_math.cpp
int Add(int a, int b)
{
    return a+b;
}

int math_class::Add(int a, int b)
{
    return a+b;
}

extern "C" 探討
我看其他人講解應該是能讓程式在 C 與 C++ 之間混用,如果都是 C++ 環境是不是就可以不用寫,這部分我不是很清楚,求大神解惑了。
我覺得應該是將 C++ 轉為 C 語言的格式,能讓 C++ dll 程式在 C 語言上執行,因為曾經在 function 參數的部分使用 std::string 資料型態,結果使用這個 dll 的人說他無法讀到 std::string 裡面的資料,須注意可能只能使用 C 語言有的資料型態。

二、實作

檔案架構

根目錄
├─common
│  └─SVdef.h
├─Dll_1Layer
│  ├─math
│  │  ├─include
│  │  │  └─my_math.h
│  │  └─src
│  │     └─my_math.cpp
│  └─CMakeFiles.txt
└─CMakeFiles.txt
以下的定義檔是判斷在哪個系統環境,是否用 extern "C" 及 __declspec(dllexport),我的環境已知會開啟 __cplusplus 、_MSC_VER、EXPORT_API,所以 SV_EXTERN_C = extern "C",SV_EXPORTS = __declspec(dllexport)。
判斷式裡有個判斷 defined EXPORT_API,但你可能會發現 EXPORT_API 這個未定義,或找不到他定義在哪了,那是因為這是判斷要不要編譯成 dll 的條件,所以這個定義寫在 CMakeLists.txt 裡。

SVdef.h
#ifndef SV_DEF_h_
#define SV_DEF_h_

#ifndef SV_EXTERN_C
#  ifdef __cplusplus && defined EXPORT_API
#    define SV_EXTERN_C extern "C"
#  else
#    define SV_EXTERN_C
#  endif
#endif

#ifndef SV_EXPORTS
#   if (defined _WIN32 || defined WINCE || defined __CYGWIN__ || defined _MSC_VER) && defined EXPORT_API
#       define SV_EXPORTS __declspec(dllexport)
#   elif defined __GNUC__ && __GNUC__ >= 4 && defined EXPORT_API
#       define SV_EXPORTS __attribute__ ((visibility ("default")))
#   endif
#endif

#ifndef SV_EXPORTS
#   define SV_EXPORTS
#endif

#endif //SV_DEF_h_

my_math.h
#ifndef math_H_
#define math_H_
#include "SVdef.h"

SV_EXTERN_C SV_EXPORTS int Add(int a, int b);
SV_EXTERN_C SV_EXPORTS int Subtraction(int a, int b);
SV_EXTERN_C SV_EXPORTS int Multiply(int a, int b);
SV_EXTERN_C SV_EXPORTS int Divided(int a, int b);
SV_EXTERN_C SV_EXPORTS int Squared(int a, int b);

class SV_EXPORTS math_class
{ public: int Add(int a, int b); int Subtraction(int a, int b); int Multiply(int a, int b); }; #endif //math_H_
SV_EXTERN_C 會被取代為 extern "C"
SV_EXPORTS 會被取代為 __declspec(dllexport)

my_math.cpp
int Add(int a, int b)
{
    return a+b;
}

int math_class::Add(int a, int b)
{
    return a+b;
}
int math_class::Subtraction(int a, int b)
{
    return a-b;
}
int math_class::Multiply(int a, int b)
{
    return a*b;
}

三、使用CMake編譯

1.CMake 語法簡單介紹:

# 設定變數
set (變數名稱 變數值)

# 加入include header
INCLUDE_DIRECTORIES(資料夾路徑)

# 編譯成"動態程式庫"或"靜態程式庫"
# 動態程式庫參數是SHARED,靜態程式庫參數是STATIC
ADD_LIBRARY(專案名稱 動/靜態參數 需要的cpp檔案)

# c++ define
target_compile_definitions(專案名稱 屬性 define的名稱)

2.實作

Dll_1Layer/CMakeLists.txt
set (THE_MODULE "Dll_1Layer_math")

# include header
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/common)
INCLUDE_DIRECTORIES(./include)

# output Dll_1Layer_math.lib Dll_1Layer_math.dll
ADD_LIBRARY(${THE_MODULE} SHARED src/my_math.cpp)
target_compile_definitions(${THE_MODULE} PRIVATE EXPORT_API) # c++ define EXPORT_API
EXPORT_API 的定義寫 target_compile_definitions(${THE_MODULE} PRIVATE EXPORT_API)裡。

編譯完會輸出兩個之後需要使用的檔案:
  • Dll_1Layer_math.dll
  • Dll_1Layer_math.lib

留言

這個網誌中的熱門文章

C# 模擬鍵盤滑鼠控制電腦

python pyautogui 簡介

android 定時通知(永久長期的) 本篇只講AlarmManager使用

raspberrypi 開機自動執行程式 與 在terminal開啟第二個terminal執行python

python nn 聲音辨識 -1 傅立葉轉換