跨平台的 plugin 開發函式庫:Boost DLL – 基本使用


Boost.DLL 是 Boost C++ Libraries 在 1.61 版加入的一個新的函式庫,他的設計目的,是讓開發者可以寫出跨平台的 plungin 架構,也就是在程式執行時、再去讀取動態函式庫(DLL、SO/DSO)的檔案來用。

這樣的好處,是可以自己開發一個程式的框架,然後把擴充的方法定義好、讓其他人自行開發符合架構的模組來做擴充;基本上,應該算是一個滿有效、增加程式彈性的方法~

也由於採用這樣的機制可以不需要釋出主程式的原始碼、也可以在不修改主程式的情況下、擴增功能,所以很多軟體,也都是用這種方法來作為擴充、或是提供第三方開發的方法。

而這篇 Heresy 就是簡單測試一下 Boost.DLL 的紀錄了。

首先,官方的文件是:http://www.boost.org/doc/html/boost_dll.html,最基本的使用方法,則可以參考《Getting started》的部分。

Boost.DLL 提供可以匯出、匯入的東西,包括了函式和變數;而由於變數也可以是自己定義的型別,所以基本上彈性也就相當地大了。

要開發這樣的程式,基本上需要兩個(或以上的專案):

  • 一個是本來的主程式

    以下簡稱主程式。
    在 Windows 上來說,就是 .exe 的可執行檔。在他的程式碼裡面,要加入 Boost.DLL 的匯入(import)外部 DLL 檔的功能。
    以 Heresy 個人習慣,在 Visual Studio 中,在建立新專案的時候,會選擇「Win32 主控台應用程式」,然後在「應用程式設定」的部分,選擇「主控台應用程式」。

  • 另一個專案則是要被匯入的動態函式庫

    以下簡稱模組。
    以 Windows 來說,就是 DLL 檔。在這個專案的程式碼中,要根據預先定義好的規範,來實作對應的函式,並把這些函式匯出、讓主程式可以使用。
    以 Heresy 個人習慣,在 Visual Studio 中,在建立新專案的時候,會選擇「Win32 主控台應用程式」,然後在「應用程式設定」的部分,選擇「DLL」。


撰寫模組

而在一般狀況下,如果要撰寫模組的話,就是要先 include boost/config.hpp 這個檔案,然後在要匯出的變數、或是函式前面,加上「extern "C" BOOST_SYMBOL_EXPORT」就可以了;如果要好看一點的話,也可以像官方範例依樣,先定義:

#define API extern "C" BOOST_SYMBOL_EXPORT

如此一來,要匯出一個變數,就可以簡單地寫成:

API std::string sModuleName;

下面就是一個完整的模組的程式碼的範例:

// STD Headers
#include <string>

// Boost Headers
#include <boost/config.hpp>
#define API extern "C" BOOST_SYMBOL_EXPORT

API std::string sModuleName = "Module A";

API std::string getName()
{
    return sModuleName;
}

 

在這個範例裡面,匯出了 sModuleName 這個變數、以及 getName() 這個函式。

之後,只要建置這個專案,產生 DLL 檔就可以了。


撰寫主程式

在主程式的部分,則是要先 include boost/dll.hpp 這個檔案,然後再用 boost::dll::import<>() 這個函式,來把所需要的變數、或是函式,從模組檔案中匯入使用。

他最簡單的使用方法,就是透過 template 的形式、指定要匯入的東西的型別,然後再用引數來指定模組檔案的路徑、以及要匯入的項目名稱,這樣就可以了。

以上面的例子來說,如果要匯入 sModuleName 這個變數的話,可以寫成:

boost::shared_ptr<std::string> pVar = 
boost::dll::import<std::string>(rPath, "sModuleName");

其中,rPath 的型別是 boost::filesystempath,代表的是 DLL 檔所在的路徑。

而由於 sModuleName 是一個字串,所以這邊匯進來的變數(pVar)的型別,就是一個 boost::shared_ptr<std::string> 的 smart pointer(參考)。

至於函式的部分,也是使用同樣的匯入方法,型別的話則是可以使用標準函式庫的 std::function<>參考);下面就是要匯入 getName() 這個函式的方法:

std::function<std::string()> funcExt = 
boost::dll::import<std::string()>(pathDLL, "getName");

之後就可以把 funcExt 當作一般的 function object 來使用了。

整個完整的主程式的程式碼,則會像下面這樣:

// STD Headers
#include <iostream>

// Boost Headers
#include <boost/dll.hpp>

int main(int argc, char** argv)
{
    #ifdef _WIN32
    boost::filesystem::path pathDLL = "DLLa.dll";
    #else
    boost::filesystem::path pathDLL = "libDLLa.so";
    #endif

    boost::shared_ptr<std::string> pVar = boost::dll::import<std::string>( pathDLL, "sModuleName" );
    std::cout << "Variable: " << *pVar << std::endl;

    std::function<std::string()> funcExt = boost::dll::import<std::string()>(pathDLL, "getName");
    std::cout << "Function: " << funcExt() << std::endl;

    return 0;
}

這邊可以看到,由於 Windows 和 Linux 習慣的動態函式庫檔案命名方式不同,所以還是使用 ifdef 的方法來做區隔,但是其他的部分,程式碼就完全相同了~

而這樣的執行結果,會是:

Variable: Module A
Function: Module A

不過可能要注意的是,由於 Visual Studio 預設的執行路徑會是專案路徑,所以直接在 Visual Studio 按 F5 的話,會因為讀不到所需要的 DLLa.dll 檔、而導致程式當到;所以在執行的時候,建議請把偵錯路徑設定到方案(solution)路徑下對應的 debug 或 release 資料夾(例如 x64 debug 版就是「$(SolutionDir)\x64\Debug」)再執行。

如果有問題的話,也建議請確認 App.exe 和 DLLa.dll 檔有位在同一個目錄中,或是手動將他們放到一起再執行;當然,另一個方法就是修改 pathDLL 的路徑、甚至讓他用自動搜尋的。


Linux 的建置

老實說,Heresy 雖然因為工作上的關係,寫的程式大多會注意跨平台,但是由於主力開發環境還是 Windows + Visual C++,所以其實 Linux 的開發、尤其是 Makefile 的撰寫,並不是那麼熟系。

而這邊,則算是簡單紀錄一下 Heresy 自己摸出來、可以用的 Makefile 了;老實說,細節的參數 Heresy 自己也不熟就是了…

首先,是模組的部分。

INC_PATH 	= -I/usr/include
LIB_PATH	= -L/usr/lib
TARGET_PATH	= ../Bin

# for gcc
C++ = g++

# system command
CP = cp -f
RM = rm -f

CFLAGS	= --std=c++11 -fPIC -g -fvisibility=hidden

DLL		= libDLLa.so
DLL_OBJ		= DLLa.o

.SUFFIXES:
.SUFFIXES:	.cpp .o

all: $(DLL)

$(DLL): $(DLL_OBJ)
	$(C++) -shared -o $@ $^ $(LIB_PATH) -lboost_system -lboost_filesystem -ldl
	$(CP) $@ $(TARGET_PATH)

.cpp.o:
	$(C++) $(CFLAGS) $(INC_PATH) -c $<

clean:
	$(RM) *.o $(DLL)

根據這個 Makefile,在建置的時候會先把 DLLa.cpp 編譯成 DLLa.o,然後再和其他函式庫 link、產生出最後的 libDLLa.so。這邊和編譯一般城市的不同,主要就是編譯和連結階段的參數了。

而主程式的 Makefile 則就比較一般了。

INC_PATH 	= -I/usr/include
LIB_PATH	= -L/usr/lib
TARGET_PATH	= ../Bin

# for gcc
C++ = g++

# system command
CP = cp -f
RM = rm -f

CFLAGS	= --std=c++11

APP		= App.out
APP_OBJ		= AppMain.o

.SUFFIXES:
.SUFFIXES:	.cpp .o

all: $(APP)

$(APP): $(APP_OBJ)
	$(C++) -pg -o $@ $^ $(LIB_PATH) -lboost_system -lboost_filesystem -ldl
	$(CP) $@ $(TARGET_PATH)

.cpp.o:
	$(C++) $(CFLAGS) $(INC_PATH) -c $<

clean:
	$(RM) *.o $(APP)

最後,這兩個專案都建置好了之後,檔案會被放到外層的 Bin 資料下,然後再執行 App.out,就可以進行測試了。


Boost.DLL 最基本的使用方法,大概就是這樣了。

這篇文的完整程式碼(含專案、Makefile),可以在 Heresy 的 GitHub 上找到(連結);有興趣的話可以自己試試看。

而實際上,Boost.DLL 還有一些東西可能是會用到的。

像是除了直接使用 BOOST_SYMBOL_EXPORT 把變數或函式匯出外,他也還有提供 BOOST_DLL_ALIAS 這個巨集,可以把現有的函式、變數,取一個新的別名在匯出。

另外,library_info 這個類別(官網)也有提供基本的功能,可以查詢匯入的模組中有那些東西可以用;而 shared_library 這個類別(官網)感覺上則是可以用更物件化的方式、來操作一個匯入的模組。

之後,有機會再來整理後續吧~


Advertisements

關於 Heresy
https://kheresy.wordpress.com

4 Responses to 跨平台的 plugin 開發函式庫:Boost DLL – 基本使用

  1. Sam says:

    版主您好,拜讀文章多時,受用無窮。

    先前不曾留意 Boost.DLL,讀了版主的文章才知道,學習了。我想本文的範例碼主要在示範 Boost.DLL 的用法,不過,關於 DLL 的介面,國內 C++ 同好點出一個潛在問題:

    “跨 DLL/EXE 使用 std::string 的話,如果使用的 libc 版本不同,會出問題喔。"

    供您參考。

    喜歡

    • Heresy says:

      感謝提醒。
      Heresy 自己還沒用多少,還沒碰過,不過看來的確是要注意的。

      喜歡

  2. 通告: 跨平台的 plugin 開發函式庫:Boost DLL-搭配自訂類別 | Heresy's Space

  3. 通告: 跨平台的 plugin 開發函式庫:Boost DLL – 進階 | Heresy's Space

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s

%d 位部落客按了讚: