使用Cygwin在Windows上進行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)調用的API;還有一套GNU開發(fā)工具集(比如GCC、GDB),這樣可以進行簡單的軟件開發(fā);還有一些UNIX系統(tǒng)下的常見程序。2001年,新增了X Window System。另外還有一個名為MinGW的庫,可以跟Windows本地的MSVCRT庫(Windows API)一起工作。
二、在Cygwin中使用GCC
下面我們開始介紹如何在Cygwin中使用GCC開發(fā)控制臺模式的應用程序和GUI模式的應用程序。
控制臺模式的應用程序
使用gcc編譯程序跟在UNIX操作系統(tǒng)之下非常相似,關于gcc的標準用法和選項可以參考其用戶手冊。下面是一個簡單的示例:
例1:利用GCC構建Hello World
C:\> gcc hello.c -o hello.exe C:\> hello.exe Hello, World C:\> |
GUI模式的應用程序
Cygwin使我們可以編譯出能夠訪問所有標準Windows32位API的程序,其中包括定義在微軟公司和暢銷出版物中的那些GUI函數。然而,使用GNU工具跟使用微軟公司的工具構建應用程序的過程會稍有不同。絕大多數情況下,根本無需修改源代碼。然而,您應該刪除函數中的全部__export屬性,并將其換成下面的內容:
int foo (int) __attribute__ ((__dllexport__)); int foo (int i) |
Cygwin Makefile與其他任何類UNIX的Makefile非常類似,唯一區(qū)別在于我們需要使用gcc -mwindows來把程序連接成一個圖形用戶界面應用程序,而非命令行應用程序。下面是一個例子:
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文件,這樣就能把您需要的所有位圖、圖標及其他資源放到一個目標文件中。 正常情況下,如果您省略了“-O coff”的話,它就會創(chuàng)建一個Windows格式的文件,但是我們只能鏈接COFF格式的目標文件。所以,我們吩咐windres生成COFF格式的目標文件。我們的大部分示例都假定你的鏈接程序能夠直接處理Windows的資源文件,我們保留.res的命名約定。關于windres的更多信息請參見有關手冊。下面是一個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#
三、調試Cygwin程序
如果您的程序無法正常運行,通常情況下是由于其中的bug所致,因為程序本身存在錯誤會導致出乎意料的結果甚至崩潰。借助于一種稱為調試器的專用工具可以使得查找和修正錯誤變得更簡單一些。就Cygwin而論,其調試器是GDB,即GNU debugger的縮寫。這個工具使我們可以在一個受控環(huán)境中運行我們的程序,在這個環(huán)境中,我們可以考察程序運行過程中或其崩潰之后的狀態(tài)。有時候,程序崩潰時操作系統(tǒng)就會把程序當掉時的內存內容轉儲出來,現(xiàn)在通常是寫在一個叫core 的file 里面。在Cygwin中,這些文件通常是些常規(guī)的文本文件,所以無法直接為GDB所用。
在調試程序之前,需要對需要進行調試的程序做一些準備工作,具體來說,就是在把源程序編譯成目標程序的時候為所有標志添加-g。
例2:利用-g進行編譯
$ gcc -g -O2 -c myapp.c $ gcc -g myapp.c -o myapp |
這樣就會在生產的目標文件中加入額外的信息來告知調試器有關行號、變量名及其他有用的信息,不過這會使得目標文件的尺寸驟增。這些額外的符號和調試信息提供了原始代碼的足夠信息,所以調試器在調試它們的時候會更加容易。
在Windows版本的GNUPro中,GDB具有一個全功能的圖形界面。在Cygwin的Net發(fā)行版本中,GDB只能在命令行下使用。要調用GDB,只需在命令提示符下輸入gdb myapp.exe即可。這時會顯示一些您自己的有關文本信息,之后GDB會提示您繼續(xù)輸入其他命令。只要看到這個提示符,就表示gdb正在等待您輸入命令,如果您鍵入help命令,那么就會收到您可以使用的各個命令的幫助信息,當然您也可以通過閱讀“GDB User's Manual”來全面細致地了解gdb及其使用方法。
如果你的程序崩潰了,并且您想弄清它為什么崩潰的話,最好的辦法是鍵入run來運行您的程序。等它崩潰之后,您可以鍵入where命令來看看它在哪里崩潰的,或者輸入info locals命令來查看所有局部變量的值。此外,如果鍵入print命令的話,我們還可以檢查單獨的變量以及指向這些變量的指針。
如果您的程序做了出乎意料之外的事情,那么可以使用break命令讓gdb在程序到達指定的函數或者行號的時候停止程序的運行:
例3:gdb中的中斷命令
(gdb) break my_function (gdb) break 47 |
現(xiàn)在,當我們輸入run命令之后,我們的程序會在斷點處停下來,這樣我們就能使用其他的gdb命令來查看程序當時的狀態(tài)、修改變量以及單步調試程序的各個語句。需要注意的是,我們可以給run命令附加其他的參數,以便向我們的程序提供相應的命令行參數。例如,下面的兩條命令的效果是一樣的:
例4:利用命令行參數進行調試
$ myprog -t foo --queue 47 $ gdb myprog (gdb) run -t foo --queue 47 |
#p#
四、動態(tài)鏈接庫的構建和使用
動態(tài)鏈接庫(DLL)是指在程序運行時而非編譯時鏈接進我們的程序的那些庫。一個動態(tài)鏈接庫有三部分組成:
◆導出表
◆代碼和數據
◆導入庫
代碼和數據是我們需要編寫的函數、變量等內容,它們將被合并到一起放入dll,你可以簡單的理解成建立了一個碩大的目標文件。但是它們卻不會放入您的.exe文件。導出表含有動態(tài)庫為其他程序提供給的函數和變量,可以簡單的理解成這是一個“全局”符號表,除此之外的內容都是不可見的。通常情況下,我們需要利用文本編輯程序手工建立該表,不過我們也可以利用代碼中的函數表來自動生成這個導出表。dlltool程序可以根據導出符號組成的文本文件來創(chuàng)建動態(tài)鏈接庫的導出部分。輸入庫類似于類UNIX系統(tǒng)中的.a程序庫,但是它只包含通知操作系統(tǒng)應用程序跟dll交互方式(即導入方法)所需信息。這些信息可以鏈接到我們的.exe程序中。當然這些信息也可以利用dlltool程序來建立。
構建動態(tài)鏈接庫
我們這里將簡單介紹如何利用gcc來建立動態(tài)鏈接庫,有關gcc建立動態(tài)庫的更詳盡的選項,可以參考gcc的有關文檔。首先提供一個簡單的示例來演示建立一個動態(tài)鏈接庫的過程。本例中,我們的程序(myprog.exe) 由單個源文件myprog.c組成,而動態(tài)鏈接庫(mydll.dll)的內容則由稱為mydll.c的文件得到。
幸運的是,在最新的gcc和binutils的幫助下,建立動態(tài)鏈接庫的過程非常簡單。下面我們介紹編譯mydll.c的具體過程。
#include <stdio.h> int hello() { printf ("Hello World!\n"); } |
首先將mydll.c編譯為目標代碼,命令如下所示:
gcc -c mydll.c
然后,告訴gcc我們要構建一個共享庫,命令如下所示:
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)鏈接庫做成一個導出庫的話,可以使用下列語法:
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}是我們全部的目標文件,被捆綁成靜態(tài)庫或者一個目標文件;${dependency_libs}是需要鏈接的靜態(tài)庫,如“-lpng -lz -L/usr/local/special -lmyspeciallib”。
鏈接動態(tài)鏈接庫
假設您已有一個動態(tài)鏈接庫,并需要建立一個與Cygwin兼容的輸入庫,如果您有源代碼的話,可以參考本文的構建動態(tài)鏈接庫部分。如果您沒有源代碼或者沒有可用的輸入庫,那么您可以在bash中創(chuàng)建一個.def 文件,命令如下所示:
echo EXPORTS > foo.def
nm foo.dll | grep ' T _' | sed 's/.* T _//' >> foo.def
只有動態(tài)鏈接庫沒有去除有關符號信息的情況下上述命令才能正常工作,否則,就會出現(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),并把它轉換成res格式文件或者coff格式文件。輸入文件的語法和語義的同其他任何資源編譯器沒有任何區(qū)別,所以詳情可參閱任何有關描述Windows資源格式的文獻。此外,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 $@ |
六、結束語
Cygwin是許多自由軟件的集合,用于各種版本的Microsoft Windows上運行UNIX類系統(tǒng)。Cygwin的主要目的是通過重新編譯,將POSIX系統(tǒng)上的軟件移植到Windows上。Cygwin包括了一套庫,該庫在Win32系統(tǒng)下實現(xiàn)了POSIX系統(tǒng)調用的API;還有一套GNU開發(fā)工具集(比如GCC、GDB),這樣可以進行簡單的軟件開發(fā);還有一些UNIX系統(tǒng)下的常見程序。而本文則為讀者介紹了如何在Cygwin下進行程序開發(fā)。我們首先介紹使用GCC開發(fā)控制臺模式的應用程序和GUI模式的應用程序,然后闡述在Cygwin下如何調試程序,隨后詳細講解在Cygwin下動態(tài)鏈接庫的構建和使用,最后介紹資源文件的有關知識。
【編輯推薦】