QT呼叫VS生成的DLL(無標頭檔案)
目錄
一、準備知識
1.1QT呼叫DLL的兩種常見方法簡介
呼叫說明:關於QT呼叫DLL方法主要分兩種,一種是顯示呼叫,另一種是隱式呼叫。
宣告: 事先我已經自己動手寫了一個簡單的dll檔案(myDLL.dll),C版介面的。首先,從dll中匯出了匯入庫(.lib)檔案,dll中有兩個函式,原型如下:
void HelloWorld(); //函式內部呼叫Win32 API,功能是彈出一個helloworld提示框
int add(int a,int b); //實現兩個數相加,並返回結果
1.2顯示呼叫
顯示呼叫需要的檔案:.h標頭檔案、.dll庫檔案。
通常Windows下程式顯示呼叫dll的步驟分為三步(三個函式):LoadLibrary()、GetProcAdress()、FreeLibrary()
其中,LoadLibrary() 函式用來載入指定的dll檔案,載入到呼叫程式的記憶體中(DLL沒有自己的記憶體!)
GetProcAddress() 函式檢索指定的動態連結庫(DLL)中的輸出庫函式地址,以備呼叫
FreeLibrary() 釋放dll所佔空間
顯示呼叫方法:
#include <QApplication> #include <QLibrary> #include <QDebug> #include <QMessageBox> #include "dll.h" //引入標頭檔案 typedef int (*Fun)(int,int); //定義函式指標,以備呼叫 int main(int argc,char **argv) { QApplication app(argc,argv); QLibrary mylib("myDLL.dll"); //宣告所用到的dll檔案 int result; if (mylib.load()) //判斷是否正確載入 { QMessageBox::information(NULL,"OK","DLL load is OK!"); Fun open=(Fun)mylib.resolve("add"); //援引 add() 函式 if (open) //是否成功連線上 add() 函式 { QMessageBox::information(NULL,"OK","Link to Function is OK!"); result=open(5,6); //這裡函式指標呼叫dll中的 add() 函式 qDebug()<<result; } else QMessageBox::information(NULL,"NO","Linke to Function is not OK!!!!"); } else QMessageBox::information(NULL,"NO","DLL is not loaded!"); return 0; //載入失敗則退出28}
myDLL.dll為自定義的dll檔案,將其複製到程式的輸出目錄下(debug)就可以呼叫。顯然,顯示呼叫程式碼書寫量巨大,實在不方便。
1.3隱式呼叫
隱式呼叫需要的檔案:.h標頭檔案、.lib庫檔案以及.dll庫檔案
隱式呼叫方法:
1、首先我們把 .h 與 .lib/.a 檔案複製到程式當前目錄下,然後再把dll檔案複製到程式的輸出目錄,
2、下面我們在pro檔案中,新增 .lib 檔案的位置: LIBS+= -L D:/hitempt/api/ -l myDLL
-L 引數指定 .lib/.a 檔案的位置
-l 引數指定匯入庫檔名(不要加副檔名)
另外,匯入庫檔案的路徑中,反斜槓用的是向右傾斜的
3、在程式中include標頭檔案(我試驗用的dll是用C寫的,因此要用 extern "C" { #include "dll.h" } )
下面是隱式呼叫的例項程式碼:
#include <QApplication>
#include <QDebug>
extern "C" //由於是C版的dll檔案,在C++中引入其標頭檔案要加extern "C" {},注意
{
#include "dll.h"
}
int main(int argv ,char **argv)
{
QApplication app(argv,argv);
HelloWordl(); //呼叫Win32 API 彈出helloworld對話方塊
qDebug()<<add(5,6); // dll 中我自己寫的一個加法函式
return 0; //完成使命後,直接退出,不讓它進入事件迴圈
}
二、QT無標頭檔案的呼叫DLL(隱式呼叫)
2.1標頭檔案的作用及與.lib庫檔案和.dll庫檔案的關係
2.1.1.h .lib .dll三者的作用分別是:
H檔案作用是:宣告函式介面
DLL檔案作用是: 函式可執行程式碼
.h標頭檔案是編譯時必須的,lib是連結時需要的,dll是執行時需要的。
附加依賴項的是.lib不是.dll,若生成了DLL,則肯定也生成 LIB檔案。如果要完成原始碼的編譯和連結,有標頭檔案和lib就夠了。如果也使動態連線的程式執行起來,有dll就夠了。在開發和除錯階段,當然最好都有。
2.1.2三者之間的關係是:
當我們在自己的程式中引用了一個H檔案裡的函式,編鏈器怎麼知道該呼叫哪個DLL檔案呢?這就是LIB檔案的作用: 告訴連結器 呼叫的函式在哪個DLL中,函式執行程式碼在DLL中的什麼位置,這也就是為什麼需要附加依賴項 .LIB檔案,它起到橋樑的作用。如果生成靜態庫檔案,則沒有DLL ,只有lib,這時函式可執行程式碼部分也在lib檔案中
.lib庫檔案(分為兩種:靜態庫&&匯入庫)
目前以lib字尾的庫有兩種,一種為靜態連結庫(Static Libary,以下簡稱“靜態庫”),另一種為動態連線庫(DLL,以下簡稱“動態庫”)的匯入庫(Import Libary,以下簡稱“匯入庫”)。靜態庫是一個或者多個obj檔案的打包,所以有人乾脆把從obj檔案生成lib的過程稱為Archive,即合併到一起。比如你連結一個靜態庫,如果其中有錯,它會準確的找到是哪個obj有錯,即靜態lib只是殼子。動態庫一般會有對應的匯入庫,方便程式靜態載入動態連結庫,否則你可能就需要自己LoadLibary調入DLL檔案,然後再手工GetProcAddress獲得對應函數了。有了匯入庫,你只需要連結匯入庫後按照標頭檔案函式介面的宣告呼叫函式就可以了。匯入庫和靜態庫的區別很大,他們實質是不一樣的東西。靜態庫本身就包含了實際執行程式碼、符號表等等,而對於匯入庫而言,其實際的執行程式碼位於動態庫中,匯入庫只包含了地址符號表等,確保程式找到對應函式的一些基本地址資訊。
2.2標頭檔案的書寫格式
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#ifdef DLL_API
#else
#define DLL_API __declspec(dllexport)
#endif
// 這裡以加法為例,其中DLL_API代表可供呼叫者呼叫的函式
DLL_API int addd(int a, int b);
2.3替代.dll標頭檔案的方法
如果在相應的.dll沒有對應的標頭檔案,則可以把宣告寫在QT工程的.cpp檔案中或者呼叫dll功能函式的.cpp檔案對應的標頭檔案中
2.4無標頭檔案呼叫DLL案例(隱式呼叫)
宣告:事先用VS生成了C++介面版的.dll以及.lib(此例題是xmldll.dll及xmldll.lib)
.cpp程式碼如下:
#include "stdafx.h"
#include <iostream>
//#include <opencv2/core/core.hpp>
//#include<opencv2/highgui/highgui.hpp>
//#include <opencv2/imgproc/imgproc.hpp>
//#include "cv.h"
//#include "cxcore.h"
//#include "highgui.h"
#include <math.h>
#include <stdio.h>
#include <windows.h>
//using namespace cv;
using namespace std;
__declspec(dllexport) int addd(int a,int b) //輸入兩個數,輸出和
{
int c;
c=a+b;
cout<<c<<endl;
return c;
}
__declspec(dllexport) int opencv_dll() //讀取名字為1.jpg的圖片,並顯示;翻轉圖片,儲存為flip.jpg
{
cv::Mat image = cv::imread("1.jpg");
cv::flip(image,image,1);
imshow("圖片", image);
cv::imwrite("flip.jpg",image);
int a=55;
cout<<a<<endl;
waitKey(0);
裡面有兩個功能函式:addd()和opencv_dll().
配置:將xmldll.dll和xmldll.lib放在debug目錄下,在QT中右擊QT工程檔案,新增庫->外部庫->新增xmldll.dll。這時專案檔案.pro多出兩行。
win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../build-test3-Desktop_Qt_5_5_0_MSVC2010_32bit-Debug/release/ -lxmldll
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../build-test3-Desktop_Qt_5_5_0_MSVC2010_32bit-Debug/debug/ -lxmldll
INCLUDEPATH += $$PWD/../build-test3-Desktop_Qt_5_5_0_MSVC2010_32bit-Debug/debug
DEPENDPATH += $$PWD/../build-test3-Desktop_Qt_5_5_0_MSVC2010_32bit-Debug/debug
這裡也可以手動新增,新增格式參見1.2顯示呼叫。接下來在標頭檔案中新增函式宣告(也可以寫在.cpp檔案中,這裡寫在widget.h中):
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#ifdef DLL_API
#else
#define DLL_API __declspec(dllexport)
#endif
// 這裡以加法為例,其中DLL_API代表可供呼叫者呼叫的函式
DLL_API int addd(int a, int b);
DLL_API int opencv_dll();
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
函式呼叫(位置:widget.cpp):
#include "widget.h"
#include "ui_widget.h"
#include <QApplication>
#include <QLibrary>
#include <QDebug>
#include <QMessageBox>
#include <iostream>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
qDebug()<<addd(4,5);
opencv_dll();
}
Widget::~Widget()
{
delete ui;
}
輸出:
9
9
55
除錯過程中遇到的問題及解決方案:
1、本例題由於涉及到opencv的配置問題,所以debug目錄下還需配齊所需的opencv庫檔案。
2、錯誤報告:
OpenCV Error: Assertion failed (size.width>0 && size.height>0) in cv::imshow, file ..\..\..\..\opencv\modules\highgui\src\window.cpp, line 261
出現這個錯誤的原因是找不到圖片的意思。檢查圖片所放置的路徑以及名稱是否與程式中的一至。圖片路徑應該放在debug的同級目錄下。程式中的名稱包含了圖片副檔名,當圖片格式為.jpg時,圖片命名為1就可以了。如果命名為1.jpg實際上是1.jpg.jpg,程式便會報錯找不到圖片。