第17章 內存映射文件(1)_內存映射文件簡介


17.1 操作系統的內存管理

(1)內存管理基礎

 

  ①虛擬內存函數:主要用於預留/提交/釋放虛擬內存,在虛擬內存頁上改變保護方式、鎖定虛擬內存頁,以及查詢一個進程的虛擬內存等操作,是一組位於底層的函數。

  ②堆管理函數:相對比較高級一點。Win32中的堆分為兩種,一種是進程的“默認堆”,默認堆只有一個,指的是進程可以使用的整個地址空間。一種是“私有堆”,可以隨意創意多個私有堆。也可以隨意的釋放,私有堆全部位於默認堆中。

  ③標准內存管理函數:總是在默認堆中分配和釋釋內存,這組函數是常規意義上的內存管理函數。

  ④內存映射文件:相對比較獨立,是為了文件操作的方便性而設立的,當對文件進行操作的時候,一般總是先打開文件,然后申請一塊內存用做緩沖區,再將文件數據循環讀入並處理,當文件長度大於緩沖區長度的時候需要多次讀入,每次讀入后處理緩沖區邊界位置的數據往往是個麻煩的問題。而內存映射文件是將一個文件直接映射到進程的進程空間中,這樣可以通過內存指針用讀寫內存的辦法直接存取文件內容。

(2)虛擬內存與內存映射文件的比較

  ①兩者的聯系:和虛擬內存一樣,內存映射文件可以用來保留一個進程地址區域,兩者都是將一部分內容映射到內存,另一部分放在磁盤上。但不同的是虛擬內存的后備物理存儲設備是頁交文件,而內存映射文件可以是任何的磁盤文件(如exe和dll文件本身)。

  ②虛擬內存的實現基礎是分頁機制與局部性原理。局部性原理是應用虛擬內存提升性能的主要原因,也是虛擬內存有別於內存映射文件的本質。內存映射文件是使進程地址空間的某個區域建立與磁盤文件全部或部分內容的映射,該區域可以像用指針讀寫內存的方式一樣的直接讀寫,而不必執行文件IO操作,也需要對文件進行緩沖處理

17.2內存映射文件的簡介

(1)內存映射文件的概念

  ①操作系統提供了一種機制,使應用程序能夠通過內存指針,像訪問內存一樣對磁盤上的文件進行訪問。

  ②通過內存映射文件函數可以將磁盤上文件的全部或部分映射到進程的虛擬地址空間的某個位置,一旦完成了映射,對文件內容的訪問就如同在該地址區域內直接對內存訪問一樣的簡單。這樣向文件寫入數據的操作就是直接對內存進行賦值,而從文件的某個特定位置讀取數據也就是直接從內存中取數據。

  ③當內存映射文件提供了對文件某個特定位置的直接讀寫時,真正對磁盤文件的讀寫操作是交由系統底層處理的。而且在寫操作時,數據也並非在每次操作時都即時寫入磁盤,而是底層會通過緩沖處理來提高系統的整體性能。

(2)使用內存映射文件的好處

  ①系統對所有的數據傳輸都是通過4KB大小的數據頁面來實現的,這意味着一些小文件的操作將被緩沖入一個大的頁面中(4KB),也就是首次存取文件中的某段數據時,會引發一次磁盤操作並將數據所在的一個頁面全部讀入,到以后對附近數據進行操作時,所需的數據己經被前一次頁面操作讀入到內存,無需再進行一次磁盤操作,從而提高了系統的性能

  ②當內存映射機制是以標准的內存地址形式來訪問文件數據,操作系統底層會在后台按頁面大小周期性地從磁盤讀入數據,這個過程對應用程序是完全透明的。雖然用內存映射文件最終還是要將文件從磁盤讀入內存,實質上並沒有省略掉什么操作,整體性能可能並沒有獲得什么提高。但程序的結構發生了變化,緩沖區邊界問題不復存在,而且對文件內容的更新操作是由操作系統自動完成的,操作系統會自動判斷頁面是否為臟頁面,並僅將臟頁面寫入磁盤,比程序自己將全部數據寫入文件的效率要高很多

