1.位圖和調色板的概念
如今Windows(3.x以及95,NT)系列已經成為決大多數用戶使用的操作系統(tǒng)。它比DOS成功的一個重要因素是它可視化的漂亮界面,例如你可以在桌面上鋪上你喜歡的墻紙。那么Windows是如何顯示圖象的呢?這就要談到位圖(Bitmap)。
我們知道,普通的顯示器屏幕是由許許多多的點構成的,我們稱之為象素。顯示時采用掃描的方法:電子槍每次從左到右掃描一行,為每個象素著色,然后從上到下這樣掃描若干行,就掃過了一屏。為了防止閃爍,每秒要重復上述過程幾十次。例如我們常說的屏幕分辨率為640*480,刷新頻率為70Hz,意思是說每行要掃描640個象素,一共有480行,每秒重復掃描屏幕70次。我們稱這種顯示器為位映象設備。所謂位映象,就是指一個二維的象素矩陣,而位圖就是采用位映象方法顯示和存儲的圖象。舉個例子,下圖1是一幅普通的黑白位圖,圖2是被放大后的圖,圖中每個方格代表了一個象素,我們可以看到:整個骷髏就是由這樣一些黑點和白點組成的。

圖1.骷髏(左) 圖2.放大后的骷髏位圖(右)
那么,彩色圖是怎么回事呢?
我們先來說說三元色RGB概念。我們知道,自然界中的所有顏色都可以由紅,綠,藍(R,G,B)組合而成。有的顏色含有紅色成分多一些,如深紅;有的含有紅色成分少一些,如淡紅。針對含有紅色成分的多少,可以分成0到255共256個等級,0級表示不含紅色成分,255級表示含有100%的紅色成分。同樣,綠色和藍色也被分成256級。這種分級的概念被稱作量化。這樣,根據紅,綠,藍各種不同的組合我們就能表示出256*256*256,約1千6百萬種顏色。這么多顏色對于我們人眼來已經足夠了。
下表是常見的一些顏色的RGB組合值。
顏色RGB
紅25500
藍00255
綠02550
黃2552550
紫2550255
青0255255
白255255255
黑000
灰128128128
你大概已經明白了,當一幅圖中每個象素賦予不同的RGB值時,就能呈現出五彩繽紛的顏色了,這樣就形成了彩色圖。對,是這樣的,但實際上的做法還有些差別。
讓我們來看看下面的例子。
有一個長寬各為200個象素,顏色數為16色的彩色圖,每一個象素都用R,G,B三個分量表示,因為每個分量有256個級別,要用8位(bit),即一個字節(jié)(byte)來表示,所以每個象素需要用3個字節(jié)。整個圖象要用200*200*3,約120k字節(jié),可不是一個小數目呀!如果我們用下面的方法,就能省的多。 因為是一個16色圖,也就是說這幅圖中最多只有16種顏色,我們可以用一個表:表中的每一行記錄一種顏色的R,G,B值。這樣當我們表示一個象素的顏色時,只需要指出該顏色是在第幾行,即該顏色在表中的索引值。舉個例子,如果表的第0行為255,0,0(紅色),那么當某個象素為紅色時,只需要標明0即可。 讓我們再來計算一下:16種狀態(tài)可以用4位(bit)表示,所以一個象素要用半個字節(jié)。整個圖象要用200*200*0.5,約20k字節(jié),再加上表占用的字節(jié)為3*16=48字節(jié).整個占用的字節(jié)數約為前面的1/6,省很多吧。
這張RGB的表,即是我們常說的調色板(Palette),另一種叫法是顏色查找表LUT(LookUpTable),似乎更確切一些。Windows位圖中便用到了調色板技術.其實是不光是Windows位圖,許多圖象文件格式如pcx,tif,gif等都用到了。所以很好地掌握調色板的概念是十分重要的.
有一種圖,它的顏色數高達256*256*256種,也就是說包含我們上述提到的R,G,B顏色表示方法中所有的顏色,這種圖叫做真彩色圖(TrueColor)。真彩色圖并不是說一幅圖包含了所有的顏色,而是說它具有顯示所有顏色的能力,即最多可以包含所有的顏色。表示真彩色圖時,每個象素直接用R,G,B三個分量字節(jié)表示,而不采用調色板技術,原因很明顯:如果用調色板,表示一個象素也要用24位,這是因為每種顏色的索引要用24位(因為總共有2的24次方種顏色,即調色板有2的24次方行),和直接用R,G,B三個分量表示用的字節(jié)數一樣,不但沒有任何便宜,還要加上一個256*256*256*3個字節(jié)的大調色板。所以真彩色圖直接用R,G,B三個分量表示,它又叫做24位色圖。
2.Bmp文件格式
介紹完位圖和調色板的概念,下面就讓我們來看一看Windows的位圖文件(.bmp文件)的格式是什么樣子的。 bmp文件大體上分成四個部分,如圖3所示。
位圖文件頭BITMAPFILEHEADER
|
位圖信息頭BITMAPINFOHEADER
|
調色板Palette
|
實際的位圖數據ImageDate
|
圖3.Windows位圖文件結構示意圖
第一部分為位圖文件頭BITMAPFILEHEADER,是一個結構,其定義如下:
typedefstructtagBITMAPFILEHEADER{
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
這個結構的長度是固定的,為14個字節(jié)(WORD為無符號16位整數,DWORD為無符號32位整數),各個域的說明如下:
bfType
指定文件類型,必須是0x424D,即字符串"BM",也就是說所有.bmp文件的頭兩個字節(jié)都是"BM"
bfSize
指定文件大小,包括這14個字節(jié)
bfReserved1,bfReserved2
為保留字,不用考慮
bfOffBits
為從文件頭到實際的位圖數據的偏移字節(jié)數,即圖3中前三個部分的長度之和。
第二部分為位圖信息頭BITMAPINFOHEADER,也是一個結構,其定義如下:
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER; 這個結構的長度是固定的,為40個字節(jié)(WORD為無符號16位整數,DWORD無符號32位整數,LONG為32位整數),各個域的說明如下:
biSize
指定這個結構的長度,為40
biWidth
指定圖象的寬度,單位是象素
biHeight
指定圖象的高度,單位是象素
biPlanes
必須是1,不用考慮
biBitCount
指定表示顏色時要用到的位數,常用的值為1(黑白二色圖),4(16色圖),8(256色),24(真彩色圖)(新的.bmp格式支持32位色,這里就不做討論了)。
biCompression
指定位圖是否壓縮,有效的值為BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些Windows定義好的常量)。要說明的是,Windows位圖可以采用RLE4,和RLE8的壓縮格式,但用的不多。我們今后所討論的只有第一種不壓縮的情況,即biCompression為BI_RGB的情況。
biSizeImage
指定實際的位圖數據占用的字節(jié)數,其實也可以從以下的公式中計算出來:
biSizeImage=biWidth'*biHeight
要注意的是:上述公式中的biWidth'必須是4的整倍數(所以不是biWidth,而是biWidth',表示大于或等于biWidth的,離4最近的整倍數。舉個例子,如果biWidth=240,則biWidth'=240;如果biWidth=241,biWidth'=244)如果biCompression為BI_RGB,則該項可能為零
biXPelsPerMeter
指定目標設備的水平分辨率,單位是每米的象素個數,關于分辨率的概念,我們將在打印部分詳細介紹。
biYPelsPerMeter
指定目標設備的垂直分辨率,單位同上。
biClrUsed
指定本圖象實際用到的顏色數,如果該值為零,則用到的顏色數為2的biBitCount次方。
biClrImportant
指定本圖象中重要的顏色數,如果該值為零,則認為所有的顏色都是重要的。
第三部分為調色板(Palette),當然,這里是對那些需要調色板的位圖文件而言的。有些位圖,如真彩色圖,前面已經講過,是不需要調色板的,BITMAPINFOHEADER后直接是位圖數據。
調色板實際上是一個數組,共有biClrUsed個元素(如果該值為零,則有2的biBitCount次方個元素)。數組中每個元素的類型是一個RGBQUAD結構,占4個字節(jié),其定義如下:
typedef struct tagRGBQUAD{
BYTE rgbBlue; //該顏色的藍色分量
BYTE rgbGreen; //該顏色的綠色分量
BYTE rgbRed; //該顏色的紅色分量
BYTE rgbReserved; //保留值
} RGBQUAD;
第四部分就是實際的圖象數據了。對于用到調色板的位圖,圖象數據就是該像素顏在調色板中的索引值,對于真彩色圖,圖象數據就是實際的R,G,B值。下面就2色,16色,256色位圖和真彩色位圖分別介紹。
對于2色位圖,用1位就可以表示該像素的顏色(一般0表示黑,1表示白),所以一個字節(jié)可以表示8個像素。
對于16色位圖,用4位可以表示一個像素的顏色,所以一個字節(jié)可以表示2個像素。
對于256色位圖,一個字節(jié)剛好可以表示1個像素。
對于真彩色圖,三個字節(jié)才能表示1個像素。
要注意兩點:
1.每一行的字節(jié)數必須是4的整倍數,如果不是,則需要補齊。這在前面介紹biSizeImage時已經提到了。
2.一般來說,.BMP文件的數據從下到上,從左到右的。也就是說,從文件中最先讀到的是圖象最下面一行的左邊第一個像素,然后是左邊第二個像素…接下來是倒數第二行左邊第一個像素,左邊第二個像素…依次類推,最后得到的是最上面一行的最右一個像素。
好了,終于介紹完bmp文件結構了,是不是覺得頭有些大?別著急,對照著下面的程序,你就會很清楚了.
3.顯示一個bmp文件的C程序
下面的函數LoadBmpFile,其功能是從一個.bmp文件中讀取數據(包括BITMAPINFOHEADER,調色板和實際圖象數據)將其存儲在一個全局內存句柄hImgData中,這個hImgData將在以后的圖象處理程序中用到。同時填寫一個類型為HBITMAP的全局變量hBitmap和一個類型為HPALETTE的全局變量hPalette。這兩個變量將在處理WM_PAINT消息時用到,用來顯示出位圖。該函數的兩個參數分別是用來顯示位圖的窗口句柄,和.bmp文件名(全路徑),當函數成功時,返回TRUE,否則返回FALSE.
BITMAPFILEHEADER bf;
BITMAPINFOHEADER bi;
BOOL LoadBmpFile(HWND hWnd,char* BmpFileName)
{
HFILE hf; //文件句柄
LPBITMAPINFOHEADER lpImgData; //指向BITMAPINFOHEADER結構的指針
LOGPALETTE *pPal; //指向邏輯調色板結構的指針
LPRGBQUAD lpRGB; //指向RGBQUAD結構的指針
HPALETTE hPrevPalette;//用來保存設備中原來的調色板
HDC hDc; //設備句柄
HLOCAL hPal; //存儲調色板的局部內存句柄
DWORD LineBytes; //每一行的字節(jié)數
DWORD ImgSize; //實際的圖象數據占用的字節(jié)數
DWORD NumColors; //實際用到的顏色數,即調色板數組中的顏色個數
DWORD i;
if((hf=_lopen(BmpFileName,OF_READ))==HFILE_ERROR){
MessageBox (hWnd,"Filec:\\test.bmpnotfound!","ErrorMessage",
MB_OK|MB_ICONEXCLAMATION);
return FALSE;//打開文件錯誤,返回
}
//將BITMAPFILEHEADER結構從文件中讀出,填寫到bf中
_lread(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER));
//將BITMAPINFOHEADER結構從文件中讀出,填寫到bi中
_lread(hf,(LPSTR)&bi,sizeof(BITMAPINFOHEADER));
/*我們定義了一個宏#define WIDTHBYTES(i) ((i+31)/32*4),上面曾經提到過,每一行的字節(jié)數必須是4的整倍數,只要調用WIDTHBYTES(bi.biWidth*bi.biBitCount)就能完成這一換算.舉一個例子,對于2色圖,如果圖象寬是31,則每一行需要31位存儲,合3個字節(jié)加7位,因為字節(jié)數必須是4的整倍數,所以應該是4,而此時的biWidth=31,biBitCount=1,WIDTHBYTES(31*1)=4,和我們設想的一樣。再舉一個256色的例子,如果圖象寬是31,則每一行需要31個字節(jié)存儲,因為字節(jié)數必須是4的整倍數,所以應該是32,而此時的biWidth=31,biBitCount=8,WIDTHBYTES(31*8)=32,和我們設想的一樣。你可以多舉幾個例子來驗證一下*/
//LineBytes為每一行的字節(jié)數
LineBytes=(DWORD)WIDTHBYTES(bi.biWidth*bi.biBitCount);
//ImgSize為實際的圖象數據占用的字節(jié)數
ImgSize=(DWORD)LineBytes*bi.biHeight;
//NumColors為實際用到的顏色數,即調色板數組中的顏色個數
if(bi.biClrUsed!=0)
NumColors=(DWORD)bi.biClrUsed;//如果bi.biClrUsed不為零,就是本圖象實際
//用到的顏色數
else//否則,用到的顏色數為2的biBitCount次方。
switch(bi.biBitCount){
case1:
NumColors=2;
break;
case4:
NumColors=16;
break;
case8:
NumColors=256;
break;
case24:
NumColors=0;//對于真彩色圖,沒用到調色板
break;
default:
//不處理其它的顏色數,認為出錯。
MessageBox(hWnd,"Invalidcolornumbers!","ErrorMessage",
MB_OK|MB_ICONEXCLAMATION);
_lclose(hf);
return FALSE;//關閉文件,返回FALSE
}
if(bf.bfOffBits!=(DWORD)(NumColors*sizeof(RGBQUAD)+sizeof(BITMAPFILEHEADER)
+sizeof(BITMAPINFOHEADER)))
{
//計算出的偏移量與實際偏移量不符,一定是顏色數出錯
MessageBox(hWnd,"Invalidcolornumbers!","ErrorMessage",
MB_OK|MB_ICONEXCLAMATION);
_lclose(hf);
return FALSE;//關閉文件,返回FALSE
}
bf.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+NumColors
*sizeof(RGBQUAD)+ImgSize;
//分配內存,大小為BITMAPINFOHEADER結構長度加調色板+實際位圖數據
if((hImgData=GlobalAlloc(GHND,(DWORD)(sizeof(BITMAPINFOHEADER)+
NumColors*sizeof(RGBQUAD)+ImgSize)))==NULL)
{
//分配內存錯誤
MessageBox(hWnd,"Errorallocmemory!","ErrorMessage",
MB_OK|MB_ICONEXCLAMATION);
_lclose(hf);
return FALSE;//關閉文件,返回FALSE
}
//指針lpImgData指向該內存區(qū)
lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);
//文件指針重新定位到BITMAPINFOHEADER開始處
_llseek(hf,sizeof(BITMAPFILEHEADER),SEEK_SET);
//將文件內容讀入lpImgData
_hread(hf,(char*)lpImgData,(long)sizeof(BITMAPINFOHEADER)
+(long)NumColors*sizeof(RGBQUAD)+ImgSize);
_lclose(hf);//關閉文件
if(NumColors!=0) //NumColors不為零,說明用到了調色板
{
//為邏輯調色板分配局部內存,大小為邏輯調色板結構長度加NumColors個
//PALETTENTRY大小
hPal=LocalAlloc(LHND,sizeof(LOGPALETTE)+NumColors*sizeof(PALETTEENTRY));
//指針pPal指向該內存區(qū)
pPal=(LOGPALETTE*)LocalLock(hPal);
//填寫邏輯調色板結構的頭
pPal->palNumEntries=NumColors;
pPal->palVersion=0x300;
//lpRGB指向的是調色板開始的位置
lpRGB=(LPRGBQUAD)((LPSTR)lpImgData+(DWORD)sizeof(BITMAPINFOHEADER));
//填寫每一項
for(i=0;i<NumColors;i++)
{
pPal->palPalEntry[i].peRed=lpRGB->rgbRed;
pPal->palPalEntry[i].peGreen=lpRGB->rgbGreen;
pPal->palPalEntry[i].peBlue=lpRGB->rgbBlue;
pPal->palPalEntry[i].peFlags=(BYTE)0;
lpRGB++;//指針移到下一項
}
//產生邏輯調色板,hPalette是一個全局變量
hPalette=CreatePalette(pPal);
//釋放局部內存
LocalUnlock(hPal);
LocalFree(hPal);
}
//獲得設備上下文句柄
hDc=GetDC(hWnd);
if(hPalette)//如果剛才產生了邏輯調色板
{
//將新的邏輯調色板選入DC,將舊的邏輯調色板句柄保存在hPrevPalette
hPrevPalette=SelectPalette(hDc,hPalette,FALSE);
RealizePalette(hDc);
}
//產生位圖句柄
hBitmap=CreateDIBitmap(hDc, (LPBITMAPINFOHEADER)lpImgData,(LONG)CBM_INIT,
(LPSTR)lpImgData+sizeof(BITMAPINFOHEADER)+NumColors*sizeof(RGBQUAD),
(LPBITMAPINFO)lpImgData,DIB_RGB_COLORS);
//將原來的調色板(如果有的話)選入設備上下文句柄
if(hPalette&&hPrevPalette)
{
SelectPalette(hDc,hPrevPalette,FALSE);
RealizePalette(hDc);
}
ReleaseDC(hWnd,hDc); //釋放設備上下文
GlobalUnlock(hImgData); //解鎖內存區(qū)
Return TRUE; //成功返回
}
上面的程序中,要說明的有兩點:
第一,對于需要調色板的圖,要想正確的顯示,必須根據.bmp文件,產生邏輯調色板。產生的方法是:1.為邏輯調色板指針分配內存,大小為邏輯調色板結構(LOGPALETTE)長度加NumColors個PALETTENTRY大小。(調色板的每一項都是一個PALETTEENTRY結構),2.填寫邏輯調色板結構的頭pPal->palNumEntries=NumColors;pPal->palVersion=0x300;3.從文件中讀取調色板的RGB值,填寫到每一項中。4,產生邏輯調色板:hPalette=CreatePalette(pPal)
第二,產生位圖(BITMAP)句柄,該項工作由函數CreateDIBitmap來完成。hBitmap=CreateDIBitmap(hDc,LPBITMAPINFOHEADER)lpImgData,(LONG)CBM_INIT, (LPSTR)lpImgData+sizeof(BITMAPINFOHEADER)+NumColors*sizeof(RGBQUAD), (LPBITMAPINFO)lpImgData,DIB_RGB_COLORS); CreateDIBitmap的作用是產生一個和Windows設備無關的位圖。該函數的第一項參數為設備上下文句柄,如果位圖用到了調色板,要在調用CreateDIBitmap之前將邏輯調色板選入該設備上下文中,產生hBitmap后,再把原調色板選入該設備上下文中,并釋放該上下文;第二項為指向BITMAPINFOHEADER的指針;第三項就用常量CBM_INI,不用考慮;第四項為指向調色板的指針;第五項為指向BITMAPINFO(包括BITMAPINFOHEADER,調色板,及實際的圖象數據)的指針;第六項就用常量DIB_RGB_COLORS,不用考慮。
上面提到了設備上下文,相信編過Windows程序的讀者對它并不陌生,這里再簡單的介紹一下。Windows操作系統(tǒng)統(tǒng)一管理著諸如顯示,打印等操作,將它們看作是一個個的設備,每一個設備都有一個復雜的數據結構來維護。所謂設備上下文就是指這個數據結構。然而,我們不能直接和這些設備上下文打交道,只能通過引用標識它的句柄(實際上是一個整數),讓Windows去做相應的處理。產生的邏輯調色板句柄hPalette和位圖句柄hBitmap要在處理WM_PAINT消息時使用,這樣才能在屏幕上顯示出來,處理過程如下面的程序。
StaticHDC hDC,hMemDC;
PAINTSTRUCT ps;
case WM_PAINT:
{
hDC=BeginPaint(hwnd,&ps);//獲得屏幕設備上下文
if(hBitmap)//hBitmap一開始是NULL,當不為NULL時表示有圖
{
hMemDC=CreateCompatibleDC(hDC);//建立一個內存設備上下文
if(hPalette)//有調色板
{
//將調色板選入屏幕設備上下文
SelectPalette(hDC,hPalette,FALSE);
//將調色板選入內存設備上下文
SelectPalette(hMemDC,hpalette,FALSE);
RealizePalette(hDC);
}
//將位圖選入內存設備上下文
SelectObject(hMemDC,hBitmap);
//顯示位圖
BitBlt(hDC,0,0,bi.biWidth,bi.biHeight,hMemDC,0,0,SRCCOPY);
//釋放內存設備上下文
DeleteDC(hMemDC);
}
//釋放屏幕設備上下文
EndPaint(hwnd,&ps);
break;
}
在上面的程序中,我們調用CreateCompatibleDC創(chuàng)建一個內存設備上下文。SelectObject函數將于設備無關的位圖選入內存設備上下文中。然后我們調用BitBlt函數在內存設備上下文和屏幕設備上下文中進行位拷貝。由于所有操作都是在內存中進行,所以是最快的。
BitBlt函數的參數分別為:1.目標設備上下文,在上面的程序里,為屏幕設備上下文,如果改成打印設備上下文,就不是顯示位圖,而是打?。?.目標矩形左上角點x坐標;3.目標矩形左上角點y坐標,在上面的程序中,2和3為(0,0),表示顯示在窗口的左上角;4.目標矩形的寬度;5.目標矩形的高度;6.源設備上下文,在上面的程序里,為內存設備上下文;7.源矩形左上角點x坐標;8.源矩形左上角點y坐標;9.操作方式,在這里為SRCCOPY,表示直接將源矩形拷貝到目標矩形。還可以是反色,擦除,做"與"運算等操作,具體細節(jié)見VC++幫助。你可以試著改改第2,3,4,5,7,8,9項參數,就能體會到它們的含義了。
終于講完了。是不是覺得有點枯燥?這一講是有點兒枯燥,特別是當你對Windows的編程并不很清楚時,就更覺得如此。不過,當一幅漂亮的bmp圖顯示在屏幕上時,你還是會興奮的大叫"Yeah",至少當年我是這樣。
最后,再介紹一個命令行編譯的竅門。為什么要用命令行編譯呢?主要有兩個好處:
第一,不用進入IDE(集成開發(fā)環(huán)境),節(jié)省了時間,而且編譯速度也比較快。
第二,對于簡單的程序,不用生成項目文件.mdp或.mak,直接就能生成.exe文件,這一點,在下面的例子中可以看到。
在安裝VisualC++完畢時,在bin目錄下會產生一個VCVARS32.BAT文件,它的作用是在命令行編譯時設置正確的環(huán)境變量,如存放頭文件的INCLUDE目錄,存放庫文件的LIB目錄等,如果你沒找到這個批處理文件,可以參考下面的例子,自己做一個批處理。
@echo off
set MSDevDir=d:\MSDEV
set VcOsDir=WIN95
set PATH="%MSDevDir%\BIN";"%MSDevDir%\BIN\%VcOsDir%";"%PATH%"
set INCLUDE=%MSDevDir%\INCLUDE;%MSDevDir%\MFC\INCLUDE;%INCLUDE%
set LIB=%MSDevDir%\LIB;%MSDevDir%\MFC\LIB;%LIB%
set VcOsDir=
只要把上面的"d:\MSDEV"改成你自己的VC目錄就可以了。在DOSPROMPT下執(zhí)行該批處理文件,執(zhí)行set命令,你就能看到新設置的環(huán)境變量了。如下所示:
PATH=D:\MSDEV\BIN;D:\MSDEV\BIN\WIN95;C:\WIN95;C:\WIN95\COMMAND;C:\WIN95\SYSTEM;
INCLUDE=d:\msdev\INCLUDE;d:\msdev\MFC\INCLUDE;
LIB=d:\msdev\LIB;d:\msdev\MFC\LIB;
現在我們就可以進行命令行編譯了。(當然,你也可以使用IDE,先new一個project,然后把.c和.rc文件插入到project中,編譯運行。)
首先編譯資源文件,輸入rc bmp.rc,將生成bmp.res文件,接著輸入cl bmp.c bmp.res user32.lib gdi32.lib,就生成bmp.exe了。可以看到,我們并沒有用到項目文件,所以,對于這種簡單的程序來說,使用命令行編譯還是非常方便的。好了,運行bmp.exe,欣賞一下你今天的勞動成果。
注意事項:
命令行編譯過程如下:
vcvars32
rc bmp.rc
cl bmp.c bmp.res user32.lib gdi32.lib
========
后來發(fā)現原文出自
http://vipbase.net/ipbook/此書是清華學生寫的,做圖像編程的入門教材很不錯!
posted @
2009-07-10 22:59 鷹擊長空 閱讀(1172) |
評論 (0) |
編輯 收藏
前言:表弟想要學編程,我推薦他學習.Net和C#。這一推薦不打緊,我卻承擔上了指導的職責。我又出差在外,直接輔導是不行了,通過郵件也太麻煩。推薦了幾本書,可惜他太菜了,總有無從下手的感覺。推及他人,在初學C#時,是否也有這樣的感覺呢?所以,就有了這個系列文章。表弟是我把他帶入計算機行業(yè)的,當初什么都不懂,我曾經打開計算機機箱,指點他哪里是硬盤、哪里是內存,是CPU,現在對于計算機硬件他早已可以做我?guī)煾怠OM麑W軟件編程也能這樣。
一、解決方案、項目、程序集、命名空間
初學者很容易把這些概念搞混淆。先說說項目(Project),通俗的說,一個項目可以就是你開發(fā)的一個軟件。在.Net下,一個項目可以表現為多種類型,如控制臺應用程序,Windows應用程序,類庫(Class Library),Web應用程序,Web Service,Windows控件等等。如果經過編譯,從擴展名來看,應用程序都會被編譯為.exe文件,而其余的會被編譯為.dll文件。既然是.exe文件,就表明它是可以被執(zhí)行的,表現在程序中,這些應用程序都有一個主程序入口點,即方法Main()。而類庫,Windows控件等,則沒有這個入口點,所以也不能直接執(zhí)行,而僅提供一些功能,給其他項目調用。
在Visual Studio.Net中,可以在“File”菜單中,選擇“new”一個“Project”,來創(chuàng)建一個新的項目。例如創(chuàng)建控制臺應用程序。注意在此時,Visual Studio除了建立了一個控制臺項目之外,該項目同時還屬于一個解決方案(Solution)。這個解決方案有什么用?如果你只需要開發(fā)一個Hello World的項目,解決方案自然毫無用處。但是,一個稍微復雜一點的軟件,都需要很多模塊來組成,為了體現彼此之間的層次關系,利于程序的復用,往往需要多個項目,每個項目實現不同的功能,最后將這些項目組合起來,就形成了一個完整的解決方案。形象地說,解決方案就是一個容器,在這個容器里,分成好多層,好多格,用來存放不同的項目。一個解決方案與項目是大于等于的關系。建立解決方案后,會建立一個擴展名為.sln的文件。
在解決方案里添加項目,不能再用“new”的方法,而是要在“File”菜單中,選擇“Add Project”。添加的項目,可以是新項目,也可以是已經存在的項目。
程序集叫Assembly。學術的概念我不想提,通俗的角度來說,一個項目也就是一個程序集。從設計的角度來說,也可以看成是一個完整的模塊(Module),或者稱為是包(Package)。因此,一個程序集也可以體現為一個dll文件,或者exe文件。怎樣劃分程序集也是大有文章的,不過初學者暫時不用考慮它。
命名空間(namespace)是在C++里面就有的概念。引入它,主要是為了避免一個項目中,可能會存在的相同對象名的沖突。這個命名空間的定義,沒有特殊的要求。不過基本上來說,為了保證其唯一性,最好是用uri的格式,例如BruceZhang.com。這個命名空間有點像我們姓名中的姓,然后每個對象的名字則是姓名中的名。如果有重復,在國外的命名中,還可以加上middle name。那么名都為“勇”的,由于姓氏不同也就分開了,或者叫張勇,或者叫趙勇。當然人的姓氏重復者居多,所以我們?yōu)槊臻g取名時,盡可能的復雜一點。
有許多初學者,常常把一個項目就理解為一個命名空間。其實這兩者沒有絕對的聯系,在項目里我們也可以定義很多不相同的命名空間。但為了用戶便于使用,最好在一個項目中,其命名空間最好是一體的層次結構。在Visual Studio里,我們可以在項目中新建一個文件夾,默認情況下,該文件夾下對象的命名空間,應該是“項目的命名空間.文件夾名”。當然,我們也可以在namespace中修改它。
命名空間和程序集名,都可以在Visual Studio中設置。用鼠標右鍵單擊項目名,就可以彈出如下對話框:

在圖中,Assembly Name就是程序集名,如果經過編譯,則為該項目的文件名。而Default Namespace則為默認的命名空間。在開發(fā)軟件時,我們要養(yǎng)成良好的習慣,在建立新項目后,就將這些屬性設置好。一旦設置好了Default Namespace,則以后新建的對象,其命名空間即為該設定的值。至于程序集名,如果是dll文件,建議其名最好與Default Namespace一致。
實例演練:
(一)創(chuàng)建控制臺應用程序“Hello World!”
1、打開Visual Studio.Net,選擇“File”菜單的“new”,選擇“Project”;
2、選擇Visual C# Projects中的“Console Application”,如圖所示:

在Location中,定位你要保存的項目的路徑,而名字則為“FirstExample”。該名字此時既是解決方案的名字,同時也是該項目的名字。
3、用鼠標右鍵單擊項目名,在彈出的對話框中,將Assembly Name命名為HelloWorld,將Default Namespace命名為:BruceZhang.com.FirstExample。
4、此時Visual Studio中已經建立了一個文件,其名為Class1.cs(如果是Visual Studio 2005,則默認為Program.cs);修改該文件的文件名為HelloWorld.cs,同時修改文件中的namespace,和類名,如下:
namespace BruceZhang.com.FirstExample
{
///
/// Summary description for Class1.
///
class HelloWorld
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
//
// TODO: Add code to start application here
//
}
}
}
5、注意在HelloWorld.cs中,有一個Main()方法。這是因為我們建立的是控制臺應用程序。在Main()方法中添加如下代碼:
Console.WriteLine(”Hello World!”);
Console.Read();
這里的Console是一個能對控制臺進行操作的類。
6、運行。
檢查保存項目的路徑文件夾FirstExample/bin/debug,已經存在了一個HelloWorld.exe文件。
(二)為解決方案添加一個新項目
1、在“File”菜單中,選擇“Add Project”,添加“New Project”。在對話框中選擇“Class Library”,名字為Printer。至于保存路徑,可以放在之前建立的FirstExample文件夾下:

