1. 程式人生 > >Qt中純C++專案釋出為dll的方法(超詳細步驟)

Qt中純C++專案釋出為dll的方法(超詳細步驟)

目錄

  • 一般建立方法
  • 匯出普通函式的方法&呼叫方法
  • 匯出類及其成員函式的方法&呼叫方法

眾所周知,我們可以將C++專案中的類以及函式匯出,形成 .dll 檔案,以供其他程式使用,下面將說明Qt環境下的使用方法。

首先建立共享庫,步驟如下:

下一步會出現類對話方塊等等,不用管它,直接點選建立即可,稍後再將類都刪了。

建立好以後你會發現有一個包含global的標頭檔案,這個檔案中定義了__declspec(dllexport)、__declspec(dllimport)等,也可以刪掉(如果要按照下面的方法的話,刪掉即可)。

接下來還有重要的一步,請在.pro檔案中,加入

CONFIG += dll //即使你的程式碼中寫成了 CONFIG += staticlib,也要改過來

接下來就開始我們具體的的建立方法吧!

按照匯出dll的的操作劃分,有兩種模式:

  • 匯出普通方法(匯出後可靜態呼叫,也可動態呼叫)

首先是標頭檔案內容:

複製程式碼

標頭檔案:

在方法宣告時,前面加上__declspec(dllimport),也可用#define定義,看程式碼:

//為了和將來用到的程式中公用一個頭檔案,建立dll時用到的是__declspec(dllexport),而使用dll時用到__declspec(dllimport),完全可以各自寫一下
#define TESTDLLSHARED_EXPORT __declspec(dllexport)
#ifdef TESTDLLSHARED_EXPORT
#else
#define TESTDLLSHARED_EXPORT __declspec(dllimport)
#endif

//下面我要定義4個普通函式:
TESTDLLSHARED_EXPORT int test1();

TESTDLLSHARED_EXPORT int test2(void);

TESTDLLSHARED_EXPORT int test3(int a);

TESTDLLSHARED_EXPORT int test4(int a,int b);

複製程式碼

 然後在對應的cpp原始檔中實現test1、test2、test3、test4這幾個方法(為了簡單,我只輸出了一句話):

複製程式碼

.cpp原始檔:

//記得加上上面的標頭檔案
//記得加上iostream標頭檔案,不然cout不能用

int test1()
{
    std::cout<<"test1"<<std::endl;
}

int test2(void)
{
    std::cout<<"test2"<<std::endl;
}

int test3(int a)
{
    std::cout<<"test3"<<std::endl;
}

int test4(int a,int b)
{
    std::cout<<"test4"<<std::endl;
}

複製程式碼

接下來就可以建立了,建立成功後(建立失敗請仔細檢查,程式碼已驗證過),你會在你的專案輸出目錄下找到一個和專案名稱一致的.dll檔案。

OK!接下來我們來使用這個dll:

使用dll時有兩種呼叫方法,一種是靜態呼叫,一種是動態呼叫。

  • 靜態呼叫

首先,建立一個常規的C++專案,將上面生成.dll複製到你的專案輸出目錄中(也就是和.exe檔案在一起);

接下來,開啟常規C++專案中的.pro檔案,加入詳細的.dll檔案地址,格式如下

LIBS += 專案輸出路徑\dll檔案全稱

如我的就是

LIBS += D:\Desktop\Go\C++learnProgram\Qt\build-test_dll-Qt-Release\release\HpTickDll.dll
D:\Desktop\Go\C++learnProgram\Qt\build-test_dll-Qt-Release\release是我的專案輸出路徑,HpTickDll.dll是我的dll檔案,使用時改動一下就好

接下來,將建立.dll檔案時的標頭檔案複製到當前專案路徑下,並新增到專案中,注意:如果你沒有按照我上面的#define條件定義方式,請重新寫__declspec(dllimport)。

接下來,在.cpp檔案中包含該標頭檔案,就可以盡情地使用之前的函數了,如直接用test1()等等,就和平時編寫一樣的。

  • 動態呼叫

!!!特別注意:我們在建立時沒有用到extern "C",也沒有用到.def 檔案保持函式名不變(嘗試了很多次也不會用.def檔案,會的歡迎留言),因此動態呼叫時函式名要改!因為編譯器已經將函式名改了!

So,你一定會問我們怎麼知道dll中的函式名變成啥了?不要著急,網上直接搜“.dll檢視器”,遍地都是,下載下來後,選擇我們剛才的.dll檔案就可以看了,下面是我們的這幾個函式test1、test2、test3、test4的新名稱(你的可能和我的不一樣哦):

請注意看紅色框中的部分(不要管其他的,我的檔案裡面還有其他東西),這就是四個函式在.dll檔案中的名稱,我小小地猜測了一下,後面的v代表引數為void型別,i的個數代表int型別引數的個數,前面的字母就不太清楚了(注意:引數個數並未正確列出,不過我們只需要正確的函式名,不影響)。

OK!這就好辦了!看步驟:

同樣是建立一個常規C++專案,不同的是.pro檔案中不用加“LIBS += 專案輸出路徑\dll檔案全稱”這句話了。

接下來在.cpp檔案中寫主程式碼(不用新增之前的標頭檔案):

複製程式碼

#include <iostream>
#include <windows.h>

int main()
{
    //首先定義函式指標,用來接收不同引數的函式
    typedef int (CALLBACK *Fucv)();
    typedef int (CALLBACK *Fuci)(int);
    typedef int (CALLBACK *Fucii)(int,int);

    //獲得.dll檔案的控制代碼,需要標頭檔案windows.h的支援
    HINSTANCE hdll=LoadLibrary(L"testDll.dll");     //L指寬字串,若不寫L,則會出現錯誤,詳情請自查

    //注意這裡要用.dll檔案中的函式名
    Fucv t1=(Fucv)GetProcAddress(hdll,"_Z5test1v");
    Fucv t2=(Fucv)GetProcAddress(hdll,"_Z5test2v");
    Fuci t3=(Fuci)GetProcAddress(hdll,"_Z5test3i");
    Fucii t4=(Fucii)GetProcAddress(hdll,"_Z5test4ii");

    //現在的t1就執行的test1的功能,以此類推
    t1();
    t2();
    t3(1);
    t4(1,2);

    FreeLibrary(hdll);

    return 0;
}

複製程式碼

以上就是普通函式建立.dll和使用.dll的方法,看起來動態呼叫是不是很麻煩?但它有許多優點(請自查),而且據說這裡的函式名可以利用.def檔案實現不改變名稱,省去好多麻煩,但是我嘗試了各種方法,如在.pro中利用DEF_FILE新增.def檔案還是不行55555,會的可以留言哦!

  • 匯出類及其成員函式(匯出後可靜態呼叫。暫時不會動態呼叫,某些書上說類不支援動態呼叫,網上有說在類中寫一個方法返回類物件,但是個人認為這種做法是不對的,因為此時肯定是用自己定義的函式指標去定義這個返回類物件方法,不可能成功【已驗證】,當然或許還有更好的方法,有知道的歡迎交流哈)

對於類的話,建立時:在class的後面,類名的前面加上定義的__declspec(dllexport);使用時,換成__declspec(dllimport),也可以參照前面的#define條件定義法。舉例:

複製程式碼

#define HPTICKDLLSHARED_EXPORT __declspec(dllexport)
#ifdef HPTICKDLLSHARED_EXPORT
#else
#define HPTICKDLLSHARED_EXPORT __declspec(dllimport)
#endif

class HPTICKDLLSHARED_EXPORT HpTickDll  //我在這裡定義了類HpTickDll
{   
public:
    int  Start();                       //注意成員函式之前不用加HPTICKDLLSHARED_EXPORT
    int  GetTime();

private:

    LARGE_INTEGER li;
    LONGLONG start, end, freq;
    int useTime;
};

複製程式碼

靜態呼叫的方法和普通函式一樣,直接可以使用類及其成員函式(別忘了新增標頭檔案),就不細說了。

『注:本文來自部落格園“小溪的部落格”,若非宣告均為原創內容,請勿用於商業用途,轉載請註明出處http://www.cnblogs.com/xiaoxi666/』

=============================================================================================

關於“小溪的部落格”所提到的.def檔案,實現函式名不改變名稱的方法。

本文給出如下使用方法:

.def檔案的規則為:

DLL匯出函式的宣告有兩種方式:一種為在函式宣告中加上__declspec(dllexport),這裡不再舉例說明;另外一種方式是採用模組定義(.def) 檔案宣告,.def檔案為連結器提供了有關被連結程式的匯出、屬性及其他方面的資訊。

            首先建立一個DLL程式.cpp

int __stdcall Add(int numa, int numb)

{

       return (numa + numb);

}

int __stdcall Sub(int numa, int numb)

{

        return (numa - numb);

}

             然後建立一個.def的檔案,在裡面加上

;DllTestDef.lib : 匯出DLL函式

;作者:----

LIBRARY DllTestDef

EXPORTS

Add @ 1

Sub @ 2

           最後建立一個測試程式:.cpp檔案如下:

#include <iostream>

#include <windows.h>

using namespace std;

typedef int (__stdcall *FUN)(int, int);

HINSTANCE hInstance;

FUN   fun;

int main()

{

       hInstance = LoadLibrary("DLLTestDef.dll");

       if(!hInstance)

           cout << "Not Find this Dll" << endl;

       fun = (FUN)GetProcAddress(hInstance, MAKEINTRESOURCE(1));

       if (!fun)

       {

              cout << "not find this fun" << endl;

       }

       cout << fun(1, 2) << endl;

       FreeLibrary(hInstance);

       return 0;

}

函式解釋:

  1. MAKEINTRESOURCE是一個資源名轉換的巨集,這個巨集是把一個數字型別轉換指標型別的巨集,它不存在釋放的問題。是把一個"數字形ID",轉化為"字串".

FARPROC GetProcAddress(

HMODULE hModule, // DLL模組控制代碼

LPCSTR lpProcName // 函式名

);

hModule 包含此函式的DLL模組的控制代碼。LoadLibraryAfxLoadLibrary 或者GetModuleHandle函式可以返回此控制代碼。

lpProcName指標指向的函式名,拼寫和大小寫必須和DLL原始碼中的模組定義檔案(.DEF)中輸出段(EXPORTS)中指定的相同。Win32 API函式的輸出名可能不同於你在程式碼中呼叫的這些函式名,這個不同被巨集隱含在相關的SDK標頭檔案中。如果想得到更多資訊,請參考Win32函式原型(Win32 Function Prototypes)

lpProcName引數能夠識別DLL中的函式,通過指定一個與函式相聯絡的序數值(.DEF中的EXPORTS)GetProcAddress函式驗證那個指定的序數值是否在輸出的序數1和最高序數值之間(.DEF)。函式用這個序數值作為索引從函式表中讀函式地址,假如.DEF 檔案不連續地定義函式的序數值,如從1N(N是輸出的函式序數值),錯誤將會發生,GetProcAddress將會返回一個錯誤的、非空的地址,雖然指定的序數沒有對應的函式。

為了防止函式不存在,函式應該通過名字指定而不是序數值。