Sample Code
The following code shows how to obtain the filename or path and description of a given link file:
#include <windows.h>
#include <shlobj.h>
// GetLinkInfo() fills the filename and path buffer
// with relevant information.
// hWnd - calling application's window handle.
//
// lpszLinkName - name of the link file passed into the function.
//
// lpszPath - the buffer that receives the file's path name.
//
// lpszDescription - the buffer that receives the file's
// description.
HRESULT
GetLinkInfo( HWND hWnd,
LPCTSTR lpszLinkName,
LPSTR lpszPath,
LPSTR lpszDescription)
{
HRESULT hres;
IShellLink *pShLink;
WIN32_FIND_DATA wfd;
// Initialize the return parameters to null strings.
*lpszPath = '\0';
*lpszDescription = '\0';
// Call CoCreateInstance to obtain the IShellLink
// Interface pointer. This call fails if
// CoInitialize is not called, so it is assumed that
// CoInitialize has been called.
hres = CoCreateInstance( &CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
&IID_IShellLink,
(LPVOID *)&pShLink );
if (SUCCEEDED(hres))
{
IPersistFile *ppf;
// The IShellLink Interface supports the IPersistFile
// interface. Get an interface pointer to it.
hres = pShLink->lpVtbl->QueryInterface(pShLink,
&IID_IPersistFile,
(LPVOID *)&ppf );
if (SUCCEEDED(hres))
{
WORD wsz[MAX_PATH];
// Convert the given link name string to a wide character string.
MultiByteToWideChar( CP_ACP, 0,
lpszLinkName,
-1, wsz, MAX_PATH );
// Load the file.
hres = ppf->lpVtbl->Load(ppf, wsz, STGM_READ );
if (SUCCEEDED(hres))
{
// Resolve the link by calling the Resolve() interface function.
// This enables us to find the file the link points to even if
// it has been moved or renamed.
hres = pShLink->lpVtbl->Resolve(pShLink, hWnd,
SLR_ANY_MATCH | SLR_NO_UI);
if (SUCCEEDED(hres))
{
// Get the path of the file the link points to.
hres = pShLink->lpVtbl->GetPath( pShLink, lpszPath,
MAX_PATH,
&wfd,
SLGP_SHORTPATH );
// Only get the description if we successfully got the path
// (We can't return immediately because we need to release ppf &
// pShLink.)
if(SUCCEEDED(hres))
{
// Get the description of the link.
hres = pShLink->lpVtbl->GetDescription(pShLink,
lpszDescription,
MAX_PATH );
}
}
}
ppf->lpVtbl->Release(ppf);
}
pShLink->lpVtbl->Release(pShLink);
}
return hres;
}
posted @
2008-08-14 06:02 幽幽 閱讀(579) |
評論 (0) |
編輯 收藏
GDI+ 將字樣相同但字形不同的字體分組為字體系列。例如,Arial 字體系列中包含以下字體:
-
Arial Regular
-
Arial Bold
-
Arial Italic
-
Arial Bold Italic
GDI+ 使用四種字形形成字體系列:常規、粗體、傾斜和粗斜體。像 narrow 和 rounded 之類的形容詞不被視為字形;而是作為字體系列名的一部分。例如,Arial Narrow 是包含以下成員的字體系列:
-
Arial Narrow Regular
-
Arial Narrow Bold
-
Arial Narrow Italic
-
Arial Narrow Bold Italic
在可以使用 GDI+ 繪制文本之前,您需要構造一個 FontFamily 對象和一個 Font 對象。FontFamily 對象指定字樣(例如 Arial),而 Font 對象指定字號、字形和單位。
示例
下面的示例構造一個字號為 16 像素、常規字形的 Arial 字體。在下面的代碼中,傳遞給 Font 構造函數的第一個參數是 FontFamily 對象。第二個參數指定字體的大小,其單位由第四個參數確定。第三個參數確定字形。
Pixel 為 GraphicsUnit 枚舉的一個成員,Regular 是 FontStyle 枚舉的一個成員。
FontFamily fontFamily = new FontFamily("Arial");
Font font = new Font(
fontFamily,
16,
FontStyle.Regular,
GraphicsUnit.Pixel);
posted @
2008-08-14 03:51 幽幽 閱讀(1514) |
評論 (0) |
編輯 收藏
SHGetFileInfo函數
function SHGetFileInfo(pszPath: PAnsiChar; dwFileAttributes: DWORD;
var psfi: TSHFileInfo; cbFileInfo, uFlags: UINT): DWORD; stdcall;
pszPath 參數:指定的文件名。
當uFlags的取值中不包含 SHGFI_PIDL時,可直接指定;
當uFlags的取值中包含 SHGFI_PIDL時pszPath要通過計算獲得,不能直接指定;
dwFileAttributes參數:文件屬性。
僅當uFlags的取值中包含SHGFI_USEFILEATTRIBUTES時有效,一般不用此參數;
psfi 參數:返回獲得的文件信息,是一個記錄類型,有以下字段:
_SHFILEINFOA = record
hIcon: HICON; { out: icon } //文件的圖標句柄
iIcon: Integer; { out: icon index } //圖標的系統索引號
dwAttributes: DWORD; { out: SFGAO_ flags } //文件的屬性值
szDisplayName: array [0..MAX_PATH-1] of AnsiChar; { out: display name (or path) } //文件的顯示名
szTypeName: array [0..79] of AnsiChar; { out: type name } //文件的類型名
end;
cbFileInfo 參數:psfi的比特值;
uFlags 參數:指明需要返回的文件信息標識符,常用的有以下常數:
SHGFI_ICON; //獲得圖標
SHGFI_DISPLAYNAME; //獲得顯示名
SHGFI_TYPENAME; //獲得類型名
SHGFI_ATTRIBUTES; //獲得屬性
SHGFI_LARGEICON; //獲得大圖標
SHGFI_SMALLICON; //獲得小圖標
SHGFI_PIDL; // pszPath是一個標識符
函數SHGetFileInfo()的返回值也隨uFlags的取值變化而有所不同。
可見通過調用SHGetFileInfo()可以由psfi參數得到文件的圖標句柄。但要注意在uFlags參數中不使用SHGFI_PIDL時,SHGetFileInfo()不能獲得“我的電腦”等虛似文件夾的信息。
應該注意的是,在調用SHGetFileInfo()之前,必須使用 CoInitialize 或者OleInitialize 初始化COM,否則表面上能夠使用,但是會造成不安全或者喪失部分功能。例如,一個常見的例子:如果不初始化COM,那么調用該函數就無法得到.htm/.mht/.xml文件的圖標。
以下是兩個例子:
1.獲得系統圖標列表:
//取得系統圖標列表
uses
ShellAPI
var
ImageListHandle : THandle;
FileInfo: TSHFileInfo;
//小圖標
ImageListHandle := SHGetFileInfo('C:\',
0,
FileInfo,
SizeOf(FileInfo),
SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
//把圖標列表同一個名叫ListView1的ListView控件的小圖標關聯。
SendMessage(ListView1.Handle, LVM_SETIMAGELIST, LVSIL_SMALL, ImageListHandle);
//大圖標
ImageListHandle := SHGetFileInfo('C:\',
0,
FileInfo,
SizeOf(FileInfo),
SHGFI_SYSICONINDEX or SHGFI_LARGEICON);
//把圖標列表同一個名叫ListView1的ListView控件的大圖標關聯。
SendMessage(ListView1.Handle, LVM_SETIMAGELIST, LVSIL_NORMAL, ImageListHandle);
2.獲得一個文件的顯示名和圖標
var
sfi: TSHFileInfo;
IconIndex : Integer;
//取圖標的索引號等信息
SHGetFileInfo(PAnsiChar(FileName),
0,
sfi,
sizeof(TSHFileInfo),
ShellAPI.SHGFI_DISPLAYNAME or ShellAPI.SHGFI_TYPENAME or ShellAPI.SHGFI_LARGEICON or ShellAPI.SHGFI_ICON);
//顯示名和圖標在系統圖標列表中的編號就分別在sfi.szDisplayName和sfi.iIcon中
posted @
2008-08-13 23:11 幽幽 閱讀(1382) |
評論 (0) |
編輯 收藏
一、 簡介
屏幕抓圖程序在處理圖形中應用廣泛。作為Windows XP及以后版本操作系統的圖形處理內核,GDI+在二維幾何圖形處理、圖像顯示與轉換和字符排版等方面簡直是傳統GDI程序員的一種解脫。但是,至少在目前情況下,GDI+尚不能完全代替GDI。與GDI相比,它至少還存在以下不足:
不支持從內存到屏幕的位傳輸操作;
不支持光柵“位運算”操作;
如果程序性能、速度要求比較嚴格,在圖片輸出方面的表現較差時,GDI往往能取代實現高性能的輸出。
本文通過對流行的屏幕抓圖程序工作原理的剖析,力圖向讀者闡明GDI+與GDI各自在圖形處理方面的優缺點,并給出相應的VC++ .NET代碼實現。
二、 GDI在抓圖中的關鍵作用
要實現屏幕抓圖,關鍵有兩點:一是獲取圖片所在窗口的窗口句柄,即在何處捕獲圖片;二是保存抓取的圖片,實現這一點正是GDI+的強項。
對于問題一,可以利用SetCapture函數,它能夠追蹤鼠標指針的移動(包括在屏幕抓圖程序窗口之外的窗口)。在移動鼠標的過程中,它還可以根據鼠標的指針所在位置來判斷當前窗口的窗口句柄。我們還可以使用函數WindowFromPoint,這個函數能夠找出鼠標指針當前位置所對應的窗口句柄。
使用過知名的抓圖軟件SnagIT的讀者都知道,在選擇抓圖窗口時,鼠標指針所在位置的窗口都會出現加粗的紅色邊框,以提醒目前所選擇的窗口,這個功能實現起來有些復雜。下面介紹在GDI中如何使這個紅色邊框出現。
【注意】正是由于這個紅色邊框的實現,讀者才能發現GDI+在這方面的弱點。
在GDI中,一個最基本的概念就是設備環境(DC),每一個窗口都具有自己的DC。如果能夠找到窗口的DC,那么,用戶就能夠在該窗口的任何位置繪圖。然而,在屏幕抓圖程序中,由于用戶所選擇的窗口不固定,所以,要想得到鼠標指針所處窗口的DC并不容易。這一問題的答案在于GetDC函數。下面是GetDC的函數聲明:
HDC GetDC(HWND hWnd);
這里,hWnd是DC對應的窗口句柄。注意,當hWnd為空時,該函數返回的是整個屏幕的設備環境句柄。這就意味著,開發人員可以在屏幕上的任何位置進行任意的繪圖操作。
在鼠標指針所處的窗口繪圖時,繪圖的目的只是為了提醒用戶目前所選擇的窗口,所以,在繪圖時,必須保證不會破壞窗口原有的畫面。這時可將窗口的繪圖模式設為RS_NOTXORPEN,將畫筆顏色與屏幕顏色進行異或運算之后,再對屏幕顏色取反即可。RS_NOTXORPEN運算方式的特點在于:對同一像素進行兩次RS_NOTXORPEN運算后,像素值并不會發生變化。這樣,在同一個地方進行兩次繪圖后,窗口的畫面并不會發生任何變化。
【注意】這些功能在GDI+中很難實現。
三、 編碼實現
由上可知,屏幕抓圖至少分為3個步驟:
(1) 啟用鼠標指針捕獲。
(2) 在鼠標指針所在處的窗口進行繪圖,提示抓圖的目標。
(3) 選定目標窗口時,將目標窗口的畫面保存為自定義的位圖并終止鼠標指針捕獲。
以下是具體的編程步驟:
(1)在Visual C++ .NET中按照GDI+程序的框架新建一個基于對話框的項目ScreenCapture,然后準備好一個外形為相機的光標文件(*.cur),將之引入資源管理器(IDC_CAMERA)。接著在CScreenCaptureDlg類中加入以下兩個全局變量:
HWND hwndCapture;
Crect rectCapture;
(2)通過類向導加入對WM_MOUSEMOVE及WM_LBUTTONUP事件的響應函數,分別如下所示。
void CScreenCaptureDlg::OnMouseMove(UINT nFlags, CPoint point)
{
//如果用戶按隹鼠標左鍵不放,則開始抓取圖片
if(nFlags==MK_LBUTTON){
//隱藏程序窗口,以免影響在抓取時的“視野”
ShowWindow(SW_HIDE);
//載入“照相機”鼠標指針,開始追蹤鼠標指針的移動
HCURSOR cur=LoadCursor(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDC_CAMERA));
SetCursor(cur);
SetCapture();
//獲得鼠標指針所在窗口的句柄
this->ClientToScreen(&point);
hwndCapture=(HWND)::WindowFromPoint(point);
//取得屏幕的設備環境句柄,以便在屏幕的任何位置繪圖
HDC hDC=::GetDC(NULL);
//建立一個紅色的畫筆
HPEN hPen=CreatePen(PS_INSIDEFRAME,6,RGB(255,0,0));
//將繪圖模式設為R2_NOTXORPEN,在繪圖時可以不破壞原有的背景
int nMode=SetROP2(hDC,R2_NOTXORPEN);
HPEN hpenOld=(HPEN)SelectObject(hDC,hPen);
//得到鼠標指針所在窗口的區域
::GetWindowRect(hwndCapture,&rectCapture);
//在鼠標指針所在處的窗口四周畫一紅色的矩形,做為選定時的提示
POINT pt[5];
pt[0]=CPoint(rectCapture.left,rectCapture.top);
pt[1]=CPoint(rectCapture.right,rectCapture.top);
pt[2]=CPoint(rectCapture.right,rectCapture.bottom);
pt[3]=CPoint(rectCapture.left,rectCapture.bottom);
pt[4]=CPoint(rectCapture.left,rectCapture.top);
::Polyline(hDC,pt,5);
//延時后再重繪紅色矩形,這樣不會破壞原有的內容
Sleep(100);
::Polyline(hDC,pt,5);
::SelectObject(hDC,hpenOld);
::ReleaseDC(NULL,hDC);
}
CDialog::OnMouseMove(nFlags, point);
}
void CScreenCaptureDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
// 得到鼠標指針所在窗口的區域寬、高
int nWidth=rectCapture.Width();
int nHeight=rectCapture.Height();
HDC hdcScreen,hMemDC;
HBITMAP hBitmap,hOldBitmap;
//建立一個屏幕設備環境句柄
hdcScreen=CreateDC("DISPLAY",NULL,NULL,NULL);
hMemDC=CreateCompatibleDC(hdcScreen);
//建立一個與屏幕設備環境句柄兼容、與鼠標指針所在窗口的區域等大的位圖
hBitmap=CreateCompatibleBitmap(hdcScreen,nWidth,nHeight);
//把新位圖選到內存設備描述表中
hOldBitmap=(HBITMAP)SelectObject(hMemDC,hBitmap);
//把屏幕設備描述表拷貝到內存設備描述表中
BitBlt(hMemDC,0,0,nWidth,nHeight,hdcScreen,rectCapture.left,rectCapture.top,SRCCOPY);
DeleteDC(hdcScreen);
DeleteDC(hMemDC);
//返回位圖句柄
//打開剪貼板,并將位圖拷到剪貼板上
OpenClipboard();
EmptyClipboard();
SetClipboardData(CF_BITMAP,hBitmap);
//關閉剪貼板
CloseClipboard();
MessageBox("屏幕內容已經拷到剪貼板!");
ReleaseCapture();
//恢復窗口顯示模式
ShowWindow(SW_NORMAL);
CDialog::OnLButtonUp(nFlags, point);
}
至此,一個具有專業效果的屏幕抓圖程序的核心已經搞定。
四、 用GDI+實現畫面的保存
經過上面兩步,如果用戶在對話框中按住鼠標左鍵不放,程序便開始“抓圖”。當選擇好抓圖的目標后,松開鼠標左鍵,抓圖的目標窗口的畫面就自動保存到剪貼板中了。但是,把畫面保存到文件中更為重要。如果用GDI的方式來操作,需要對各種類位圖的結構有詳盡的了解,極其麻煩。但如果用GDI+來實現之則極為容易。下面介紹如何將已經抓到的圖片保存到一個BMP文件中。
由上面知,抓圖程序已經得到了所捕獲的窗口的位圖句柄,接下來要將位圖句柄保存為相應的位圖文件。這一切歸功于GDI+的Bitmap類,詳見下列代碼。
void CScreenCaptureDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
//……省略
if(GetSaveFileName(&ofn))
{
CLSID pngClsid;
Bitmap bmp(hBitmap,NULL);
//獲取BMP文件的編碼方式
GetEncoderClsid(L"image/bmp",&pngClsid);//幫助函數
CString tmp(ofn.lpstrFile);
CStringW filename((LPCSTR)tmp);
//保存所截取的屏幕圖片
bmp.Save(filename,&pngClsid);
}
ReleaseCapture();
MessageBox("屏幕內容已經保存到文件中!");
//恢復窗口顯示模式
ShowWindow(SW_NORMAL);
CDialog::OnLButtonUp(nFlags, point);
}
五、 小結
本文通過一個專業的屏幕抓圖程序的核心實現,對比分析了GDI與GDI+各自的優缺點。但我們相信,GDI+作為新一代圖形引擎,隨著版本的不斷升級,其遲早要淘汰掉GDI。本人拙見,不足處還望讀者指正。
另外,本文源碼在Windows 2000/VC++.NET 2003環境中調試通過。調試過程中注意:
確保工程對GDI+庫的正確引用:在頭文件stdafx.h中要加入相應引用;在應用程序類的InitInstance成員函數前后及其析構函數中加適當的操作;工程編譯時要加入對gdiplus.lib的引用(“項目”|“添加現有項”,我的機器上是在C:\Program Files\Microsoft Visual Studio.NET\vc7\platformSDK\lib下找到庫文件)。
posted @
2008-08-13 23:02 幽幽 閱讀(2020) |
評論 (0) |
編輯 收藏
下面是GetSystemMetrics函數參數nIndex的定義:
SM_ARRANGE Flags specifying how the system arranged minimized windows. For more information about minimized windows, see the following Remarks section.
SM_CLEANBOOT 返回系統啟動方式:
0 正常啟動
1 安全模式啟動
2 網絡安全模式啟動
SM_CMOUSEBUTTONS 返回值為系統支持的鼠標鍵數,返回0,則系統中沒有安裝鼠標。
SM_CXBORDER,
SM_CYBORDER 返回以像素值為單位的Windows窗口邊框的寬度和高度,如果Windows的為3D形態,則
等同于SM_CXEDGE參數
SM_CXCURSOR,
SM_CYCURSOR 返回以像素值為單位的標準光標的寬度和高度
SM_CXDLGFRAME,
SM_CYDLGFRAME 等同與SM_CXFIXEDFRAME and SM_CYFIXEDFRAME
SM_CXDOUBLECLK,
SM_CYDOUBLECLK 以像素值為單位的雙擊有效的矩形區域
SM_CXEDGE,SM_CYEDGE 以像素值為單位的3D邊框的寬度和高度
SM_CXFIXEDFRAME,
SM_CYFIXEDFRAME 圍繞具有標題但無法改變尺寸的窗口(通常是一些對話框)的邊框的厚度
SM_CXFRAME,SM_CYFRAME 等同于SM_CXSIZEFRAME and SM_CYSIZEFRAME
SM_CXFULLSCREEN,
SM_CYFULLSCREEN 全屏幕窗口的窗口區域的寬度和高度
SM_CXHSCROLL,
SM_CYHSCROLL 水平滾動條的高度和水平滾動條上箭頭的寬度
SM_CXHTHUMB 以像素為單位的水平滾動條上的滑動塊寬度
SM_CXICON,SM_CYICON 系統缺省的圖標的高度和寬度(一般為32*32)
SM_CXICONSPACING,
SM_CYICONSPACING 以大圖標方式查看Item時圖標之間的間距,這個距離總是大于等于
SM_CXICON and SM_CYICON.
SM_CXMAXIMIZED,
SM_CYMAXIMIZED 處于頂層的最大化窗口的缺省尺寸
SM_CXMAXTRACK,
SM_CYMAXTRACK 具有可改變尺寸邊框和標題欄的窗口的缺省最大尺寸,如果窗口大于這個
尺寸,窗口是不可移動的。
SM_CXMENUCHECK,
SM_CYMENUCHECK 以像素為單位計算的菜單選中標記位圖的尺寸
SM_CXMENUSIZE,
SM_CYMENUSIZE 以像素計算的菜單欄按鈕的尺寸
SM_CXMIN,SM_CYMIN 窗口所能達到的最小尺寸
SM_CXMINIMIZED,
SM_CYMINIMIZED 正常的最小化窗口的尺寸
SM_CXMINTRACK,
SM_CYMINTRACK 最小跟蹤距離,當使用者拖動窗口移動距離小于這個值,窗口不會移動。
SM_CXSCREEN,
SM_CYSCREEN 以像素為單位計算的屏幕尺寸。
SM_CXSIZE,SM_CYSIZE 以像素計算的標題欄按鈕的尺寸
SM_CXSIZEFRAME,
SM_CYSIZEFRAME 圍繞可改變大小的窗口的邊框的厚度
SM_CXSMICON,
SM_CYSMICON 以像素計算的小圖標的尺寸,小圖標一般出現在窗口標題欄上。
SM_CXVSCROLL,
SM_CYVSCROLL 以像素計算的垂直滾動條的寬度和垂直滾動條上箭頭的高度
SM_CYCAPTION 以像素計算的普通窗口標題的高度
SM_CYMENU 以像素計算的單個菜單條的高度
SM_CYSMCAPTION 以像素計算的窗口小標題欄的高度
SM_CYVTHUMB 以像素計算的垂直滾動條中滾動塊的高度
SM_DBCSENABLED 如果為TRUE或不為0的值表明系統安裝了雙字節版本的USER.EXE,為FALSE或0則不是。
SM_DEBUG 如果為TRUE或不為0的值表明系統安裝了debug版本的USER.EXE,為FALSE或0則不是。
SM_MENUDROPALIGNMENT 如果為TRUE或不為0的值下拉菜單是右對齊的否則是左對齊的。
SM_MOUSEPRESENT 如果為TRUE或不為0的值則安裝了鼠標,否則沒有安裝。
SM_MOUSEWHEELPRESENT 如果為TRUE或不為0的值則安裝了滾輪鼠標,否則沒有安裝。(Windows NT only)
SM_SWAPBUTTON 如果為TRUE或不為0的值則鼠標左右鍵交換,否則沒有。
posted @
2008-08-10 21:42 幽幽 閱讀(813) |
評論 (0) |
編輯 收藏
在 Windows 中實現 Java 本地方法
 |
 |
 |
級別: 初級
David WendtWebSphere Development Research Triangle Park, NC
1999 年 5 月 01 日
本文為在 32 位 Windows 平臺上實現 Java 本地方法提供了實用的示例、步驟和準則。這些示例包括傳遞和返回常用的數據類型。
本文中的示例使用 Sun Microsystems 公司創建的 Java DevelopmentKit (JDK) 版本 1.1.6 和 Java本地接口 (JNI) 規范。 用 C 語言編寫的本地代碼是用 MicrosoftVisual C++ 編譯器編譯生成的。
簡介
本文提供調用本地 C 代碼的 Java 代碼示例,包括傳遞和返回某些常用的數據類型。本地方法包含在特定于平臺的可執行文件中。就本文中的示例而言,本地方法包含在 Windows 32 位動態鏈接庫 (DLL) 中。
不過我要提醒您,對 Java 外部的調用通常不能移植到其他平臺上,在 applet 中還可能引發安全異常。實現本地代碼將使您的 Java 應用程序無法通過 100% 純 Java 測試。但是,如果必須執行本地調用,則要考慮幾個準則:
- 將您的所有本地方法都封裝在單個類中,這個類調用單個 DLL。對于每種目標操作系統,都可以用特定于適當平臺的版本替換這個 DLL。這樣就可以將本地代碼的影響減至最小,并有助于將以后所需的移植問題包含在內。
- 本地方法要簡單。盡量將您的 DLL 對任何第三方(包括 Microsoft)運行時 DLL 的依賴減到最小。使您的本地方法盡量獨立,以將加載您的 DLL 和應用程序所需的開銷減到最小。如果需要運行時 DLL,必須隨應用程序一起提供它們。
Java 調用 C
對于調用 C 函數的 Java 方法,必須在 Java 類中聲明一個本地方法。在本部分的所有示例中,我們將創建一個名為 MyNative 的類,并逐步在其中加入新的功能。這強調了一種思想,即將本地方法集中在單個類中,以便將以后所需的移植工作減到最少。
示例 1 -- 傳遞參數
在第一個示例中,我們將三個常用參數類型傳遞給本地函數: String、 int和 boolean 。本例說明在本地 C 代碼中如何引用這些參數。
public class MyNative
{
public void showParms( String s, int i, boolean b )
{
showParms0( s, i , b );
}
private native void showParms0( String s, int i, boolean b );
static
{
System.loadLibrary( "MyNative" );
}
}
|
請注意,本地方法被聲明為專用的,并創建了一個包裝方法用于公用目的。這進一步將本地方法同代碼的其余部分隔離開來,從而允許針對所需的平臺對它進行優化。 static子句加載包含本地方法實現的 DLL。
下一步是生成 C 代碼來實現 showParms0 方法。此方法的 C 函數原型是通過對 .class 文件使用 javah 實用程序來創建的,而 .class 文件是通過編譯 MyNative.java 文件生成的。這個實用程序可在 JDK 中找到。下面是 javah 的用法:
javac MyNative.java(將 .java 編譯為 .class)
javah -jni -classpath . (指定源代碼的當前目錄,這里要注意,是指package目錄所在的目錄)
MyNative(生成 .h 文件)
|
這將生成一個 MyNative.h 文件,其中包含一個本地方法原型,如下所示:
/*
* Class: MyNative
* Method: showParms0
* Signature: (Ljava/lang/String;IZ)V
*/
JNIEXPORT void JNICALL Java_MyNative_showParms0
(JNIEnv *, jobject, jstring, jint, jboolean);
|
第一個參數是調用 JNI 方法時使用的 JNI Environment 指針。第二個參數是指向在此 Java 代碼中實例化的 Java 對象 MyNative 的一個句柄。其他參數是方法本身的參數。請注意,MyNative.h 包括頭文件 jni.h。jni.h 包含 JNI API 和變量類型(包括jobject、jstring、jint、jboolean,等等)的原型和其他聲明。
本地方法是在文件 MyNative.c 中用 C 語言實現的:
#include <stdio.h>
#include "MyNative.h"
JNIEXPORT void JNICALL Java_MyNative_showParms0
(JNIEnv *env, jobject obj, jstring s, jint i, jboolean b)
{
const char* szStr = (*env)->GetStringUTFChars( env, s, 0 );
printf( "String = [%s]\n", szStr );
printf( "int = %d\n", i );
printf( "boolean = %s\n", (b==JNI_TRUE ? "true" : "false") );
(*env)->ReleaseStringUTFChars( env, s, szStr );
}
|
JNI API,GetStringUTFChars,用來根據 Java 字符串或 jstring 參數創建 C 字符串。這是必需的,因為在本地代碼中不能直接讀取 Java 字符串,而必須將其轉換為 C 字符串或 Unicode。有關轉換 Java 字符串的詳細信息,請參閱標題為 NLS Strings and JNI 的一篇論文。但是,jboolean 和 jint 值可以直接使用。
MyNative.dll 是通過編譯 C 源文件創建的。下面的編譯語句使用 Microsoft Visual C++ 編譯器:
cl -Ic:\jdk1.1.6\include -Ic:\jdk1.1.6\include\win32 -LD MyNative.c
-FeMyNative.dll
|
其中 c:\jdk1.1.6 是 JDK 的安裝路徑。
MyNative.dll 已創建好,現在就可將其用于 MyNative 類了。 可以這樣測試這個本地方法:在 MyNative 類中創建一個 main 方法來調用 showParms 方法,如下所示:
public static void main( String[] args )
{
MyNative obj = new MyNative();
obj.showParms( "Hello", 23, true );
obj.showParms( "World", 34, false );
}
|
當運行這個 Java 應用程序時,請確保 MyNative.dll 位于 Windows 的 PATH 環境變量所指定的路徑中或當前目錄下。當執行此 Java 程序時,如果未找到這個 DLL,您可能會看到以下的消息:
java MyNative
Can't find class MyNative
|
這是因為 static 子句無法加載這個 DLL,所以在初始化 MyNative 類時引發異常。Java 解釋器處理這個異常,并報告一個一般錯誤,指出找不到這個類。 如果用 -verbose 命令行選項運行解釋器,您將看到它因找不到這個 DLL 而加載 UnsatisfiedLinkError 異常。
如果此 Java 程序完成運行,就會輸出以下內容:
java MyNative
String = [Hello]
int = 23
boolean = true
String = [World]
int
= 34
|
boolean = false 示例 2 -- 返回一個值
本例將說明如何在本地方法中實現返回代碼。 將這個方法添加到 MyNative 類中,這個類現在變為以下形式:
public class MyNative
{
public void showParms( String s, int i, boolean b )
{
showParms0( s, i , b );
}
public int hypotenuse( int a, int b )
{
return hyptenuse0( a, b );
}
private native void showParms0( String s, int i, boolean b );
private native int hypotenuse0( int a, int b );
static
{
System.loadLibrary( "MyNative" );
}
/* 測試本地方法 */
public static void main( String[] args )
{
MyNative obj = new MyNative();
System.out.println( obj.hypotenuse(3,4) );
System.out.println( obj.hypotenuse(9,12) );
}
}
|
公用的 hypotenuse 方法調用本地方法 hypotenuse0 來根據傳遞的參數計算值,并將結果作為一個整數返回。這個新本地方法的原型是使用 javah 生成的。請注意,每次運行這個實用程序時,它將自動覆蓋當前目錄中的 MyNative.h。按以下方式執行 javah:
生成的 MyNative.h 現在包含 hypotenuse0 原型,如下所示:
/*
* Class: MyNative
* Method: hypotenuse0
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
(JNIEnv *, jobject, jint, jint);
|
該方法是在 MyNative.c 源文件中實現的,如下所示:
#include <stdio.h>
#include <math.h>
#include "MyNative.h"
JNIEXPORT void JNICALL Java_MyNative_showParms0
(JNIEnv *env, jobject obj, jstring s, jint i, jboolean b)
{
const char* szStr = (*env)->GetStringUTFChars( env, s, 0 );
printf( "String = [%s]\n", szStr );
printf( "int = %d\n", i );
printf( "boolean = %s\n", (b==JNI_TRUE ? "true" : "false") );
(*env)->ReleaseStringUTFChars( env, s, szStr );
}
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
(JNIEnv *env, jobject obj, jint a, jint b)
{
int rtn = (int)sqrt( (double)( (a*a) + (b*b) ) );
return (jint)rtn;
}
|
再次請注意,jint 和 int 值是可互換的。 使用相同的編譯語句重新編譯這個 DLL:
cl -Ic:\jdk1.1.6\include -Ic:\jdk1.1.6\include\win32 -LD MyNative.c
-FeMyNative.dll
|
現在執行 java MyNative 將輸出 5 和 15 作為斜邊的值。
示例 3 -- 靜態方法
您可能在上面的示例中已經注意到,實例化的 MyNative 對象是沒必要的。實用方法通常不需要實際的對象,通常都將它們創建為靜態方法。本例說明如何用一個靜態方法實現上面的示例。更改 MyNative.java 中的方法簽名,以使它們成為靜態方法:
public static int hypotenuse( int a, int b )
{
return hypotenuse0(a,b);
}
...
private static native int hypotenuse0( int a, int b );
|
現在運行 javah 為 hypotenuse0創建一個新原型,生成的原型如下所示:
/*
* Class: MyNative
* Method: hypotenuse0
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
(JNIEnv *, jclass, jint, jint);
|
C 源代碼中的方法簽名變了,但代碼還保持原樣:
JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
(JNIEnv *env, jclass cls, jint a, jint b)
{
int rtn = (int)sqrt( (double)( (a*a) + (b*b) ) );
return (jint)rtn;
}
|
本質上,jobject 參數已變為 jclass 參數。此參數是指向 MyNative.class 的一個句柄。main 方法可更改為以下形式:
public static void main( String[] args )
{
System.out.println( MyNative.hypotenuse( 3, 4 ) );
System.out.println( MyNative.hypotenuse( 9, 12 ) );
}
|
因為方法是靜態的,所以調用它不需要實例化 MyNative 對象。本文后面的示例將使用靜態方法。
示例 4 -- 傳遞數組
本例說明如何傳遞數組型參數。本例使用一個基本類型,boolean,并將更改數組元素。下一個示例將訪問 String(非基本類型)數組。將下面的方法添加到 MyNative.java 源代碼中:
public static void setArray( boolean[] ba )
{
for( int i=0; i < ba.length; i++ )
ba[i] = true;
setArray0( ba );
}
...
private static native void setArray0( boolean[] ba );
|
在本例中,布爾型數組被初始化為 true,本地方法將把特定的元素設置為 false。同時,在 Java 源代碼中,我們可以更改 main 以使其包含測試代碼:
boolean[] ba = new boolean[5];
MyNative.setArray( ba );
for( int i=0; i < ba.length; i++ )
System.out.println( ba[i] );
|
在編譯源代碼并執行 javah 以后,MyNative.h 頭文件包含以下的原型:
/*
* Class: MyNative
* Method: setArray0
* Signature: ([Z)V
*/
JNIEXPORT void JNICALL Java_MyNative_setArray0
(JNIEnv *, jclass, jbooleanArray);
|
請注意,布爾型數組是作為單個名為 jbooleanArray 的類型創建的。 基本類型有它們自已的數組類型,如 jintArray 和 jcharArray。 非基本類型的數組使用 jobjectArray 類型。下一個示例中包括一個 jobjectArray。這個布爾數組的數組元素是通過 JNI 方法 GetBooleanArrayElements 來訪問的。 針對每種基本類型都有等價的方法。這個本地方法是如下實現的:
JNIEXPORT void JNICALL Java_MyNative_setArray0
(JNIEnv *env, jclass cls, jbooleanArray ba)
{
jboolean* pba = (*env)->GetBooleanArrayElements( env, ba, 0 );
jsize len = (*env)->GetArrayLength(env, ba);
int i=0;
// 更改偶數數組元素
for( i=0; i < len; i+=2 )
pba[i] = JNI_FALSE;
(*env)->ReleaseBooleanArrayElements( env, ba, pba, 0 );
}
|
指向布爾型數組的指針可以使用 GetBooleanArrayElements 獲得。 數組大小可以用 GetArrayLength 方法獲得。使用 ReleaseBooleanArrayElements 方法釋放數組。現在就可以讀取和修改數組元素的值了。jsize 聲明等價于 jint(要查看它的定義,請參閱 JDK 的 include 目錄下的 jni.h 頭文件)。
示例 5 -- 傳遞 Java String 數組
本例將通過最常用的非基本類型,Java String,說明如何訪問非基本對象的數組。字符串數組被傳遞給本地方法,而本地方法只是將它們顯示到控制臺上。 MyNative 類定義中添加了以下幾個方法:
public static void showStrings( String[] sa )
{
showStrings0( sa );
}
private static void showStrings0( String[] sa );
|
并在 main 方法中添加了兩行進行測試:
String[] sa = new String[] { "Hello,", "world!", "JNI", "is", "fun." };
MyNative.showStrings( sa );
|
本地方法分別訪問每個元素,其實現如下所示。
JNIEXPORT void JNICALL Java_MyNative_showStrings0
(JNIEnv *env, jclass cls, jobjectArray sa)
{
int len = (*env)->GetArrayLength( env, sa );
int i=0;
for( i=0; i < len; i++ )
{
jobject obj = (*env)->GetObjectArrayElement(env, sa, i);
jstring str = (jstring)obj;
const char* szStr = (*env)->GetStringUTFChars( env, str, 0 );
printf( "%s ", szStr );
(*env)->ReleaseStringUTFChars( env, str, szStr );
}
printf( "\n" );
}
|
數組元素可以通過 GetObjectArrayElement 訪問。 在本例中,我們知道返回值是 jstring 類型,所以可以安全地將它從 jobject 類型轉換為 jstring 類型。字符串是通過前面討論過的方法打印的。有關在 Windows 中處理 Java 字符串的信息,請參閱標題為 NLS Strings and JNI 的一篇論文。
示例 6 -- 返回 Java String 數組
最后一個示例說明如何在本地代碼中創建一個字符串數組并將它返回給 Java 調用者。MyNative.java 中添加了以下幾個方法:
public static String[] getStrings()
{
return getStrings0();
}
private static native String[] getStrings0();
|
更改 main 以使 showStrings 將 getStrings 的輸出顯示出來:
MyNative.showStrings( MyNative.getStrings() );
|
實現的本地方法返回五個字符串。
JNIEXPORT jobjectArray JNICALL Java_MyNative_getStrings0
(JNIEnv *env, jclass cls)
{
jstring str;
jobjectArray args = 0;
jsize len = 5;
char* sa[] = { "Hello,", "world!", "JNI", "is", "fun" };
int i=0;
args = (*env)->NewObjectArray(env, len, (*env)->FindClass(env, "java/lang/String"), 0);
for( i=0; i < len; i++ )
{
str = (*env)->NewStringUTF( env, sa[i] );
(*env)->SetObjectArrayElement(env, args, i, str);
}
return args;
}
|
字符串數組是通過調用 NewObjectArray 創建的,同時傳遞了 String 類和數組長度兩個參數。Java String 是使用 NewStringUTF 創建的。String 元素是使用 SetObjectArrayElement 存入數組中的。
調試
現在您已經為您的應用程序創建了一個本地 DLL,但在調試時還要牢記以下幾點。如果使用 Java 調試器 java_g.exe,則還需要創建 DLL 的一個“調試”版本。這只是表示必須創建同名但帶有一個 _g 后綴的 DLL 版本。就 MyNative.dll 而言,使用 java_g.exe 要求在 Windows 的 PATH 環境指定的路徑中有一個 MyNative_g.dll 文件。在大多數情況下,這個 DLL 可以通過將原文件重命名或復制為其名稱帶綴 _g 的文件。
現在,Java 調試器不允許您進入本地代碼,但您可以在 Java 環境外使用 C 調試器(如 Microsoft Visual C++)調試本地方法。首先將源文件導入一個項目中。 將編譯設置調整為在編譯時將 include 目錄包括在內:
c:\jdk1.1.6\include;c:\jdk1.1.6\include\win32
|
將配置設置為以調試模式編譯 DLL。在 Project Settings 中的 Debug 下,將可執行文件設置為 java.exe(或者 java_g.exe,但要確保您生成了一個 _g.dll 文件)。程序參數包括包含 main 的類名。如果在 DLL 中設置了斷點,則當調用本地方法時,執行將在適當的地方停止。
下面是設置一個 Visual C++ 6.0 項目來調試本地方法的步驟。
- 在 Visual C++ 中創建一個 Win32 DLL 項目,并將 .c 和 .h 文件添加到這個項目中。
- 在 Tools 下拉式菜單的 Options 設置下設置 JDK 的 include 目錄。下面的對話框顯示了這些目錄。
- 選擇 Build 下拉式菜單下的 Build MyNative.dll 來建立這個項目。確保將項目的活動配置設置為調試(這通常是缺省值)。
- 在 Project Settings 下,設置 Debug 選項卡來調用適當的 Java 解釋器,如下所示:
當執行這個程序時,忽略“在 java.exe 中找不到任何調試信息”的消息。當調用本地方法時,在 C 代碼中設置的任何斷點將在適當的地方停止 Java 程序的執行。
其他信息
JNI 方法和 C++
上面這些示例說明了如何在 C 源文件中使用 JNI 方法。如果使用 C++,則請將相應方法的格式從:
(*env)->JNIMethod( env, .... );
|
更改為:
在 C++ 中,JNI 函數被看作是 JNIEnv 類的成員方法。
字符串和國家語言支持
本文中使用的技術用 UTF 方法來轉換字符串。使用這些方法只是為了方便起見,如果應用程序需要國家語言支持 (NLS),則不能使用這些方法。有關在 Windows 和 NLS 環境中處理 Java 字符串正確方法,請參標題為 NLS Strings and JNI 的一篇論文。
小結
本文提供的示例用最常用的數據類據(如 jint 和 jstring)說明了如何實現本地方法,并討論了 Windows 特定的幾個問題,如顯示字符串。本文提供的示例并未包括全部 JNI,JNI 還包括其他參數類型,如 jfloat、jdouble、jshort、jbyte 和 jfieldID,以及用來處理這些類型的方法。有關這個主題的詳細信息,請參閱 Sun Microsystems 提供的 Java 本地接口規范。
關于作者
 |
