使用Cygwin在Windows上進(jìn)行Unix開發(fā)
原創(chuàng)一、Cygwin簡介
Cygwin是許多自由軟件的集合,最初由Cygnus Solutions開發(fā),用于各種版本的Microsoft Windows上,運行UNIX類系統(tǒng)。Cygwin的主要目的是通過重新編譯,將POSIX系統(tǒng)(例如Linux、BSD,以及其他Unix系統(tǒng))上的軟件移植到Windows上。Cygwin包括了一套庫,該庫在Win32系統(tǒng)下實現(xiàn)了POSIX系統(tǒng)調(diào)用的API;還有一套GNU開發(fā)工具集(比如GCC、GDB),這樣可以進(jìn)行簡單的軟件開發(fā);還有一些UNIX系統(tǒng)下的常見程序。2001年,新增了X Window System。另外還有一個名為MinGW的庫,可以跟Windows本地的MSVCRT庫(Windows API)一起工作。
二、在Cygwin中使用GCC
下面我們開始介紹如何在Cygwin中使用GCC開發(fā)控制臺模式的應(yīng)用程序和GUI模式的應(yīng)用程序。
控制臺模式的應(yīng)用程序
使用gcc編譯程序跟在UNIX操作系統(tǒng)之下非常相似,關(guān)于gcc的標(biāo)準(zhǔn)用法和選項可以參考其用戶手冊。下面是一個簡單的示例:
例1:利用GCC構(gòu)建Hello World
C:\> gcc hello.c -o hello.exe C:\> hello.exe Hello, World C:\> |
GUI模式的應(yīng)用程序
Cygwin使我們可以編譯出能夠訪問所有標(biāo)準(zhǔn)Windows32位API的程序,其中包括定義在微軟公司和暢銷出版物中的那些GUI函數(shù)。然而,使用GNU工具跟使用微軟公司的工具構(gòu)建應(yīng)用程序的過程會稍有不同。絕大多數(shù)情況下,根本無需修改源代碼。然而,您應(yīng)該刪除函數(shù)中的全部__export屬性,并將其換成下面的內(nèi)容:
int foo (int) __attribute__ ((__dllexport__)); int foo (int i) |
Cygwin Makefile與其他任何類UNIX的Makefile非常類似,唯一區(qū)別在于我們需要使用gcc -mwindows來把程序連接成一個圖形用戶界面應(yīng)用程序,而非命令行應(yīng)用程序。下面是一個例子:
myapp.exe : myapp.o myapp.res gcc -mwindows myapp.o myapp.res -o $@ myapp.res : myapp.rc resource.h windres $< -O coff -o $@ |
注意,通過利用windres可把Windows資源編譯成一個COFF格式的.res文件,這樣就能把您需要的所有位圖、圖標(biāo)及其他資源放到一個目標(biāo)文件中。 正常情況下,如果您省略了“-O coff”的話,它就會創(chuàng)建一個Windows格式的文件,但是我們只能鏈接COFF格式的目標(biāo)文件。所以,我們吩咐windres生成COFF格式的目標(biāo)文件。我們的大部分示例都假定你的鏈接程序能夠直接處理Windows的資源文件,我們保留.res的命名約定。關(guān)于windres的更多信息請參見有關(guān)手冊。下面是一個GUI模式入門之用的“Hello ,World !”程序:
/*-------------------------------------------------*/ /* hellogui.c :一個圖形模式的hello world程序 */ /*編譯命令:gcc -mwindows hellogui.c -o hellogui.exe */ /*-------------------------------------------------*/ #include <windows.h>char glpszText[1024]; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { sprintf(glpszText, "Hello World\nGetCommandLine(): [%s]\n" "WinMain lpCmdLine: [%s]\n", lpCmdLine, GetCommandLine() ); WNDCLASSEX wcex; wcex.cbSize = sizeof(wcex); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = "HELLO"; wcex.hIconSm = NULL; if (!RegisterClassEx(&wcex)) return FALSE; HWND hWnd; hWnd = CreateWindow("HELLO", "Hello", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; switch (message) { case WM_PAINT: hdc = BeginPaint(hWnd, &ps); RECT rt; GetClientRect(hWnd, &rt); DrawText(hdc, glpszText, strlen(glpszText), &rt, DT_TOP | DT_LEFT); EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
#p#
三、調(diào)試Cygwin程序
如果您的程序無法正常運行,通常情況下是由于其中的bug所致,因為程序本身存在錯誤會導(dǎo)致出乎意料的結(jié)果甚至崩潰。借助于一種稱為調(diào)試器的專用工具可以使得查找和修正錯誤變得更簡單一些。就Cygwin而論,其調(diào)試器是GDB,即GNU debugger的縮寫。這個工具使我們可以在一個受控環(huán)境中運行我們的程序,在這個環(huán)境中,我們可以考察程序運行過程中或其崩潰之后的狀態(tài)。有時候,程序崩潰時操作系統(tǒng)就會把程序當(dāng)?shù)魰r的內(nèi)存內(nèi)容轉(zhuǎn)儲出來,現(xiàn)在通常是寫在一個叫core 的file 里面。在Cygwin中,這些文件通常是些常規(guī)的文本文件,所以無法直接為GDB所用。
在調(diào)試程序之前,需要對需要進(jìn)行調(diào)試的程序做一些準(zhǔn)備工作,具體來說,就是在把源程序編譯成目標(biāo)程序的時候為所有標(biāo)志添加-g。
例2:利用-g進(jìn)行編譯
$ gcc -g -O2 -c myapp.c $ gcc -g myapp.c -o myapp |
這樣就會在生產(chǎn)的目標(biāo)文件中加入額外的信息來告知調(diào)試器有關(guān)行號、變量名及其他有用的信息,不過這會使得目標(biāo)文件的尺寸驟增。這些額外的符號和調(diào)試信息提供了原始代碼的足夠信息,所以調(diào)試器在調(diào)試它們的時候會更加容易。
在Windows版本的GNUPro中,GDB具有一個全功能的圖形界面。在Cygwin的Net發(fā)行版本中,GDB只能在命令行下使用。要調(diào)用GDB,只需在命令提示符下輸入gdb myapp.exe即可。這時會顯示一些您自己的有關(guān)文本信息,之后GDB會提示您繼續(xù)輸入其他命令。只要看到這個提示符,就表示gdb正在等待您輸入命令,如果您鍵入help命令,那么就會收到您可以使用的各個命令的幫助信息,當(dāng)然您也可以通過閱讀“GDB User's Manual”來全面細(xì)致地了解gdb及其使用方法。
如果你的程序崩潰了,并且您想弄清它為什么崩潰的話,最好的辦法是鍵入run來運行您的程序。等它崩潰之后,您可以鍵入where命令來看看它在哪里崩潰的,或者輸入info locals命令來查看所有局部變量的值。此外,如果鍵入print命令的話,我們還可以檢查單獨的變量以及指向這些變量的指針。
如果您的程序做了出乎意料之外的事情,那么可以使用break命令讓gdb在程序到達(dá)指定的函數(shù)或者行號的時候停止程序的運行:
例3:gdb中的中斷命令
(gdb) break my_function (gdb) break 47 |
現(xiàn)在,當(dāng)我們輸入run命令之后,我們的程序會在斷點處停下來,這樣我們就能使用其他的gdb命令來查看程序當(dāng)時的狀態(tài)、修改變量以及單步調(diào)試程序的各個語句。需要注意的是,我們可以給run命令附加其他的參數(shù),以便向我們的程序提供相應(yīng)的命令行參數(shù)。例如,下面的兩條命令的效果是一樣的:
例4:利用命令行參數(shù)進(jìn)行調(diào)試
$ myprog -t foo --queue 47 $ gdb myprog (gdb) run -t foo --queue 47 |
#p#
四、動態(tài)鏈接庫的構(gòu)建和使用
動態(tài)鏈接庫(DLL)是指在程序運行時而非編譯時鏈接進(jìn)我們的程序的那些庫。一個動態(tài)鏈接庫有三部分組成:
◆導(dǎo)出表
◆代碼和數(shù)據(jù)
◆導(dǎo)入庫
代碼和數(shù)據(jù)是我們需要編寫的函數(shù)、變量等內(nèi)容,它們將被合并到一起放入dll,你可以簡單的理解成建立了一個碩大的目標(biāo)文件。但是它們卻不會放入您的.exe文件。導(dǎo)出表含有動態(tài)庫為其他程序提供給的函數(shù)和變量,可以簡單的理解成這是一個“全局”符號表,除此之外的內(nèi)容都是不可見的。通常情況下,我們需要利用文本編輯程序手工建立該表,不過我們也可以利用代碼中的函數(shù)表來自動生成這個導(dǎo)出表。dlltool程序可以根據(jù)導(dǎo)出符號組成的文本文件來創(chuàng)建動態(tài)鏈接庫的導(dǎo)出部分。輸入庫類似于類UNIX系統(tǒng)中的.a程序庫,但是它只包含通知操作系統(tǒng)應(yīng)用程序跟dll交互方式(即導(dǎo)入方法)所需信息。這些信息可以鏈接到我們的.exe程序中。當(dāng)然這些信息也可以利用dlltool程序來建立。
構(gòu)建動態(tài)鏈接庫
我們這里將簡單介紹如何利用gcc來建立動態(tài)鏈接庫,有關(guān)gcc建立動態(tài)庫的更詳盡的選項,可以參考gcc的有關(guān)文檔。首先提供一個簡單的示例來演示建立一個動態(tài)鏈接庫的過程。本例中,我們的程序(myprog.exe) 由單個源文件myprog.c組成,而動態(tài)鏈接庫(mydll.dll)的內(nèi)容則由稱為mydll.c的文件得到。
幸運的是,在最新的gcc和binutils的幫助下,建立動態(tài)鏈接庫的過程非常簡單。下面我們介紹編譯mydll.c的具體過程。
#include <stdio.h> int hello() { printf ("Hello World!\n"); } |
首先將mydll.c編譯為目標(biāo)代碼,命令如下所示:
gcc -c mydll.c
然后,告訴gcc我們要構(gòu)建一個共享庫,命令如下所示:
gcc -shared -o mydll.dll mydll.o
就這么簡單!現(xiàn)在,我們將這個動態(tài)鏈接庫鏈接到一個簡單程序上,程序代碼如下所示:
int |
現(xiàn)在我們用下列命令來連接動態(tài)鏈接庫,命令如下所示:
gcc -o myprog myprog.c -L./ -lmydll
然而,如果想要把動態(tài)鏈接庫做成一個導(dǎo)出庫的話,可以使用下列語法:
gcc -shared -o cyg${module}.dll \
-Wl,--out-implib=lib${module}.dll.a \
-Wl,--export-all-symbols \
-Wl,--enable-auto-import \
-Wl,--whole-archive ${old_libs} \
-Wl,--no-whole-archive ${dependency_libs}
我們的程序庫的名稱是${module},動態(tài)鏈接庫的前綴為cyg,輸入庫的前綴為lib。Cygwin的動態(tài)鏈接庫使用cyg作為前綴,以作為本地Windows的MinGW動態(tài)鏈接庫的區(qū)別。${old_libs}是我們?nèi)康哪繕?biāo)文件,被捆綁成靜態(tài)庫或者一個目標(biāo)文件;${dependency_libs}是需要鏈接的靜態(tài)庫,如“-lpng -lz -L/usr/local/special -lmyspeciallib”。
鏈接動態(tài)鏈接庫
假設(shè)您已有一個動態(tài)鏈接庫,并需要建立一個與Cygwin兼容的輸入庫,如果您有源代碼的話,可以參考本文的構(gòu)建動態(tài)鏈接庫部分。如果您沒有源代碼或者沒有可用的輸入庫,那么您可以在bash中創(chuàng)建一個.def 文件,命令如下所示:
echo EXPORTS > foo.def
nm foo.dll | grep ' T _' | sed 's/.* T _//' >> foo.def
只有動態(tài)鏈接庫沒有去除有關(guān)符號信息的情況下上述命令才能正常工作,否則,就會出現(xiàn)“No symbols in foo.dll”錯誤信息。一旦得到了.def文件,就可以從中創(chuàng)建一個輸入庫,命令如下所示:
dlltool --def foo.def --dllname foo.dll --output-lib foo.a
#p#
五、定義Windows資源
Windres能夠讀取Windows資源文件(*.rc),并把它轉(zhuǎn)換成res格式文件或者coff格式文件。輸入文件的語法和語義的同其他任何資源編譯器沒有任何區(qū)別,所以詳情可參閱任何有關(guān)描述Windows資源格式的文獻(xiàn)。此外,windres程序本身在Binutils手冊中也有詳盡的說明。下面是一個使用windres的例子:
myapp.exe : myapp.o myapp.res gcc -mwindows myapp.o myapp.res -o $@ myapp.res : myapp.rc resource.h windres $< -O coff -o $@ |
六、結(jié)束語
Cygwin是許多自由軟件的集合,用于各種版本的Microsoft Windows上運行UNIX類系統(tǒng)。Cygwin的主要目的是通過重新編譯,將POSIX系統(tǒng)上的軟件移植到Windows上。Cygwin包括了一套庫,該庫在Win32系統(tǒng)下實現(xiàn)了POSIX系統(tǒng)調(diào)用的API;還有一套GNU開發(fā)工具集(比如GCC、GDB),這樣可以進(jìn)行簡單的軟件開發(fā);還有一些UNIX系統(tǒng)下的常見程序。而本文則為讀者介紹了如何在Cygwin下進(jìn)行程序開發(fā)。我們首先介紹使用GCC開發(fā)控制臺模式的應(yīng)用程序和GUI模式的應(yīng)用程序,然后闡述在Cygwin下如何調(diào)試程序,隨后詳細(xì)講解在Cygwin下動態(tài)鏈接庫的構(gòu)建和使用,最后介紹資源文件的有關(guān)知識。
【編輯推薦】