vs編譯靜態庫
A. Visual Studio C++程序正常運行的代碼把"Use of MFC"切換成Static library後,就編譯不通過,請教怎麼辦
因為MFC作為動態庫和靜態庫的情況下,生成的代碼框架都完全不同,所以無法直接轉換,要麼你就重新建工程,一開始就選擇靜態MFC。
當然,還有一種方法,其實很簡單,在你電腦上類似「C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\redist\x86\Microsoft.VC100.MFC」的目錄里,找到mfc的那幾個dll動態庫,跟你的發布的exe做成一個包,全部放在一起,就可以在任何電腦上直接運行了。
比如,我用的VS2010,依賴的dll就是mfc100.dll
B. 關於c/c++靜態庫和動態庫的區別
靜態庫
之所以成為【靜態庫】,是因為在鏈接階段,會將匯編生成的目標文件.o與引用到的庫一起鏈接打包到可執行文件中。因此對應的鏈接方式稱為靜態鏈接。
試想一下,靜態庫與匯編生成的目標文件一起鏈接為可執行文件,那麼靜態庫必定跟.o文件格式相似。其實一個靜態庫可以簡單看成是一組目標文件(.o/.obj文件)的集合,即很多目標文件經過壓縮打包後形成的一個文件。靜態庫特點總結:
l 靜態庫對函數庫的鏈接是放在編譯時期完成的。
l 程序在運行時與函數庫再無瓜葛,移植方便。
l 浪費空間和資源,因為所有相關的目標文件與牽涉到的函數庫被鏈接合成一個可執行文件。
下面編寫一些簡單的四則運算C++類,將其編譯成靜態庫給他人用,頭文件如下所示:
StaticMath.h頭文件
#pragma once
class StaticMath
{
public:
StaticMath(void);
~StaticMath(void);
static double add(double a, double b);//加法
static double sub(double a, double b);//減法
static double mul(double a, double b);//乘法
static double div(double a, double b);//除法
void print();
};
linux下使用ar工具、Windows下vs使用lib.exe,將目標文件壓縮到一起,並且對其進行編號和索引,以便於查找和檢索。一般創建靜態庫的步驟如圖所示:
圖:創建靜態庫過程
Linux下創建與使用靜態庫
Linux靜態庫命名規則
Linux靜態庫命名規范,必須是"lib[your_library_name].a":lib為前綴,中間是靜態庫名,擴展名為.a。
創建靜態庫(.a)
通過上面的流程可以知道,Linux創建靜態庫過程如下:
l 首先,將代碼文件編譯成目標文件.o(StaticMath.o)
g++ -c StaticMath.cpp
注意帶參數-c,否則直接編譯為可執行文件
l 然後,通過ar工具將目標文件打包成.a靜態庫文件
ar -crv libstaticmath.a StaticMath.o
生成靜態庫libstaticmath.a。
大一點的項目會編寫makefile文件(CMake等等工程管理工具)來生成靜態庫,輸入多個命令太麻煩了。
使用靜態庫
編寫使用上面創建的靜態庫的測試代碼:
測試代碼:
#include "StaticMath.h"
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
double a = 10;
double b = 2;
cout << "a + b = " << StaticMath::add(a,
b) << endl;
cout << "a - b = " << StaticMath::sub(a,
b) << endl;
cout << "a * b = " << StaticMath::mul(a,
b) << endl;
cout << "a / b = " << StaticMath::div(a,
b) << endl;
StaticMath sm;
sm.print();
system("pause");
return 0;
}
Linux下使用靜態庫,只需要在編譯的時候,指定靜態庫的搜索路徑(-L選項)、指定靜態庫名(不需要lib前綴和.a後綴,-l選項)。
# g++ TestStaticLibrary.cpp -L../StaticLibrary -lstaticmath
l -L:表示要連接的庫所在目錄
l -l:指定鏈接時需要的動態庫,編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.a或.so來確定庫的名稱。
Windows下創建與使用靜態庫
創建靜態庫(.lib)
如果是使用VS命令行生成靜態庫,也是分兩個步驟來生成程序:
l 首先,通過使用帶編譯器選項 /c 的 Cl.exe 編譯代碼 (cl
/c StaticMath.cpp),創建名為「StaticMath.obj」的目標文件。
l 然後,使用庫管理器 Lib.exe 鏈接代碼 (lib StaticMath.obj),創建靜態庫StaticMath.lib。
當然,我們一般不這么用,使用VS工程設置更方便。創建win32控制台程序時,勾選靜態庫類型;打開工程「屬性面板」è」配置屬性」è」常規」,配置類型選擇靜態庫。
圖:vs靜態庫項目屬性設置
Build項目即可生成靜態庫。
使用靜態庫
測試代碼Linux下面的一樣。有3種使用方法:
方法一:
在VS中使用靜態庫方法:
l 工程「屬性面板」è「通用屬性」è 「框架和引用」è」添加引用」,將顯示「添加引用」對話框。 「項目」選項卡列出了當前解決方案中的各個項目以及可以引用的所有庫。 在「項目」選項卡中,選擇 StaticLibrary。 單擊「確定」。
l 添加StaticMath.h 頭文件目錄,必須修改包含目錄路徑。打開工程「屬性面板」è」配置屬性」è 「C/C++」è」 常規」,在「附加包含目錄」屬性值中,鍵入StaticMath.h 頭文件所在目錄的路徑或瀏覽至該目錄。
編譯運行OK。
圖:靜態庫測試結果(vs)
如果引用的靜態庫不是在同一解決方案下的子工程,而是使用第三方提供的靜態庫lib和頭文件,上面的方法設置不了。還有2中方法設置都可行。
方法二:
打開工程「屬性面板」è」配置屬性」è 「鏈接器」è」命令行」,輸入靜態庫的完整路徑即可。
方法三:
l 「屬性面板」è」配置屬性」è 「鏈接器」è」常規」,附加依賴庫目錄中輸入,靜態庫所在目錄;
l 「屬性面板」è」配置屬性」è 「鏈接器」è」輸入」,附加依賴庫中輸入靜態庫名StaticLibrary.lib。
動態庫
通過上面的介紹發現靜態庫,容易使用和理解,也達到了代碼復用的目的,那為什麼還需要動態庫呢?
為什麼還需要動態庫?
為什麼需要動態庫,其實也是靜態庫的特點導致。
l 空間浪費是靜態庫的一個問題。
l 另一個問題是靜態庫對程序的更新、部署和發布頁會帶來麻煩。如果靜態庫liba.lib更新了,所以使用它的應用程序都需要重新編譯、發布給用戶(對於玩家來說,可能是一個很小的改動,卻導致整個程序重新下載,全量更新)。
動態庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入。不同的應用程序如果調用相同的庫,那麼在內存里只需要有一份該共享庫的實例,規避了空間浪費問題。動態庫在程序運行是才被載入,也解決了靜態庫對程序的更新、部署和發布頁會帶來麻煩。用戶只需要更新動態庫即可,增量更新。
動態庫特點總結:
l 動態庫把對一些庫函數的鏈接載入推遲到程序運行的時期。
l 可以實現進程之間的資源共享。(因此動態庫也稱為共享庫)
l 將一些程序升級變得簡單。
l 甚至可以真正做到鏈接載入完全由程序員在程序代碼中控制(顯示調用)。
Window與Linux執行文件格式不同,在創建動態庫的時候有一些差異。
l 在Windows系統下的執行文件格式是PE格式,動態庫需要一個DllMain函數做出初始化的入口,通常在導出函數的聲明時需要有_declspec(dllexport)關鍵字。
l Linux下gcc編譯的執行文件默認是ELF格式,不需要初始化入口,亦不需要函數做特別的聲明,編寫比較方便。
與創建靜態庫不同的是,不需要打包工具(ar、lib.exe),直接使用編譯器即可創建動態庫。
Linux下創建與使用動態庫
linux動態庫的命名規則
動態鏈接庫的名字形式為 libxxx.so,前綴是lib,後綴名為「.so」。
l 針對於實際庫文件,每個共享庫都有個特殊的名字「soname」。在程序啟動後,程序通過這個名字來告訴動態載入器該載入哪個共享庫。
l 在文件系統中,soname僅是一個鏈接到實際動態庫的鏈接。對於動態庫而言,每個庫實際上都有另一個名字給編譯器來用。它是一個指向實際庫鏡像文件的鏈接文件(lib+soname+.so)。
創建動態庫(.so)
編寫四則運算動態庫代碼:
DynamicMath.h頭文件
#pragma once
class DynamicMath
{
public:
DynamicMath(void);
~DynamicMath(void);
static double add(double a, double b);//¼Ó·¨
static double sub(double a, double b);//¼õ·¨
static double mul(double a, double b);//³Ë·¨
static double div(double a, double b);//³ý·¨
void print();
};
l 首先,生成目標文件,此時要加編譯器選項-fpic
g++ -fPIC -c DynamicMath.cpp
-fPIC 創建與地址無關的編譯程序(pic,position independent code),是為了能夠在多個應用程序間共享。
l 然後,生成動態庫,此時要加鏈接器選項-shared
g++ -shared -o libdynmath.so DynamicMath.o
-shared指定生成動態鏈接庫。
其實上面兩個步驟可以合並為一個命令:
g++ -fPIC -shared -o libdynmath.so DynamicMath.cpp
使用動態庫
編寫使用動態庫的測試代碼:
測試代碼:
#include "../DynamicLibrary/DynamicMath.h"
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
double a = 10;
double b = 2;
cout << "a + b = " << DynamicMath::add(a, b) << endl;
cout << "a - b = " << DynamicMath::sub(a, b) << endl;
cout << "a * b = " << DynamicMath::mul(a, b) << endl;
cout << "a / b = " << DynamicMath::div(a, b) << endl;
DynamicMath dyn;
dyn.print();
return 0;
}
引用動態庫編譯成可執行文件(跟靜態庫方式一樣):
g++ TestDynamicLibrary.cpp -L../DynamicLibrary -ldynmath
然後運行:./a.out,發現竟然報錯了!!!
可能大家會猜測,是因為動態庫跟測試程序不是一個目錄,那我們驗證下是否如此:
發現還是報錯!!!那麼,在執行的時候是如何定位共享庫文件的呢?
1) 當系統載入可執行代碼時候,能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑。此時就需要系統動態載入器(dynamic linker/loader)。
2) 對於elf格式的可執行程序,是由ld-linux.so*來完成的,它先後搜索elf文件的DT_RPATH段—環境變數LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib 目錄找到庫文件後將其載入內存。
如何讓系統能夠找到它:
l 如果安裝在/lib或者/usr/lib下,那麼ld默認能夠找到,無需其他操作。
l 如果安裝在其他目錄,需要將其添加到/etc/ld.so.cache文件中,步驟如下:
n 編輯/etc/ld.so.conf文件,加入庫文件所在目錄的路徑
n 運行ldconfig ,該命令會重建/etc/ld.so.cache文件
我們將創建的動態庫復制到/usr/lib下面,然後運行測試程序。
Windows下創建與使用動態庫
創建動態庫(.dll)
與Linux相比,在Windows系統下創建動態庫要稍微麻煩一些。首先,需要一個DllMain函數做出初始化的入口(創建win32控制台程序時,勾選DLL類型會自動生成這個文件):
dllmain.cpp入口文件
// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"
BOOL APIENTRY DllMain( HMODULE hMole,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
通常在導出函數的聲明時需要有_declspec(dllexport)關鍵字:
DynamicMath.h頭文件
#pragma once
class DynamicMath
{
public:
__declspec(dllexport) DynamicMath(void);
__declspec(dllexport) ~DynamicMath(void);
static __declspec(dllexport) double add(double a, double b);//加法
static __declspec(dllexport) double sub(double a, double b);//減法
static __declspec(dllexport) double mul(double a, double b);//乘法
static __declspec(dllexport) double div(double a, double b);//除法
__declspec(dllexport) void print();
};
生成動態庫需要設置工程屬性,打開工程「屬性面板」è」配置屬性」è」常規」,配置類型選擇動態庫。
圖:v動態庫項目屬性設置
Build項目即可生成動態庫。
使用動態庫
創建win32控制台測試程序:
TestDynamicLibrary.cpp測試程序
#include "stdafx.h"
#include "DynamicMath.h"
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
double a = 10;
double b = 2;
cout << "a + b = " << DynamicMath::add(a,
b) << endl;
cout << "a - b = " << DynamicMath::sub(a,
b) << endl;
cout << "a * b = " << DynamicMath::mul(a,
b) << endl;
cout << "a / b = " << DynamicMath::div(a,
b) << endl;
DynamicMath dyn;
dyn.print();
system("pause");
return 0;
}
方法一:
l 工程「屬性面板」è「通用屬性」è 「框架和引用」è」添加引用」,將顯示「添加引用」對話框。「項目」選項卡列出了當前解決方案中的各個項目以及可以引用的所有庫。 在「項目」選項卡中,選擇 DynamicLibrary。 單擊「確定」。
l 添加DynamicMath.h 頭文件目錄,必須修改包含目錄路徑。打開工程「屬性面板」è」配置屬性」è 「C/C++」è」 常規」,在「附加包含目錄」屬性值中,鍵入DynamicMath.h 頭文件所在目錄的路徑或瀏覽至該目錄。
編譯運行OK。
圖:動態庫測試結果(vs)
方法二:
l 「屬性面板」è」配置屬性」è 「鏈接器」è」常規」,附加依賴庫目錄中輸入,動態庫所在目錄;
l 「屬性面板」è」配置屬性」è 「鏈接器」è」輸入」,附加依賴庫中輸入動態庫編譯出來的DynamicLibrary.lib。
這里可能大家有個疑問,動態庫怎麼還有一個DynamicLibrary.lib文件?即無論是靜態鏈接庫還是動態鏈接庫,最後都有lib文件,那麼兩者區別是什麼呢?其實,兩個是完全不一樣的東西。
StaticLibrary.lib的大小為190KB,DynamicLibrary.lib的大小為3KB,靜態庫對應的lib文件叫靜態庫,動態庫對應的lib文件叫【導入庫】。實際上靜態庫本身就包含了實際執行代碼、符號表等等,而對於導入庫而言,其實際的執行代碼位於動態庫中,導入庫只包含了地址符號表等,確保程序找到對應函數的一些基本地址信息。
動態庫的顯式調用
上面介紹的動態庫使用方法和靜態庫類似屬於隱式調用,編譯的時候指定相應的庫和查找路徑。其實,動態庫還可以顯式調用。【在C語言中】,顯示調用一個動態庫輕而易舉!
在Linux下顯式調用動態庫
#include <dlfcn.h>,提供了下面幾個介面:
l void * dlopen( const char * pathname, int mode ):函數以指定模式打開指定的動態連接庫文件,並返回一個句柄給調用進程。
l void* dlsym(void* handle,const char* symbol):dlsym根據動態鏈接庫操作句柄(pHandle)與符號(symbol),返回符號對應的地址。使用這個函數不但可以獲取函數地址,也可以獲取變數地址。
l int dlclose (void *handle):dlclose用於關閉指定句柄的動態鏈接庫,只有當此動態鏈接庫的使用計數為0時,才會真正被系統卸載。
l const char *dlerror(void):當動態鏈接庫操作函數執行失敗時,dlerror可以返回出錯信息,返回值為NULL時表示操作函數執行成功。
在Windows下顯式調用動態庫
應用程序必須進行函數調用以在運行時顯式載入 DLL。為顯式鏈接到 DLL,應用程序必須:
l 調用 LoadLibrary(或相似的函數)以載入 DLL 和獲取模塊句柄。
l 調用 GetProcAddress,以獲取指向應用程序要調用的每個導出函數的函數指針。由於應用程序是通過指針調用 DLL 的函數,編譯器不生成外部引用,故無需與導入庫鏈接。
l 使用完 DLL 後調用 FreeLibrary。
顯式調用C++動態庫注意點
對C++來說,情況稍微復雜。顯式載入一個C++動態庫的困難一部分是因為C++的name
mangling;另一部分是因為沒有提供一個合適的API來裝載類,在C++中,您可能要用到庫中的一個類,而這需要創建該類的一個實例,這不容易做到。
name mangling可以通過extern "C"解決。C++有個特定的關鍵字用來聲明採用C
binding的函數:extern "C" 。用 extern "C"聲明的函數將使用函數名作符號名,就像C函數一樣。因此,只有非成員函數才能被聲明為extern
"C",並且不能被重載。盡管限制多多,extern "C"函數還是非常有用,因為它們可以象C函數一樣被dlopen動態載入。冠以extern
"C"限定符後,並不意味著函數中無法使用C++代碼了,相反,它仍然是一個完全的C++函數,可以使用任何C++特性和各種類型的參數。
C. 在vs2017中生成靜態庫時顯示編譯錯誤:MSB802 不允許C++使用靜態庫,怎麼設置讓它允許使
項目屬性-配置屬性-常規-MFC的使用-使用標准Windows庫(不用MFC)然後,C/C++-代碼生成-運行庫-多線程/MT或者多線程調試/MTd帶d的是鏈接調試版本,DEBUG項目選/MTd,Release項目選/MT如果使用MFC,在MFC的使用中設置靜態、動態鏈接,代碼生成
D. 如何用vs和makefile文件進行編譯
運行cmd.exe (or command.com in win9x)->進到vc/bin目錄->運行vc-vars32.bat->進到makefile 所在的目錄->nmake /f makefile
從sourceforge上下載下來的libjpeg源代碼中有一個makefile.vc的文件,可以通過nmake /f makefile.vc [nodebug=1]來編譯libjpeg,但是只能編譯靜態庫,如果需要編譯dll以便在emacs等程序中使用的話,需要修改makefile.vc和jmorecfg.h文件。在makefile.vc文件中添加編譯dll規則:
以下內容為程序代碼:
libjpeg.lib: $(LIBOBJECTS) $(RM) libjpeg.lib lib -out:libjpeg.lib $(LIBOBJECTS) #
添加以下這行 libjpeg.dll: $(LIBOBJECTS) $(RM) libjpeg.dll link -dll -out:libjpeg.dll $(LIBOBJECTS) 在jmorecfg.h中添加#define _WIN32_#define JPEG_DLL 然後nmake /f makefile.vc nodebug=1就可以編譯了。
將makefile復制為一個.mak文件,然後用VC打開即可!
.mak 就是一個makefile
可以指定怎樣編譯(命令行,必須先設置VC命令行環境)
vcvars32.bat可設置環境,在vc98/bin下 nmake /f XXXX.mak
如果有一個makefile就只要nmake就可以了。