|
 |
David Wendt 是 IBM WebSphere Studio 的一名程序員,該工作室位于北卡羅萊納州的 Research Triangle Park。可以通過 wendt@us.ibm.com 與他聯系。
|
|
posted @
2008-08-08 05:17 幽幽 閱讀(618) |
評論 (0) |
編輯 收藏
1、基本用法
JFileChooser dlg = new JFileChooser();
dlg.setDialogTitle("Open JPEG file");
int result = dlg.showOpenDialog(this); // 打開"打開文件"對話框
// int result = dlg.showSaveDialog(this); // 打"開保存文件"對話框
if (result == JFileChooser.APPROVE_OPTION) {
File file = dlg.getSelectedFile();
...
}
2、自定義FileFilter
JDK沒有提供默認的文件過濾器,但提供了過濾器的抽象超類,我們可以繼承它。
import javax.swing.filechooser.FileFilter;
public final class PictureFileFilter extends FileFilter {
private String extension;
private String description;
public PictureFileFilter(String extension, String description) {
super();
this.extension = extension;
this.description = description;
}
public boolean accept(File f) {
if (f != null) {
if (f.isDirectory()) {
return true;
}
String extension = getExtension(f);
if (extension != null && extension.equalsIgnoreCase(this.extension)) {
return true;
}
}
return false;
}
public String getDescription() {
return description;
}
private String getExtension(File f) {
if (f != null) {
String filename = f.getName();
int i = filename.lastIndexOf('.');
if (i > 0 && i < filename.length() - 1) {
return filename.substring(i + 1).toLowerCase();
}
}
return null;
}
}
其實主要就是accept(File f)函數。上例中只有一個過濾器,多個過濾器可參考JDK目錄中“demo\jfc\FileChooserDemo\src”中的“ExampleFileFilter.java”
3、多選
在基本用法中,設置
c.setMultiSelectionEnabled(true);
即可實現文件的多選。
讀取選擇的文件時需使用
File[] files = c.getSelectedFiles();
4、選擇目錄
利用這個打開對話框,不僅可以選擇文件,還可以選擇目錄。
其實,對話框有一個FileSelectionMode屬性,其默認值為“JFileChooser.FILES_ONLY”,只需要將其修改為“JFileChooser.DIRECTORIES_ONLY”即可。
JFileChooser c = new JFileChooser();
c.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
c.setDialogTitle("Select path to save");
int result = c.showOpenDialog(PrintDatetime.this);
if (result == JFileChooser.APPROVE_OPTION) {
String path = c.getSelectedFile().getAbsolutePath());
...
}
posted @
2008-08-08 01:38 幽幽 閱讀(10113) |
評論 (1) |
編輯 收藏
清除屏幕閃爍
(轉自網上)
<一>
由于作圖過于復雜和頻繁,所以時常出現閃爍的情況,一些防止閃爍的方法,如下:
(1)將Invalidate()替換為InvalidateRect()。
Invalidate()會導致整個窗口的圖象重畫,需要的時間比較長,而InvalidateRect()僅僅重畫Rect區域內的內容,所以所需時間會少一些。不要為一小塊區域的重畫就調用Invalidate(),不愿意自己去計算需要重畫的Rect,事實上,如果你確實需要改善閃爍的情況,計算一個Rect所用的時間比起重畫那些不需要重畫的內容所需要的時間要少得多。
(2)禁止系統擦除你的窗口。
系統在需要重畫窗口的時候會幫你用指定的背景色來擦除窗口。可是,也許需要重畫的區域也許非常小。或者,在你重畫這些東西之間還要經過大量的計算才能開始.這個時候你可以禁止系統擦掉原來的圖象。直到你已經計算好了所有的數據,自己把那些需要擦掉的部分用背景色覆蓋掉(如:dc.FillRect(rect,&brush);rect是需要擦除的區域,brush是帶背景色的刷子),再畫上新的圖形。要禁止系統擦除你的窗口,可以重載OnEraseBkgnd()函數,讓其直接返回TRUE就可以了。如
BOOL CmyWin::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
//return CWnd::OnEraseBkgnd(pDC);//把系統原來的這條語句注釋掉。
}
(3)有效的進行擦除。
擦除背景的時候,不要該擦不該擦的地方都擦。比如,你在一個窗口上放了一個很大的Edit框,幾乎占了整個窗口,那么你頻繁的擦除整個窗口背景將導致Edit不停重畫形成劇烈的閃爍.事實上你可以CRgn創建一個需要擦除的區域,只擦除這一部分.如
GetClientRect(rectClient);
rgn1.CreateRectRgnIndirect(rectClient);
rgn2.CreateRectRgnIndirect(m_rectEdit);
if(rgn1.CombineRgn(&rgn1,&rgn2,RGN_XOR)= ERROR)
//處理后的rgn1只包括了Edit框之外的客戶區域,這樣,Edit將不會被我的背景覆蓋而導致重畫.
{
ASSERT(FALSE);
return ;
}
brush.CreateSolidBrush(m_clrBackgnd);
pDC->FillRgn(&rgn1,&brush);
brush.DeleteObject();
注意:在使用這個方法的時候要同時使用方法二。
(4).使用MemoryDC先在內存里把圖畫好,再復制到屏幕上。
這對于一次畫圖過程很長的情況比較管用。畢竟內存操作比較快,而且復制到屏幕又是一次性的,至少不會出現可以明顯看出一個東西從左畫到右的情況。
void CMyWiew::OnDraw() //CScrollView下雙緩沖內存的實現:
{
CRect rect;
GetClientRect(&rect);
CDC* m_pMemoryDC = new CDC();
CBitmap * m_pBitmap = new CBitmap();
CPoint ScrollPoint=GetScrollPosition();
m_pMemoryDC->CreateCompatibleDC(pDC);
m_pBitmap->CreateCompatibleBitmap(pDC,rect.right+1,rect.bottom+1);//這里的Bitmap是必須的,否則當心弄出一個大黑塊.
CBitmap * pOldbmp=m_pMemoryDC->SelectObject(m_pBitmap);
//m_pMemoryDC->SelectStockObject(WHITE_BRUSH);//畫出白色背景方法一
//m_pMemoryDC->Rectangle(-1,-1,rect.right + 2 , rect.bottom + 2 );
//m_pMemoryDC->SelectStockObject(NULL_BRUSH);
m_pMemoryDC->PatBlt(0,0,rect.right, rect.bottom,WHITENESS);//畫出白色背景方法二
//-----------------如下是顯示圖片的方法----------------------------------------------------------
//BITMAP BM;
//CBitmap pBitmap;
//pBitmap.LoadBitmap(IDB_BITMAP2);
//CDC * pTdc = new CDC();
//pTdc->CreateCompatibleDC(pDC);
//CBitmap* pom = pTdc->SelectObject(&pBitmap);
//pBitmap->GetObject(sizeof(BM),&BM);
//m_pMemoryDC->BitBlt(0-ScrollPoint.x,0-ScrollPoint.y, BM.bmWidth,BM.bmHeight, pTdc,0,0,SRCCOPY);
//pTdc->DeleteDC();
//delete pTdc;
//--------------圖片顯示完畢----------------------------------------------------------------------
//m_pMemoryDC->SetROP2(R2_NOT);//設定繪圖模式
m_pMemoryDC->MoveTo(0-ScrollPoint.x,0-ScrollPoint.y);
m_pMemoryDC->LineTo(1000-ScrollPoint.x,5000-ScrollPoint.y);
pDC->BitBlt(ScrollPoint.x, ScrollPoint.y, rect.right, rect.bottom, m_pMemoryDC, 0, 0, SRCCOPY);
m_pMemoryDC->SelectObject(pOldbmp);
m_pBitmap->DeleteObject();
m_pMemoryDC->DeleteDC();
delete m_pBitmap;
delete m_pMemoryDC;
}
*******************************
解決Windows程序界面閃爍問題的一些經驗
(轉自網上)
<二>
一般的windows 復雜的界面需要使用多層窗口而且要用貼圖來美化,所以不可避免在窗口移動或者改變大小的時候出現閃爍。
先來談談閃爍產生的原因
原因一:
如果熟悉顯卡原理的話,調用GDI函數向屏幕輸出的時候并不是立刻就顯示在屏幕
上只是寫到了顯存里,而顯卡每隔一段時間把顯存的內容輸出到屏幕上,這就是刷新周期。
一般顯卡的刷新周期是 1/80秒左右,具體數字可以自己設置的。
這樣問題就來了,一般畫圖都是先畫背景色,然后再把內容畫上去,如果這兩次操作不在同一個
刷新周期內完成,那么給人的視覺感受就是,先看到只有背景色的圖像,然后看到畫上內容的圖像,
這樣就會感覺閃爍了。
解決方法:盡量快的輸出圖像,使輸出在一個刷新周期內完成,如果輸出內容很多比較慢,那么采用
內存緩沖的方法,先把要輸出的內容在內存準備好,然后一次輸出到顯存。要知道一次API調用一般可以
在一個刷新周期內完成。
對于GDI,用創建內存DC的方法就可以了
原因二:
復雜的界面有多層窗口組成,當windows在窗口改變大小的時候是先重畫父窗口,然后重畫子窗口,子父
窗口重畫的過程一般無法在一個刷新周期內完成,所以會呈現閃爍。
我們知道父窗口上被子窗口擋住的部分其實沒必要重畫的
解決方法:給窗口加個風格 WS_CLIPCHILDREN ,這樣父窗口上被子窗口擋住的部分就不會重畫了。
如果同級窗口之間有重疊,那么需要再加上 WS_CLIPSIBLINGS 風格
原因三:
有時候需要在窗口上使用一些控件,比如IE,當你的窗口改變大小的時候IE會閃爍,即使你有了WS_CLIPCHILDREN
也沒用。原因在于窗口的類風格有CS_HREDRAW 或者 CS_VREDRAW,這兩個風格表示窗口在寬度或者高度變化的時候
重畫,但是這樣就會引起IE閃爍
解決方法:注冊窗口類的時候不要使用這兩個風格,如果窗口需要在改變大小的時候重畫,那么可以在WM_SIZE的時候
調用RedrawWindow。
原因四:
界面上窗口很多,而且改變大小時很多窗口都要移動和改變大小,如果使用MoveWindow或者SetWindowPos兩個API來
改變窗口的大小和位置,由于他們是等待窗口重畫完成后才返回,所以過程很慢,這樣視覺效果就可能會閃爍。
解決方法:
使用以下API來處理窗口移動,BeginDeferWindowPos, DeferWindowPos,EndDeferWindowPos
先調用 BeginDeferWindowPos 設定需要移動的窗口的個數
使用DeferWindowPos,來移動窗口,這個API并不真的造成窗口移動
EndDeferWindowPos 一次性完成所有窗口的大小和位置的改變。
有個地方要特別注意,要仔細計算清楚要移動多少個窗口,BeginDeferWindowPos設定
的個數一定要和實際的個數一致,否則在Win9x下,如果實際移動的窗口數多于調用BeginDeferWindowPos
時設定的個數,可能會造成系統崩潰。在Windows NT系列下不會有這樣的問題。
*******************************
使用內存DC解決重畫閃爍問題
(轉自網上)
<三>
下述代碼在OnDraw時繪圖:
void CRedrawDemoView::OnDraw(CDC* pDC)
{
CRedrawDemoDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
static const char* pText = "解決重畫閃爍問題!";
RECT clRect;
::GetClientRect(m_hWnd, &clRect);
pDC->FillSolidRect(&clRect, RGB(255, 255, 255));
int x = 100, y = 100;
RECT rect = { x - 20, y - 20};
rect.right = rect.left + 160;
rect.bottom = rect.top + 60;
pDC->FillSolidRect(&rect, RGB(0, 255, 0));
pDC->TextOut(x, y, pText, strlen(pText));
}
首先將背景填充白色,然后畫一綠色的矩形,再在矩形上輸出一段文字,如此過程必然會引起畫面閃爍,
解決辦法:使用內存DC,先將圖形繪制到內存DC,然后拷貝到屏幕,實現無閃爍繪圖。
修改后的代碼如下:
void CRedrawDemoView::OnDraw(CDC* pDC)
{
CRedrawDemoDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
static const char* pText = "解決重畫閃爍問題!";
CRect clRect;
::GetClientRect(m_hWnd, &clRect);
CDC memDC;
memDC.CreateCompatibleDC(pDC);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(pDC, clRect.Width(), clRect.Height());
CBitmap * pOldBitmap = memDC.SelectObject(&bitmap);
memDC.FillSolidRect(&clRect, RGB(255, 255, 255));
int x = 100, y = 100;
RECT rect = { x - 20, y - 20};
rect.right = rect.left + 160;
rect.bottom = rect.top + 60;
memDC.FillSolidRect(&rect, RGB(0, 255, 0));
memDC.TextOut(x, y, pText, strlen(pText));
pDC->BitBlt(0, 0, clRect.Width(), clRect.Height(), &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(pOldBitmap);
}
也可以在上述代碼中加入繪制Bitmap位圖代碼,注意應該阻止窗口擦除背景,重載OnEraseBkgnd函數
BOOL CRedrawDemoView::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
return TRUE;
// return CView::OnEraseBkgnd(pDC);
}
為易于理解,以上代碼未經優化。
*******************************
用:
CreateCompatibleBitmap
CreateCompatibleDC
等函數在內存中把要畫的圖先畫出來,然后使用
BitBlt復制到設備上就OK!
*******************************
posted @
2008-08-04 23:39 幽幽 閱讀(1969) |
評論 (0) |
編輯 收藏
PE格式,是Windows的可執行檔的格式。 Windows中的 exe檔,dll檔,都是PE格式。 PE 就是Portable Executable 的縮寫。 Portable 是指對於不同的Windows版本和不同的CPU類型上PE檔的格式是一樣的,當然CPU不一樣了,CPU指令的二進位編碼是不一樣的。只是檔中各種東西的佈局是一樣的。 能告示根底嗎! 摘要 Matt Pietrek(姜慶東譯) 對可執行檔的深入認識將帶你深入到系統深處。如果你知道你的exe/dll裏是些什麼東東,你就是一個更有知識的程式師。作為系列文章的第一章,將關注這幾年來PE格式的變化,同時也簡單介紹一下PE格式。經過這次更新,作者加入了PE格式是如何與.NET協作的及PE檔表格(PE FILE SECTIONS),RVA,The DataDirectory,函數的輸入等內容。 ==================== 很久以前,我給Microsoft Systems Journal(現在的MSDN)寫了一篇名為“Peering Inside the PE: A Tour of the Win32 Portable Executable File format”的文章。後來比我期望的還流行,到現在我還聽說有人在用它(它還在MSDN裏)。不幸的是,那篇文章的問題依舊存在,WIN32的世界靜悄悄地變了好多,那篇文章已顯得過期了。從這個月開始我將用這兩篇文章來彌補。 你可能會問為什麼我應當瞭解PE格式,答案依舊:作業系統的可執行檔格式和資料結構暴露出系統的底層細節。通過瞭解這些,你的程式將編的更出色。 當然,你可以閱讀微軟的文檔來瞭解我將要告訴你的。但是,像很多文檔一樣,‘寧可晦澀,但為瓦全’。 我把焦點放在提供一些不適合放在正式文檔裏的內容。另外,這篇文章裏的一些知識不見得能在官方文檔裏找到。 1. 裂縫的撕開 讓我給你一些從1994年我寫那篇文章來PE格式變化的例子。WIN16已經成為歷史,也就沒有必要作什麼比較和說明了。另外一個可憎的東西就是用在WINDOWS 3.1 中的WIN32S,在它上面運行程式是那麼的不穩定。 那時候,WINDOWS 95(也叫Chicago)還沒有發行。NT還是3.5版。微軟的連接器還沒開始大規模的優化,儘管如此,there were MIPS and DEC Alpha implementations of Windows NT that added to the story. 那麼究竟,這麼些年來,有些什麼新的東西出來呢?64位的WINDOWS有了它自己的PE變種,WINDOWS CE 支持各種CPU了,各種優化如DLL的延遲載入,節表的合併,動態捆綁等也已出臺。 有很多類似的東西發生了。 讓我們最好忘了.NET。它是如何與系統切入的呢?對於作業系統,.NET的可執行檔格式是與舊的PE格式相容的。雖然這麼說,在運行時期,.NET還是按元資料和中間語言來組織資料的,這畢竟是它的核心。這篇文章當中,我將打開.NET元資料這扇門,但不做深入討論。 如果WIN32的這些變化都不足以讓我重寫這篇文章,就是原來的那些錯誤也讓我汗顏。比如我對TLS的描述只是一帶而過,我對時間戳的描述只有你生活在美國西部才行等等。還有,一些東西已是今是作非了,我曾說過.RDATA幾乎沒排上用場,今天也是,我還說過.IDATA節是可讀可寫的,但是一些搞API攔截的人發現好像是錯的。 在更新這篇文章的過程當中,我也檢查了PEDUMP這個用來傾印PE檔的程式.這個程式能夠在0X86和IA-64平臺下編譯和運行。 2. PE格式概覽 微軟的可執行檔格式,也就是大家熟悉的PE 格式,是官方文檔的一部分。但是,它是從VAX/VMS上的COFF派生出來的,就WINDOWS NT小組的大部分是從DEC轉過來的看來,這是可以理解的。很自然,這些人在NT的開發上會用他們以往的代碼。 採用術語“PORTABLE EXECUTABLE”是因為微軟希望有一個通用在所有WINDOWS平臺上和所有CPU上的檔格式。從大的方面講,這個目標已經實現。它適用于NT及其後代,95及其後代,和CE. 微軟產生的OBJ檔是用COFF格式的。當你看到它的很多域都是用八進制的編碼的,你會發現她是多麼古老了。COFF OBJ檔用到了很多和PE一樣的資料結構和枚舉,我馬上會提到一些。 64位的WINDOWS只對PE格式作了一點點改變。這個新的格式叫做PE32+。沒有增加一個欄位,且只刪了一個欄位。其他的改變就是把以前的32位欄位擴展成64位。對於C++代碼,通過巨集定義WINDOWS的頭檔已經遮罩了這些差別。 EXE與DLL的差別完全是語義上的。它們用的都是同樣一種檔格式-PE。唯一的區別就是其中有一個欄位標識出是EXE還是DLL.還有很多DLL的擴展比如OCX,CPL等都是DLL.它們有一樣的實體。 你首先要知道的關於PE的知識就是磁片中的資料結構佈局和記憶體中的資料結構佈局是一樣的。載入可執行檔(比如LOADLIBARY)的首要任務就是把磁片中的檔映射到進程的位址空間.因此像IMAGE_NT_HEADER(下面解釋)在磁片和記憶體中是一樣的。關鍵的是你要懂得你怎樣在磁片中獲得PE檔某些資訊的,當它載入記憶體時你可以一樣獲得,基本上是沒什麼不同的(即記憶體映射檔)。但是知道與映射普通的記憶體映射檔不同是很重要的。WINDOWS載入器察看PE檔才決定映射到哪里,然後從檔的開始處往更高的位址映射,但是有的東西在檔中的偏移和在記憶體中的偏移會不一樣。儘管如此,你也有了足夠的資訊把檔偏移轉化成記憶體偏移。見圖一: 當Windows載入器把PE載入記憶體,在記憶體中它稱作模組(MODULE),檔從HMODULE這個位址開始映射。記住這點:給你個HMODULE,從那你可以知道一個資料結構(IMAGE_DOS_HEADER),然後你還可以知道所有得資料結構。這個強大的功能對於API攔截特別有意義。(準確地說:對於WINDOWS CE,這是不成立的,不過這是後話)。 記憶體中的模組代表著進程從這個可執行檔中所需要的所有代碼,資料,資源。其他部分可以被讀入,但是可能不映射(如,重定位節)。還有一些部分根本就不映射,比如當調試資訊放到檔的尾部的時候。有一個欄位告訴系統把檔映射到記憶體需要多少記憶體。不需要的資料放在檔的尾部,而在過去,所有部分都映射。 在WINNT.H描述了PE 格式。在這個檔中,幾乎有所有的關於PE的資料結構,枚舉,#DEFINE。當然,其他地方也有相關文檔,但是還是WINNT.H說了算。 有很多檢測PE文件的工具,有VISUAL STUDIO的DUMPBIN,SDK中的DEPENDS,我比較喜歡DEPENDS,因為它以一種簡潔的方式檢測出檔的引入引出。一個免費的PE察看器,PEBrowse,來自smidgenosoft。我的pedump也是很有用的,它和dumpbin有一樣的功能。 從api的立場看,imagehlp.dll提供了讀寫pe檔的機制。 在開始討論pe檔前,回顧一下pe檔的一些基本概念是有意義的。在下面幾節,我將討論:pe 節,相對虛擬位址(rva),資料目錄,函數的引入。 3. PE節 PE節以某鍾順序表示代碼或資料。代碼就是代碼了,但是卻有多種類型的資料,可讀寫的程式資料(如總體變數),其他的節包含API的引入引出表,資源,重定位。每個節有自己的屬性,包括是否是代碼節,是否唯讀還是可讀可寫,節的資料是否全局共用。 通常,節中的資料邏輯上是關聯的。PE檔一般至少要有兩個節,一個是代碼,另一個為資料。一般還有一個其他類型的資料的節。後面我將描述各種類型的節。 每個節都有一個獨特的名字。這個名字是用來傳達這個節的用途的。比如,.RDATA表示一個唯讀節,節的名字對於作業系統毫無意義,只是為了人們便於理解。把一個節命名為FOOBAR和.TEXT是一樣有用的。微軟給他們的節命名了個有特色的名字,但是這不是必需的。Borland的連接器用的是code和data 一般編譯器將產生一系列標準的節,但這沒有什麼不可思議的。你可以建立和命名自己的節,連接器會自動在程式檔中包含它們。在visual c++中,你能用#pragma指令讓編譯器插入資料到一個節中。像下面這樣: #pragma data_seg("MY_DATA") ...有必要初始化 #pragma data_seg() 你也可以對.data做同樣的事。大部分的程式都只用編譯器產生的節,但是有時候你卻需要這樣。比如建立一個全局共用節。 節並不是全部由連接器確定的,他們可以在編譯階段由編譯器放入obj檔。連接器的工作就是合併所有obj和庫中需要的節成一個最終的合適的節。比如,你的工程中的所有obj可能都有一個包含代碼的.text節,連接器把這些節合併成一個.text節。同樣對於.data等。這些主題超出了這篇文章的範圍了。還有更多的規則關於連接器的。在obj文件中是專門給linker用的,並不放入到pe檔中,這種節是用來給連接器傳遞資訊的。 節有兩個關於對齊的欄位,一個對應磁片檔,另一個對應記憶體中的檔。Pe檔頭指出了這兩個值,他們可以不一樣。每個節的偏移從對齊值的倍數開始。比如,典型的對齊值是0x200,那麼每個節的的偏移必須是0x200的倍數。一旦載入記憶體,節的起始位址總是以頁對齊。X86cpu的頁大小為4k,al-64為8k。 下麵是pedump傾印出的Windows XP KERNEL32.DLL.的.text .data節的信息: Section Table 01 .text VirtSize: 00074658 VirtAddr: 00001000 raw data offs: 00000400 raw data size: 00074800 ... 02 .data VirtSize: 000028CA VirtAddr: 00076000 raw data offs: 00074C00 raw data size: 00002400 建立一個節在檔中的偏移和它相對於載入位址的偏移相同的pe檔是可能的。在98/me中,這會加速大檔的載入。Visual studio 6.0 的默認選項 /opt:win98j就是這樣產生檔的。在Visual studio.net中是否用/opt:nowin98取決於檔是否夠小。 一個有趣的連接器特徵是合併節的能力。如果兩個節有相似相容的屬性,連接的時候就可以合併為一個節。這取決於是否用/merger開關。像下麵就把.rdata和.text合併為一個節.text /MERGE:.rdata=.text 合併節的優點就是對於磁片和記憶體節省空間。每個節至少佔用一頁記憶體,如果你可以把可執行檔的節數從4減到3,很可能就可以少用一頁記憶體。當然,這取決於兩個節的空餘空間加起來是否達到一頁。 當你合併節事情會變得有意思,因為這沒有什麼硬性和容易的規則。比如你可以合併.rdata到.text, 但是你不可以把.rsrc.reloc.pdata合併到別的節。先前Visual Studio .NET允許把.idata合併,後來又不允許了。但是當發行的時候,連接器還是可以把.idata合併到別的節。 因為引入節的一部分在載入器載入時將被寫入,你可能驚奇它是如何被放入一個唯讀節的。是這樣的,在載入的時候系統會臨時改變那些包含引入節的頁為可讀可寫,初始化完成後,又恢復原來屬性。 4. 相對虛擬位址 在可執行檔中,有很多地方需要指定記憶體位址,比如,引用總體變數時,需要指定它的位址。Pe檔儘管有一個首選的載入位址,但是他們可以載入到進程空間的任何地方,所以你不能依賴於pe的載入點。由於這點,必須有一個方法來指定位址而不依賴於pe載入點的地址。為了避免把記憶體位址硬編碼進pe檔,提出了RVA。RVA是一個簡單的相對於PE載入點的記憶體偏移。比如,PE載入點為0X400000,那麼代碼節中的地址0X401000的RVA為(target address) 0x401000 - (load address)0x400000 = (RVA)0x1000。把RVA加上PE的載入點的實際位址就可以把RVA轉化實際位址。順便說一下,按PE的說法,記憶體中的實際位址稱為VA(VIRTUAL ADDRESS).不要忘了早點我說的PE的載入點就是HMODULE。 想對探索記憶體中的任意DLL嗎?用GetModuleHanle(LPCTSTR)取得載入點,用你的PE知識來幹活吧 5. 資料目錄 PE檔中有很多資料結構需要快速定位。顯然的例子有引入函數,引出函數,資源,重定位。這些東西是以一致的方式來定位的,這就是資料目錄。 資料目錄是一個結構陣列,包含16個結構。每個元素有一個定義好的標識,如下: // Export Directory #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Import Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Exception Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Security Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Debug Directory #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Description String #define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // Machine value (MIPS GP) #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // TLS Directory #define IMAGE_DIRECTORY_ENTRY_TLS 9 // Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 typedef struct _IMAGE_DATA_DIRECTORY { ULONG VirtualAddress; ULONG Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; 6. 引入函數 當你使用別的DLL中的代碼或資料,稱為引入。當PE載入時,載入器的工作之一就是定位所有引入函數及資料,使那些位址對於載入的PE可見。具體細節在後面討論,在這裏只是大概講一下。 當你用到了一個DLL中的代碼或資料,你就暗中連接到這個DLL。但是你不必為“把這些位址變得對你的代碼有效”做任何事情,載入器為你做這些。方法之一就是顯式連接,這樣你就要確定DLL已被載入,及函數的位址。調用LOADLIBARY和GETPROCADDRESS就可以了。 當你暗式連接DLL,LOADLIBARY和GETPROCADDRESS同樣還是執行了的。只不過載入器為你做了這些。載入器還保證PE檔所需得任何附加的DLL都已被載入。比如,當你連接了KERNEL32.DLL,而它又引入了NTDLL.DLL的函數,又比如當你連接了GDI32.DLL,而它又依賴於USER32, ADVAPI32,NTDLL, 和 KERNEL32 DLLs的函數,載入器會保證這些DLL被載入及函數的決議。 暗式連接時,決議過程在PE檔在載入時就發生了。如果這時有什麼問題(比如這個DLL檔找不到),進程終止。 VISUAL C++ 6.0 加入了DLL的延遲載入的特徵。它是暗式連接和顯式連接的混合。當你延遲載入DLL,連接器做出一些和引入標準規則DLL類似的東西,但是作業系統卻不管這些東西,而是在第一次調用這個DLL中的函數的時候載入(如果還沒載入),然後調用GetProcAddress取得函數的位址。 對於pe檔要引入的dll都有一個對應的結構陣列,每個結構指出這個dll的名字及指向一個函數指標陣列的指標,這個函數指標陣列就是所謂的IAT(IMORT ADDRESS TABLE)。每個輸入函數,在IAT中都有一個保留槽,載入器將在那裏寫入真正的函數位址。最後特別重要一點的是:模組一旦載入,IAT中包含所要調用的引入函數的位址。 把所有輸入函數放在IAT一個地方是很有意義的,這樣無論代碼中多少次調用一個引入函數,都是通過IAT中的一個函數指標。 讓我們看看是怎樣調用一個引入函數的。有兩種情況需要考慮:有效率的和效率差的。最好的情況像下面這樣: CALL DWORD PTR [0x00405030] 直接調用[0x405030]中的函數,0x405030位於IAT部分。效率差的方式如下: CALL 0x0040100C ... 0x0040100C: JMP DWORD PTR [0x00405030] 這種情況,CALL把控制權轉到一個子程式,副程式中的JMP指令跳轉到位於IAT中的0x00405030,簡單說,它多用了5位元組和JMP多花的時間。 你可能驚訝引入函數就採用了這種方式,有個很好的解釋,編譯器無法區別引入函數的調用和普通函數調用,對於每個函數調用,編譯器只產生如下指令: CALL XXXXXXXX XXXXXXXX是一個由連接器填入的RVA。注意,這條指令不是通過函數指標來的,而是代碼中的實際地址。 為了因果的平衡,連接器必須產生一塊代碼來代替取代XXXXXXXX,簡單的方法就是象上面所示調用一個JMP STUB. 那麼JMP STUB 從那裏來呢?令人驚異的是,它取自輸入函數的引入庫。如果你去察看一個引入庫,在輸入函數名字的關聯處,你會發現與上面JMP STUB相似的指令。 接著,另一個問題就是如何優化這種形式,答案是你給編譯器的修飾符,__declspec(import) 修飾符告訴編譯器,這個函數來自另一個dll,這樣編譯器就會產生第一種指令。另外,編譯器將給函數加上__imp_首碼然後送給連接器決議,這樣可以直接把__imp_xxx送到iat,就不需要jmp stub了。 對於我們這有什麼意義呢,如果你在寫一個引出函數的東西並提供一個頭檔的話,別忘了在函數前加上修飾符__declspec(import) __declspec(dllimport) void Foo(void); 在winnt.h等系統頭檔中就是這樣做的。 7. PE 檔結構 現在讓我們開始研究PE檔格式,我將從檔的頭部開始,描述每個PE檔中都有的各種資料結構,然後,我將討論更多的專門的資料結構比如引入表和資源,除非特殊說明,這些結構都定義在WINNT.H中。 一般地,這些結構都有32和64位之分,如IMAGE_NT_HEADERS32 ,IMAGE_NT_HEADER64等,他們基本上是一樣的,除了64位的擴展了某些欄位。通過#DEFINE WINNT.H都遮罩了這些區別,選擇那個資料結構取決於你要如何編譯了(如,是否定義_WIN64) The MS-DOS Header 每個PE檔是以一個DOS程式開始的,這讓人想起WINDOWS在沒有如此可觀的使用者的早期年代。當可執行檔在非WINDOWS平臺上運行的時候至少可以顯示出一條資訊表示它需要WINDOWS。 PE檔的開頭是一個IMAGE_DOS_HEADER結構,結構中只有兩個重要的欄位e_magic and e_lfanew。e_lfanew指出pe file header的偏移,e_magic需要設定位0x5a4d,被#define 成IMAGE_DOS_SIGNATURE 它的ascii為’MZ’,Mark Zbikowski的首字母,DOS 的原始構建者之一。 The IMAGE_NT_HEADERS Header 這個結構是PE檔的主要定位資訊的所在。它的偏移由IMAGE_DOS_HEADER的e_lfanew給出 確實有64和32位之分,但我在討論中將不作考慮,他們幾乎沒有區別。 typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; 在一個有效的pe檔裏,Signture被設為0x00004500,ascii 為’PE00’,#define IMAGE_NT_SIGNTURE 0X00004500;第二個欄位是一個IMAGE_FILE_HEADER結構,它包含檔的基本資訊,特別重要的是它指出了IMAGE_OPTIONAL_HEADER的大小(重要嗎?);在PE文件中,IMAGE_OPTIONAL_HEADER是非常重要的,但是仍稱作IMAGE_OPTIONAL_HEADER。 IMAGE_OPTIONAL_HEADER結構的末尾就是用來定位pe檔中重要資訊的位址簿-資料目錄,它的定義如下: typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; // RVA of the data DWORD Size; // Size of the data }; The Section Table 緊接著IMAGE_NT_HEADERS後的就是節表,節表就是IMAGE_SECTION_HEADER的陣列。IMAGE_SECTION_HEADER包含了它所關聯的節的資訊,如位置,長度,特徵;該陣列的數目由IMAGE_NT_HEADERS.FileHeader.NumberOfSections指出。具體見下圖 PE中的節的大小的總和最後是要對齊的,Visual Studio 6.0中的預設值是4k,除非你使用/OPT:NOWIN98 或/ALIGN開關;在.NET中,依然用了默認的/OPT:WIN98,但是如果檔小於一特定大小時,就會採用0X200為對齊值。 .NET文檔中有關於對齊的另一件有趣的事。.NET檔的記憶體對齊值為8K而不是普通X86平臺上的4K,這樣就保證了在X86平臺編譯的程式可以在IA-64平臺上運行。如果記憶體對齊值為4K,那麼IA-64的載入器就不能載入這個程式,因為它的頁為8K
posted @
2008-07-28 04:04 幽幽 閱讀(693) |
評論 (0) |
編輯 收藏
在看yoda's Protector源代碼的時候,發現
const DWORD ALIGN_CORRECTION =0x1000;// this big value is e.g. needed for WATCOM compiled files
上網一查,發現WATCOM竟然有這樣一段傳奇:
一、Watcom的發展史
在編譯器混戰的時代,一家加拿大的小公司出品了Watcom C/C++編譯器,但是以在DOS下能夠產生最佳化程序代碼聞名于世的,許多寫游戲和DOS Extender的廠商都指名要使用Watcom C/C++,因為不論是Borland C/C++還是Visual C/C++,它們產生的最佳化程序代碼都比Watcom C/C++的最佳化程序代碼差上一截。再加上當時最有名的DOS Extender廠商PharLap公司也是使用Watcom C/C++,因此Watcom C/C++在當時專業的C/C++程序員以及系統程序員心中是第一品牌的C/C++開發工具。
Watcom C/C++在DOS市場站穩了腳跟之后,由于Windows已經逐漸成為市場的主流,DOS勢必將被逐漸淘汰出局,因此,Watcom C/C++如果要繼續生存下去,也就一定要推出Windows平臺的C/C++開發工具。大約是在1993、1994年左右,Watcom終于推出第一個Windows下的C/C++開發工具。
不過,當時Watcom C/C++在Windows推出的C/C++開發工具實在是平淡無奇。其集成開發環境和另外三個對手比較起來簡直像是遠古的產品,一點特色都沒有。不過Watcom C/C++仍然是以它的最佳化編譯器作為號召。因此當時發生了一個非常有趣的現象,那就是許多軟件公司會同時買Borland C/C++,或是Visual C/C++,Symantec C/C++之一,再搭配一套Watcom C/C++。在開發應用系統時使用其他三套開發工具之一,最后要出貨時再使用Watcom C/C++來編譯以產生最佳的程序代碼。
在Watcom C/C++推出了Windows平臺的開發工具之后,也吸引了一群使用者。雖然Watcom C/C++的市場比起其他的三家來說是最小的,但是總算撐起了一片天,成為四大C/C++開發工具之一。稍后Watcom C/C++被Sybase并購,成為Sybase的Optima++的前身。
二、石破天驚還是巨星隕落
1996年左右,Sybase并購了Watcom之后終于推出了石破天驚的C/C++開發工具:Optima++。Optima++是當初結合了Watcom的最佳化編譯器以及類似Delphi的組件拖曳開發環境的第一個RAD C/C++開發工具。更棒的是Optima++的組件架構(類似Delphi的VCL)完全是以純正的C/C++程序代碼撰寫的。這可不得了,因為這代表Optima++是一個融合了Visual C/C++和Delphi兩大王者開發工具為一身的超級賽亞人工具。
在我(《Borland傳奇》作者李維,下同)知道這個工具、并且嘗試實際使用之后,極為震驚。因為對于我這個使用了C/C++ 五六年的人來說,它比Delphi更具有吸引力。因此我立刻在《RUN!PC》上介紹了這個不可置信的工具。果然,Optima++很快開始風靡市場,雖然沒有立刻占據很大的市場份額,但是已經造成了一股氣勢,開始為Visual C/C++和Delphi帶來壓力。
我記得當時臺灣Sybase辦的產品發表會也吸引了數百人與會,不可一世。我的文章在《RUN!PC》6上發表之后,臺灣的Sybase立刻和我聯絡,由當時的余協理和我見面,也是希望我繼續為Optima++寫文章,臺灣Sybase也提供額外一字加2元稿費的待遇。但是我告訴余協理,Optima++ 1.0雖然很棒,但是仍然有一些臭蟲,而且和中文環境相沖突,無法處理中文,需要立刻解決這個問題才能夠在臺灣的市場成功。她答應我立刻向總公司反映。我也老實地告訴她,在問題沒有解決之前,我無法寫一些不確實的東西。后來臺灣Borland的總經理方先生也找我去詢問有關Optima++的事情,我告訴他Optima++是好東西,但是中文有問題。如果中文問題能夠解決,那么將對Borland和Microsoft的產品有很大的影響,當時我還不知道Borland由于Optima++的影響,已經開始準備開發C++ Builder。
在1996年底左右吧,Optima++ 1.5終于進入Beta的階段。但是在我拿到Beta版時非常失望,因為中文的問題仍然沒有解決。后來臺灣Sybase又找我去,這次和我見面的是臺灣Sybase總經理郭俊男先生,以及Sybase的新加坡技術總裁,不過我忘記這位先生的名字了。見了面之后,我立刻把Optima++ 1.5中文的問題以及許多的臭蟲告訴他們,希望他們能夠解決,如此Optima++ 1.5才能夠在中文市場成功。可是出乎我意料之外的是,他們似乎并不著急這些問題,反而詢問我是否有意愿為Sybase工作,做PowerBuilder的產品經理。
也許是因為我為Delphi寫了太多的東西,讓PowerBuilder在臺灣受了很大的影響,因此他們希望我到Sybase工作,以打擊Delphi并且Promote PowerBuilder。當時他們提出的待遇條件實在是非常、非常的誘人,比我當時的薪水高出一倍左右(我當時在資策會工作)。不過由于我對PowerBuilder實在沒有什么興趣,因此我告訴他們,如果是做Optima++的產品經理,那么我將會考慮并且接受。
沒有想到,Sybase的新加坡技術總裁告訴我Optima++在1.5推出之后就可能會停止,因為Sybase要把資源移去為當時愈來愈紅的Java研發一個新的Java RAD開發工具,那就是后來的PowerJ。于是他詢問我如果不愿意做PowerBuilder的產品經理,那么是不是愿意做PowerJ的產品經理?由于當時我已經知道Borland開始了Open JBuilder的研發,而我對Open JBuilder的興趣遠大于PowerJ,因此沒有答應Sybase。果然,在Optima++ 1.5推出之后,不但中文的問題沒有解決,Sybase之后也沒有繼續對Optima++研發下去。
Optima++一個如此有潛力的產品就這樣消失了,真是令人遺憾。Optima++應該有很好的機會可以成功。我相信,如果當時Sybase知道C++ Builder后來的成果,可能就不會放棄Optima++了,而C/C++的RAD工具一直要到后來的C++ Builder來完成這個夢。
至此,和Visual C/C++競爭的只有Borland的編譯器了,然而雖然后來Borland繼續推出了Borland C/C++ 5.0,但是品質仍然不夠好,市場反應也不佳。后來終于在Borland C/C++ 5.02之后宣布停止此條產品線的開發,Borland C/C++的光榮歷史也就從此打住,真是令人不勝感嘆,而Visual C/C++從此在C/C++開發工具市場中再也沒有對手。不過沒有競爭的市場的確會讓人松懈,后來的Visual C/C++進步的幅度愈來愈小,MFC也數年沒有什么大進步,不像當時和Borland C/C++競爭時每一個版本都有大幅的改善。看來寡占的市場的確是不好的,這也讓人回想起Visual C/C++、Borland C/C++、Symantec C/C++、Watcom C/C++四雄逐鹿的輝煌時代了。
三、開源潮流
Watcom C/C++產生目標程序的質量還是非常讓人難忘的,這也是不少程序員(尤其是游戲程序員)青睞于這個編譯器的原因,這也促成了OpenWatcom C/C++的誕生,免費、開源,也希望很多的人使用,最新版支持C/C++/Fortran的編譯。
posted @
2008-07-26 11:52 幽幽 閱讀(2136) |
評論 (0) |
編輯 收藏