![]() |
Wyjście Spis treści Poprzedni Następny
Autor:
©Iczelion |
©2008 mgr
Jerzy Wałaszek
|
Na tej lekcji pokażę, czym są pliki odwzorowane w pamięci
(memory mapped files) oraz jak z nich korzystać
dla własnych korzyści. Zobaczysz w dalszej części, iż ich wykorzystanie
jest zupełnie proste.

Pobierz plik z przykładem {z tego archiwum}.
Jeśli dokładnie przestudiujesz przykład podany w poprzednim rozdziale, to zauważysz, iż posiada on poważne ograniczenie: co będzie, jeśli odczytywany plik jest większy niż przydzielony blok pamięci? Albo co się stanie, jeśli wyszukiwany przez ciebie fragment tekstu zostanie obcięty na końcu bloku pamięci? Tradycyjna odpowiedź na pierwsze pytanie brzmi: powinieneś w kółko odczytywać dane z pliku, aż napotkasz jego koniec. Odpowiedź na drugie pytanie brzmi: powinieneś być przygotowany na specjalny przypadek na końcu bloku pamięci. Fachowo nazywa się to problemem wartości granicznej (boundary value problem). Powoduje on ból głowy u programistów i jest przyczyną niezliczonych błędów.
Byłoby miło móc przydzielić sobie bardzo duży blok pamięci, wystarczający do pomieszczenia całego pliku, lecz wtedy nasz program pochłonąłby olbrzymią ilość zasobów komputera. Na ratunek przychodzi odwzorowanie plików (file mapping). Używając odwzorowania plików możesz potraktować cały plik tak, jakby już znajdował się w pamięci i skorzystać ze wskazania pamięci do odczytu i zapisu danych w pliku. To jest właśnie tak proste. Nie ma już potrzeby stosowania funkcji API obsługujących pamięć i oddzielnych funkcji plikowego wejścia/wyjścia, przy odwzorowaniu pliku są one jednym i tym samym. Odwzorowanie plików jest również używane przy współdzieleniu danych z innymi procesami. Użycie tej technologii w ten sposób oznacza, iż w rzeczywistości nie jest tutaj zaangażowany żaden plik. To bardziej jak zarezerwowany blok pamięci, który jest widoczny dla każdego procesu. Jednakże dzielenie danych pomiędzy procesami jest delikatną sprawą, której nie wolno traktować lekko. Należy zaimplementować synchronizacje procesu i wątku, w przeciwnym razie twoja aplikacja bardzo szybko zakończy swoje działanie.
W tym kursie nie poruszymy tematu odwzorowania pliku jako środka tworzenia wspólnego obszaru pamięci. Skoncentrujemy się na sposobie wykorzystania odwzorowania pliku do umieszczenia go w pamięci. W rzeczy samej program ładujący wykorzystuje odwzorowanie pliku do załadowania plików wykonywalnych do pamięci. Jest to bardzo wygodne, ponieważ tylko niezbędne fragmenty będą odczytywane z pliku na dysku. W systemie Win32 powinieneś korzystać z tej techniki tak często, jak to możliwe.
Jednakże istnieją pewne ograniczenia przy odwzorowywaniu plików. Gdy zostanie już utworzony plik odwzorowany w pamięci, jego rozmiar nie może być zmieniony w danej sesji. Zatem odwzorowanie plików jest wspaniałe dla plików tylko do odczytu lub operacji plikowych nie zmieniających rozmiaru pliku. Nie oznacza to, iż nie możesz stosować odwzorowania pliku, gdy chcesz zwiększyć jego rozmiar. Możesz oszacować nowy rozmiar i utworzyć plik odwzorowany w pamięci na podstawie tego nowego rozmiaru, a rozmiar pliku wzrośnie. Jest to tylko pewna niewygoda, to wszystko.
Dosyć wyjaśnień. Zanurzmy się w implementację odwzorowania pliku. Oto niezbędne kroki:
Program wylistowany poniżej pozwala ci otworzyć plik
poprzez okno dialogowe otwierania pliku. Otwiera on wybrany plik
wykorzystując odwzorowanie pliku i jeśli mu się to powiedzie, to napis
na pasku tytułowym zmieni się na nazwę otwartego pliku. Możesz zapisać
ten plik pod inną nazwą wybierając z menu opcję
Plik/Zapisz Jako. Program skopiuje całą zawartość otwartego pliku
do nowego pliku. Zwróć uwagę, iż w tym programie nie musisz wywoływać
funkcji GlobalAlloc
do przydzielenia bloku pamięci.
Plik FILEMAP.ASM
.386
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
WinMain PROTO :DWORD, :DWORD, :DWORD, :DWORD
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\user32.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDE \masm32\include\comdlg32.inc
INCLUDELIB \masm32\lib\user32.lib
INCLUDELIB \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\comdlg32.lib
.CONST
IDM_OPEN EQU 1
IDM_SAVE EQU 2
IDM_EXIT EQU 3
MAXSIZE EQU 260
.DATA
ClassName DB "Win32ASMFileMappingClass", 0
AppName DB "Pliki odwzorowane w pamięci", 0
MenuName DB "FirstMenu", 0
ofn OPENFILENAME <>
FilterString DB "Wszystkie pliki (*.*)", 0, "*.*", 0
DB "Pliki tekstowe (*.txt)", 0, "*.txt", 0, 0
buffer DB MAXSIZE DUP(0)
hMapFile HANDLE 0
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hFileRead HANDLE ?
hFileWrite HANDLE ?
hMenu HANDLE ?
pMemory DWORD ?
SizeWritten DWORD ?
.CODE
start:
INVOKE GetModuleHandle, NULL
mov hInstance, eax
INVOKE GetCommandLine
mov CommandLine, eax
INVOKE WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT
INVOKE ExitProcess, eax
WinMain PROC hInst: HINSTANCE,\
hPrevInst: HINSTANCE,\
CmdLine: LPSTR,\
CmdShow: DWORD
LOCAL wc: WNDCLASSEX
LOCAL msg: MSG
LOCAL hwnd: HWND
mov wc.cbSize, SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW OR CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra, NULL
mov wc.cbWndExtra, NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground, COLOR_WINDOW+1
mov wc.lpszMenuName, OFFSET MenuName
mov wc.lpszClassName, OFFSET ClassName
INVOKE LoadIcon, NULL, IDI_APPLICATION
mov wc.hIcon, eax
mov wc.hIconSm, eax
INVOKE LoadCursor, NULL, IDC_ARROW
mov wc.hCursor, eax
INVOKE RegisterClassEx, ADDR wc
INVOKE CreateWindowEx, WS_EX_CLIENTEDGE,\
ADDR ClassName, ADDR AppName,\
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,\
CW_USEDEFAULT, 300, 200, NULL, NULL,\
hInst, NULL
mov hwnd, eax
INVOKE ShowWindow, hwnd, SW_SHOWNORMAL
INVOKE UpdateWindow, hwnd
.WHILE TRUE
INVOKE GetMessage, ADDR msg, NULL, 0, 0
.BREAK .IF (!eax)
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
.ENDW
mov eax, msg.wParam
ret
WinMain ENDP
WndProc PROC hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
INVOKE GetMenu, hWnd
mov hMenu, eax
mov ofn.lStructSize, SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile, MAXSIZE
.ELSEIF uMsg==WM_DESTROY
.IF hMapFile!=0
call CloseMapFile
.ENDIF
INVOKE PostQuitMessage, NULL
.ELSEIF uMsg==WM_COMMAND
mov eax, wParam
.IF lParam==0
.IF ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST OR \
OFN_PATHMUSTEXIST OR \
OFN_LONGNAMES OR \
OFN_EXPLORER OR \
OFN_HIDEREADONLY
INVOKE GetOpenFileName, ADDR ofn
.IF eax==TRUE
INVOKE CreateFile, ADDR buffer,\
GENERIC_READ,\
0, NULL,\
OPEN_EXISTING,\
FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileRead, eax
INVOKE CreateFileMapping, hFileRead,\
NULL, PAGE_READONLY,\
0, 0, NULL
mov hMapFile, eax
mov eax, OFFSET buffer
movzx edx, ofn.nFileOffset
add eax, edx
INVOKE SetWindowText, hWnd, eax
INVOKE EnableMenuItem, hMenu, IDM_OPEN, MF_GRAYED
INVOKE EnableMenuItem, hMenu, IDM_SAVE, MF_ENABLED
.ENDIF
.ELSEIF ax==IDM_SAVE
mov ofn.Flags, OFN_LONGNAMES OR \
OFN_EXPLORER OR OFN_HIDEREADONLY
INVOKE GetSaveFileName, ADDR ofn
.IF eax==TRUE
INVOKE CreateFile, ADDR buffer,\
GENERIC_READ OR GENERIC_WRITE,\
FILE_SHARE_READ OR FILE_SHARE_WRITE,\
NULL, CREATE_NEW,\
FILE_ATTRIBUTE_ARCHIVE, NULL
mov hFileWrite, eax
INVOKE MapViewOfFile, hMapFile, FILE_MAP_READ, 0, 0, 0
mov pMemory, eax
INVOKE GetFileSize, hFileRead, NULL
INVOKE WriteFile, hFileWrite,\
pMemory,\
eax,\
ADDR SizeWritten,\
NULL
INVOKE UnmapViewOfFile, pMemory
call CloseMapFile
INVOKE CloseHandle, hFileWrite
INVOKE SetWindowText, hWnd, ADDR AppName
INVOKE EnableMenuItem, hMenu, IDM_OPEN, MF_ENABLED
INVOKE EnableMenuItem, hMenu, IDM_SAVE, MF_GRAYED
.ENDIF
.ELSE
INVOKE DestroyWindow, hWnd
.ENDIF
.ENDIF
.ELSE
INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.ENDIF
xor eax, eax
ret
WndProc ENDP
CloseMapFile PROC
INVOKE CloseHandle, hMapFile
mov hMapFile, 0
INVOKE CloseHandle, hFileRead
ret
CloseMapFile ENDP
END start
Plik FILEMAP.RC
// Stałe dla menu
#define IDM_OPEN 1
#define IDM_SAVE 2
#define IDM_EXIT 3
FirstMenu MENU
{
POPUP "&Plik"
{
MENUITEM "&Otwórz...", IDM_OPEN
MENUITEM "&Zapisz Jako...", IDM_SAVE, GRAYED
MENUITEM SEPARATOR
MENUITEM "&Koniec", IDM_EXIT
}
}
INVOKE CreateFile, ADDR buffer,\
GENERIC_READ,\
0, NULL,\
OPEN_EXISTING,\
FILE_ATTRIBUTE_ARCHIVE,\
NULL
Gdy użytkownik wybierze plik w oknie dialogowym otwierania pliku, wywołujemy
CreateFile, aby go otworzyć. Zwróć uwagę, iż przy otwieraniu pliku określamy
znacznik GENERIC_READ dla dostępu tylko do odczytu oraz
zerujemy parametr dwShareMode, ponieważ nie życzymy
sobie, aby inny proces modyfikował nam dane podczas tej operacji.
INVOKE CreateFileMapping, hFileRead,\
NULL, PAGE_READONLY,\
0, 0, NULL
Następnie wywołujemy CreateFileMapping w celu utworzeniu
odwzorowanego w pamięci pliku z pliku otwartego.
CreateFileMapping posiada następującą składnię:
CreateFileMapping PROTO hFile: DWORD,\
lpFileMappingAttributes: DWORD,\
flProtect: DWORD,\
dwMaximumSizeHigh: DWORD,\
dwMaximumSizeLow: DWORD,\
lpName: DWORD
Powinieneś najpierw wiedzieć, iż funkcja CreateFileMapping
nie musi odwzorowywać całego pliku w pamięci. Możesz wykorzystać tę funkcję od
odwzorowania w pamięci jedynie fragmentu pliku. Rozmiar pliku odwzorowanego w
pamięci określasz przy pomocy parametrów dwMaximumSizeHigh
oraz
dwMaximumSizeLow. Jeśli określisz rozmiar większy
od rzeczywistego pliku, to plik ten zostanie powiększony do nowego rozmiaru.
Jeśli nie chcesz zmieniać rozmiaru pliku, to ustaw oba wspomniane parametry na
zero.
Możesz podać wartość NULL w parametrze lpFileMappingAttributes, aby pozwolić systemowi Windows utworzyć standardowe atrybuty zabezpieczeń dla pliku odwzorowanego w pamięci.
flProtect określa wymaganą ochronę dla pliku odwzorowanego w pamięci. W naszym przykładzie stosujemy znacznik PAGE_READONLY, który zezwala jedynie na odczyt pliku odwzorowanego w pamięci. Zwróć uwagę, aby atrybut ten nie był w sprzeczności z atrybutem użytym w wywołaniu funkcji CreateFile, w przeciwnym razie funkcja CreateFileMapping zawiedzie.
lpName wskazuje nazwę pliku odwzorowanego w pamięci. Jeśli chcesz dzielić się tym plikiem z innymi procesami, to musisz nadać mu nazwę. Jednakże w naszym przykładzie nasz proces jest jedynym, który używa tego pliku, więc parametr ignorujemy.
mov eax, OFFSET buffer
movzx edx, ofn.nFileOffset
add eax, edx
INVOKE SetWindowText, hWnd, eax
Jeśli wywołanie funkcji CreateFileMapping
powiedzie się, zmieniamy tytuł okna na nazwę otwartego pliku. Nazwa pliku
wraz z pełną ścieżką do niego umieszczona jest w buforze, lecz my chcemy
wyświetlić na pasku tytułowym okna samą nazwę pliku, zatem musimy dodać wartość
pola nFileOffset struktury
OPENFILENAME do adresu bufora.
INVOKE EnableMenuItem, hMenu, IDM_OPEN, MF_GRAYED
INVOKE EnableMenuItem, hMenu, IDM_SAVE, MF_ENABLED
Dla zabezpieczenia nie chcemy, aby użytkownik otwierał kilka plików na raz,
zatem blokujemy element menu Otwórz, a uaktywniamy
element menu Zapisz. Do zmiany atrybutu elementu menu
wykorzystujemy funkcję EnableMenuItem.
Po tej czynności czekamy, aż użytkownik wybierze element menu Plik/Zapisz lub zamknie nasz program. Jeśli użytkownik zdecyduje się zamknąć program, musimy zamknąć plik odwzorowany w pamięci oraz plik na dysku, co wykona poniższy kod:
.ELSEIF uMsg==WM_DESTROY
.IF hMapFile!=0
call CloseMapFile
.ENDIF
INVOKE PostQuitMessage, NULL
W powyższym fragmencie programu gdy procedura okna otrzyma wiadomość
WM_DESTROY, sprawdza ona wartość zmiennej
hMapFile (zawierającej uchwyt do
pliku odwzorowanego w pamięci) i jeśli nie zawiera ona zera, wywołuje
funkcję CloseMapFile zawierającą następujący kod:
CloseMapFile PROC
INVOKE CloseHandle, hMapFile
mov hMapFile, 0
INVOKE CloseHandle, hFileRead
ret
CloseMapFile ENDP
CloseMapFile zamyka plik odwzorowany w pamięci i plik na dysku, zatem nie
będzie wycieku zasobów, gdy nasz program wyjdzie do systemu
Windows.
Jeśli użytkownik wybierze opcję zapisu danych do innego pliku, program zaprezentuje mu okienko dialogowe zapisu pod inną nazwą. Gdy wpisze on nazwę nowego pliku, plik ten zostanie utworzony przez funkcję CreateFile.
INVOKE MapViewOfFile, hMapFile, FILE_MAP_READ, 0, 0, 0
mov pMemory, eax
Bezpośrednio po utworzeniu pliku wyjściowego wywołujemy
MapViewOfFile, aby odwzorować pożądaną część pliku
w pamięci. Funkcja ta posiada następującą składnię:
MapViewOfFile PROTO hFileMappingObject: DWORD,\
dwDesiredAccess: DWORD,\
dwFileOffsetHigh: DWORD,\
dwFileOffsetLow: DWORD,\
dwNumberOfBytesToMap: DWORDPo wywołaniu MapViewOfFile żądana część pliku zostaje załadowana do pamięci. Otrzymasz wskazanie bloku pamięci zawierającego dane z pliku.
INVOKE GetFileSize, hFileRead, NULL
Dowiadujemy się ile wynosi długość pliku. Rozmiar pliku zwrócony zostanie w
rejestrze eax. Jeśli plik jest większy od 4GB, starsze podwójne słowo rozmiaru
pliku jest umieszczone w FileSizeHighWord. Ponieważ nie
będziemy obsługiwać tak dużych plików, zatem możemy je zignorować.
INVOKE WriteFile, hFileWrite,\
pMemory,\
eax,\
ADDR SizeWritten,\
NULL
Zapisujemy dane odwzorowane w pamięci do pliku wyjściowego.
INVOKE UnmapViewOfFile, pMemory
Gdy skończymy z plikiem wejściowym, usuwamy z pamięci jego odwzorowanie.
call CloseMapFile
INVOKE CloseHandle, hFileWrite
I zamykamy wszystkie pliki.
INVOKE SetWindowText, hWnd, ADDR AppName
Odtwarzamy pierwotny tytuł okna.
INVOKE EnableMenuItem, hMenu, IDM_OPEN, MF_ENABLED
INVOKE EnableMenuItem, hMenu, IDM_SAVE, MF_GRAYED
Uaktywniamy element menu Otwórz, a blokujemy element
Zapisz Jako.
Ta sama aplikacja w Pascalu:
{********************************
** I Liceum Ogólnokształcące **
** w Tarnowie **
** mgr Jerzy Wałaszek **
********************************}
program FileMap;
uses Windows;
const
IDM_OPEN = 1;
IDM_SAVE = 2;
IDM_EXIT = 3;
MAXSIZE = 260;
ClassName = 'Win32ASMFileMappingClass';
AppName = 'Pliki odwzorowane w pamięci';
MenuName = 'FirstMenu';
FilterString = 'Wszystkie pliki (*.*)'#0'*.*'#0'Pliki tekstowe (*.txt)'#0'*.txt'#0#0;
var
ofn : OPENFILENAME;
buffer : array[0..MAXSIZE] of byte;
hMapFile : HANDLE;
hInstance : HINST;
hFileRead : HANDLE;
hFileWrite : HANDLE;
hMenu : HANDLE;
pMemory : pointer;
SizeWritten : longint;
procedure CloseMapFile;
begin
CloseHandle(hMapFile);
hMapFile := 0;
CloseHandle(hFileRead);
end;
function WndProc(hWnd:HWND;uMsg:UINT;wParam:WPARAM;lParam:LPARAM) : longint;
begin
WndProc := 0;
case uMsg of
WM_CREATE :
begin
hMenu := GetMenu(hWnd);
ofn.lStructSize := sizeof(ofn);
ofn.hWndOwner := hWnd;
ofn.hInstance := hInstance;
ofn.lpstrFilter := FilterString;
ofn.lpstrFile := @buffer;
ofn.nMaxFile := MAXSIZE;
end;
WM_DESTROY :
begin
if hMapFile = 0 then CloseMapFile;
PostQuitMessage(0);
end;
WM_COMMAND:
begin
if lParam = 0 then
case (wParam and $ffff) of
IDM_OPEN :
begin
ofn.Flags := OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or
OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY;
if GetOpenFileName(@ofn) then
begin
hFileRead := CreateFile(@buffer,GENERIC_READ,0,0,
OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,0);
hMapFile := CreateFileMapping(hFileRead,0,PAGE_READONLY,0,0,0);
SetWindowText(hWnd,@buffer[ofn.nFileOffset]);
EnableMenuItem(hMenu,IDM_OPEN,MF_GRAYED);
EnableMenuItem(hMenu,IDM_SAVE,MF_ENABLED);
end;
end;
IDM_SAVE :
begin
ofn.Flags := OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY;
if GetSaveFileName(@ofn) then
begin
hFileWrite := CreateFile(@buffer,GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE,
0,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,0);
pMemory := MapViewOfFile(hMapFile,FILE_MAP_READ,0,0,0);
WriteFile(hFileWrite,pMemory^,GetFileSize(hFileRead,0),
SizeWritten,0);
UnmapViewOfFile(pMemory);
CloseMapFile;
CloseHandle(hFileWrite);
SetWindowText(hWnd,AppName);
EnableMenuItem(hMenu,IDM_OPEN,MF_ENABLED);
EnableMenuItem(hMenu,IDM_SAVE,MF_GRAYED);
end;
end;
else DestroyWindow(hWnd);
end;
end;
else WndProc := DefWindowProc(hWnd,uMsg,wParam,lParam);
end;
end;
function WinMain(hInst,hPrevInst:HINST;CmdLine:LPSTR;CmdShow:DWORD) : longint;
var
wc : WNDCLASSEX;
msg : MSG;
hwnd : HWND;
begin
wc.cbSize := sizeof(WNDCLASSEX);
wc.style := CS_HREDRAW or CS_VREDRAW;
wc.lpfnWndProc := @WndProc;
wc.cbClsExtra := 0;
wc.cbWndExtra := 0;
wc.hInstance := hInst;
wc.hbrBackground := COLOR_WINDOW + 1;
wc.lpszMenuName := MenuName;
wc.lpszClassName := ClassName;
wc.hIcon := LoadIcon(0,IDI_APPLICATION);
wc.hIconSm := wc.hIcon;
wc.hCursor := LoadCursor(0,IDC_ARROW);
RegisterClassEx(wc);
hwnd := CreateWindowEx(WS_EX_CLIENTEDGE,ClassName,AppName,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,300,200,0,0,hInst,0);
ShowWindow(hwnd,SW_SHOWNORMAL);
UpdateWindow(hwnd);
while GetMessage(msg,0,0,0) do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
WinMain := msg.wParam;
end;
begin
hInstance := GetModuleHandle(0);
ExitProcess(WinMain(hInstance,0,GetCommandLine,SW_SHOWDEFAULT));
end.
|
Autorem
kursu jest
Iczelion. Kurs
programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną
zgodą autora. |
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe