配置本身的OpenGL庫,glew、freeglut庫編譯,庫衝突解決(附OpenGL Demo程序)

 

平臺:Windows7,Visual C++ 2010ios

 

1. 引言windows

    實驗室的一個項目,用到OpenGL進行實時繪製,還用到一些其餘的庫,一個困擾我好久的問題就是編譯時遇到的各類符號未定義,符號重定義之類的連接錯誤,其通常形式以下:多線程

xxx.obj : error LNK2019: 沒法解析的外部符號__xx_xxx@xx該符號在函數 _xxx 中被引用函數

MSVCRTD.lib(ti_inst.obj) : error LNK2005: "private: class type_info & __thiscall type_info::operator=(class type_info const &)" (??4type_info@@AAEAAV0@ABV0@@Z) 已經在 LIBCMTD.lib(typinfo.obj) 中定義ui

    簡單的說,這種問題通常是缺乏庫(library,或庫的版本不對)或多個庫引用的CRT(C run-time library,C語言運行庫)不一致形成的。本文對這一問題作簡要探討,並用glew、freeglut庫的配置做爲例子。this

 

2. 靜態連接庫、動態連接庫、CRT、STLspa

    咱們要到一個函數,要麼是須要該函數的源代碼,要麼是知道該函數的聲明並有該函數的實現,這裏的「實現」又分爲靜態連接庫、動態連接庫。在windows平臺上,靜態連接庫對應以.lib爲後綴的庫文件,動態連接庫對應.dll爲後綴的動態連接庫文件。關於靜態連接庫、動態連接庫請參考wikipedia相應條目:.net

http://en.wikipedia.org/wiki/Static_library
http://en.wikipedia.org/wiki/Dynamic-link_library線程

    咱們用VC++寫的程序默認編譯爲可執行文件(.exe),若是想發佈本身的庫,能夠在VS的「項目屬性 >> 配置屬性 >> 常規 >> 配置類型」修改。這樣若是之後想用這些函數就不須要引入對應.cpp文件,而只需包含帶有該函數聲明的頭文件,並引用庫文件便可——對於靜態連接庫,能夠#pragma comment (lib, "xxx.lib")指令,或在VS的「項目屬性 >> 配置屬性 >> 連接器 >> 輸入 >> 附加依賴」中添加;對於動態連接庫,能夠用「__declspec(dllimport)」聲明要用的函數,若是爲.dll文件實現了導入庫(對應的.lib文件,裏面實現了函數導入,使用同靜態連接庫),則動態庫的使用同靜態庫,只是程序執行時須要.dll文件。msdn上有靜態庫和動態庫的使用教程:debug

http://msdn.microsoft.com/en-us/library/ms235627.aspx
http://msdn.microsoft.com/en-us/library/ms235636.aspx

    簡單總結,可執行文件(.exe)和庫文件(.lib、.dll)都含有源代碼編譯出來的可執行二進制代碼。靜態連接和動態連接的區別在於:靜態連接編譯出的可執行代碼體積較大,動態連接編譯出的可執行代碼執行時依賴對應的.dll文件。

    CRT(C語言運行庫)實現了C語言相關初始化代碼以及實現了C函數庫,C++能夠看作C語言的超集,因此C++並無「CPRT(C++運行庫)」,C++也使用CRT,標準C++除CRT外還實現了STL(standard C++ library,C++標準庫,注意STL是Standard Template Library的縮寫,由於C++標準庫主要是用模板實現的)。既然函數的「實現」至少有靜態和動態之分,那CRT或STL也有不止一個版本,後文針對VC2010平臺討論這些版本。

    總結,CRT是C語言函數庫及初始化代碼的實現,STL是C++標準庫的實現,所謂「實現」就是由源代碼編譯出來的.lib、.dll文件等。

 

3. VS的編譯選項

    在VC2010上,CRT和STL至少分爲靜態和動態,靜態和動態中又各自有Debug和Release版本(早期VC還有單線程和多線程之分,目前VC++中只提供多線程版本),這樣CRT和STL都有至少四個版本。如今來解釋引言中的符號未定義、符號重定義連接錯誤的可能情景,程序A中調用了函數f,函數f是在程序B中編寫的,爲了使用f,將程序B編譯爲庫(而非.exe)——靜態庫:B.lib\動態庫:B.lib、B.dll,程序A爲了使用f,包含頭文件B.h(其中有函數f的聲明)並引用B.lib:

1 #include"B.h"
2 #pragma comment (lib, "B.lib")

    若是沒有上面的第二句代碼,則出現了符號未定義的連接錯誤:

main.obj : error LNK2019: 沒法解析的外部符號 _f@0,該符號在函數 _main 中被引用

    上面錯誤信息中的「_f@0」具體取決於函數調用約定的命名方式(_cdecl、_stdcall等)。

    若是編譯程序B時使用了動態版本的CRT而編譯A時使用的是靜態版本CRT(即A、B使用了不一樣版本的CRT),則出現了符號重定義之類的連接錯誤(不絕對)。

    固然若是用動態連接版本的B,程序A運行時可執行文件搜索路徑中必須包含B.dll,不然報告「丟失xxx.dll」之類的錯誤。

    設置程序到底使用哪一個版本的CRT可在VS的「項目屬性 >> 配置屬性 >> C/C++ >> 代碼生成 >> 運行庫」中設置,如今將幾種設置對應的庫文件,編譯器的宏定義列在下表:

Option

Preprocessor directives

C run-time library (without iostream or standard C++ library)

Standard C++ Library

/MT

_MT

libcmt.lib

LIBCPMT.LIB

/MD

_MT, _DLL

msvcrt.lib (import library for MSVCR100.DLL)

MSVCPRT.LIB (import library for MSVCP100.dll)

/MTd

_DEBUG, _MT

libcmtd.lib

LIBCPMTD.LIB

/MDd

_DEBUG, _MT, _DLL

msvcrtd.lib (import library for MSVCR100D.DLL)

