配置自己的OpenGL庫,glew、freeglut庫編譯,庫沖突解決(附OpenGL Demo程序)


 

平台:Windows7,Visual C++ 2010

 

1. 引言

    實驗室的一個項目,用到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) 中定義

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

 

2. 靜態鏈接庫、動態鏈接庫、CRT、STL

    我們要到一個函數,要么是需要該函數的源代碼,要么是知道該函數的聲明並有該函數的實現,這里的“實現”又分為靜態鏈接庫、動態鏈接庫。在windows平台上,靜態鏈接庫對應以.lib為后綴的庫文件,動態鏈接庫對應.dll為后綴的動態鏈接庫文件。關於靜態鏈接庫、動態鏈接庫請參考wikipedia相應條目:

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上有靜態庫和動態庫的使用教程:

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,VC2003、VC2005、VC2008、VC2010、VC2012分別為71、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;

    應根據自己程序的編譯選項(用編譯器預置宏來判斷)鏈接對應的開源庫,否則很有可能出現符號未定義、符號重定義的鏈接錯誤。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM