• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            幽幽
             
            posts - 51,  comments - 28,  trackbacks - 0

            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 幽幽 閱讀(582) | 評論 (0)編輯 收藏

            GDI+ 將字樣相同但字形不同的字體分組為字體系列。例如,Arial 字體系列中包含以下字體:

            • Arial Regular

            • Arial Bold

            • Arial Italic

            • Arial Bold Italic

            GDI+ 使用四種字形形成字體系列:常規(guī)、粗體、傾斜和粗斜體。像 narrow 和 rounded 之類的形容詞不被視為字形;而是作為字體系列名的一部分。例如,Arial Narrow 是包含以下成員的字體系列:

            • Arial Narrow Regular

            • Arial Narrow Bold

            • Arial Narrow Italic

            • Arial Narrow Bold Italic

            在可以使用 GDI+ 繪制文本之前,您需要構(gòu)造一個 FontFamily 對象和一個 Font 對象。FontFamily 對象指定字樣(例如 Arial),而 Font 對象指定字號、字形和單位。

            示例

            下面的示例構(gòu)造一個字號為 16 像素、常規(guī)字形的 Arial 字體。在下面的代碼中,傳遞給 Font 構(gòu)造函數(shù)的第一個參數(shù)是 FontFamily 對象。第二個參數(shù)指定字體的大小,其單位由第四個參數(shù)確定。第三個參數(shù)確定字形。

            PixelGraphicsUnit 枚舉的一個成員,RegularFontStyle 枚舉的一個成員。

            FontFamily fontFamily = new FontFamily("Arial");
            Font font = new Font(
                   fontFamily,
                   16,
                   FontStyle.Regular,
                   GraphicsUnit.Pixel);
             
             
            posted @ 2008-08-14 03:51 幽幽 閱讀(1521) | 評論 (0)編輯 收藏
            SHGetFileInfo函數(shù)
            function SHGetFileInfo(pszPath: PAnsiChar; dwFileAttributes: DWORD;
              var psfi: TSHFileInfo; cbFileInfo, uFlags: UINT): DWORD; stdcall;
            pszPath 參數(shù):指定的文件名。
            當(dāng)uFlags的取值中不包含 SHGFI_PIDL時,可直接指定;
            當(dāng)uFlags的取值中包含 SHGFI_PIDL時pszPath要通過計算獲得,不能直接指定;
            dwFileAttributes參數(shù):文件屬性。
            僅當(dāng)uFlags的取值中包含SHGFI_USEFILEATTRIBUTES時有效,一般不用此參數(shù);
            psfi 參數(shù):返回獲得的文件信息,是一個記錄類型,有以下字段:
              _SHFILEINFOA = record
                hIcon: HICON;                      { out: icon }  //文件的圖標(biāo)句柄
                iIcon: Integer;                    { out: icon index }     //圖標(biāo)的系統(tǒng)索引號
                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 參數(shù):psfi的比特值;
            uFlags 參數(shù):指明需要返回的文件信息標(biāo)識符,常用的有以下常數(shù):
                SHGFI_ICON;           //獲得圖標(biāo)
                SHGFI_DISPLAYNAME;    //獲得顯示名
                SHGFI_TYPENAME;       //獲得類型名
                SHGFI_ATTRIBUTES;     //獲得屬性
                SHGFI_LARGEICON;      //獲得大圖標(biāo)
                SHGFI_SMALLICON;      //獲得小圖標(biāo)
                SHGFI_PIDL;           // pszPath是一個標(biāo)識符
            函數(shù)SHGetFileInfo()的返回值也隨uFlags的取值變化而有所不同。
            可見通過調(diào)用SHGetFileInfo()可以由psfi參數(shù)得到文件的圖標(biāo)句柄。但要注意在uFlags參數(shù)中不使用SHGFI_PIDL時,SHGetFileInfo()不能獲得“我的電腦”等虛似文件夾的信息。
            應(yīng)該注意的是,在調(diào)用SHGetFileInfo()之前,必須使用 CoInitialize 或者OleInitialize 初始化COM,否則表面上能夠使用,但是會造成不安全或者喪失部分功能。例如,一個常見的例子:如果不初始化COM,那么調(diào)用該函數(shù)就無法得到.htm/.mht/.xml文件的圖標(biāo)。
            以下是兩個例子:
            1.獲得系統(tǒng)圖標(biāo)列表:
            //取得系統(tǒng)圖標(biāo)列表
            uses
            ShellAPI
            var
              ImageListHandle : THandle;
              FileInfo: TSHFileInfo;
            //小圖標(biāo)
            ImageListHandle := SHGetFileInfo('C:\',
                                       0,
                                       FileInfo,
                                       SizeOf(FileInfo),
                                       SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
            //把圖標(biāo)列表同一個名叫ListView1的ListView控件的小圖標(biāo)關(guān)聯(lián)。                           
            SendMessage(ListView1.Handle, LVM_SETIMAGELIST, LVSIL_SMALL, ImageListHandle);  
            //大圖標(biāo)   
            ImageListHandle := SHGetFileInfo('C:\',
                                       0,
                                       FileInfo,
                                       SizeOf(FileInfo),
                                       SHGFI_SYSICONINDEX or SHGFI_LARGEICON);
            //把圖標(biāo)列表同一個名叫ListView1的ListView控件的大圖標(biāo)關(guān)聯(lián)。                           
            SendMessage(ListView1.Handle, LVM_SETIMAGELIST, LVSIL_NORMAL, ImageListHandle);
            2.獲得一個文件的顯示名和圖標(biāo)
            var
              sfi: TSHFileInfo;
            IconIndex : Integer;
            //取圖標(biāo)的索引號等信息
            SHGetFileInfo(PAnsiChar(FileName),
                            0,
                            sfi,
                            sizeof(TSHFileInfo),
                            ShellAPI.SHGFI_DISPLAYNAME or ShellAPI.SHGFI_TYPENAME or ShellAPI.SHGFI_LARGEICON or ShellAPI.SHGFI_ICON);
            //顯示名和圖標(biāo)在系統(tǒng)圖標(biāo)列表中的編號就分別在sfi.szDisplayName和sfi.iIcon中

            posted @ 2008-08-13 23:11 幽幽 閱讀(1388) | 評論 (0)編輯 收藏
            一、 簡介

            屏幕抓圖程序在處理圖形中應(yīng)用廣泛。作為Windows XP及以后版本操作系統(tǒng)的圖形處理內(nèi)核,GDI+在二維幾何圖形處理、圖像顯示與轉(zhuǎn)換和字符排版等方面簡直是傳統(tǒng)GDI程序員的一種解脫。但是,至少在目前情況下,GDI+尚不能完全代替GDI。與GDI相比,它至少還存在以下不足:

            不支持從內(nèi)存到屏幕的位傳輸操作;

            不支持光柵“位運算”操作;

            如果程序性能、速度要求比較嚴(yán)格,在圖片輸出方面的表現(xiàn)較差時,GDI往往能取代實現(xiàn)高性能的輸出。

            本文通過對流行的屏幕抓圖程序工作原理的剖析,力圖向讀者闡明GDI+與GDI各自在圖形處理方面的優(yōu)缺點,并給出相應(yīng)的VC++ .NET代碼實現(xiàn)。

                二、 GDI在抓圖中的關(guān)鍵作用

                要實現(xiàn)屏幕抓圖,關(guān)鍵有兩點:一是獲取圖片所在窗口的窗口句柄,即在何處捕獲圖片;二是保存抓取的圖片,實現(xiàn)這一點正是GDI+的強項。

                對于問題一,可以利用SetCapture函數(shù),它能夠追蹤鼠標(biāo)指針的移動(包括在屏幕抓圖程序窗口之外的窗口)。在移動鼠標(biāo)的過程中,它還可以根據(jù)鼠標(biāo)的指針?biāo)谖恢脕砼袛喈?dāng)前窗口的窗口句柄。我們還可以使用函數(shù)WindowFromPoint,這個函數(shù)能夠找出鼠標(biāo)指針當(dāng)前位置所對應(yīng)的窗口句柄。

                使用過知名的抓圖軟件SnagIT的讀者都知道,在選擇抓圖窗口時,鼠標(biāo)指針?biāo)谖恢玫拇翱诙紩霈F(xiàn)加粗的紅色邊框,以提醒目前所選擇的窗口,這個功能實現(xiàn)起來有些復(fù)雜。下面介紹在GDI中如何使這個紅色邊框出現(xiàn)。

                【注意】正是由于這個紅色邊框的實現(xiàn),讀者才能發(fā)現(xiàn)GDI+在這方面的弱點。

            在GDI中,一個最基本的概念就是設(shè)備環(huán)境(DC),每一個窗口都具有自己的DC。如果能夠找到窗口的DC,那么,用戶就能夠在該窗口的任何位置繪圖。然而,在屏幕抓圖程序中,由于用戶所選擇的窗口不固定,所以,要想得到鼠標(biāo)指針?biāo)幋翱诘腄C并不容易。這一問題的答案在于GetDC函數(shù)。下面是GetDC的函數(shù)聲明:

            HDC GetDC(HWND hWnd);

                這里,hWnd是DC對應(yīng)的窗口句柄。注意,當(dāng)hWnd為空時,該函數(shù)返回的是整個屏幕的設(shè)備環(huán)境句柄。這就意味著,開發(fā)人員可以在屏幕上的任何位置進行任意的繪圖操作。

                在鼠標(biāo)指針?biāo)幍拇翱诶L圖時,繪圖的目的只是為了提醒用戶目前所選擇的窗口,所以,在繪圖時,必須保證不會破壞窗口原有的畫面。這時可將窗口的繪圖模式設(shè)為RS_NOTXORPEN,將畫筆顏色與屏幕顏色進行異或運算之后,再對屏幕顏色取反即可。RS_NOTXORPEN運算方式的特點在于:對同一像素進行兩次RS_NOTXORPEN運算后,像素值并不會發(fā)生變化。這樣,在同一個地方進行兩次繪圖后,窗口的畫面并不會發(fā)生任何變化。

                【注意】這些功能在GDI+中很難實現(xiàn)。

                三、 編碼實現(xiàn)

            由上可知,屏幕抓圖至少分為3個步驟:

                (1) 啟用鼠標(biāo)指針捕獲。

                (2) 在鼠標(biāo)指針?biāo)谔幍拇翱谶M行繪圖,提示抓圖的目標(biāo)。

                (3) 選定目標(biāo)窗口時,將目標(biāo)窗口的畫面保存為自定義的位圖并終止鼠標(biāo)指針捕獲。

                以下是具體的編程步驟:

                (1)在Visual C++ .NET中按照GDI+程序的框架新建一個基于對話框的項目ScreenCapture,然后準(zhǔn)備好一個外形為相機的光標(biāo)文件(*.cur),將之引入資源管理器(IDC_CAMERA)。接著在CScreenCaptureDlg類中加入以下兩個全局變量:

            HWND hwndCapture;

            Crect rectCapture;

               (2)通過類向?qū)Ъ尤雽M_MOUSEMOVE及WM_LBUTTONUP事件的響應(yīng)函數(shù),分別如下所示。

            void CScreenCaptureDlg::OnMouseMove(UINT nFlags, CPoint point)

            {

            //如果用戶按隹鼠標(biāo)左鍵不放,則開始抓取圖片

            if(nFlags==MK_LBUTTON){

            //隱藏程序窗口,以免影響在抓取時的“視野”

            ShowWindow(SW_HIDE);

            //載入“照相機”鼠標(biāo)指針,開始追蹤鼠標(biāo)指針的移動

            HCURSOR cur=LoadCursor(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDC_CAMERA));

            SetCursor(cur);

            SetCapture();

            //獲得鼠標(biāo)指針?biāo)诖翱诘木浔?br>
            this->ClientToScreen(&point);

            hwndCapture=(HWND)::WindowFromPoint(point);

            //取得屏幕的設(shè)備環(huán)境句柄,以便在屏幕的任何位置繪圖

            HDC hDC=::GetDC(NULL);

            //建立一個紅色的畫筆

            HPEN hPen=CreatePen(PS_INSIDEFRAME,6,RGB(255,0,0));

            //將繪圖模式設(shè)為R2_NOTXORPEN,在繪圖時可以不破壞原有的背景

            int nMode=SetROP2(hDC,R2_NOTXORPEN);

            HPEN hpenOld=(HPEN)SelectObject(hDC,hPen);

            //得到鼠標(biāo)指針?biāo)诖翱诘膮^(qū)域

            ::GetWindowRect(hwndCapture,&rectCapture);

            //在鼠標(biāo)指針?biāo)谔幍拇翱谒闹墚嬕患t色的矩形,做為選定時的提示

            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);

            //延時后再重繪紅色矩形,這樣不會破壞原有的內(nèi)容

            Sleep(100);

            ::Polyline(hDC,pt,5);

            ::SelectObject(hDC,hpenOld);

            ::ReleaseDC(NULL,hDC);

            }

            CDialog::OnMouseMove(nFlags, point);

            }

            void CScreenCaptureDlg::OnLButtonUp(UINT nFlags, CPoint point)

            {

            // 得到鼠標(biāo)指針?biāo)诖翱诘膮^(qū)域?qū)挕⒏?br>
            int nWidth=rectCapture.Width();

            int nHeight=rectCapture.Height();

            HDC hdcScreen,hMemDC;

            HBITMAP hBitmap,hOldBitmap;

            //建立一個屏幕設(shè)備環(huán)境句柄

            hdcScreen=CreateDC("DISPLAY",NULL,NULL,NULL);

            hMemDC=CreateCompatibleDC(hdcScreen);

            //建立一個與屏幕設(shè)備環(huán)境句柄兼容、與鼠標(biāo)指針?biāo)诖翱诘膮^(qū)域等大的位圖

            hBitmap=CreateCompatibleBitmap(hdcScreen,nWidth,nHeight);

            //把新位圖選到內(nèi)存設(shè)備描述表中

            hOldBitmap=(HBITMAP)SelectObject(hMemDC,hBitmap);

            //把屏幕設(shè)備描述表拷貝到內(nèi)存設(shè)備描述表中

            BitBlt(hMemDC,0,0,nWidth,nHeight,hdcScreen,rectCapture.left,rectCapture.top,SRCCOPY);

            DeleteDC(hdcScreen);

            DeleteDC(hMemDC);

            //返回位圖句柄

            //打開剪貼板,并將位圖拷到剪貼板上

            OpenClipboard();

            EmptyClipboard();

            SetClipboardData(CF_BITMAP,hBitmap);

            //關(guān)閉剪貼板

            CloseClipboard();

            MessageBox("屏幕內(nèi)容已經(jīng)拷到剪貼板!");

            ReleaseCapture();

            //恢復(fù)窗口顯示模式

            ShowWindow(SW_NORMAL);

            CDialog::OnLButtonUp(nFlags, point);

            }

                至此,一個具有專業(yè)效果的屏幕抓圖程序的核心已經(jīng)搞定。

                四、 用GDI+實現(xiàn)畫面的保存

                經(jīng)過上面兩步,如果用戶在對話框中按住鼠標(biāo)左鍵不放,程序便開始“抓圖”。當(dāng)選擇好抓圖的目標(biāo)后,松開鼠標(biāo)左鍵,抓圖的目標(biāo)窗口的畫面就自動保存到剪貼板中了。但是,把畫面保存到文件中更為重要。如果用GDI的方式來操作,需要對各種類位圖的結(jié)構(gòu)有詳盡的了解,極其麻煩。但如果用GDI+來實現(xiàn)之則極為容易。下面介紹如何將已經(jīng)抓到的圖片保存到一個BMP文件中。

            由上面知,抓圖程序已經(jīng)得到了所捕獲的窗口的位圖句柄,接下來要將位圖句柄保存為相應(yīng)的位圖文件。這一切歸功于GDI+的Bitmap類,詳見下列代碼。

            void CScreenCaptureDlg::OnLButtonUp(UINT nFlags, CPoint point)

            {

            //……省略

            if(GetSaveFileName(&ofn))

            {

            CLSID pngClsid;

            Bitmap bmp(hBitmap,NULL);

            //獲取BMP文件的編碼方式

            GetEncoderClsid(L"image/bmp",&pngClsid);//幫助函數(shù)

            CString tmp(ofn.lpstrFile);

            CStringW filename((LPCSTR)tmp);

            //保存所截取的屏幕圖片

            bmp.Save(filename,&pngClsid);

            }

            ReleaseCapture();

            MessageBox("屏幕內(nèi)容已經(jīng)保存到文件中!");

            //恢復(fù)窗口顯示模式

            ShowWindow(SW_NORMAL);

            CDialog::OnLButtonUp(nFlags, point);

            }

                五、 小結(jié)

                本文通過一個專業(yè)的屏幕抓圖程序的核心實現(xiàn),對比分析了GDI與GDI+各自的優(yōu)缺點。但我們相信,GDI+作為新一代圖形引擎,隨著版本的不斷升級,其遲早要淘汰掉GDI。本人拙見,不足處還望讀者指正。

            另外,本文源碼在Windows 2000/VC++.NET 2003環(huán)境中調(diào)試通過。調(diào)試過程中注意:

            確保工程對GDI+庫的正確引用:在頭文件stdafx.h中要加入相應(yīng)引用;在應(yīng)用程序類的InitInstance成員函數(shù)前后及其析構(gòu)函數(shù)中加適當(dāng)?shù)牟僮鳎还こ叹幾g時要加入對gdiplus.lib的引用(“項目”|“添加現(xiàn)有項”,我的機器上是在C:\Program Files\Microsoft Visual Studio.NET\vc7\platformSDK\lib下找到庫文件)。

             

            posted @ 2008-08-13 23:02 幽幽 閱讀(2027) | 評論 (0)編輯 收藏
            下面是GetSystemMetrics函數(shù)參數(shù)nIndex的定義:

              SM_ARRANGE Flags specifying how the system arranged minimized windows. For more information about minimized windows, see the following Remarks section.

              SM_CLEANBOOT 返回系統(tǒng)啟動方式:

              0 正常啟動

              1 安全模式啟動

              2 網(wǎng)絡(luò)安全模式啟動

              SM_CMOUSEBUTTONS 返回值為系統(tǒng)支持的鼠標(biāo)鍵數(shù),返回0,則系統(tǒng)中沒有安裝鼠標(biāo)。

              SM_CXBORDER,

              SM_CYBORDER 返回以像素值為單位的Windows窗口邊框的寬度和高度,如果Windows的為3D形態(tài),則

              等同于SM_CXEDGE參數(shù)

              SM_CXCURSOR,

              SM_CYCURSOR 返回以像素值為單位的標(biāo)準(zhǔn)光標(biāo)的寬度和高度

              SM_CXDLGFRAME,

              SM_CYDLGFRAME 等同與SM_CXFIXEDFRAME and SM_CYFIXEDFRAME

              SM_CXDOUBLECLK,

              SM_CYDOUBLECLK 以像素值為單位的雙擊有效的矩形區(qū)域

              SM_CXEDGE,SM_CYEDGE 以像素值為單位的3D邊框的寬度和高度

              SM_CXFIXEDFRAME,

              SM_CYFIXEDFRAME 圍繞具有標(biāo)題但無法改變尺寸的窗口(通常是一些對話框)的邊框的厚度

              SM_CXFRAME,SM_CYFRAME 等同于SM_CXSIZEFRAME and SM_CYSIZEFRAME

              SM_CXFULLSCREEN,

              SM_CYFULLSCREEN 全屏幕窗口的窗口區(qū)域的寬度和高度

              SM_CXHSCROLL,

              SM_CYHSCROLL 水平滾動條的高度和水平滾動條上箭頭的寬度

              SM_CXHTHUMB 以像素為單位的水平滾動條上的滑動塊寬度

              SM_CXICON,SM_CYICON 系統(tǒng)缺省的圖標(biāo)的高度和寬度(一般為32*32)

              SM_CXICONSPACING,

              SM_CYICONSPACING 以大圖標(biāo)方式查看Item時圖標(biāo)之間的間距,這個距離總是大于等于

              SM_CXICON and SM_CYICON.

              SM_CXMAXIMIZED,

              SM_CYMAXIMIZED 處于頂層的最大化窗口的缺省尺寸

              SM_CXMAXTRACK,

              SM_CYMAXTRACK 具有可改變尺寸邊框和標(biāo)題欄的窗口的缺省最大尺寸,如果窗口大于這個

              尺寸,窗口是不可移動的。

              SM_CXMENUCHECK,

              SM_CYMENUCHECK 以像素為單位計算的菜單選中標(biāo)記位圖的尺寸

              SM_CXMENUSIZE,

              SM_CYMENUSIZE 以像素計算的菜單欄按鈕的尺寸

              SM_CXMIN,SM_CYMIN 窗口所能達到的最小尺寸

              SM_CXMINIMIZED,

              SM_CYMINIMIZED 正常的最小化窗口的尺寸

              SM_CXMINTRACK,

              SM_CYMINTRACK 最小跟蹤距離,當(dāng)使用者拖動窗口移動距離小于這個值,窗口不會移動。

            SM_CXSCREEN,

              SM_CYSCREEN 以像素為單位計算的屏幕尺寸。

              SM_CXSIZE,SM_CYSIZE 以像素計算的標(biāo)題欄按鈕的尺寸

              SM_CXSIZEFRAME,

              SM_CYSIZEFRAME 圍繞可改變大小的窗口的邊框的厚度

              SM_CXSMICON,

              SM_CYSMICON 以像素計算的小圖標(biāo)的尺寸,小圖標(biāo)一般出現(xiàn)在窗口標(biāo)題欄上。

              SM_CXVSCROLL,

              SM_CYVSCROLL 以像素計算的垂直滾動條的寬度和垂直滾動條上箭頭的高度

              SM_CYCAPTION 以像素計算的普通窗口標(biāo)題的高度

              SM_CYMENU 以像素計算的單個菜單條的高度

              SM_CYSMCAPTION 以像素計算的窗口小標(biāo)題欄的高度

              SM_CYVTHUMB 以像素計算的垂直滾動條中滾動塊的高度

              SM_DBCSENABLED 如果為TRUE或不為0的值表明系統(tǒng)安裝了雙字節(jié)版本的USER.EXE,為FALSE或0則不是。

              SM_DEBUG 如果為TRUE或不為0的值表明系統(tǒng)安裝了debug版本的USER.EXE,為FALSE或0則不是。

              SM_MENUDROPALIGNMENT 如果為TRUE或不為0的值下拉菜單是右對齊的否則是左對齊的。

              SM_MOUSEPRESENT 如果為TRUE或不為0的值則安裝了鼠標(biāo),否則沒有安裝。

              SM_MOUSEWHEELPRESENT 如果為TRUE或不為0的值則安裝了滾輪鼠標(biāo),否則沒有安裝。(Windows NT only)

              SM_SWAPBUTTON 如果為TRUE或不為0的值則鼠標(biāo)左右鍵交換,否則沒有。
            posted @ 2008-08-10 21:42 幽幽 閱讀(817) | 評論 (0)編輯 收藏

            在 Windows 中實現(xiàn) Java 本地方法

            developerWorks
            文檔選項
            將此頁作為電子郵件發(fā)送

            將此頁作為電子郵件發(fā)送


            級別: 初級

            David WendtWebSphere Development Research Triangle Park, NC

            1999 年 5 月 01 日

            本文為在 32 位 Windows 平臺上實現(xiàn) Java 本地方法提供了實用的示例、步驟和準(zhǔn)則。這些示例包括傳遞和返回常用的數(shù)據(jù)類型。

            本文中的示例使用 Sun Microsystems 公司創(chuàng)建的 Java DevelopmentKit (JDK) 版本 1.1.6 和 Java本地接口 (JNI) 規(guī)范。 用 C 語言編寫的本地代碼是用 MicrosoftVisual C++ 編譯器編譯生成的。

            簡介

            本文提供調(diào)用本地 C 代碼的 Java 代碼示例,包括傳遞和返回某些常用的數(shù)據(jù)類型。本地方法包含在特定于平臺的可執(zhí)行文件中。就本文中的示例而言,本地方法包含在 Windows 32 位動態(tài)鏈接庫 (DLL) 中。

            不過我要提醒您,對 Java 外部的調(diào)用通常不能移植到其他平臺上,在 applet 中還可能引發(fā)安全異常。實現(xiàn)本地代碼將使您的 Java 應(yīng)用程序無法通過 100% 純 Java 測試。但是,如果必須執(zhí)行本地調(diào)用,則要考慮幾個準(zhǔn)則:

            1. 將您的所有本地方法都封裝在單個類中,這個類調(diào)用單個 DLL。對于每種目標(biāo)操作系統(tǒng),都可以用特定于適當(dāng)平臺的版本替換這個 DLL。這樣就可以將本地代碼的影響減至最小,并有助于將以后所需的移植問題包含在內(nèi)。
            2. 本地方法要簡單。盡量將您的 DLL 對任何第三方(包括 Microsoft)運行時 DLL 的依賴減到最小。使您的本地方法盡量獨立,以將加載您的 DLL 和應(yīng)用程序所需的開銷減到最小。如果需要運行時 DLL,必須隨應(yīng)用程序一起提供它們。




            回頁首


            Java 調(diào)用 C

            對于調(diào)用 C 函數(shù)的 Java 方法,必須在 Java 類中聲明一個本地方法。在本部分的所有示例中,我們將創(chuàng)建一個名為 MyNative 的類,并逐步在其中加入新的功能。這強調(diào)了一種思想,即將本地方法集中在單個類中,以便將以后所需的移植工作減到最少。





            回頁首


            示例 1 -- 傳遞參數(shù)

            在第一個示例中,我們將三個常用參數(shù)類型傳遞給本地函數(shù): Stringintboolean 。本例說明在本地 C 代碼中如何引用這些參數(shù)。

                                    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" );
                                    }
                                    }
                                    

            請注意,本地方法被聲明為專用的,并創(chuàng)建了一個包裝方法用于公用目的。這進一步將本地方法同代碼的其余部分隔離開來,從而允許針對所需的平臺對它進行優(yōu)化。 static子句加載包含本地方法實現(xiàn)的 DLL。

            下一步是生成 C 代碼來實現(xiàn) showParms0 方法。此方法的 C 函數(shù)原型是通過對 .class 文件使用 javah 實用程序來創(chuàng)建的,而 .class 文件是通過編譯 MyNative.java 文件生成的。這個實用程序可在 JDK 中找到。下面是 javah 的用法:

                                 javac MyNative.java(將 .java 編譯為 .class)
                                    javah -jni -classpath . (指定源代碼的當(dāng)前目錄,這里要注意,是指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);
                                    

            第一個參數(shù)是調(diào)用 JNI 方法時使用的 JNI Environment 指針。第二個參數(shù)是指向在此 Java 代碼中實例化的 Java 對象 MyNative 的一個句柄。其他參數(shù)是方法本身的參數(shù)。請注意,MyNative.h 包括頭文件 jni.h。jni.h 包含 JNI API 和變量類型(包括jobject、jstring、jint、jboolean,等等)的原型和其他聲明。

            本地方法是在文件 MyNative.c 中用 C 語言實現(xiàn)的:

                                    #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,用來根據(jù) Java 字符串或 jstring 參數(shù)創(chuàng)建 C 字符串。這是必需的,因為在本地代碼中不能直接讀取 Java 字符串,而必須將其轉(zhuǎn)換為 C 字符串或 Unicode。有關(guān)轉(zhuǎn)換 Java 字符串的詳細信息,請參閱標(biāo)題為 NLS Strings and JNI 的一篇論文。但是,jboolean 和 jint 值可以直接使用。

            MyNative.dll 是通過編譯 C 源文件創(chuàng)建的。下面的編譯語句使用 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 已創(chuàng)建好,現(xiàn)在就可將其用于 MyNative 類了。
            可以這樣測試這個本地方法:在 MyNative 類中創(chuàng)建一個 main 方法來調(diào)用 showParms 方法,如下所示:

                                        public static void main( String[] args )
                                    {
                                    MyNative obj = new MyNative();
                                    obj.showParms( "Hello", 23, true );
                                    obj.showParms( "World", 34, false );
                                    }
                                    

            當(dāng)運行這個 Java 應(yīng)用程序時,請確保 MyNative.dll 位于 Windows 的 PATH 環(huán)境變量所指定的路徑中或當(dāng)前目錄下。當(dāng)執(zhí)行此 Java 程序時,如果未找到這個 DLL,您可能會看到以下的消息:

                                       java MyNative
                                    Can't find class MyNative
                                    

            這是因為 static 子句無法加載這個 DLL,所以在初始化 MyNative 類時引發(fā)異常。Java 解釋器處理這個異常,并報告一個一般錯誤,指出找不到這個類。
            如果用 -verbose 命令行選項運行解釋器,您將看到它因找不到這個 DLL 而加載 UnsatisfiedLinkError 異常。

            如果此 Java 程序完成運行,就會輸出以下內(nèi)容:

                                    java MyNative
                                    String = [Hello]
                                    int = 23
                                    boolean = true
                                    String = [World]
                                    int
                                    = 34
                                    

            boolean = false 示例 2 -- 返回一個值

            本例將說明如何在本地方法中實現(xiàn)返回代碼。
            將這個方法添加到 MyNative 類中,這個類現(xiàn)在變?yōu)橐韵滦问剑?

                                    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 方法調(diào)用本地方法 hypotenuse0 來根據(jù)傳遞的參數(shù)計算值,并將結(jié)果作為一個整數(shù)返回。這個新本地方法的原型是使用 javah 生成的。請注意,每次運行這個實用程序時,它將自動覆蓋當(dāng)前目錄中的 MyNative.h。按以下方式執(zhí)行 javah:

             javah -jni MyNative
                                    

            生成的 MyNative.h 現(xiàn)在包含 hypotenuse0 原型,如下所示:

                                    /*
                                    * Class:     MyNative
                                    * Method:    hypotenuse0
                                    * Signature: (II)I
                                    */
                                    JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0
                                    (JNIEnv *, jobject, jint, jint);
                                    

            該方法是在 MyNative.c 源文件中實現(xiàn)的,如下所示:

                                    #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
                                    

            現(xiàn)在執(zhí)行 java MyNative 將輸出 5 和 15 作為斜邊的值。

            示例 3 -- 靜態(tài)方法

            您可能在上面的示例中已經(jīng)注意到,實例化的 MyNative 對象是沒必要的。實用方法通常不需要實際的對象,通常都將它們創(chuàng)建為靜態(tài)方法。本例說明如何用一個靜態(tài)方法實現(xiàn)上面的示例。更改 MyNative.java 中的方法簽名,以使它們成為靜態(tài)方法:

                                    public static int hypotenuse( int a, int b )
                                    {
                                    return hypotenuse0(a,b);
                                    }
                                    ...
                                    private static native int  hypotenuse0( int a, int b );
                                    

            現(xiàn)在運行 javah 為 hypotenuse0創(chuàng)建一個新原型,生成的原型如下所示:

                                    /*
                                    * 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;
                                    }
                                    

            本質(zhì)上,jobject 參數(shù)已變?yōu)?jclass 參數(shù)。此參數(shù)是指向 MyNative.class 的一個句柄。main 方法可更改為以下形式:

                                    public static void main( String[] args )
                                    {
                                    System.out.println( MyNative.hypotenuse( 3, 4 ) );
                                    System.out.println( MyNative.hypotenuse( 9, 12 ) );
                                    }
                                    

            因為方法是靜態(tài)的,所以調(diào)用它不需要實例化 MyNative 對象。本文后面的示例將使用靜態(tài)方法。

            示例 4 -- 傳遞數(shù)組

            本例說明如何傳遞數(shù)組型參數(shù)。本例使用一個基本類型,boolean,并將更改數(shù)組元素。下一個示例將訪問 String(非基本類型)數(shù)組。將下面的方法添加到 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 );
                                    

            在本例中,布爾型數(shù)組被初始化為 true,本地方法將把特定的元素設(shè)置為 false。同時,在 Java 源代碼中,我們可以更改 main 以使其包含測試代碼:

                                    boolean[] ba = new boolean[5];
                                    MyNative.setArray( ba );
                                    for( int i=0; i < ba.length; i++ )
                                    System.out.println( ba[i] );
                                    

            在編譯源代碼并執(zhí)行 javah 以后,MyNative.h 頭文件包含以下的原型:

                                    /*
                                    * Class:     MyNative
                                    * Method:    setArray0
                                    * Signature: ([Z)V
                                    */
                                    JNIEXPORT void JNICALL Java_MyNative_setArray0
                                    (JNIEnv *, jclass, jbooleanArray);
                                    

            請注意,布爾型數(shù)組是作為單個名為 jbooleanArray 的類型創(chuàng)建的。
            基本類型有它們自已的數(shù)組類型,如 jintArray 和 jcharArray。
            非基本類型的數(shù)組使用 jobjectArray 類型。下一個示例中包括一個 jobjectArray。這個布爾數(shù)組的數(shù)組元素是通過 JNI 方法 GetBooleanArrayElements 來訪問的。
            針對每種基本類型都有等價的方法。這個本地方法是如下實現(xiàn)的:

                                    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;
                                    // 更改偶數(shù)數(shù)組元素
                                    for( i=0; i < len; i+=2 )
                                    pba[i] = JNI_FALSE;
                                    (*env)->ReleaseBooleanArrayElements( env, ba, pba, 0 );
                                    }
                                    

            指向布爾型數(shù)組的指針可以使用 GetBooleanArrayElements 獲得。
            數(shù)組大小可以用 GetArrayLength 方法獲得。使用 ReleaseBooleanArrayElements 方法釋放數(shù)組。現(xiàn)在就可以讀取和修改數(shù)組元素的值了。jsize 聲明等價于 jint(要查看它的定義,請參閱 JDK 的 include 目錄下的 jni.h 頭文件)。

            示例 5 -- 傳遞 Java String 數(shù)組

            本例將通過最常用的非基本類型,Java String,說明如何訪問非基本對象的數(shù)組。字符串?dāng)?shù)組被傳遞給本地方法,而本地方法只是將它們顯示到控制臺上。
            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 );
                                    

            本地方法分別訪問每個元素,其實現(xiàn)如下所示。

                                    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" );
                                    }
                                    

            數(shù)組元素可以通過 GetObjectArrayElement 訪問。
            在本例中,我們知道返回值是 jstring 類型,所以可以安全地將它從 jobject 類型轉(zhuǎn)換為 jstring 類型。字符串是通過前面討論過的方法打印的。有關(guān)在 Windows 中處理 Java 字符串的信息,請參閱標(biāo)題為 NLS Strings and JNI 的一篇論文。

            示例 6 -- 返回 Java String 數(shù)組

            最后一個示例說明如何在本地代碼中創(chuàng)建一個字符串?dāng)?shù)組并將它返回給 Java 調(diào)用者。MyNative.java 中添加了以下幾個方法:

                                    public static String[] getStrings()
                                    {
                                    return getStrings0();
                                    }
                                    private static native String[] getStrings0();
                                    

            更改 main 以使 showStringsgetStrings 的輸出顯示出來:

                                    MyNative.showStrings( MyNative.getStrings() );
                                    

            實現(xiàn)的本地方法返回五個字符串。

                                    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;
                                    }
                                    

            字符串?dāng)?shù)組是通過調(diào)用 NewObjectArray 創(chuàng)建的,同時傳遞了 String 類和數(shù)組長度兩個參數(shù)。Java String 是使用 NewStringUTF 創(chuàng)建的。String 元素是使用 SetObjectArrayElement 存入數(shù)組中的。





            回頁首


            調(diào)試

            現(xiàn)在您已經(jīng)為您的應(yīng)用程序創(chuàng)建了一個本地 DLL,但在調(diào)試時還要牢記以下幾點。如果使用 Java 調(diào)試器 java_g.exe,則還需要創(chuàng)建 DLL 的一個“調(diào)試”版本。這只是表示必須創(chuàng)建同名但帶有一個 _g 后綴的 DLL 版本。就 MyNative.dll 而言,使用 java_g.exe 要求在 Windows 的 PATH 環(huán)境指定的路徑中有一個 MyNative_g.dll 文件。在大多數(shù)情況下,這個 DLL 可以通過將原文件重命名或復(fù)制為其名稱帶綴 _g 的文件。

            現(xiàn)在,Java 調(diào)試器不允許您進入本地代碼,但您可以在 Java 環(huán)境外使用 C 調(diào)試器(如 Microsoft Visual C++)調(diào)試本地方法。首先將源文件導(dǎo)入一個項目中。
            將編譯設(shè)置調(diào)整為在編譯時將 include 目錄包括在內(nèi):

             c:\jdk1.1.6\include;c:\jdk1.1.6\include\win32
                                    

            將配置設(shè)置為以調(diào)試模式編譯 DLL。在 Project Settings 中的 Debug 下,將可執(zhí)行文件設(shè)置為 java.exe(或者 java_g.exe,但要確保您生成了一個 _g.dll 文件)。程序參數(shù)包括包含 main 的類名。如果在 DLL 中設(shè)置了斷點,則當(dāng)調(diào)用本地方法時,執(zhí)行將在適當(dāng)?shù)牡胤酵V埂?/p>

            下面是設(shè)置一個 Visual C++ 6.0 項目來調(diào)試本地方法的步驟。

            1. 在 Visual C++ 中創(chuàng)建一個 Win32 DLL 項目,并將 .c 和 .h 文件添加到這個項目中。




            • 在 Tools 下拉式菜單的 Options 設(shè)置下設(shè)置 JDK 的 include 目錄。下面的對話框顯示了這些目錄。


            • 選擇 Build 下拉式菜單下的 Build MyNative.dll 來建立這個項目。確保將項目的活動配置設(shè)置為調(diào)試(這通常是缺省值)。
            • 在 Project Settings 下,設(shè)置 Debug 選項卡來調(diào)用適當(dāng)?shù)?Java 解釋器,如下所示:


            當(dāng)執(zhí)行這個程序時,忽略“在 java.exe 中找不到任何調(diào)試信息”的消息。當(dāng)調(diào)用本地方法時,在 C 代碼中設(shè)置的任何斷點將在適當(dāng)?shù)牡胤酵V?Java 程序的執(zhí)行。





            回頁首


            其他信息

            JNI 方法和 C++

            上面這些示例說明了如何在 C 源文件中使用 JNI 方法。如果使用 C++,則請將相應(yīng)方法的格式從:

             (*env)->JNIMethod( env, .... );
                                    

            更改為:

             env->JNIMethod( ... );
                                    

            在 C++ 中,JNI 函數(shù)被看作是 JNIEnv 類的成員方法。

            字符串和國家語言支持

            本文中使用的技術(shù)用 UTF 方法來轉(zhuǎn)換字符串。使用這些方法只是為了方便起見,如果應(yīng)用程序需要國家語言支持 (NLS),則不能使用這些方法。有關(guān)在 Windows 和 NLS 環(huán)境中處理 Java 字符串正確方法,請參標(biāo)題為 NLS Strings and JNI 的一篇論文。





            回頁首


            小結(jié)

            本文提供的示例用最常用的數(shù)據(jù)類據(jù)(如 jint 和 jstring)說明了如何實現(xiàn)本地方法,并討論了 Windows 特定的幾個問題,如顯示字符串。本文提供的示例并未包括全部 JNI,JNI 還包括其他參數(shù)類型,如 jfloat、jdouble、jshort、jbyte 和 jfieldID,以及用來處理這些類型的方法。有關(guān)這個主題的詳細信息,請參閱 Sun Microsystems 提供的 Java 本地接口規(guī)范。



            關(guān)于作者

             

            David Wendt 是 IBM WebSphere Studio 的一名程序員,該工作室位于北卡羅萊納州的 Research Triangle Park。可以通過 wendt@us.ibm.com 與他聯(lián)系。


            posted @ 2008-08-08 05:17 幽幽 閱讀(624) | 評論 (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)函數(shù)。上例中只有一個過濾器,多個過濾器可參考JDK目錄中“demo\jfc\FileChooserDemo\src”中的“ExampleFileFilter.java”


            3、多選

            在基本用法中,設(shè)置

            c.setMultiSelectionEnabled(true);

            即可實現(xiàn)文件的多選。

            讀取選擇的文件時需使用

            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 幽幽 閱讀(10123) | 評論 (1)編輯 收藏
            清除屏幕閃爍
            (轉(zhuǎn)自網(wǎng)上)
            <一>

            由于作圖過于復(fù)雜和頻繁,所以時常出現(xiàn)閃爍的情況,一些防止閃爍的方法,如下:

            (1)將Invalidate()替換為InvalidateRect()。
            Invalidate()會導(dǎo)致整個窗口的圖象重畫,需要的時間比較長,而InvalidateRect()僅僅重畫Rect區(qū)域內(nèi)的內(nèi)容,所以所需時間會少一些。不要為一小塊區(qū)域的重畫就調(diào)用Invalidate(),不愿意自己去計算需要重畫的Rect,事實上,如果你確實需要改善閃爍的情況,計算一個Rect所用的時間比起重畫那些不需要重畫的內(nèi)容所需要的時間要少得多。

            (2)禁止系統(tǒng)擦除你的窗口。
            系統(tǒng)在需要重畫窗口的時候會幫你用指定的背景色來擦除窗口。可是,也許需要重畫的區(qū)域也許非常小。或者,在你重畫這些東西之間還要經(jīng)過大量的計算才能開始.這個時候你可以禁止系統(tǒng)擦掉原來的圖象。直到你已經(jīng)計算好了所有的數(shù)據(jù),自己把那些需要擦掉的部分用背景色覆蓋掉(如:dc.FillRect(rect,&brush);rect是需要擦除的區(qū)域,brush是帶背景色的刷子),再畫上新的圖形。要禁止系統(tǒng)擦除你的窗口,可以重載OnEraseBkgnd()函數(shù),讓其直接返回TRUE就可以了。如
            BOOL CmyWin::OnEraseBkgnd(CDC* pDC)
            {
             return TRUE;
             //return CWnd::OnEraseBkgnd(pDC);//把系統(tǒng)原來的這條語句注釋掉。
            }

            (3)有效的進行擦除。
            擦除背景的時候,不要該擦不該擦的地方都擦。比如,你在一個窗口上放了一個很大的Edit框,幾乎占了整個窗口,那么你頻繁的擦除整個窗口背景將導(dǎo)致Edit不停重畫形成劇烈的閃爍.事實上你可以CRgn創(chuàng)建一個需要擦除的區(qū)域,只擦除這一部分.如

            GetClientRect(rectClient);
            rgn1.CreateRectRgnIndirect(rectClient);
            rgn2.CreateRectRgnIndirect(m_rectEdit);

            if(rgn1.CombineRgn(&rgn1,&rgn2,RGN_XOR)= ERROR)
            //處理后的rgn1只包括了Edit框之外的客戶區(qū)域,這樣,Edit將不會被我的背景覆蓋而導(dǎo)致重畫.
            {
             ASSERT(FALSE);
             return ;
            }
            brush.CreateSolidBrush(m_clrBackgnd);
            pDC->FillRgn(&rgn1,&brush);
            brush.DeleteObject();
            注意:在使用這個方法的時候要同時使用方法二。

            (4).使用MemoryDC先在內(nèi)存里把圖畫好,再復(fù)制到屏幕上。
            這對于一次畫圖過程很長的情況比較管用。畢竟內(nèi)存操作比較快,而且復(fù)制到屏幕又是一次性的,至少不會出現(xiàn)可以明顯看出一個東西從左畫到右的情況。

            void CMyWiew::OnDraw() //CScrollView下雙緩沖內(nèi)存的實現(xiàn):
            {
             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是必須的,否則當(dāng)心弄出一個大黑塊. 
             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);//設(shè)定繪圖模式

             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程序界面閃爍問題的一些經(jīng)驗
            (轉(zhuǎn)自網(wǎng)上)
            <二>

            一般的windows 復(fù)雜的界面需要使用多層窗口而且要用貼圖來美化,所以不可避免在窗口移動或者改變大小的時候出現(xiàn)閃爍。

            先來談?wù)勯W爍產(chǎn)生的原因

            原因一:
            如果熟悉顯卡原理的話,調(diào)用GDI函數(shù)向屏幕輸出的時候并不是立刻就顯示在屏幕
            上只是寫到了顯存里,而顯卡每隔一段時間把顯存的內(nèi)容輸出到屏幕上,這就是刷新周期。

            一般顯卡的刷新周期是 1/80秒左右,具體數(shù)字可以自己設(shè)置的。

            這樣問題就來了,一般畫圖都是先畫背景色,然后再把內(nèi)容畫上去,如果這兩次操作不在同一個
            刷新周期內(nèi)完成,那么給人的視覺感受就是,先看到只有背景色的圖像,然后看到畫上內(nèi)容的圖像,
            這樣就會感覺閃爍了。

            解決方法:盡量快的輸出圖像,使輸出在一個刷新周期內(nèi)完成,如果輸出內(nèi)容很多比較慢,那么采用
            內(nèi)存緩沖的方法,先把要輸出的內(nèi)容在內(nèi)存準(zhǔn)備好,然后一次輸出到顯存。要知道一次API調(diào)用一般可以
            在一個刷新周期內(nèi)完成。

            對于GDI,用創(chuàng)建內(nèi)存DC的方法就可以了

            原因二:

            復(fù)雜的界面有多層窗口組成,當(dāng)windows在窗口改變大小的時候是先重畫父窗口,然后重畫子窗口,子父
            窗口重畫的過程一般無法在一個刷新周期內(nèi)完成,所以會呈現(xiàn)閃爍。

            我們知道父窗口上被子窗口擋住的部分其實沒必要重畫的

            解決方法:給窗口加個風(fēng)格 WS_CLIPCHILDREN ,這樣父窗口上被子窗口擋住的部分就不會重畫了。

            如果同級窗口之間有重疊,那么需要再加上 WS_CLIPSIBLINGS 風(fēng)格

            原因三:

            有時候需要在窗口上使用一些控件,比如IE,當(dāng)你的窗口改變大小的時候IE會閃爍,即使你有了WS_CLIPCHILDREN
            也沒用。原因在于窗口的類風(fēng)格有CS_HREDRAW 或者 CS_VREDRAW,這兩個風(fēng)格表示窗口在寬度或者高度變化的時候
            重畫,但是這樣就會引起IE閃爍

            解決方法:注冊窗口類的時候不要使用這兩個風(fēng)格,如果窗口需要在改變大小的時候重畫,那么可以在WM_SIZE的時候
            調(diào)用RedrawWindow。

            原因四:

            界面上窗口很多,而且改變大小時很多窗口都要移動和改變大小,如果使用MoveWindow或者SetWindowPos兩個API來
            改變窗口的大小和位置,由于他們是等待窗口重畫完成后才返回,所以過程很慢,這樣視覺效果就可能會閃爍。

            解決方法:

            使用以下API來處理窗口移動,BeginDeferWindowPos, DeferWindowPos,EndDeferWindowPos
            先調(diào)用 BeginDeferWindowPos 設(shè)定需要移動的窗口的個數(shù)
            使用DeferWindowPos,來移動窗口,這個API并不真的造成窗口移動
            EndDeferWindowPos 一次性完成所有窗口的大小和位置的改變。

            有個地方要特別注意,要仔細計算清楚要移動多少個窗口,BeginDeferWindowPos設(shè)定
            的個數(shù)一定要和實際的個數(shù)一致,否則在Win9x下,如果實際移動的窗口數(shù)多于調(diào)用BeginDeferWindowPos
            時設(shè)定的個數(shù),可能會造成系統(tǒng)崩潰。在Windows NT系列下不會有這樣的問題。

            *******************************

            使用內(nèi)存DC解決重畫閃爍問題

            (轉(zhuǎn)自網(wǎng)上)
            <三>

             
            下述代碼在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));

            }

             

            首先將背景填充白色,然后畫一綠色的矩形,再在矩形上輸出一段文字,如此過程必然會引起畫面閃爍,
            解決辦法:使用內(nèi)存DC,先將圖形繪制到內(nèi)存DC,然后拷貝到屏幕,實現(xiàn)無閃爍繪圖。
            修改后的代碼如下:

             

            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位圖代碼,注意應(yīng)該阻止窗口擦除背景,重載OnEraseBkgnd函數(shù)


            BOOL CRedrawDemoView::OnEraseBkgnd(CDC* pDC)

            {

                   // TODO: Add your message handler code here and/or call default

                   return TRUE;

                   // return CView::OnEraseBkgnd(pDC);

            }


            為易于理解,以上代碼未經(jīng)優(yōu)化。

             

             

            *******************************
            用:
            CreateCompatibleBitmap 
            CreateCompatibleDC
            等函數(shù)在內(nèi)存中把要畫的圖先畫出來,然后使用  
            BitBlt復(fù)制到設(shè)備上就OK!
            *******************************
            posted @ 2008-08-04 23:39 幽幽 閱讀(1973) | 評論 (0)編輯 收藏
            PE格式,是Windows的可執(zhí)行檔的格式。 Windows中的 exe檔,dll檔,都是PE格式。 PE 就是Portable Executable 的縮寫。 Portable 是指對於不同的Windows版本和不同的CPU類型上PE檔的格式是一樣的,當(dāng)然CPU不一樣了,CPU指令的二進位編碼是不一樣的。只是檔中各種東西的佈局是一樣的。 能告示根底嗎! 摘要 Matt Pietrek(姜慶東譯) 對可執(zhí)行檔的深入認識將帶你深入到系統(tǒng)深處。如果你知道你的exe/dll裏是些什麼東東,你就是一個更有知識的程式師。作為系列文章的第一章,將關(guān)注這幾年來PE格式的變化,同時也簡單介紹一下PE格式。經(jīng)過這次更新,作者加入了PE格式是如何與.NET協(xié)作的及PE檔表格(PE FILE SECTIONS),RVA,The DataDirectory,函數(shù)的輸入等內(nèi)容。 ==================== 很久以前,我給Microsoft Systems Journal(現(xiàn)在的MSDN)寫了一篇名為“Peering Inside the PE: A Tour of the Win32 Portable Executable File format”的文章。後來比我期望的還流行,到現(xiàn)在我還聽說有人在用它(它還在MSDN裏)。不幸的是,那篇文章的問題依舊存在,WIN32的世界靜悄悄地變了好多,那篇文章已顯得過期了。從這個月開始我將用這兩篇文章來彌補。 你可能會問為什麼我應(yīng)當(dāng)瞭解PE格式,答案依舊:作業(yè)系統(tǒng)的可執(zhí)行檔格式和資料結(jié)構(gòu)暴露出系統(tǒng)的底層細節(jié)。通過瞭解這些,你的程式將編的更出色。 當(dāng)然,你可以閱讀微軟的文檔來瞭解我將要告訴你的。但是,像很多文檔一樣,‘寧可晦澀,但為瓦全’。 我把焦點放在提供一些不適合放在正式文檔裏的內(nèi)容。另外,這篇文章裏的一些知識不見得能在官方文檔裏找到。 1. 裂縫的撕開 讓我給你一些從1994年我寫那篇文章來PE格式變化的例子。WIN16已經(jīng)成為歷史,也就沒有必要作什麼比較和說明了。另外一個可憎的東西就是用在WINDOWS 3.1 中的WIN32S,在它上面運行程式是那麼的不穩(wěn)定。 那時候,WINDOWS 95(也叫Chicago)還沒有發(fā)行。NT還是3.5版。微軟的連接器還沒開始大規(guī)模的優(yōu)化,儘管如此,there were MIPS and DEC Alpha implementations of Windows NT that added to the story. 那麼究竟,這麼些年來,有些什麼新的東西出來呢?64位的WINDOWS有了它自己的PE變種,WINDOWS CE 支持各種CPU了,各種優(yōu)化如DLL的延遲載入,節(jié)表的合併,動態(tài)捆綁等也已出臺。 有很多類似的東西發(fā)生了。 讓我們最好忘了.NET。它是如何與系統(tǒng)切入的呢?對於作業(yè)系統(tǒng),.NET的可執(zhí)行檔格式是與舊的PE格式相容的。雖然這麼說,在運行時期,.NET還是按元資料和中間語言來組織資料的,這畢竟是它的核心。這篇文章當(dāng)中,我將打開.NET元資料這扇門,但不做深入討論。 如果WIN32的這些變化都不足以讓我重寫這篇文章,就是原來的那些錯誤也讓我汗顏。比如我對TLS的描述只是一帶而過,我對時間戳的描述只有你生活在美國西部才行等等。還有,一些東西已是今是作非了,我曾說過.RDATA幾乎沒排上用場,今天也是,我還說過.IDATA節(jié)是可讀可寫的,但是一些搞API攔截的人發(fā)現(xiàn)好像是錯的。 在更新這篇文章的過程當(dāng)中,我也檢查了PEDUMP這個用來傾印PE檔的程式.這個程式能夠在0X86和IA-64平臺下編譯和運行。 2. PE格式概覽 微軟的可執(zhí)行檔格式,也就是大家熟悉的PE 格式,是官方文檔的一部分。但是,它是從VAX/VMS上的COFF派生出來的,就WINDOWS NT小組的大部分是從DEC轉(zhuǎn)過來的看來,這是可以理解的。很自然,這些人在NT的開發(fā)上會用他們以往的代碼。 採用術(shù)語“PORTABLE EXECUTABLE”是因為微軟希望有一個通用在所有WINDOWS平臺上和所有CPU上的檔格式。從大的方面講,這個目標(biāo)已經(jīng)實現(xiàn)。它適用于NT及其後代,95及其後代,和CE. 微軟產(chǎn)生的OBJ檔是用COFF格式的。當(dāng)你看到它的很多域都是用八進制的編碼的,你會發(fā)現(xiàn)她是多麼古老了。COFF OBJ檔用到了很多和PE一樣的資料結(jié)構(gòu)和枚舉,我馬上會提到一些。 64位的WINDOWS只對PE格式作了一點點改變。這個新的格式叫做PE32+。沒有增加一個欄位,且只刪了一個欄位。其他的改變就是把以前的32位欄位擴展成64位。對於C++代碼,通過巨集定義WINDOWS的頭檔已經(jīng)遮罩了這些差別。 EXE與DLL的差別完全是語義上的。它們用的都是同樣一種檔格式-PE。唯一的區(qū)別就是其中有一個欄位標(biāo)識出是EXE還是DLL.還有很多DLL的擴展比如OCX,CPL等都是DLL.它們有一樣的實體。 你首先要知道的關(guān)於PE的知識就是磁片中的資料結(jié)構(gòu)佈局和記憶體中的資料結(jié)構(gòu)佈局是一樣的。載入可執(zhí)行檔(比如LOADLIBARY)的首要任務(wù)就是把磁片中的檔映射到進程的位址空間.因此像IMAGE_NT_HEADER(下面解釋)在磁片和記憶體中是一樣的。關(guān)鍵的是你要懂得你怎樣在磁片中獲得PE檔某些資訊的,當(dāng)它載入記憶體時你可以一樣獲得,基本上是沒什麼不同的(即記憶體映射檔)。但是知道與映射普通的記憶體映射檔不同是很重要的。WINDOWS載入器察看PE檔才決定映射到哪里,然後從檔的開始處往更高的位址映射,但是有的東西在檔中的偏移和在記憶體中的偏移會不一樣。儘管如此,你也有了足夠的資訊把檔偏移轉(zhuǎn)化成記憶體偏移。見圖一: 當(dāng)Windows載入器把PE載入記憶體,在記憶體中它稱作模組(MODULE),檔從HMODULE這個位址開始映射。記住這點:給你個HMODULE,從那你可以知道一個資料結(jié)構(gòu)(IMAGE_DOS_HEADER),然後你還可以知道所有得資料結(jié)構(gòu)。這個強大的功能對於API攔截特別有意義。(準(zhǔn)確地說:對於WINDOWS CE,這是不成立的,不過這是後話)。 記憶體中的模組代表著進程從這個可執(zhí)行檔中所需要的所有代碼,資料,資源。其他部分可以被讀入,但是可能不映射(如,重定位節(jié))。還有一些部分根本就不映射,比如當(dāng)調(diào)試資訊放到檔的尾部的時候。有一個欄位告訴系統(tǒng)把檔映射到記憶體需要多少記憶體。不需要的資料放在檔的尾部,而在過去,所有部分都映射。 在WINNT.H描述了PE 格式。在這個檔中,幾乎有所有的關(guān)於PE的資料結(jié)構(gòu),枚舉,#DEFINE。當(dāng)然,其他地方也有相關(guān)文檔,但是還是WINNT.H說了算。 有很多檢測PE文件的工具,有VISUAL STUDIO的DUMPBIN,SDK中的DEPENDS,我比較喜歡DEPENDS,因為它以一種簡潔的方式檢測出檔的引入引出。一個免費的PE察看器,PEBrowse,來自smidgenosoft。我的pedump也是很有用的,它和dumpbin有一樣的功能。 從api的立場看,imagehlp.dll提供了讀寫pe檔的機制。 在開始討論pe檔前,回顧一下pe檔的一些基本概念是有意義的。在下面幾節(jié),我將討論:pe 節(jié),相對虛擬位址(rva),資料目錄,函數(shù)的引入。 3. PE節(jié) PE節(jié)以某鍾順序表示代碼或資料。代碼就是代碼了,但是卻有多種類型的資料,可讀寫的程式資料(如總體變數(shù)),其他的節(jié)包含API的引入引出表,資源,重定位。每個節(jié)有自己的屬性,包括是否是代碼節(jié),是否唯讀還是可讀可寫,節(jié)的資料是否全局共用。 通常,節(jié)中的資料邏輯上是關(guān)聯(lián)的。PE檔一般至少要有兩個節(jié),一個是代碼,另一個為資料。一般還有一個其他類型的資料的節(jié)。後面我將描述各種類型的節(jié)。 每個節(jié)都有一個獨特的名字。這個名字是用來傳達這個節(jié)的用途的。比如,.RDATA表示一個唯讀節(jié),節(jié)的名字對於作業(yè)系統(tǒng)毫無意義,只是為了人們便於理解。把一個節(jié)命名為FOOBAR和.TEXT是一樣有用的。微軟給他們的節(jié)命名了個有特色的名字,但是這不是必需的。Borland的連接器用的是code和data 一般編譯器將產(chǎn)生一系列標(biāo)準(zhǔn)的節(jié),但這沒有什麼不可思議的。你可以建立和命名自己的節(jié),連接器會自動在程式檔中包含它們。在visual c++中,你能用#pragma指令讓編譯器插入資料到一個節(jié)中。像下面這樣:  #pragma data_seg("MY_DATA")  ...有必要初始化  #pragma data_seg() 你也可以對.data做同樣的事。大部分的程式都只用編譯器產(chǎn)生的節(jié),但是有時候你卻需要這樣。比如建立一個全局共用節(jié)。 節(jié)並不是全部由連接器確定的,他們可以在編譯階段由編譯器放入obj檔。連接器的工作就是合併所有obj和庫中需要的節(jié)成一個最終的合適的節(jié)。比如,你的工程中的所有obj可能都有一個包含代碼的.text節(jié),連接器把這些節(jié)合併成一個.text節(jié)。同樣對於.data等。這些主題超出了這篇文章的範(fàn)圍了。還有更多的規(guī)則關(guān)於連接器的。在obj文件中是專門給linker用的,並不放入到pe檔中,這種節(jié)是用來給連接器傳遞資訊的。 節(jié)有兩個關(guān)於對齊的欄位,一個對應(yīng)磁片檔,另一個對應(yīng)記憶體中的檔。Pe檔頭指出了這兩個值,他們可以不一樣。每個節(jié)的偏移從對齊值的倍數(shù)開始。比如,典型的對齊值是0x200,那麼每個節(jié)的的偏移必須是0x200的倍數(shù)。一旦載入記憶體,節(jié)的起始位址總是以頁對齊。X86cpu的頁大小為4k,al-64為8k。 下麵是pedump傾印出的Windows XP KERNEL32.DLL.的.text .data節(jié)的信息:  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 建立一個節(jié)在檔中的偏移和它相對於載入位址的偏移相同的pe檔是可能的。在98/me中,這會加速大檔的載入。Visual studio 6.0 的默認選項 /opt:win98j就是這樣產(chǎn)生檔的。在Visual studio.net中是否用/opt:nowin98取決於檔是否夠小。 一個有趣的連接器特徵是合併節(jié)的能力。如果兩個節(jié)有相似相容的屬性,連接的時候就可以合併為一個節(jié)。這取決於是否用/merger開關(guān)。像下麵就把.rdata和.text合併為一個節(jié).text  /MERGE:.rdata=.text 合併節(jié)的優(yōu)點就是對於磁片和記憶體節(jié)省空間。每個節(jié)至少佔用一頁記憶體,如果你可以把可執(zhí)行檔的節(jié)數(shù)從4減到3,很可能就可以少用一頁記憶體。當(dāng)然,這取決於兩個節(jié)的空餘空間加起來是否達到一頁。 當(dāng)你合併節(jié)事情會變得有意思,因為這沒有什麼硬性和容易的規(guī)則。比如你可以合併.rdata到.text, 但是你不可以把.rsrc.reloc.pdata合併到別的節(jié)。先前Visual Studio .NET允許把.idata合併,後來又不允許了。但是當(dāng)發(fā)行的時候,連接器還是可以把.idata合併到別的節(jié)。 因為引入節(jié)的一部分在載入器載入時將被寫入,你可能驚奇它是如何被放入一個唯讀節(jié)的。是這樣的,在載入的時候系統(tǒng)會臨時改變那些包含引入節(jié)的頁為可讀可寫,初始化完成後,又恢復(fù)原來屬性。 4. 相對虛擬位址 在可執(zhí)行檔中,有很多地方需要指定記憶體位址,比如,引用總體變數(shù)時,需要指定它的位址。Pe檔儘管有一個首選的載入位址,但是他們可以載入到進程空間的任何地方,所以你不能依賴於pe的載入點。由於這點,必須有一個方法來指定位址而不依賴於pe載入點的地址。為了避免把記憶體位址硬編碼進pe檔,提出了RVA。RVA是一個簡單的相對於PE載入點的記憶體偏移。比如,PE載入點為0X400000,那麼代碼節(jié)中的地址0X401000的RVA為(target address) 0x401000 - (load address)0x400000 = (RVA)0x1000。把RVA加上PE的載入點的實際位址就可以把RVA轉(zhuǎn)化實際位址。順便說一下,按PE的說法,記憶體中的實際位址稱為VA(VIRTUAL ADDRESS).不要忘了早點我說的PE的載入點就是HMODULE。 想對探索記憶體中的任意DLL嗎?用GetModuleHanle(LPCTSTR)取得載入點,用你的PE知識來幹活吧 5. 資料目錄 PE檔中有很多資料結(jié)構(gòu)需要快速定位。顯然的例子有引入函數(shù),引出函數(shù),資源,重定位。這些東西是以一致的方式來定位的,這就是資料目錄。 資料目錄是一個結(jié)構(gòu)陣列,包含16個結(jié)構(gòu)。每個元素有一個定義好的標(biāo)識,如下:  // 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. 引入函數(shù) 當(dāng)你使用別的DLL中的代碼或資料,稱為引入。當(dāng)PE載入時,載入器的工作之一就是定位所有引入函數(shù)及資料,使那些位址對於載入的PE可見。具體細節(jié)在後面討論,在這裏只是大概講一下。 當(dāng)你用到了一個DLL中的代碼或資料,你就暗中連接到這個DLL。但是你不必為“把這些位址變得對你的代碼有效”做任何事情,載入器為你做這些。方法之一就是顯式連接,這樣你就要確定DLL已被載入,及函數(shù)的位址。調(diào)用LOADLIBARY和GETPROCADDRESS就可以了。 當(dāng)你暗式連接DLL,LOADLIBARY和GETPROCADDRESS同樣還是執(zhí)行了的。只不過載入器為你做了這些。載入器還保證PE檔所需得任何附加的DLL都已被載入。比如,當(dāng)你連接了KERNEL32.DLL,而它又引入了NTDLL.DLL的函數(shù),又比如當(dāng)你連接了GDI32.DLL,而它又依賴於USER32, ADVAPI32,NTDLL, 和 KERNEL32 DLLs的函數(shù),載入器會保證這些DLL被載入及函數(shù)的決議。 暗式連接時,決議過程在PE檔在載入時就發(fā)生了。如果這時有什麼問題(比如這個DLL檔找不到),進程終止。 VISUAL C++ 6.0 加入了DLL的延遲載入的特徵。它是暗式連接和顯式連接的混合。當(dāng)你延遲載入DLL,連接器做出一些和引入標(biāo)準(zhǔn)規(guī)則DLL類似的東西,但是作業(yè)系統(tǒng)卻不管這些東西,而是在第一次調(diào)用這個DLL中的函數(shù)的時候載入(如果還沒載入),然後調(diào)用GetProcAddress取得函數(shù)的位址。 對於pe檔要引入的dll都有一個對應(yīng)的結(jié)構(gòu)陣列,每個結(jié)構(gòu)指出這個dll的名字及指向一個函數(shù)指標(biāo)陣列的指標(biāo),這個函數(shù)指標(biāo)陣列就是所謂的IAT(IMORT ADDRESS TABLE)。每個輸入函數(shù),在IAT中都有一個保留槽,載入器將在那裏寫入真正的函數(shù)位址。最後特別重要一點的是:模組一旦載入,IAT中包含所要調(diào)用的引入函數(shù)的位址。 把所有輸入函數(shù)放在IAT一個地方是很有意義的,這樣無論代碼中多少次調(diào)用一個引入函數(shù),都是通過IAT中的一個函數(shù)指標(biāo)。 讓我們看看是怎樣調(diào)用一個引入函數(shù)的。有兩種情況需要考慮:有效率的和效率差的。最好的情況像下面這樣:  CALL DWORD PTR [0x00405030] 直接調(diào)用[0x405030]中的函數(shù),0x405030位於IAT部分。效率差的方式如下:  CALL 0x0040100C  ...  0x0040100C:  JMP DWORD PTR [0x00405030] 這種情況,CALL把控制權(quán)轉(zhuǎn)到一個子程式,副程式中的JMP指令跳轉(zhuǎn)到位於IAT中的0x00405030,簡單說,它多用了5位元組和JMP多花的時間。 你可能驚訝引入函數(shù)就採用了這種方式,有個很好的解釋,編譯器無法區(qū)別引入函數(shù)的調(diào)用和普通函數(shù)調(diào)用,對於每個函數(shù)調(diào)用,編譯器只產(chǎn)生如下指令:  CALL XXXXXXXX XXXXXXXX是一個由連接器填入的RVA。注意,這條指令不是通過函數(shù)指標(biāo)來的,而是代碼中的實際地址。 為了因果的平衡,連接器必須產(chǎn)生一塊代碼來代替取代XXXXXXXX,簡單的方法就是象上面所示調(diào)用一個JMP STUB. 那麼JMP STUB 從那裏來呢?令人驚異的是,它取自輸入函數(shù)的引入庫。如果你去察看一個引入庫,在輸入函數(shù)名字的關(guān)聯(lián)處,你會發(fā)現(xiàn)與上面JMP STUB相似的指令。 接著,另一個問題就是如何優(yōu)化這種形式,答案是你給編譯器的修飾符,__declspec(import) 修飾符告訴編譯器,這個函數(shù)來自另一個dll,這樣編譯器就會產(chǎn)生第一種指令。另外,編譯器將給函數(shù)加上__imp_首碼然後送給連接器決議,這樣可以直接把__imp_xxx送到iat,就不需要jmp stub了。 對於我們這有什麼意義呢,如果你在寫一個引出函數(shù)的東西並提供一個頭檔的話,別忘了在函數(shù)前加上修飾符__declspec(import)  __declspec(dllimport) void Foo(void); 在winnt.h等系統(tǒng)頭檔中就是這樣做的。 7. PE 檔結(jié)構(gòu) 現(xiàn)在讓我們開始研究PE檔格式,我將從檔的頭部開始,描述每個PE檔中都有的各種資料結(jié)構(gòu),然後,我將討論更多的專門的資料結(jié)構(gòu)比如引入表和資源,除非特殊說明,這些結(jié)構(gòu)都定義在WINNT.H中。 一般地,這些結(jié)構(gòu)都有32和64位之分,如IMAGE_NT_HEADERS32 ,IMAGE_NT_HEADER64等,他們基本上是一樣的,除了64位的擴展了某些欄位。通過#DEFINE WINNT.H都遮罩了這些區(qū)別,選擇那個資料結(jié)構(gòu)取決於你要如何編譯了(如,是否定義_WIN64) The MS-DOS Header 每個PE檔是以一個DOS程式開始的,這讓人想起WINDOWS在沒有如此可觀的使用者的早期年代。當(dāng)可執(zhí)行檔在非WINDOWS平臺上運行的時候至少可以顯示出一條資訊表示它需要WINDOWS。 PE檔的開頭是一個IMAGE_DOS_HEADER結(jié)構(gòu),結(jié)構(gòu)中只有兩個重要的欄位e_magic and e_lfanew。e_lfanew指出pe file header的偏移,e_magic需要設(shè)定位0x5a4d,被#define 成IMAGE_DOS_SIGNATURE 它的ascii為’MZ’,Mark Zbikowski的首字母,DOS 的原始構(gòu)建者之一。 The IMAGE_NT_HEADERS Header 這個結(jié)構(gòu)是PE檔的主要定位資訊的所在。它的偏移由IMAGE_DOS_HEADER的e_lfanew給出 確實有64和32位之分,但我在討論中將不作考慮,他們幾乎沒有區(qū)別。  typedef struct _IMAGE_NT_HEADERS {   DWORD Signature;   IMAGE_FILE_HEADER FileHeader;   IMAGE_OPTIONAL_HEADER32 OptionalHeader;  } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; 在一個有效的pe檔裏,Signture被設(shè)為0x00004500,ascii 為’PE00’,#define IMAGE_NT_SIGNTURE 0X00004500;第二個欄位是一個IMAGE_FILE_HEADER結(jié)構(gòu),它包含檔的基本資訊,特別重要的是它指出了IMAGE_OPTIONAL_HEADER的大小(重要嗎?);在PE文件中,IMAGE_OPTIONAL_HEADER是非常重要的,但是仍稱作IMAGE_OPTIONAL_HEADER。 IMAGE_OPTIONAL_HEADER結(jié)構(gòu)的末尾就是用來定位pe檔中重要資訊的位址簿-資料目錄,它的定義如下:  typedef struct _IMAGE_DATA_DIRECTORY {   DWORD VirtualAddress; // RVA of the data   DWORD Size; // Size of the data  }; The Section Table 緊接著IMAGE_NT_HEADERS後的就是節(jié)表,節(jié)表就是IMAGE_SECTION_HEADER的陣列。IMAGE_SECTION_HEADER包含了它所關(guān)聯(lián)的節(jié)的資訊,如位置,長度,特徵;該陣列的數(shù)目由IMAGE_NT_HEADERS.FileHeader.NumberOfSections指出。具體見下圖 PE中的節(jié)的大小的總和最後是要對齊的,Visual Studio 6.0中的預(yù)設(shè)值是4k,除非你使用/OPT:NOWIN98 或/ALIGN開關(guān);在.NET中,依然用了默認的/OPT:WIN98,但是如果檔小於一特定大小時,就會採用0X200為對齊值。 .NET文檔中有關(guān)於對齊的另一件有趣的事。.NET檔的記憶體對齊值為8K而不是普通X86平臺上的4K,這樣就保證了在X86平臺編譯的程式可以在IA-64平臺上運行。如果記憶體對齊值為4K,那麼IA-64的載入器就不能載入這個程式,因為它的頁為8K
            posted @ 2008-07-28 04:04 幽幽 閱讀(697) | 評論 (0)編輯 收藏
            在看yoda's Protector源代碼的時候,發(fā)現(xiàn)
            const DWORD ALIGN_CORRECTION    =0x1000;// this big value is e.g. needed for WATCOM compiled files
            上網(wǎng)一查,發(fā)現(xiàn)WATCOM竟然有這樣一段傳奇:


            一、Watcom的發(fā)展史

                    在編譯器混戰(zhàn)的時代,一家加拿大的小公司出品了Watcom C/C++編譯器,但是以在DOS下能夠產(chǎn)生最佳化程序代碼聞名于世的,許多寫游戲和DOS Extender的廠商都指名要使用Watcom C/C++,因為不論是Borland C/C++還是Visual C/C++,它們產(chǎn)生的最佳化程序代碼都比Watcom C/C++的最佳化程序代碼差上一截。再加上當(dāng)時最有名的DOS Extender廠商PharLap公司也是使用Watcom C/C++,因此Watcom C/C++在當(dāng)時專業(yè)的C/C++程序員以及系統(tǒng)程序員心中是第一品牌的C/C++開發(fā)工具。

                   Watcom C/C++在DOS市場站穩(wěn)了腳跟之后,由于Windows已經(jīng)逐漸成為市場的主流,DOS勢必將被逐漸淘汰出局,因此,Watcom C/C++如果要繼續(xù)生存下去,也就一定要推出Windows平臺的C/C++開發(fā)工具。大約是在1993、1994年左右,Watcom終于推出第一個Windows下的C/C++開發(fā)工具。

                   不過,當(dāng)時Watcom C/C++在Windows推出的C/C++開發(fā)工具實在是平淡無奇。其集成開發(fā)環(huán)境和另外三個對手比較起來簡直像是遠古的產(chǎn)品,一點特色都沒有。不過Watcom C/C++仍然是以它的最佳化編譯器作為號召。因此當(dāng)時發(fā)生了一個非常有趣的現(xiàn)象,那就是許多軟件公司會同時買Borland C/C++,或是Visual C/C++,Symantec C/C++之一,再搭配一套Watcom C/C++。在開發(fā)應(yīng)用系統(tǒng)時使用其他三套開發(fā)工具之一,最后要出貨時再使用Watcom C/C++來編譯以產(chǎn)生最佳的程序代碼。

                   在Watcom C/C++推出了Windows平臺的開發(fā)工具之后,也吸引了一群使用者。雖然Watcom C/C++的市場比起其他的三家來說是最小的,但是總算撐起了一片天,成為四大C/C++開發(fā)工具之一。稍后Watcom C/C++被Sybase并購,成為Sybase的Optima++的前身。

            二、石破天驚還是巨星隕落

                   1996年左右,Sybase并購了Watcom之后終于推出了石破天驚的C/C++開發(fā)工具:Optima++。Optima++是當(dāng)初結(jié)合了Watcom的最佳化編譯器以及類似Delphi的組件拖曳開發(fā)環(huán)境的第一個RAD C/C++開發(fā)工具。更棒的是Optima++的組件架構(gòu)(類似Delphi的VCL)完全是以純正的C/C++程序代碼撰寫的。這可不得了,因為這代表Optima++是一個融合了Visual C/C++和Delphi兩大王者開發(fā)工具為一身的超級賽亞人工具。

                   在我(《Borland傳奇》作者李維,下同)知道這個工具、并且嘗試實際使用之后,極為震驚。因為對于我這個使用了C/C++ 五六年的人來說,它比Delphi更具有吸引力。因此我立刻在《RUN!PC》上介紹了這個不可置信的工具。果然,Optima++很快開始風(fēng)靡市場,雖然沒有立刻占據(jù)很大的市場份額,但是已經(jīng)造成了一股氣勢,開始為Visual C/C++和Delphi帶來壓力。

                   我記得當(dāng)時臺灣Sybase辦的產(chǎn)品發(fā)表會也吸引了數(shù)百人與會,不可一世。我的文章在《RUN!PC》6上發(fā)表之后,臺灣的Sybase立刻和我聯(lián)絡(luò),由當(dāng)時的余協(xié)理和我見面,也是希望我繼續(xù)為Optima++寫文章,臺灣Sybase也提供額外一字加2元稿費的待遇。但是我告訴余協(xié)理,Optima++ 1.0雖然很棒,但是仍然有一些臭蟲,而且和中文環(huán)境相沖突,無法處理中文,需要立刻解決這個問題才能夠在臺灣的市場成功。她答應(yīng)我立刻向總公司反映。我也老實地告訴她,在問題沒有解決之前,我無法寫一些不確實的東西。后來臺灣Borland的總經(jīng)理方先生也找我去詢問有關(guān)Optima++的事情,我告訴他Optima++是好東西,但是中文有問題。如果中文問題能夠解決,那么將對Borland和Microsoft的產(chǎn)品有很大的影響,當(dāng)時我還不知道Borland由于Optima++的影響,已經(jīng)開始準(zhǔn)備開發(fā)C++ Builder。

                   在1996年底左右吧,Optima++ 1.5終于進入Beta的階段。但是在我拿到Beta版時非常失望,因為中文的問題仍然沒有解決。后來臺灣Sybase又找我去,這次和我見面的是臺灣Sybase總經(jīng)理郭俊男先生,以及Sybase的新加坡技術(shù)總裁,不過我忘記這位先生的名字了。見了面之后,我立刻把Optima++ 1.5中文的問題以及許多的臭蟲告訴他們,希望他們能夠解決,如此Optima++ 1.5才能夠在中文市場成功。可是出乎我意料之外的是,他們似乎并不著急這些問題,反而詢問我是否有意愿為Sybase工作,做PowerBuilder的產(chǎn)品經(jīng)理。

                   也許是因為我為Delphi寫了太多的東西,讓PowerBuilder在臺灣受了很大的影響,因此他們希望我到Sybase工作,以打擊Delphi并且Promote PowerBuilder。當(dāng)時他們提出的待遇條件實在是非常、非常的誘人,比我當(dāng)時的薪水高出一倍左右(我當(dāng)時在資策會工作)。不過由于我對PowerBuilder實在沒有什么興趣,因此我告訴他們,如果是做Optima++的產(chǎn)品經(jīng)理,那么我將會考慮并且接受。

                   沒有想到,Sybase的新加坡技術(shù)總裁告訴我Optima++在1.5推出之后就可能會停止,因為Sybase要把資源移去為當(dāng)時愈來愈紅的Java研發(fā)一個新的Java RAD開發(fā)工具,那就是后來的PowerJ。于是他詢問我如果不愿意做PowerBuilder的產(chǎn)品經(jīng)理,那么是不是愿意做PowerJ的產(chǎn)品經(jīng)理?由于當(dāng)時我已經(jīng)知道Borland開始了Open JBuilder的研發(fā),而我對Open JBuilder的興趣遠大于PowerJ,因此沒有答應(yīng)Sybase。果然,在Optima++ 1.5推出之后,不但中文的問題沒有解決,Sybase之后也沒有繼續(xù)對Optima++研發(fā)下去。

                   Optima++一個如此有潛力的產(chǎn)品就這樣消失了,真是令人遺憾。Optima++應(yīng)該有很好的機會可以成功。我相信,如果當(dāng)時Sybase知道C++ Builder后來的成果,可能就不會放棄Optima++了,而C/C++的RAD工具一直要到后來的C++ Builder來完成這個夢。

                   至此,和Visual C/C++競爭的只有Borland的編譯器了,然而雖然后來Borland繼續(xù)推出了Borland C/C++ 5.0,但是品質(zhì)仍然不夠好,市場反應(yīng)也不佳。后來終于在Borland C/C++ 5.02之后宣布停止此條產(chǎn)品線的開發(fā),Borland C/C++的光榮歷史也就從此打住,真是令人不勝感嘆,而Visual C/C++從此在C/C++開發(fā)工具市場中再也沒有對手。不過沒有競爭的市場的確會讓人松懈,后來的Visual C/C++進步的幅度愈來愈小,MFC也數(shù)年沒有什么大進步,不像當(dāng)時和Borland C/C++競爭時每一個版本都有大幅的改善。看來寡占的市場的確是不好的,這也讓人回想起Visual C/C++、Borland C/C++、Symantec C/C++、Watcom C/C++四雄逐鹿的輝煌時代了。

            三、開源潮流

                   Watcom C/C++產(chǎn)生目標(biāo)程序的質(zhì)量還是非常讓人難忘的,這也是不少程序員(尤其是游戲程序員)青睞于這個編譯器的原因,這也促成了OpenWatcom C/C++的誕生,免費、開源,也希望很多的人使用,最新版支持C/C++/Fortran的編譯。

            posted @ 2008-07-26 11:52 幽幽 閱讀(2148) | 評論 (0)編輯 收藏
            僅列出標(biāo)題
            共6頁: 1 2 3 4 5 6 

            <2025年7月>
            293012345
            6789101112
            13141516171819
            20212223242526
            272829303112
            3456789

            常用鏈接

            留言簿(6)

            隨筆分類(35)

            隨筆檔案(51)

            文章分類(3)

            文章檔案(3)

            相冊

            我的鏈接

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            国产美女久久久| 久久美女网站免费| 模特私拍国产精品久久| 亚洲AⅤ优女AV综合久久久| 久久午夜无码鲁丝片秋霞| 久久久久久亚洲Av无码精品专口| 久久夜色tv网站| 久久久久亚洲av毛片大| 亚洲中文字幕久久精品无码喷水| 精品国产一区二区三区久久久狼| 国产精品成人99久久久久| 久久久久久精品免费看SSS| 热99re久久国超精品首页| 欧美亚洲国产精品久久高清| 国产精品美女久久久| 国产激情久久久久久熟女老人| 国产一区二区三区久久精品| 亚洲性久久久影院| 99久久国产主播综合精品| 99久久国产精品免费一区二区| 久久99热狠狠色精品一区| 久久久久波多野结衣高潮| 国产精自产拍久久久久久蜜| 国产麻豆精品久久一二三| 99精品久久精品一区二区| 日韩电影久久久被窝网| 久久精品国产亚洲av瑜伽| 久久精品视频网| 99久久精品日本一区二区免费| 国产精品久久久久免费a∨| 久久九色综合九色99伊人| 久久99精品国产99久久| 久久久久亚洲AV成人片| 国产成人精品三上悠亚久久| 午夜精品久久久久久影视777| 久久免费小视频| 大伊人青草狠狠久久| 久久99中文字幕久久| 91久久精品国产免费直播| 亚洲伊人久久大香线蕉苏妲己| 国产精品久久久久…|