1. 程式人生 > >建立DLL檔案 以及 動態載入和靜態載入 以及兩者之間的區別

建立DLL檔案 以及 動態載入和靜態載入 以及兩者之間的區別

一,首先編寫DLL (建win32空DLL工程)

標頭檔案.h

extern "C" _declspec(dllexport) int Max(int a, int b);         //extern "C"解決函式名由於不同編譯器造成的名字匹配問題,通常c++編譯器編譯時會對函式進行改名,而c編譯器不會
extern "C" _declspec(dllexport) int Min(int a, int b);         //_declspec(dllexport)說明該函式為匯出函式

實現檔案.cpp

#include"TestDLL.h"
int Max(int a, int b)
{
 return (a > b?a:b);
}
int Min(int a, int b)
{
 return (a > b?b:a);
}

二,程式設計測試DLL的工程(建win32 空工程)

1,靜態載入dll

a。將dll工程下的dll和lib檔案拷到測試工程下

b。同時編寫標頭檔案.h

extern "C" _declspec(dllimport) int Max(int a, int b);           //_declspec(dllimport)說明函式為匯入函式
extern "C" _declspec(dllimport) int Min(int a, int b);

c。編寫實現檔案.cpp

#include "test.h"
#include <iostream>
using namespace std;
#pragma comment(lib,"TestDLL001.lib")
int main()
{
 int a=Min(3,4);
 cout<<a<<endl;
 system("pause");
 return 0;
}

2.動態載入dll(僅用包含dll檔案,同時不需要.h檔案和lib檔案)

a。將dll工程下的dll檔案拷貝到測試工程下

b。編寫實現檔案.cpp

#include <Windows.h>                  // 包含 HINSTANCE
#include <iostream>
using namespace std;

int main()
{
 HINSTANCE his = LoadLibraryA("TestDLL001.dll");                    //用於載入dll
 typedef int(*pmin)(int a, int b);         
 pmin mmi=(pmin)GetProcAddress(his, "Min");                  //GetProcAddress()用於獲得函式地址
 int aa=mmi(3,4);
 cout<<aa<<endl;
 FreeLibrary(his);                                                   //釋放dll
 system("pause");
 return 0;
}

一、概述

C語言的編譯連結過程要把我們編寫的一個c程式(原始碼)轉換成可以在硬體上執行的程式(可執行程式碼),需要進行編譯和連結。編譯就是把文字形式原始碼翻譯為機器語言形式的目標檔案的過程。連結是把目標檔案、作業系統的啟動程式碼和用到的庫檔案進行組織形成最終生成可載入、可執行程式碼的過程。

過程圖解如下: 

  1. 前處理器:將.c 檔案轉化成 .i檔案,使用的gcc命令是:gcc –E,對應於預處理命令cpp;
  2. 編譯器:將.c/.h檔案轉換成.s檔案,使用的gcc命令是:gcc –S,對應於編譯命令 cc –S;
  3. 彙編器:將.s 檔案轉化成 .o檔案,使用的gcc 命令是:gcc –c,對應於彙編命令是 as;
  4. 連結器:將.o檔案轉化成可執行程式,使用的gcc 命令是: gcc,對應於連結命令是 ld;
  5. 載入器:將可執行程式載入到記憶體並進行執行,loader和ld-linux.so。

二、編譯過程

編譯過程又可以分成兩個階段:編譯和彙編。

2.1編譯

編譯是指編譯器讀取源程式(字元流),對之進行詞法和語法的分析,將高階語言指令轉換為功能等效的彙編程式碼

原始檔的編譯過程包含兩個主要階段:

第一個階段是預處理階段,在正式的編譯階段之前進行。預處理階段將根據已放置在檔案中的預處理指令來修改原始檔的內容。

主要是以下幾方面的處理:

  1. 巨集定義指令,如 #define a b 對於這種偽指令,預編譯所要做的是將程式中的所有a用b替換,但作為字串常量的 a則不被替換。還有 #undef,則將取消對某個巨集的定義,使以後該串的出現不再被替換。
  2. 條件編譯指令,如#ifdef,#ifndef,#else,#elif,#endif等。 這些偽指令的引入使得程式設計師可以通過定義不同的巨集來決定編譯程式對哪些程式碼進行處理。預編譯程式將根據有關的檔案,將那些不必要的程式碼過濾掉
  3. 標頭檔案包含指令,如#include "FileName"或者#include 等。 該指令將標頭檔案中的定義統統都加入到它所產生的輸出檔案中,以供編譯程式對之進行處理。
  4. 特殊符號,預編譯程式可以識別一些特殊的符號。 例如在源程式中出現的LINE標識將被解釋為當前行號(十進位制數),FILE則被解釋為當前被編譯的C源程式的名稱。預編譯程式對於在源程式中出現的這些串將用合適的值進行替換。

標頭檔案的目的主要是為了使某些定義可以供多個不同的C源程式使用,這涉及到標頭檔案的定位即搜尋路徑問題。標頭檔案搜尋規則如下:

  1. 所有header file的搜尋會從-I開始
  2. 然後找環境變數 C_INCLUDE_PATH,CPLUS_INCLUDE_PATH,OBJC_INCLUDE_PATH指定的路徑
  3. 再找預設目錄(/usr/include、/usr/local/include、/usr/lib/gcc-lib/i386-linux/2.95.2/include......)

第二個階段編譯、優化階段編譯程式所要作得工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之後,將其翻譯成等價的中間程式碼表示或彙編程式碼。 

2.2彙編

彙編實際上指彙編器(as)把組合語言程式碼翻譯成目標機器指令的過程。目標檔案中所存放的也就是與源程式等效的目標的機器語言程式碼。目標檔案由段組成。通常一個目標檔案中至少有兩個段:

  • 程式碼段:該段中所包含的主要是程式的指令。該段一般是可讀和可執行的,但一般卻不可寫。
  • 資料段:主要存放程式中要用到的各種全域性變數或靜態的資料。一般資料段都是可讀,可寫,可執行的。

2.3目標檔案(Executable and Linkable Format)

  1. 可重定位(Relocatable)檔案:由編譯器和彙編器生成,可以與其他可重定位目標檔案合併建立一個可執行或共享的目標檔案;
  2. 共享(Shared)目標檔案:一類特殊的可重定位目標檔案,可以在連結(靜態共享庫)時加入目標檔案或載入時或執行時(動態共享庫)被動態的載入到記憶體並執行;
  3. 可執行(Executable)檔案:由連結器生成,可以直接通過載入器載入到記憶體中充當程序執行的檔案。

2.4 靜態庫與動態庫

靜態庫(static library)就是將相關的目標模組打包形成的單獨的檔案。使用ar命令。

靜態庫的優點在於:

  • 程式設計師不需要顯式的指定所有需要連結的目標模組,因為指定是一個耗時且容易出錯的過程;
  • 連結時,連線程式只從靜態庫中拷貝被程式引用的目標模組,這樣就減小了可執行檔案在磁碟和記憶體中的大小。

動態庫(dynamic library)是一種特殊的目標模組,它可以在執行時被載入到任意的記憶體地址,或者是與任意的程式進行連結。

動態庫的優點在於:

  • 更新動態庫,無需重新連結;對於大系統,重新連結是一個非常耗時的過程;
  • 執行中可供多個程式使用,記憶體中只需要有一份,節省記憶體。

三、連結過程

連結器主要是將有關的目標檔案彼此相連線生成可載入、可執行的目標檔案。連結器的核心工作就是符號表解析和重定位。

3.1 連結的時機:

  1. 編譯時,就是原始碼被編譯成機器程式碼時(靜態連結器負責);
  2. 載入時,也就是程式被載入到記憶體時(載入器負責);
  3. 執行時,由應用程式來實施(動態連結器負責)。

3.2 連結的作用(軟體複用):

  1. 使得分離編譯成為可能;
  2. 動態繫結(binding):使定義、實現、使用分離

3.3 靜態庫搜尋路徑(由靜態連結器負責)

  1. gcc先從-L尋找;
  2. 再找環境變數LIBRARY_PATH指定的搜尋路徑;
  3. 再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程式內的。

3.4 動態庫搜尋路徑(由動態連結器負責)

  1. 編譯目的碼時指定的動態庫搜尋路徑-L;
  2. 環境變數LD_LIBRARY_PATH指定的動態庫搜尋路徑;
  3. 配置檔案/etc/ld.so.conf中指定的動態庫搜尋路徑;
  4. 預設的動態庫搜尋路徑/lib /usr/lib/ /usr/local/lib

3.5 靜態連結(編譯時)

連結器將函式的程式碼從其所在地(目標檔案或靜態連結庫中)拷貝到最終的可執行程式中。這樣該程式在被執行時這些程式碼將被裝入到該程序的虛擬地址空間中。靜態連結庫實際上是一個目標檔案的集合,其中的每個檔案含有庫中的一個或者一組相關函式的程式碼。

為建立可執行檔案,連結器必須要完成的主要任務:

  1. 符號解析:把目標檔案中符號的定義和引用聯絡起來;
  2. 重定位:把符號定義和記憶體地址對應起來,然後修改所有對符號的引用。

關於符號表和符號解析以及重定位的分析後續學習。

3.6 動態連結(載入、執行時)

在此種方式下,函式的定義在動態連結庫或共享物件的目標檔案中。在編譯的連結階段,動態連結庫只提供符號表和其他少量資訊用於保證所有符號引用都有定義,保證編譯順利通過。動態連結器(ld-linux.so)連結程式在執行過程中根據記錄的共享物件的符號定義來動態載入共享庫,然後完成重定位。在此可執行檔案被執行時,動態連結庫的全部內容將被對映到執行時相應程序的虛地址空間。動態連結程式將根據可執行程式中記錄的資訊找到相應的函式程式碼。  

四、載入過程

載入器把可執行檔案從外存載入到記憶體並進行執行。 Linux中程序執行時的記憶體映像如下:

 載入過程如下:

載入器首先建立如上圖所示的記憶體映像,然後根據段頭部表,把目標檔案拷貝到記憶體的資料和程式碼段中。然後,載入器跳轉到程式入口點(即符號_start 的地址),執行啟動程式碼(startup code),啟動程式碼的呼叫順序如所示:

五、處理目標的常用工具

UNIX系統提供了一系列工具幫助理解和處理目標檔案。GNUbinutils 包也提供了很多幫助。這些工具包括:

  • AR :建立靜態庫,插入、刪除、列出和提取成員;
  • STRINGS :列出目標檔案中所有可以列印的字串;
  • STRIP :從目標檔案中刪除符號表資訊;
  • NM :列出目標檔案符號表中定義的符號;
  • SIZE :列出目標檔案中節的名字和大小;
  • READELF :顯示一個目標檔案的完整結構,包括ELF 頭中編碼的所有資訊。
  • OBJDUMP :顯示目標檔案的所有資訊,最有用的功能是反彙編.text節中的二進位制指令。
  • LDD :列出可執行檔案在執行時需要的共享庫。

相關推薦

建立DLL檔案 以及 動態載入靜態載入 以及兩者之間區別

一,首先編寫DLL (建win32空DLL工程) 標頭檔案.h extern "C" _declspec(dllexport) int Max(int a, int b);         //extern "C"解決函式名由於不同編譯器造成的名字匹配問題,通常c++編譯

C++ 基礎(六).dll檔案動態載入靜態載入區別:畫圖並舉例說明