(3)內存映射文件的實現原理

  ①虛擬內存的原理:在Windows的頁式虛擬存儲管理中,地址空間中的每個頁面給定時刻都可以是空閑、保留或提交三種狀態之一。這些頁面根據需要由操作系統交換進內存或換出內存。當內存中的某個頁面不再需要時,操作系統將釋放該頁面以供其他程序使用;當該頁面再次成為需求頁面時,它將被從物理存儲器重新讀入內存(這里的物理存儲器即可以是物理內存,也可以是磁盤上的頁文件)

  ②內存映射文件的實現基於同樣的原理,與實現虛擬內存一樣,內存映射文件保留了一個地址空間的區域,並根據需要將物理存儲器提交給該區域。(注意這里的物理存儲器是磁盤文件)

  ③Windows不僅使用內存映射文件來訪問磁盤數據,也使用它來加載和執行exe和dll,這樣可以大大節省頁文件空間和應用程序啟用運行所需的時間。對於每個進程,系統將可執行的代碼頁提交到磁盤中的可執行文件中,而數據頁含靜態數據段及動態分配的內存)被提交到虛擬內存中。(如下圖所示)。對於不同進程間共享的數據頁只要將它們提交到虛擬內存的同樣頁面就可以了。這樣,只要一個進程改變了數據頁的內容,通過分頁映射機制,其他進程的共享數據區的內容就會同時改變,因為它們實際上存儲在同一個地方。

 

 

(4)內存映射文件的3個主要用途

 ①系統加載EXE或DLL文件

  操作系統就是用它來加載exe和dll文件,運行exe。這樣可以節省頁文件空間和應用程序啟動的時間。

 ②訪問(大)數據文件

  可以通過內存映射文件來訪問磁盤上的數據文件。這樣可以避免直接使用I/O操作和對文件內容進行緩存。同時利用內存映射文件可以處理超過進程用戶區2GB大小的文件。

 ③進程間數據共享機制

  內存映射文件是多個進程共享數據最高效的方式,它也是操作系統進程通信機制的底層實現方 法。RPC、COM、OLE、DDE、窗口消息、剪貼板、管道、Socket等都是使用內存映射文件實現的。

17.3 使用內存映射文件

17.3.1 一般內存映射文件操作的步驟

  ①使用CreateFile創建文件,獲得文件句柄(hFile);//這步非必需的

  ②調用CreateFileMapping創建文件映射(可指定大小、安全屬性、保護屬性等)

  ③使用MapViewOfFile將指定文件偏移處的指定長度的區域映射到曀虛的地址空間中(並分配物理內存),該函數返回虛擬內存地址空間的指針,可以像普通內存那樣操作(注意要求地址空間中的空閑的連續區域必須足夠大)

  ④使用UnmapViewOfFile撤消文件映射區域的映射,釋放虛擬地址空間。

⑤CloseHandle關閉文件和文件映射的句柄。

17.3.2 使用內存映射文件的詳細步驟