MSVCPRTD.LIB (import library for MSVCP100D.DLL)

    其中,MT爲是multi-thread的縮寫,上面說了,全部這些庫都是多線程的,大寫D表明DLL,小寫d表明debug,如/MDd下引用動態連接調試版本的庫,而且編譯器定義宏_DEBUG, _MT, _DLL(程序中能夠用#ifdef指令來判斷庫版本),引用的CRT實現文件爲MSVCPRTD.LIB,該文件只是導入庫並無具體的執行二進制代碼,程序運行時動態連接MSVCP100D.DLL文件,STL實現文件同理。

    文件名「MSVC[R,P]100[D]」中的「100」對應VC2010,VC200三、VC200五、VC200八、VC20十、VC2012分別爲7一、80、90、100、110,有些時候咱們運行一個程序提示「丟失msvcrxxx.dll」,能夠經過安裝對應VS來解決,若是不想安裝VS,也可經過安裝「Microsoft Visual C++ 20xx [SP1] Redistributable Package」來解決。

    可參考msdn的C run-time libraries條目:

http://msdn.microsoft.com/en-us/library/vstudio/abx4dbyh(v=vs.100).aspx

 

4. 編譯glew

    可到如下地址下載最新glew:

http://glew.sourceforge.net/

    解壓後打開...\glew-1.10.0\build\vc10\glew.sln文件,能夠看到有「glew_shared」和「glew_static」兩個項目,從右鍵屬性中能夠看到它們分別生成動態和靜態的庫:

    還能夠看到debug和release配置下分別使用相應debug和release版本CRT:

    博文寫到這裏,發現一個問題,「glew_static」應該使用靜態版本的CRT,但從上圖看到,release下是靜態連接(/MT),但debug下怎麼不是「/MTd」呢?(後面會進一步分析)

    在使用glew是須要包含相應頭文件,並連接相應庫文件,將上面生成的四個版本的庫文件拷貝出來:

    其中文件名中的s表明static,即靜態連接,d表明debug,即調試版本,不帶s的是動態連接版本,不帶d的是release版本,文件名能夠從glew工程的配置「項目屬性 >> 常規 >> 目標文件名」中看到:

    而後將...\glew-1.10.0\include\GL\下頭文件拷貝出來:

    將頭文件所在路徑添加到到VC2010項目包含目錄中,有兩種方法:「項目屬性 >> 配置屬性 >> VC++目錄 >> 包含目錄」或「項目屬性 >> 配置屬性 >> C/C++ >> 常規 >> 附加包含目錄」,將庫文件所在路徑添加到到VC2010項目庫目錄中,也有兩種方法:「項目屬性 >> 配置屬性 >> VC++目錄 >> 庫目錄」或「項目屬性 >> 配置屬性 >> 連接器 >> 常規 >> 附加庫目錄」。

    經過判斷CRT版原本引用不一樣庫(這樣避免CRT版本不一致):

 1 #ifdef _DLL // dynamic link
 2   #ifdef _DEBUG
 3     #pragma comment (lib, "glew32d.lib")
 4     #pragma comment (lib, "freeglutd.lib")
 5   #else
 6     #pragma comment (lib, "glew32.lib")
 7     #pragma comment (lib, "freeglut.lib")
 8   #endif
 9 #else // static link
10   #ifdef _DEBUG
11     #pragma comment (lib, "glew32sd.lib")
12     #pragma comment (lib, "freeglutsd.lib")
13   #else
14     #pragma comment (lib, "glew32s.lib")
15     #pragma comment (lib, "freegluts.lib")
16   #endif
17   #define GLEW_STATIC
18   #define FREEGLUT_STATIC
19 #endif
20 #include "GL/glew.h"
21 #include "GL/freeglut.h"

    上述代碼利用編譯器在不一樣配置(/MT、/MD、/MTd、/MDd)下內置的不一樣宏來判斷使用的CRT版本,並引用對應版本glew和freeglut庫版本。

    這樣配置後編譯本身的程序不會再出現引言中的連接錯誤了,但有不少以下警告:

glew32s.lib(glew.obj) : warning LNK4099: 未找到 PDB「vc100.pdb」(使用「glew32s.lib(glew.obj)」或在「C:\Users\hll\Desktop\fluid 2014.01\Release\vc100.pdb」中尋找);正在連接對象,如同沒有調試信息同樣

    將glew工程配置成不生成調試信息,或把調試信息直接生成到.obj文件中(而非.pdb文件)便可,「項目屬性 >> 配置屬性 >> C/C++ >> 常規 >> 調試信息格式」,空表示不生成調試信息,C7把調試信息直接生成到.obj文件中,默認的Zi生成.pdb文件:

    接着上面說到的「glew_static」的配置問題(往上找那段綠色的話),在本身工程配置爲「/MTd」時引用glew32sd.lib庫程序報錯以下:

1>------ 已啓動生成: 項目: exampleGL, 配置: Debug_static Win32 ------
1>生成啓動時間爲 2014/1/15 17:42:55。
1>InitializeBuildStatus:
1> 正在對「Debug_static\exampleGL.unsuccessfulbuild」執行 Touch 任務。
1>ClCompile:
1> 全部輸出均爲最新。
1>ManifestResourceCompile:
1> 全部輸出均爲最新。
1>MSVCRTD.lib(ti_inst.obj) : error LNK2005: "private: __thiscall type_info::type_info(class type_info const &)" (??0type_info@@AAE@ABV0@@Z) 已經在 LIBCMTD.lib(typinfo.obj) 中定義
1>MSVCRTD.lib(ti_inst.obj) : error LNK2005: "private: class type_info & __thiscall type_info::operator=(class type_info const &)" (??4type_info@@AAEAAV0@ABV0@@Z) 已經在 LIBCMTD.lib(typinfo.obj) 中定義
1>LINK : warning LNK4098: 默認庫「MSVCRTD」與其餘庫的使用衝突;請使用 /NODEFAULTLIB:library
1>C:\Users\hll\Desktop\exampleGL\Debug_static\exampleGL.exe : fatal error LNK1169: 找到一個或多個多重定義的符號
1>
1>生成失敗。
1>
1>已用時間 00:00:00.38
========== 生成: 成功 0 個,失敗 1 個,最新 0 個,跳過 0 個 ==========

    利用上面VC2010編譯配置表(往上找加粗的表),配置爲「/MTd」使用的是庫libcmtd.lib,而msvcrtd.lib是「/MDd」配置下使用的庫,解決上述符號重定義錯誤的一個方法以下:

#pragma comment (linker, "/NODEFAULTLIB:MSVCRTD.lib")

    但很明顯,這不是漂亮的解決方法,若是咱們「擅自」將「glew_static」的上述配置「/MDd」改成「/MTd」 (仍是往上找那段綠色的話),這個問題也會消失,看來這多是glew發佈版(1.10.0)的一個bug(除了剛分析的「glew_static」 debug的配置「/MDd」改成「/MTd」,還有一處,「glew_shared」 release的配置「/MT」改成「/MD」),但這正好成就了咱們對本文技術分析結果的完美應用~

 

5. 編譯freeglut

    可到如下地址下載最新freeglut:

http://freeglut.sourceforge.net/

    有了glew編譯經驗,以及本身的工程配置經驗以後,freeglut的編譯這裏就簡單些說了。

    解壓後打開...\freeglut-2.8.1\VisualStudio\2010\freeglut.sln文件,能夠看到它的配置略有不一樣:

    再隨便打開一個CRT配置能夠看到:

    freeglut並無像glew那樣在CRT配置上出現小bug(仍是往上找那段綠色的話)。

    好了,像glew同樣,用配置管理器的4個選項(debug、release、debug_static、release_static,分別對應4個CRT版本)分別編譯出4個版本的庫(6個文件,4個.lib,2個.dll),但freeglut並無像glew那樣將4個版本的文件分別命名用或不用s及d結尾,它的debug版和release版文件名相同,我只好本身改啦(這一改帶來不少問題):

    改成:

    其餘類推,並將freeglut_std.h文件中以下代碼:

...
#    pragma comment (lib, "freeglut_static.lib")
...
#      pragma comment (lib, "freeglut.lib")
...

    修改成:

...
#    ifdef _DEBUG
#      pragma comment (lib, "freeglutsd.lib")
#    else
#      pragma comment (lib, "freegluts.lib")
#    endif
...
#      ifdef _DEBUG
#        pragma comment (lib, "freeglutd.lib")
#      else
#        pragma comment (lib, "freeglut.lib")
#      endif
...

    修改依據相同,仍是根據CRT的4個版本引用4個版本的.lib文件。注意,我以前在freeglut項目中只作了「目標文件名」的修改,而未作.h文件的上述修改來編譯freeglut(只是將.h文件拷貝出來後才修改,這樣本身項目包含的是修改後的freeglut_std.h文件,而編譯freeglut用的是原版),這樣的結果是,生成出來的.lib文件內部仍在引用"freeglut_static.lib"(而不是"freegluts.lib"),用二進制打開生成的.lib文件以下:

    而使用修改後的freeglut_std.h文件編譯freeglut結果以下:

    使用未修改的freeglut_std.h文件生成"freegluts.lib" 後,本身工程包含修改後的freeglut_std.h,按說只引用"freegluts.lib",但連接器仍報告找不到"freeglut_static.lib"文件。

    另一個相似的問題是,當編譯動態連接debug版本的庫時,生成文件爲freeglutd.dll和freeglutd.lib(名字規則:非靜態不帶s,debug帶d),頭文件中引用"freeglutd.lib"將freeglutd.dll拷貝到VC2010自動生成的debug文件夾下(和本身工程生成的.exe文件同一文件夾),運行程序結果報告「丟失freeglut.dll」(不帶我本身修改後的名字的d),編譯freeglut生成的.lib和.dll文件名爲freeglutd,但.lib文件內部引用的.dll文件名爲freeglut(不帶d),驗證以下:

    通過一番研究, freeglut的配置下,freeglutd.lib文件是連接器根據一個.def文件生成的(glew的導入庫配置在項目屬性 >> 配置屬性 >> 連接器 >> 高級 >> 導入庫」):

    .def文件內容以下:

    經查,第一行「LIBRARY freeglut」的含義正是「引用freeglut.dll」,將該句去掉,連接器生成的.lib文件引用的.dll文件自動和生成的.dll文件同名,問題解決:

    另外值得一提的是當生成動態連接版本的.dll文件時,用到了一個資源文件,其內容以下(glew中的):

 

6. 搭建OpenGL工程

    工程原則:將glew和freeglut庫放在工程文件夾下以免對環境依賴、不能出現任何關於庫衝突等警告(錯誤固然更不能夠)、根據CRT的4個版本定義4個配置(debug,release,debug_static,release_static)。

    將上面的glew和freeglut的編譯總結在下面

glew—

1.bug修復,「glew_static」 debug的配置「/MDd」改成「/MTd」,「glew_shared」 release的配置「/MT」改成「/MD」

2.不生成調試信息,「glew_static」和「glew_shared」全部配置下的「調試信息格式」改成空

3.對「glew_static」 debug及release 和 「glew_shared」 debug及release分別編譯,獲得glew32sd.lib、glew32s.lib、glew32d.lib(glew32d.dll)、glew32.lib(glew32.dll)

freeglut—

1.生成目標文件名修改,「freeglut」的「目標文件名」項原來爲$(ProjectName)和$(ProjectName)_static,4個配置debug、release、debug_static、release_static分別改成$(ProjectName)d、$(ProjectName)、$(ProjectName)sd、$(ProjectName)s

2.不生成調試信息,「freeglut」全部配置下的「調試信息格式」改成空

3.freeglut_std.h文件修改如上述

4.freeglutdll.def文件刪去第一行的「LIBRARY freeglut」

5.對「freeglut」的4個配置debug、release、debug_static、release_static分別編譯,獲得freeglutsd.lib、freegluts.lib、freeglutd.lib(freeglutd.dll)、freeglut.lib(freeglut.dll)

    以下構造文件夾tool:

tool
  freeglut-2.8.1
    bin
      freeglut.dll, freeglutd.dll
    inc
      GL
        freeglut.h, freeglut_ext.h, freeglut_std.h, glut.h
    lib
      freeglut.lib, freeglutd.lib, freegluts.lib, freeglutsd.lib
  glew-1.10.0
    bin
      glew32.dll, glew32d.dll
    inc
      GL
        glew.h, glxew.h, wglew.h
    lib
      glew32.lib, glew32d.lib, glew32s.lib, glew32sd.lib

    以下構造VC2010工程:

新建VS C++控制檯項目,將上面tool文件夾拷貝到解決方案文件夾下

打開配置管理器,添加Debug_static(從Debug複製)和Release_static(從Release複製)配置

將Debug、Debug_static、Release、Release_static的「運行庫」分別配置爲:/MDd、/MTd、/MD、/MT

在VS「項目屬性 >> 配置屬性 >> VC++目錄 >> 包含目錄全部配置下添加以下項

$(SolutionDir)tool\glew-1.10.0\inc
$(SolutionDir)tool\freeglut-2.8.1\inc

在VS「項目屬性 >> 配置屬性 >> VC++目錄 >> 庫目錄全部配置下添加以下項

$(SolutionDir)tool\glew-1.10.0\lib
$(SolutionDir)tool\freeglut-2.8.1\lib

添加文件gl_inc.h以下:

添加main.cpp以下:

 

    程序運行結果截圖:

 

    考慮到方便本文的讀者作實驗,現將搭建的OpenGL工程exampleGL貢獻出來(庸俗的代碼水準讓你們見笑了):

 連接: http://pan.baidu.com/s/1kTuPUQz 密碼: jiky

 

7. 總結

    在VC++上,CRT和STL有4個版本,分別對應編譯選項:/MDd、/MTd、/MD、/MT;

    根據編譯選項的不一樣,開源程序編譯出的庫也分爲多個版本(通常較全面的是4個,沒有4個的能夠手動添加配置),這些版本連接不一樣的CRT;

    應根據本身程序的編譯選項(用編譯器預置宏來判斷)連接對應的開源庫,不然頗有可能出現符號未定義、符號重定義的連接錯誤。

相關文章
相關標籤/搜索