c++動態庫生成與呼叫
一、生成動態庫(含標頭檔案、不含標頭檔案)
以生成dllTest.dll為例(工程名為dllTest、 標頭檔案名為dllTest.h、 原始檔名為dllTest.cpp)1.1 不含標頭檔案的動態庫
我們生成的動態庫想要被別人呼叫,那麼一定要將想要被呼叫的函式匯出,使用_declspec(dllexport)進行匯出。//dllTest.cpp
_declspec(dllexport) int add(int a, int b)
{
return a+b;
}
_declspec(dllexport) int sub(int a, int b)
{
return a-b;
}
編譯之後,在工程目錄的Debug目錄下我們可以看到以下的幾個檔案,分別是
1.2 動態庫呼叫(隱式連線、動態連線)
1.2.1 隱式連線
首先建立一個工程,配置工程屬性,具體的步驟如下: (1)新建一個win32控制檯應用程式,工程名dllCall; (2)配置工程屬性,新增對動態庫dllTest.lib的引用: a. 工程專案右鍵->屬性->連結器->gengeral->附加庫目錄,在其中新增匯入庫dllTest.lib所在的檔案目錄,如下圖:b.工程專案右鍵->屬性->連結器->輸入->附加依賴項,在其中新增匯入庫dllTest.lib,如下圖:
(3)此時,我們就可以在我們的工程中對該動態庫進行呼叫了。 呼叫函式dllCal.cppl如下:
<pre name="code" class="cpp">// dllCall.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
using namespace std;
extern int add(int, int ); //告訴編譯器,add函式是在該原始檔外部定義的函式
_declspec(dllimport) int sub(int, int);//告訴編譯器,sub函式是從動態庫匯入的函式
//這兩種方式都可以正常的呼叫,但是下面的相對來說載入的更快一些
int _tmain(int argc, _TCHAR* argv[])
{
int a = 5, b = 3;
cout<<a<<" + "<<b<<" = "<<add(a,b)<<endl;
cout<<a<<" - "<<b<<" = "<<sub(a,b)<<endl;
return 0;
}
(4)編寫好之後,編譯、連結都能通過,但是執行的時候會出錯,因為呼叫函式不知道int add(int, int ) 以及int sub(int, int)的可執行檔案(dllTest.dll)在哪,因此當呼叫該函式時,就找不到具體的執行的程式碼,因而就會報錯。此時,我們只需將dllTest.dll放入到呼叫函式的.exe檔案所在的目錄中即可,結果如下圖: 上述的兩種形式都能正常的呼叫。
1.2.2 動態呼叫
上述的隱式呼叫需要我們在工程屬性中配置一些動態庫匯入庫(dllTest.lib)的目錄以及名稱,會很麻煩,有時候我們也會忘記配置這些屬性或者當動態庫較多的時候有遺漏,都會導致函式連結的時候出現unresolve external symbol的錯誤。而且,動態呼叫還有一個優點就是,什麼時候需要呼叫動態庫的函式的時候什麼時候載入該動態庫,這樣就不必在程式執行開始時載入所需的所有的動態庫,這樣也能加快啟動的速度。此外,我們僅僅只需要一個dllTest.dll檔案即可。 (1)我們首先刪掉工程屬性中 “附加庫目錄”以及“附加依賴項”中我們輸入的內容。 (2)修改相應的呼叫程式碼,如下:// dllCall.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include<Windows.h>
using namespace std;
//extern int add(int, int ); //告訴編譯器,add函式是在該原始檔外部定義的函式
//_declspec(dllimport) int sub(int, int);//告訴編譯器,sub函式是從動態庫匯入的函式
//這兩種方式都可以正常的呼叫,但是下面的相對來說載入的更快一些
int _tmain(int argc, _TCHAR* argv[])
{
int a = 5, b = 3;
HINSTANCE hInst = LoadLibraryA("dllTest.dll");
typedef int (*pFun)(int, int);//定義一個函式指標型別pAdd
pFun add = (pFun)GetProcAddress(hInst,"[email protected]@[email protected]");
cout<<a<<" + "<<b<<" = "<<add(a,b)<<endl;
pFun sub = (pFun)GetProcAddress(hInst,"[email protected]@[email protected]");
cout<<a<<" - "<<b<<" = "<<sub(a,b)<<endl;
return 0;
}
(3)程式執行正常,結果同上面的結果。2.含標頭檔案的動態庫
我們一般用編寫一個動態庫,同時也會提供一個頭檔案方便呼叫者使用,因為呼叫者一般情況下很難知道我們編寫的動態庫具體匯出的函式。而且,我們不想想上面的函式呼叫一樣,還要自己寫_declspec(dllimport) int add(int, int)或者extern int add(int, int);這是我們需要向呼叫者提供一個頭檔案完成這項工作,是呼叫變得更加方便。 標頭檔案://dllTest.h
#ifdef DLLTEST_API
#else
#define DLLTEST_API _declspec(dllimport)
#endif
DLLTEST_API int add(int, int);
DLLTEST_API int sub(int, int);
</pre><pre name="code" class="cpp"><span style="font-family: Arial, Helvetica, sans-serif;"> </span><span style="font-family: Arial, Helvetica, sans-serif;">原始檔:</span>
<pre name="code" class="cpp">//dllTest.cpp
#define DLLTEST_API _declspec(dllexport)
#include "dllTest.h"
DLLTEST_API int add(int a, int b)
{
return a+b;
}
DLLTEST_API int sub(int a, int b)
{
return a-b;
}
2.1 函式呼叫(隱式連結、動態連結)
2.1.1 隱式連結
(1) 新建工程,配置工程屬性(新增匯入庫目錄、匯入庫),具體的參照上面的隱式呼叫。此外,還要將動態庫標頭檔案所在的目錄加入到屬性中: 在 c\c++ -> gengeral -> additional include Directories 加入dllTest.h所在的目錄。 如下圖: (2)修改呼叫函式,具體的程式碼如下:// dllCall.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include "dllTest.h"
using namespace std;
//extern int add(int, int ); //告訴編譯器,add函式是在該原始檔外部定義的函式
//_declspec(dllimport) int sub(int, int);//告訴編譯器,sub函式是從動態庫匯入的函式
//這兩種方式都可以正常的呼叫,但是下面的相對來說載入的更快一些
int _tmain(int argc, _TCHAR* argv[])
{
int a = 5, b = 3;
cout<<a<<" + "<<b<<" = "<<add(a,b)<<endl;
cout<<a<<" - "<<b<<" = "<<sub(a,b)<<endl;
return 0;
}
(3)編譯、連結、執行正常,結果如上面。此外,我們也可以不用在屬性->連結器->輸入->附加庫目錄中新增dllTest.lib, 通過在呼叫函式中新增#pragma comment(lib,"dllTest.lib")即可。具體程式碼為:// dllCall.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include "dllTest.h"
#pragma comment(lib,"dllTest.lib")
using namespace std;
//extern int add(int, int ); //告訴編譯器,add函式是在該原始檔外部定義的函式
//_declspec(dllimport) int sub(int, int);//告訴編譯器,sub函式是從動態庫匯入的函式
//這兩種方式都可以正常的呼叫,但是下面的相對來說載入的更快一些
int _tmain(int argc, _TCHAR* argv[])
{
int a = 5, b = 3;
cout<<a<<" + "<<b<<" = "<<add(a,b)<<endl;
cout<<a<<" - "<<b<<" = "<<sub(a,b)<<endl;
return 0;
}
2.1.2 動態連結
動態連結同上面的一樣。3 不改變名字的匯出庫
但是我們發現,通過動態連結時,那個函式的名字經過c++編譯器修飾後很複雜,剛開始並不熟悉修飾的規則(後面講修飾規則)。因此,如果我們需要先用dumpbin工具查詢該動態庫匯出的函式及其名字,才方便呼叫。那麼,怎麼才能使匯出的函式名字不發生變化呢,同我們定義的函式名一樣,這樣我們在動態呼叫時更加方便。具體的方法有兩種:一是採用extern "C";二是用模組定義檔案.def。3.1 extern "C"
3.1.1 預設呼叫方式_cedel
以含標頭檔案的動態庫為例進行修改,修改後的標頭檔案和原始檔如下://dllTest.h
#ifdef DLLTEST_API
#else
#define DLLTEST_API extern "C" _declspec(dllimport)
#endif
DLLTEST_API int add(int, int);
DLLTEST_API int sub(int, int);
//dllTest.cpp
#define DLLTEST_API extern "C" _declspec(dllexport)
#include "dllTest.h"
DLLTEST_API int add(int a, int b)
{
return a+b;
}
DLLTEST_API int sub(int a, int b)
{
return a-b;
}
進行編譯得到相應的.dll、.lib檔案,通過dumpbin工具,我們可以檢視匯出函式的名字,如下:
紅色部分為 沒有加extern "C" 匯出的函式名,它經過了c++編譯器的修飾。 綠色部分為添加了extern "C" 匯出的函式名,它的意思是告訴編譯器,以C的方式匯出函式。
3.1.2 更改呼叫方式 _stdcall
但是如果改變了呼叫方式,通過這種方式進行匯出,函式的名字依舊會改變,我們採用_stdcall呼叫方式,也就是WINAPI,後面講為什麼window API 函式都採用該種呼叫方式:
(1)編寫_stdcall呼叫方式的動態庫(以含標頭檔案方式為例) 相應的標頭檔案和原始檔如下://dllTest.h
#ifdef DLLTEST_API
#else
#define DLLTEST_API extern "C" _declspec(dllimport)
#endif
DLLTEST_API int _stdcall add(int, int);
DLLTEST_API int _stdcall sub(int, int);
//dllTest.cpp
#define DLLTEST_API extern "C" _declspec(dllexport)
#include "dllTest.h"
DLLTEST_API int _stdcall add(int a, int b)
{
return a+b;
}
DLLTEST_API int _stdcall sub(int a, int b)
{
return a-b;
}
編譯後利用dumpbin工具檢視匯出函式的名字如下:
紅色的為採用c\c++預設呼叫方式匯出的函式名字; 綠色的為採用_stdcall 呼叫方式匯出的函式名字。至於為什麼會是這樣的名字在後面的名字修飾規則中進行說明。我們發現他的名字還是改變了。
3.2 模組定義檔案.def
3.2.1 預設呼叫方式(_cedel)動態庫
(1)新建一個Win32程式,選擇一個動態庫程式,勾選空工程。 (2)修改相應的程式碼(原始檔、模組定義檔案),具體的如下: 原始檔://dllTest.cpp
int add(int a, int b)
{
return a+b;
}
int sub(int a, int b)
{
return a-b;
}
模組定義檔案:
LIBRARY dllTest
EXPORTS
add
sub
其中,LIBRARY 後面要與生成的動態庫的名稱相同,EXPORTS下面寫需要匯出的函式(add、sub),它會自動與你的原始檔中的函式進行匹配。也可以用 add1 = add、 sub1 = sub 這樣的方式來改變匯出函式的名字。(3)通過dumpbin工具檢視動態庫的匯出函式,如下圖(分別是沒改名字以及改名字後的):
3.2.2 採用_stdcall,也就是WINAPI呼叫方式生成動態庫
(1) 原始檔//dllTest.cpp
int _stdcall add(int a, int b)
{
return a+b;
}
int _stdcall sub(int a, int b)
{
return a-b;
}
(2)模組定義檔案
LIBRARY dllTest
EXPORTS
add
sub
(3)利用dumpbin查詢動態庫匯出的函式
3.3 動態庫呼叫
3.3.1 動態呼叫
動態呼叫同上,只是變了一個地方,如下:<span style="white-space:pre"> </span>pFun add = (pFun)GetProcAddress(hInst,"add");
<span style="white-space:pre"> </span>pFun sub = (pFun)GetProcAddress(hInst,"sub");
也可以使用每個函式前面的編號,如add函式的編號為1 、sub函式的編號為2。只需要將上述函式的第二個引數改為
MAKEINTATOM(1)。
3.3.2 隱式呼叫
<1> 動態庫與函式庫都採用_cedel呼叫方式 (1) 配置工程屬性,加入附加庫目錄;(附加依賴項使用#pragma comment(lib,"testdll.lib"代替) (2)呼叫函式如下:// dllCall.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#pragma comment(lib,"testdll.lib")
using namespace std;
_declspec(dllimport) int add(int, int);
_declspec(dllimport) int sub(int, int);
int _tmain(int argc, _TCHAR* argv[])
{
int a = 5, b = 3;
cout<<a<<" + "<<b<<" = "<<add(a,b)<<endl;
cout<<a<<" - "<<b<<" = "<<sub(a,b)<<endl;
return 0;
}
(3)編譯、連結正常,結果正確。
<2>動態庫函式採用_stdcall呼叫方式,呼叫方採用預設的呼叫方式(_cedel)
(1)新建工程,配置工程屬性,加入附加庫目錄;(附加依賴項使用#pragma comment(lib,"testdll.lib"代替)
(2)編寫呼叫函式,如下:
// dllCall.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#pragma comment(lib,"testdll.lib")
using namespace std;
_declspec(dllimport) int add(int, int);
_declspec(dllimport) int sub(int, int);
int _tmain(int argc, _TCHAR* argv[])
{
int a = 5, b = 3;
cout<<a<<" + "<<b<<" = "<<add(a,b)<<endl;
cout<<a<<" - "<<b<<" = "<<sub(a,b)<<endl;
return 0;
}
(3)編譯、連結正常,執行出錯。
這是因為_stdcall是被呼叫的函式自己清理棧空間,而_cedel則是呼叫者清理棧。上述的呼叫方式會使棧得到兩次清理,使得函式的返回地址、ebp暫存器的值被更改從而導致失敗。通過觀察其生成的組合語言就可以看到(下面的主要的,並不是全部):
main函式在呼叫add函式之後清理的棧,而在add函式中,可以清楚的看到,add函式在執行完之後自己清理的棧。
因此,導致棧資料出現錯誤。 c\c++編譯器修飾規則和函式呼叫中棧的變化後面有時間在寫!!!
相關推薦
c++動態庫生成與呼叫
一、生成動態庫(含標頭檔案、不含標頭檔案) 以生成dllTest.dll為例(工程名為dllTest、 標頭檔案名為dllTest.h、 原始檔名為dllTest.cpp) 1.1 不含標頭檔案的動態庫 我們生成的動態庫想要被別人呼叫,那麼一定要將想要被呼叫的函式匯出,使用
純C++ 動態庫生成
top pub rar start 找到 win 編譯 nbsp tro 目錄 一般創建方法 導出普通函數的方法&調用方法 導出類及其成員函數的方法&調用方法 眾所周知,我們可以將C++項目中的類以及函數導出,形成 .dll 文件,以
Linux動態庫生成以及呼叫
Linux下動態庫檔案的檔名形如 libxxx.so,其中so是 Shared Object 的縮寫,即可以共享的目標檔案。 在連結動態庫生成可執行檔案時,並不會把動態庫的程式碼複製到執行檔案中,而是在執行檔案中記錄對動態庫的引用。 程式執行時,再去載入動態庫檔案。如果動態庫已經載入,則不必重複
linux c/c++ 動態庫和靜態庫的生成與使用
二.介紹 從原始碼到可執行程式,通常要經過最重要的兩大步是:編譯,連結。編譯就是將原始檔生成中間檔案的過程,在linux下就是生成 .obj檔案。連結就是用連結器將,這些個中間檔案有序地”糅合“在一起,構成一個可執行檔案。通常,一個.c檔案或者.cpp原始檔編譯後,就會對應生成一個.obj檔案。
Eclipse CDT生成、呼叫C動態庫
(一) 生成動態庫 1 建立動態庫工程 File->New->Project->CProject->選擇Shared Library,工程命名為test。 2 建立原始碼檔案 File->New->Source File,指定名稱為te
g++ 編譯連結C++程式碼, 生成與使用靜態庫和動態庫
例如我有A.cpp、A.h、main.cpp 三個檔案 編譯連結C++程式碼: 第一步:g++ -c A.cpp main.cpp 這樣就可以編譯A.cpp和main.cpp的程式碼生成A.o和main.o檔案【因為A.cpp包含了A.h的標頭檔案,所以一般編譯時
2017.10.11 C#呼叫C++ 動態庫記憶體溢位問題
最近在寫C#呼叫C++ 動態庫的例子,發現使用Stringbuider 時,重複使用時容易造成記憶體溢位的問題,網上查了之後 發現可以使用 Stringbuider .Remove(0, Stringbuider .Length)方法,但是經過測試後並沒有解決問題。 後來經過仔細分析,
C++---動態庫與靜態庫的區別
首先介紹一下靜態庫(靜態連結庫)、動態庫(動態連結庫)的概念,首先兩者都是程式碼共享的方式。 靜態庫:在連結步驟中,聯結器將從庫檔案取得所需的程式碼,複製到生成的可執行檔案中,這種庫稱為靜態庫,其特點是可執行檔案中包含了庫程式碼的一份完整拷貝;缺點就是被多次使用就會有多份冗餘拷貝。即靜態庫中的指
Delphi XE7呼叫C++動態庫出現亂碼問題
事情源於有個客戶需使用我們C++的中介軟體動態庫來跟裝置連線通訊,但是傳入以及傳出的字串指標格式都不正確(出現亂碼或是被截斷),估計是字元編碼的問題導致。以下是解決問題的過程: 我們C++中介軟體動態庫的介面函式宣告: extern "C" bool __stdcall Exec
關於在electron中呼叫C++動態庫的經驗總結
前言 electron以nodejs作為底層執行環境,所以自然而然就想到了他能否呼叫C++編寫的動態庫,恰好我最近在做一個關於使用electron呼叫dll的專案,也就花了一點時間去了解和實踐,這期間走
GO語言生成C動態庫,再被連結成新動態庫
目錄 目標 測試程式碼 編譯步驟 GO ----> 動態庫 再封裝成新動態庫 編譯生成可執行檔案 嘗試GO---->C靜態庫---->C動態庫。 目標 如果想把GO語言實現的功能整合到C固定介面上,中間需要多加一次封裝。 因為很難用GO直
windows 下C++動態庫的封裝以及呼叫
1、一個程式從原始檔編譯生成可執行檔案的步驟:預編譯 --> 編譯 --> 彙編 --> 連結(1)預編譯,即預處理,主要處理在原始碼檔案中以“#”開始的預編譯指令,如巨集展開、處理條件編譯指令、處理#include指令等。(2)編譯過程就是把預處理完的檔案進行一系列
Go 生成C動態庫.so和靜態庫.a
Go 生成C動態庫.so和靜態庫.a 原始碼 package main import "C" import "fmt" //export hello func hello(){ fmt.Println("hello world") } //export add func
C#呼叫C/C++動態庫 封送結構體,結構體陣列
一. 結構體的傳遞 Cpp程式碼 #define JNAAPI extern "C" __declspec(dllexport) // C方式匯出函式 typedef struct { int osVersion;
關於java jni呼叫c++動態庫的一些問題及解決方法
最近使用java jni介面技術呼叫c++完成的動態庫,平臺為虛擬機器下的centos 6.6。 編譯出來的*.so 檔案在被java呼叫過程中出現了各種錯誤。 1. java com.cmsz.znw.filevalmain.FileValServerImpl Exception in
JNA呼叫C動態庫dll、so
1.介紹jna JNA(Java Native Access )提供一組Java工具類用於在執行期動態訪問系統本地庫(native library:如Window的dll)而不需要編寫任何Native/JNI程式碼。開發人員只要在一個java介面中描述目
Linux動態庫.a與動態庫.so的生成與區別、以及.so庫檔案的封裝與使用
一、前言 如果有公司需要使用你們產品的一部分功能(通過程式碼呼叫這些功能),如果不想提供原始碼,那麼就可以通過封裝成庫檔案的形式提供給對方使用。本文主要介紹了生成動態庫與靜態庫檔案的過程、以及封裝和使用庫檔案的方法。 二、靜態庫.a與動態庫.so的生成與
C#呼叫C/C++動態庫,封裝各種複雜結構體。
現在公司要做一個使用C#程式呼叫C++的一個DLL庫,解析檔案的功能。所以在網上找了一些資料。 一、結構體傳遞 #define JNAAPI extern "C" __declspec(dllexport) // C方式匯出函式 typedef str
linux環境下的c++ 動態庫的呼叫
主要是為了平時的學習記錄,不妥的地方,煩請指點。一.下面主要是dlopen開啟動態庫.so相關的API介面函式。1. void* dlopen(const char* filename,int flag);filename 是動態庫的path路徑,flag是動態庫載入的幾種方
Linux C 靜態庫(.a) 與 動態庫(.so) 的詳解
庫從本質上來說是一種可執行程式碼的二進位制格式,可以被載入記憶體中執行。庫分靜態庫和動態庫兩種。 一、靜態庫和動態庫的區別 1、靜態函式庫 這類庫的名字一般是libxxx.a;利用靜態函式庫編譯成的檔案比較大--空間,因為整個函式庫的所有資料都會被整合進目的碼中,他的優點就顯而易見了,即編譯後的執行