2、在Visual Studio右側,可以看到現在有兩個項目了。仍然修改新項目的名稱和默認命名空間名,均為BruceZhang.com.Printer。
3、將默認建立的Class1.cs改名為MessagePrinter.cs,同時修改其代碼為:
namespace BruceZhang.com.Printer
{
///
/// Summary description for Class1.
///
public class MessagePrinter
{
public MessagePrinter()
{
//
// TODO: Add constructor logic here
//
}
public static void Print(string msg)
{
Console.WriteLine(msg);
}
}
}
在MessagePrinter類中,我們注意到并沒有Main()方法,因為它不是應用程序。新增加的Print()方法,能夠接收一個字符串,然后在控制臺中顯示出來。
4、編譯Printer項目。鼠標右鍵單擊該項目名,在菜單中選擇“Build”。成功編譯后,找到文件夾Printer/bin/debug,可以發(fā)現有文件BruceZhang.com.Printer.dll,這就是最后形成的程序集文件。
5、關聯這兩個項目。我們希望是在FirstExample項目中用到Printer項目的Print()方法,前提是需要在FirstExample項目中添加對Printer項目的引用。右鍵單擊FirstExample項目的“Reference”,選擇“Add Reference”,在對話框中選擇“Project”標簽,找到該項目并選中,最后如圖所示:

6、現在就可以在FirstExample項目中使用MessagePrinter了。首先,在命名空間中添加對它的使用(Using),然后再Main()方法中調用它,最后代碼如下:
using System;
using BruceZhang.com.Printer;
namespace BruceZhang.com.FirstExample
{
///
/// Summary description for Class1.
///
class HelloWorld
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
MessagePrinter.Print(”Hello World!”);
Console.Read();
}
}
}
7、運行。結果與前一個例子一樣。
在這個例子中,解決方案中就包含了兩個項目,一個是控制臺應用程序,一個是類庫。類庫提供一些基本的功能,如例子中的Print()方法。我們常常把一些共用的方法,放到類庫中。這樣其他的應用程序就可以去調用它。例如本例的控制臺應用程序。如果新建的Windows應用程序,也需要這個功能,就可以直接引用MessagePrinter的Print()方法,而不必重復去實現。
posted @
2008-11-21 10:16 鷹擊長空 閱讀(539) |
評論 (0) |
編輯 收藏