一、簡介 本部落格主要介紹.dll檔案的動態載入和靜態載入的區別,畫圖並舉例說明。此外,我的上一篇關於靜態載入的部落格如下: C++ 基礎(五)使用vs2015封裝c++生成.dll檔案、.lib檔案、.h檔案後,給另一個工程使用:使用前,需配置標頭檔案(.h)靜態庫(.lib)和 動態庫(

如何將程式的執行檔案靜態載入動態庫放在不同的目錄

一般windows程式的exe和dll需要放在同一個目錄,靜態載入才不會報錯,否則需要修改path環境變數,將所有沒有和exe放在同一目錄的dll的路徑加在path環境變數中。 有沒有一種方法不去手動修改path環境變數並且可以將exe和dll隨心所欲的改變路徑呢?我沒有發

Linux下怎麼建立動態靜態

前言 linux下的生成的動態庫和靜態庫沒有windows下方便 linux下gcc編譯預設動態連結和release 程式執行後呼叫動態庫 動態庫: 程式在執行的時候才去連結動態庫的程式碼,多個程式

【Qt】Qt動態靜態庫的建立使用

動態庫(共享庫)的建立 在Qt Creator中新建專案,選擇Library 點選“Choose”進入下一步 選擇建立庫的型別:共享庫 選擇Kit套件 選擇需要的模組 配置工程路徑、名字等 Qt Creator自動建立的檔案

Linux動態庫的 靜態載入 動態載入 的使用

動態庫的靜態載入  math.c #include<stdio.h> int add(int a,int b) { return a+b; } show.c #include<stdio.

linux下g++ 編譯時動態靜態庫的連結標頭檔案問題

原來編譯的時候都是用的很隨意,沒用系統的總結一下,這幾天在編譯的時候遇到一些下問題,於是就總結一下,省得過幾天又給忘了。 1.動態庫和靜態庫簡介 靜態庫在程式連結的時候會自動的連結到程式裡,所以一旦編譯完成,靜態庫就不需要了,靜態庫以.a結尾。  動態庫在編譯時不會被連線到目的碼中,而是在程式執行

ftp 站點的建立實現檔案的上傳下載 以及 ftp沒有許可權上傳檔案(錯誤:200 Type set to I)

當你已經配置好iis.步驟:一:隨便在一個盤下建立一個檔案bb(檔名可以自己取)我的在:D:\bb二:進入iis接著:接著下一步:接著下一步:然後點選完成三:進入D:\bb,新建一個資料夾開啟瀏覽器,輸入ftp:://10.203.68.51:45  其中45是我設定的埠號,

MyBatis框架的學習(四)——Mapper.xml檔案中的輸入輸出對映以及動態sql

前面對MyBatis框架的學習中,我們對Mapper.xml對映檔案多少有些瞭解。本文將對Mapper.xml對映檔案作更加細緻的梳理,首先從Mapper.xml檔案中的輸入和輸出對映開始。本文案例程式碼的編寫是建立在前文MyBatis框架的學習(三)——Dao

用CMake建立動態靜態庫(轉)

五,靜態庫與動態庫構建 讀者雲,太能羅唆了,一個Hello World就折騰了兩個大節。OK,從本節開始,我們不再折騰Hello World了,我們來折騰Hello World的共享庫。 本節的任務:1,建立一個靜態庫和動態庫,提供HelloFunc函式供其他程式程式設計使用

動態連結庫dll靜態連線庫lib的區別

          由於專業原因,一直沒有系統的學習過c++。最近在學習opencv,而opencv的配置對於程式設計經驗不豐富的人來說理解起來還是需要一個過程的。opencv配置的設定涉及到兩個很重要的概念:dll和lib。          首先什麼是dll和lib?

iOS 動態靜態庫的的區別 動態庫的隔離與靜態庫的吸附問題以及解決方法

起因 理論功底 動態庫和靜態庫 介紹 靜態庫和動態庫的區別 舉個例子, iOS 專案中使用 Embeded Framework 靜態庫和動態庫如何構建和載入 靜態庫和動態庫依賴關係

動態網頁靜態網頁的區別

get 工具 應用 間接 target 得到 代碼 讀取 發出 一、從功能方面來說動態網站與靜態網站的區別 1. 動態網站可以實現靜態網站所實現不了的功能,比方說:聊天室、論壇、音樂播放、瀏覽器、搜索等;而靜態的網站則實現不了。2. 靜態網站,如用Frontpage或Dre

Linux下RabbitMQ的編譯,生成動態靜態

執行 編譯 ast lin 目錄 off href apt-get span 一、步驟 1、代碼托管處下載代碼 最新:https://github.com/alanxz/rabbitmq-c/archive/master.zip 穩定:https://g

C# 動態方法靜態方法的區別

內存 法則 靜態 操作類 ret 我們 數據庫操作類 body method C# 動態方法和靜態方法的區別 (轉) 動態方法與靜態方法的區別: 1,使用方法上的區別:動態方法,在使用時需要先創建實例,才能調用實例方法,而靜態方法則不需要,直接使用即可。 示例代碼如下

動態靜態

linu not fine 執行文件 port pic 靜態庫 可執行 生成可執行文件 前提:本文是基於Linux系統下的編程與學習 補充:頭文件衛士,避免頭文件重復包含。 //頭文件衛士 #ifndef __HEAD_H__ #define __HEAD_H__ //頭

動態IP靜態IP的區別如何使用動態IP

動態IP需要在連線網路時自動獲取IP地址以供使用者正常上網,而靜態IP是ISP或網路裝置分配給使用者的IP地址,可以直接連線上網,不需要獲取IP地址,那靜態ip與動態ip的區別在哪裡呢?        動態IP和靜態IP對網速的影響   ISP對每個使用者所提供的網速,並

iOS裡的動態靜態

介紹 動態庫形式:.dylib和.framework 靜態庫形式:.a和.framework 動態庫和靜態庫的區別 靜態庫:連結時,靜態庫會被完整地複製到可執行檔案中,被多次使用就有多份冗餘拷貝(圖1所示) 系統動態庫:連結時不復制,程式執行時由系統動態

Linux 動態靜態

Linux作業系統中,依據函式庫是否被編譯到程式內部,將其分為兩大類,靜態函式庫和動態函式庫。 Linux下的函式庫放在/lib或/usr/lib,標頭檔案放在/usr/include。 在既有靜態庫又有動態庫的情況下,預設使用動態庫,如果強制使用靜態庫則需要加-static選項支援。

@Autowired註解靜態方法以及new的關係

一、@autowired 與new   new出來的物件無法呼叫@Autowired注入的Spring Bean,否則報空指標異常, @Autowired注入Spring Bean,則當前類必須也是Spring Bean才能呼叫它,不能用new xxx()來獲得物件,這種