(1)第1步:CreateFile創建或打開一個文件內核對象(只討論前3個參數)

  ①pszFileName參數:要創建或打開的文件的名稱(可包含路徑,也可不包含路徑)

  ②dwDesiredAccess參數:用來指定文件的訪問權限(對於內存映射文件,必須以只讀或讀/寫方式來打開

含義

0

不可讀寫。只能用來獲取文件的屬性

GENERIC_READ

讀取文件

GENERIC_WRITE

可寫文件

GENERIC_READ|GENERIC_WRITE

可讀、可寫

  ③dwShareMode:文件的共享模式

含義

0

其他任何試圖打開文件的操作都會失敗。

FILE_SHARE_READ

其他任何通過GENERIC_WRITE來打開文件的操作都會失敗。

FILE_SHARE_WRITE

其他任何試圖通過GENERIC_READ來打開文件的操作都會失敗

FILE_SHARE_READ | FILE_SHARE_WRITE

其他任何試圖打開文件的操作都會成功

  ④返回值:成功時會返回一個文件內核對象的句柄,失敗時返回INVALID_HANDLE_VALUE!

(2)第2步:創建文件映射內核對象——CreateFileMapping

  ①CreateFileMapping函數——文件映射對象(相當於虛擬內存,后備存儲器可以磁盤文件或“頁交換”文件!

參數

含義

HANDLE hFile

需要映射到進程地址空間的文件的句柄。如果指定為INVALID_HANDLE_VALUE時,表示創建一個無文件句柄的文件映射,這種主要用來共享內存。(注意CreateFile的失敗時的返回值為INVALID_HANDLE_VALUE。如果不加以檢查,可能會出現潛在的問題,即創建了一個共享內存)

PSECURITY_ATTRIBUTES psa

安全屬性。一般為NULL,表示使用默認的安全屬性)

DWORD fdwProtect

A、頁面的保護屬性

①PAGE_READONLY:完成映射時,可以讀取文件中的數據。但調用CreateFile時必須傳入GENERIC_READ

②PAGE_READWRITE:完成映射時,可讀取和寫入文件。調用CreateFile時傳GENERIC_READ|GENERIC_WRITE。

③PAGE_WRITECOPY:完成映射時,可以讀取或寫入文件。寫入操作時將導致系統為頁面創建一份副本。在調用CreateFile時必須傳入GENERIC_READ或GENERIC_READ|GENERIC_WRITE。

④PAGE_EXECUTE_READ:完成映射時,可讀取也可運行其中的代碼。在調用CreateFile時必須傳入GENERIC_READ和GENERIC_EXECUTE。

⑤PAGE_EXECUTE_READWRITE:完成映射時,可讀取、寫入和執行其中的代碼。調用CreateFile時必須傳入GENERIC_READ、GENERIC_WRITE和GENERICF_EXECUTE

B、將上面5種頁面屬性與段屬性按位或結合,段屬性有:

①SEC_NOCACHE:告訴系統不要對內存映射進行頁面緩存。把數據寫入文件時,會頻繁地更新磁盤文件。這標志一般不用。

②SEC_IMAGE:告訴系統要映射的是一個PE文件映像。當被映射到進程地址空間時。代碼段給PAGE_EXECUTE_READ來映射。數據段用PAGE_READWRITE映射。即將映射PE文件並給頁面設置相應的保護屬性。

③SEC_RESERVE和SEC_COMMIT:這兩個是互斥的,不能用於創建內存映射數據文件。一般用於“稀疏調撥的內存映射文件”。

④SEC_LARGE_PAGES:為內存映射文件使用大頁面內存。但必須滿 足以下條件:I:調用CreateFileMapping時指定為SEC_COMMIT。

II映射的大小必須大於GetLargePageMinimum函數的返回值。III必須用PAGE_READWRITE保護屬性定義映射。IV用戶必須啟用內存中“鎖定內存頁”的用戶權限。

DWORD dwMaximumSizeHigh

內存映射文件的最大大小,以字節為單位用64位來表示。其中High為高32位,Low為低32位。對於小於4GB文件High始終為0.

②如果要用當前文件的大小來創建內存映射文件,就給這兩個參數都傳入0。

③如果想給文件追加大小,這時最大大小應留有余地。如果當前磁盤上的文件大小為0字節,就不能傳兩個0,因為這相當於告訴系統要創建一個大小為0的文件映射。函數會調用失敗,返回NULL

DWORD dwMaximumSizeLow

PCTSTR pszName

以0為終止符的字符串,用來給文件映射對象指定一個名稱。一般用於進程間共享文件映射內核對象的。

備注:①返回值為文件映射的對象句柄。無法創建時,返回NULL

②當CreateFile失敗時返回的是INVALID_HANDLE_VALUE(-1),而CreateFileMapping失敗時返回的是NULL,不要將這兩個錯誤碼搞混了

(3)第3步:將文件的數據映射到進程地址空間

  ①MapViewOfFile函數

參數

含義

hFileMappingObject

文件映射對象的句柄,由CreateFileMapping或OpenFileMapping函數返回而得

DWORD dwDesiredAccess

保護屬性

①FILE_MAP_WRITE:可讀、可寫。在調用CreateFileMapping時必須傳為PAGE_READWRITE保護屬性。

②FILE_MAP_READ:可讀。在調用CreateFileMapping時可以傳入PAGE_READONLY或PAGE_READWRITE

③FILE_MAP_ALL_ACCESS:等於同FILE_MAP_WRITE|FILE_MAP_READ| FILE_MAP_COPY

④FILE_MAP_COPY:可讀、可寫。寫入時會為該頁面創建一份副本,在調用CreateFileMapping時必須傳入PAGE_WRITECOPY。此時會從頁文件中調撥物理存儲器,但寫入頁面以后,頁面屬性會被改為PAGE_READWRITE。

⑤FILE_MAP_EXECUTE:將文件中的數據作為代碼來執行。在調用CreateFileMapping時可以傳入PAGE_EXECUTE_READWRITE或PAGE_EXECUTE_READ保護屬性。

DWORD dwFileOffsetHigh

映射時,可以只將文件的一部分映射到進程的地址空間。(這部分被稱為視圖View)

這個Offset指的是將文件中的哪個字節映射到視圖中的第1個字節。(用64位來表示一個偏移,但這個偏移必須是系統分配粒度的整數倍。在Windows中,即為64KB的整數倍。)

DWORD dwFileOffsetLow

SIZE_T

 dwNumberOfBytesToMap

要把數據文件中的多少字節映射到進程的地址空間。如果指定為0,系統會從偏移量開始到文件末尾的所有部分都映射到視圖中

(4)第4步:從進程的地址空間撤銷對文件數據的映射

  ①BOOL UnmapViewOfFile(PVOID pvBaseAddress)——撤銷映射,釋放區域

    A、pvBaseAddress為區域的基地址,必須與MapViewOfFile返回值相同

    B、UnmapViewOfFile的一個特征是,如果視圖最初用FILE_MAP_COPY來映射,那么對文件數據的任何修改實際上是對保存在頁交換文件中的文件數據副本的修改。如果在這種情況下調用UnmapViewOfFile,函數不需要對磁盤文件進行任何更新,但它會釋放頁交換文件中的頁面,從而導致數據丟失,用了保留被修改過的數據,必須進行額外操作,如為同一個文件用PAGE_READWRITE標志創建另一個文件映射對象,然后在第1個視圖中查找具有PAGE_READWRITE保護屬性的頁面,將修改過的數據寫入文件。

    ②FlushViewOfFile:將緩存的頁面寫入磁盤

參數

含義

PVOID pvAddress

內存映射文件的視圖中第1個字節的地址,函數會把傳入的地址向下取整到頁面的整數倍。

SIZE_T dwNumberOfBytesToFlush

要刷新的字節數,系統會向上取整,使總字節數為頁面大小的整數倍。

備注:如果內存映射文件的物理存儲器來自網絡,那么FlushViewOfFile只保證從當前工作站寫入文件數據,並無法保證遠端的共享文件的服務器也會把數據寫入到磁盤中。為了確保服務器也會把數據寫入磁盤,應傳FILE_FLAG_WRITE_THROUGH標志給CreateFile

(5)關閉文件映射對象和文件對象

HANDLE hFile=CreateFile(…);

HANDLE hFileMapping=CreateFileMapping(hFile,…);

CloseHandle(hFile); //因MapViewOfFile函數的副作用,在此關閉,可消除潛在的資源泄漏。

 
PVOID pvFile=MapViewOfFile(hFileMapping,…); //該函數會增加對hFile和hFileMapping對象的引用計數(該函數的副作用)。

CloseHandle(hFileMapping);


 //使用內存映射文件

 UnmapViewOfFile(pvFile);

【FileReverse程序】使用內存映射文件把文本文件(ANSI或Unicode)的內容顛倒過來

  ①IsTextUnicode函數用來判斷文件是ANSI或Unicode格式

  ②_strrev是C運行庫的函數,用來將以\0字符結束的字符串反轉過來。

  ③文本文件並不是以\0結尾,所以需在文件未尾追加一個\0.

  ④每行未尾的\r\n也會被反轉成\n\r,因此必須轉回\r\n的順序。

  ⑤文件內容顛倒之后,必須將先前在文件未尾追加的\0去掉。(只需調用SetEndOfFile)

//FileRev.cpp

/************************************************************************
Module:  FileRev.cpp
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/
#include "../../CommonFiles/CmnHdr.h"
#include "resource.h"
#include <tchar.h>
#include <string.h>  //For _strrev

//////////////////////////////////////////////////////////////////////////
#define FILENAME TEXT("FileRev.dat")

//////////////////////////////////////////////////////////////////////////
BOOL FileReverse(PCTSTR pszPathname, PBOOL pbIsTextUnicode){
    *pbIsTextUnicode = FALSE; 
    
    //打開文件
    HANDLE hFile = CreateFile(pszPathname, GENERIC_WRITE | GENERIC_READ, 0, NULL,
                              OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

    if (hFile == INVALID_HANDLE_VALUE){
        chMB("無法打開文件");
        return FALSE;
    }

    //獲取文件大小
    DWORD dwFileSize = GetFileSize(hFile, NULL);

    //創建文件映射對象(該對象比原文件多1個字符,以便在文件結尾處放一個\0.
    //因為不知道文件是Unicode還是Ansi,所以這里假定最壞的情況,增加一個WCHAR
    //型的\0,而不是CHAR型
    HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0,
                                        dwFileSize + sizeof(WCHAR), NULL);
    
    if (hFileMap == NULL){
        chMB("無法打開文件!");
        CloseHandle(hFile);
        return FALSE;    
    }

    //創建文件視圖
    PVOID pvFile = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0);

    if (pvFile == NULL){
        chMB("無法創建文件視圖!");
        CloseHandle(hFileMap);
        CloseHandle(hFile);
        return FALSE;
    }

    //判斷文件是ANSI或Unicode
    int iUnicodeTestFlags = -1;  //測試所有項目
    *pbIsTextUnicode = IsTextUnicode(pvFile, dwFileSize, &iUnicodeTestFlags);

    if (!*pbIsTextUnicode){
        //ANSI,以下都是用ANSI版的函數來處理ANSI文件
        
        //將\0放入文件結尾處
        PSTR pchANSI = (PSTR)pvFile;
        pchANSI[dwFileSize / sizeof(CHAR)] = 0;

        //反轉文件的內容
        _strrev(pchANSI);

        //將所有的“\n\r”轉回“\r\n”。
        pchANSI = strstr(pchANSI, "\n\r"); //查找第一個"\n\r"

        while (pchANSI != NULL)
        {
            *pchANSI++ = '\r';
            *pchANSI++ = '\n';
            pchANSI = strstr(pchANSI, "\n\r"); //查找下一個"\n\r"
        }    
    } else{ //UNICODE
        //UNICODE,以下都是用ANSI版的函數來處理UNICODE文件

        //將\0放入文件結尾處
        PWSTR pchUnicode = (PWSTR)pvFile;
        pchUnicode[dwFileSize / sizeof(WCHAR)] = 0;
        
        if ((iUnicodeTestFlags & IS_TEXT_UNICODE_SIGNATURE)!=0){
            //如果第1個字符是Unicode字節序BOM
            //0xFEFF,則跳過該字符
            pchUnicode++;
        }

        //反轉文本的內容
        _wcsrev(pchUnicode);

        //將"\n\r"轉回"\r\n"
        pchUnicode = wcsstr(pchUnicode, L"\n\r"); //查找第1個"\n\r"

        while (pchUnicode != NULL){
            *pchUnicode++ = L'\r';
            *pchUnicode++ = L'\n';
            pchUnicode = wcsstr(pchUnicode, L"\n\r"); //查找下一個"\n\r"
        }
    }

    //撤消文件映射
    UnmapViewOfFile(pvFile);
    CloseHandle(hFileMap);

    //刪除先前在文件結尾處的添加的\0字符
    SetFilePointer(hFile, dwFileSize, NULL, FILE_BEGIN);
    SetEndOfFile(hFile);
    CloseHandle(hFile);

    return TRUE;
}

//////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam){
    
    chSETDLGICONS(hwnd, IDI_FILEREV);

    //禁用“反轉”按鈕
    EnableWindow(GetDlgItem(hwnd, IDC_REVERSE), FALSE);
    return TRUE;
}

//////////////////////////////////////////////////////////////////////////

void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify){
    TCHAR szPathName[MAX_PATH];
    switch (id)
    {
    case IDCANCEL:
        EndDialog(hwnd, id);
        break;

    case IDC_FILENAME:  //編輯框內容
        EnableWindow(GetDlgItem(hwnd, IDC_REVERSE), Edit_GetTextLength(hwndCtl) > 0);
        break;

    case IDC_REVERSE:
        GetDlgItemText(hwnd, IDC_FILENAME, szPathName, _countof(szPathName));

        //復制一份文件(副本)
        if (!CopyFile(szPathName,FILENAME,FALSE)){
            chMB("無法創建新文件!");
            break;    
        }

        BOOL bIsTextUnicode;
        if (FileReverse(FILENAME,&bIsTextUnicode)){
            SetDlgItemText(hwnd, IDC_TEXTTYPE,
                           bIsTextUnicode ? TEXT("Unicode") : TEXT("ANSI"));

            //打開記事本查看轉換后的文件
            STARTUPINFO si = { sizeof(si) };
            PROCESS_INFORMATION pi;
            TCHAR sz[] = TEXT("Notepad ")FILENAME;
            if (CreateProcess(NULL,sz,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi)){
                CloseHandle(pi.hThread);
                CloseHandle(pi.hProcess);        
            }
        
        }


        break;

    case IDC_FILESELECT:
        OPENFILENAME ofn = { OPENFILENAME_SIZE_VERSION_400 };
        ofn.hwndOwner = hwnd;
        ofn.lpstrFile = szPathName;
        ofn.lpstrFile[0] = 0;
        ofn.nMaxFile = _countof(szPathName);
        ofn.lpstrFileTitle = TEXT("選擇要反轉的文件");
        ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST;
        GetOpenFileName(&ofn);
        SetDlgItemText(hwnd, IDC_FILENAME, ofn.lpstrFile);
        SetFocus(GetDlgItem(hwnd, IDC_REVERSE));
        break;
    }
}

//////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
    switch (uMsg)
    {
        chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
        chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
    }
    return FALSE;
}
//////////////////////////////////////////////////////////////////////////

int WINAPI _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nShowCmd)
{
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_FILEREV), NULL, Dlg_Proc);
    return 0;
}
////////////////////////////////文件結束/////////////////////////////////

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 17_FileRev.rc 使用
//
#define IDD_FILEREV                     1
#define IDC_FILESELECT                  101
#define IDI_FILEREV                     102
#define IDC_FILENAME                    1000
#define IDC_REVERSE                     1001
#define IDC_TEXTTYPE                    1002

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//FileRev.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// 中文(簡體,中國) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_FILEREV DIALOGEX 15, 24, 204, 46
STYLE DS_SETFONT | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "反轉文件內容"
FONT 10, "宋體", 400, 0, 0x0
BEGIN
    LTEXT           "文件:",IDC_STATIC,6,6,20,8
    EDITTEXT        IDC_FILENAME,29,4,170,12,ES_AUTOHSCROLL
    PUSHBUTTON      "瀏覽...",IDC_FILESELECT,4,17,36,12,WS_GROUP
    DEFPUSHBUTTON   "反轉文件內容",IDC_REVERSE,4,32,80,12
    LTEXT           "文件中的字符類型:",IDC_STATIC,88,34,80,8
    LTEXT           "(未知)",IDC_TEXTTYPE,155,34,34,8
END


/////////////////////////////////////////////////////////////////////////////
//
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_FILEREV             ICON                    "FileRev.ico"

/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_FILEREV, DIALOG
    BEGIN
    END
END
#endif    // APSTUDIO_INVOKED

#endif    // 中文(簡體,中國) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM