Hex Viewer (GUI)
Я решил написать цикл статей на тему принципов создания простейшего HEX просмотрщика файлов. Не знаю точно, сколько будет статей в конечном итоге, поскольку сами статьи я пишу параллельно написанию кода. И да, не нужно ждать, что в статьях будет сделан упор на широкую функциональность или дизайн. Наш просмотрщик будет способен только открывать и читать файлы любых размеров, ну и конечно отображать их в нашем окне.
Начнем :)
Давайте возьмем программу "Блокнот" и попробуем туда загрузить файлик ну скажем размером 100 МБ. Попробовали? Сколько времени прошло, пока файл загрузился? Наверное успели налить чайку и даже больше. А теперь возьмем любой популярный HEX редактор и загрузим в него файл размером 1-2 ГБ (~1000-2000 МБ). Не успели даже моргнуть, а файл загружен. При чем, размер файла может быть на порядок большим. Как же так? Все дело в том, что текстовые редакторы (типа "Блокнот") грузят полностью все данные из файла в память. В принципе, для текстовых редакторов, функция которых лежит в редактировании небольших файлов, данный метод вполне подходит. Как же происходит загрузка огромных файлов в бинарные редакторы? В данных программах используется метод чтения выборочного небольшого участка памяти , которого с гарантией хватает для просмотра в окне редактора. Само же чтение файлов чаще всего осуществляется при помощи таких инструментов как Memory Mapped файлы. Сами по себе мэп-файлы (Memory Mapped файлы) ,при проецировании файлов в память, предоставляют указатель на любой участок загружаемого файла\участка файла, точно так же как указатель на любой адрес памяти. То есть мы с файлом работаем практически так же, как с памятью. При работе с мэп-файлами, нам необходимо проецировать файл в память при помощи функций (CreateFile и CreateFileMapping). А далее все что нам нужно, это вовремя получать указатель на нужный нам сегмент проекции памяти при помощи функции MapViewOfFile, и дальше читать данные относительно этого указателя, в рамках этого сегмента. Если нужны данные вне рамок данного сегмента, просто освобождаем указатель на старый сегмент при помощи UnmapViewOfFile и получаем новый. Мэп-файлы со своим простым и произвольным доступом к любому участку файла, позволяет быстро и легко перемещаться по файлу и читать. Именно эти функции я и буду использовать в HEX просмотрщике.
И так, что же нам будет нужно для написания простого просмотрщика файлов
в виде HEX значений:
1) Создать главное окно
2) Создать свой класс дочернего окна, которое
будет рисовать данные
3) Написать модуль загрузки данных посредством Memory
Mapped файлов
Приступим!
Вот код создания главного окна:
#INCLUDE "windows.bi" #INCLUDE "win/commctrl.bi" InitCommonControls() ' Переменные Dim Shared hinst As HINSTANCE : hinst = GetModuleHandle(0) Dim msg As MSG Dim As WNDCLASSEX wc Dim As ZString*20 sNameClass = "HexViewer" Dim Shared As HFONT hFontCourierNew Dim Shared As HWND mainHwnd ' загрузка шрифта Function LoadFontProc(Byval Name_ As String, _ Byval Size As Integer, _ Byval corner As Integer,_ Byval BOLD As Integer,_ Byval Italic As Integer,_ Byval Underline As Integer,_ Byval StrikeOut As Integer,_ Byval CharSet As Integer) As HFONT Dim As Integer size_ If BOLD=1 Then BOLD=700 ' жирный шрифт Else BOLD=400 ' обычный Endif Dim As HDC hdc = CreateDC("DISPLAY",0,0,0) ' создаем контекст size_ = -MulDiv(Size, GetDeviceCaps(hdc, LOGPIXELSY), 72) ' высчитываем размер шрифта DeleteDC(hdc) ' удаляем контекст Return CreateFont(size_,0,corner*10,0,Bold,Italic,Underline,StrikeOut, _ 'создаем шрифт CharSet, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH Or FF_DONTCARE,Name_) End Function ' оконная процедура главного окна Function wndproc(hwnd As HWND, msg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer Select Case msg Case WM_CREATE Case WM_COMMAND If lparam <> 0 Then ' если сообщения от контролов Select Case Loword(wParam) Case 1 Case 2 End Select Endif Case WM_DESTROY DeleteObject(hFontCourierNew) PostQuitMessage(0)' выходим End Select Return DefWindowProc(hwnd,msg,wparam,lparam) End Function ' настройки главного окна With wc .cbSize=SizeOf(WNDCLASSEX) .style=CS_HREDRAW Or CS_VREDRAW .lpfnWndProc=@wndproc .hInstance=Hinst .hIcon=LoadIcon(0,IDI_QUESTION) .hCursor=LoadCursor(0,IDC_ARROW) .hbrBackground=Cast(HBRUSH,COLOR_WINDOW) .lpszClassName=StrPtr(sNameClass) .hIconSm=.hIcon End With ' регистрация окна If RegisterClassEx(@wc)=0 Then Print "Register error, press any key" Sleep End Endif ' Загрузка шрифтов hFontCourierNew = LoadFontProc("Courier new",11,0,0,0,0,0,DEFAULT_CHARSET) ' создаем главное окно mainHwnd = CreateWindowEx(0,sNameClass,"HexViewer",_ WS_VISIBLE Or WS_OVERLAPPED Or WS_CAPTION Or WS_SYSMENU Or WS_MINIMIZEBOX,100,100,800,610,0,0,Hinst,0) ' главный цикл While GetMessage(@msg,0,0,0) TranslateMessage(@msg) DispatchMessage(@msg) Wend
В принципе , тут комментировать нечего. Думаю создавать простейшее окошко
средствами WinApi должен уметь каждый кодер, пишущий на языках типа FB. Так же в
этом коде есть загрузка шрифта. С помощью его мы сможем загрузить нужный нам
шрифт и использовать его при рисовании на нашем самописном контроле.
Теперь добавим так же код создания дочернего окна, в котором будем рисовать данные. Пока без всяких отрисовок, просто свое окошко , со своим названным классом. Вызывать данную процедуру будем из события WM_CREATE.
' создание HEX окна Sub CreateChildProc(hwnd As hwnd) Dim As WNDCLASSEX wc With wc .cbSize=SizeOf(WNDCLASSEX) .lpfnWndProc=@HexWndProc .hInstance=Hinst .hCursor=LoadCursor(0,IDC_IBEAM) .hbrBackground=Cast(HBRUSH,COLOR_SCROLLBAR) .lpszClassName=StrPtr(szHexClass) End With If RegisterClassEx(@wc)=0 Then Print "Register error, press any key" Sleep End Endif childHwnd = CreateWindowEx( WS_EX_CLIENTEDGE,szHexClass,"",_ WS_VSCROLL Or WS_CHILD Or WS_VISIBLE,0,25,794,552,hwnd,Cast(HMENU,1),Hinst,0) End Sub
Так же напишем каркас оконной процедуры для данного окна:
' оконная процедура своего окна, на котором будем рисовать HEX данные Function HexWndProc(hwnd As HWND, msg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer Select Case msg Case WM_PAINT ' если требуется рисование 'Return 0 Case WM_VSCROLL ' если произошло событие скроллинга End Select Return DefWindowProc(hwnd,msg,wparam,lparam) End Function
Ну и еще добавим кнопочку, для выбора загрузки файла. Лучше конечно тулбар, но зачем нам сейчас лишний код?
CreateWindowEx(0,"Button","Открыть файл",_ WS_VISIBLE Or WS_CHILD,10,1,100,20,hwnd,Cast(HMENU,2),Hinst,0) ' Создаем обычную кнопку
Ну и чтобы в будущем не отвлекаться , сразу напишем диалог получения имени файла из проводника:
Dim Shared ofn As OPENFILENAME Dim Shared filename As Zstring * 512 '.... ' диалог получения имени файла Function FileOpen(Byval Title As String,Byval curentdir As String, Byval Pattern As String, Byval templateName As String = "") As String Dim fbguiTemp As Integer filename = templateName With ofn .hwndOwner = 0 .lStructSize = Sizeof(OPENFILENAME) .lpstrFilter = Cast(LPCTSTR,Strptr(Pattern) ) .lpstrFile = Strptr(filename) .nFileOffset = 0 .nMaxFile = Sizeof(filename) .lpstrFileTitle = Cast(LPTSTR,Strptr(Title)) .nMaxFileTitle = Sizeof(Title) .lpstrInitialDir = Cast(LPCTSTR,Strptr(curentdir)) .lpstrTitle = Cast(LPCTSTR,Strptr(Title)) .Flags = OFN_EXPLORER Or OFN_FILEMUSTEXIST End With If( GetOpenFileName( @ofn ) = 0 ) Then Return "" Else Return filename End If End Function
На самом деле , большую часть кода мне и писать то не пришлось, я
банально вытаскивал куски ранее написанного мною кода и немного правил под
данный проект. Например загрузку шрифта, диалог выбора файлов я выдернул из
исходников моей библиотеки, только чуть "причесал". А например код создания
WINAPI окна у меня всегда располагается в шаблонах редактора FBEDIT.
Вот как-то так получилось все вместе:
#INCLUDE "windows.bi" #INCLUDE "win/commctrl.bi" #INCLUDE "win/commdlg.bi" InitCommonControls() Declare Function HexWndProc(hwnd As HWND, msg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer ' Переменные Dim Shared hinst As HINSTANCE : hinst = GetModuleHandle(0) Dim msg As MSG Dim As WNDCLASSEX wc Dim As ZString*20 sNameClass = "HexViewer" Dim Shared As ZString*20 szHexClass: szHexClass = "szHexClass" Dim Shared As HFONT hFontCourierNew Dim Shared As HWND mainHwnd Dim Shared As HWND childHwnd Dim Shared ofn As OPENFILENAME Dim Shared filename As Zstring * 512 ' загрузка шрифта Function LoadFontProc(Byval Name_ As String, _ Byval Size As Integer, _ Byval corner As Integer,_ Byval BOLD As Integer,_ Byval Italic As Integer,_ Byval Underline As Integer,_ Byval StrikeOut As Integer,_ Byval CharSet As Integer) As HFONT Dim As Integer size_ If BOLD=1 Then BOLD=700 ' жирный шрифт Else BOLD=400 ' обычный Endif Dim As HDC hdc = CreateDC("DISPLAY",0,0,0) ' создаем контекст size_ = -MulDiv(Size, GetDeviceCaps(hdc, LOGPIXELSY), 72) ' высчитываем размер шрифта DeleteDC(hdc) ' удаляем контекст Return CreateFont(size_,0,corner*10,0,Bold,Italic,Underline,StrikeOut, _ 'создаем шрифт CharSet, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH Or FF_DONTCARE,Name_) End Function ' диалог получения имени файла Function FileOpen(Byval Title As String,Byval curentdir As String, Byval Pattern As String, Byval templateName As String = "") As String Dim fbguiTemp As Integer filename = templateName With ofn .hwndOwner = 0 .lStructSize = Sizeof(OPENFILENAME) .lpstrFilter = Cast(LPCTSTR,Strptr(Pattern) ) .lpstrFile = Strptr(filename) .nFileOffset = 0 .nMaxFile = Sizeof(filename) .lpstrFileTitle = Cast(LPTSTR,Strptr(Title)) .nMaxFileTitle = Sizeof(Title) .lpstrInitialDir = Cast(LPCTSTR,Strptr(curentdir)) .lpstrTitle = Cast(LPCTSTR,Strptr(Title)) .Flags = OFN_EXPLORER Or OFN_FILEMUSTEXIST End With If( GetOpenFileName( @ofn ) = 0 ) Then Return "" Else Return filename End If End Function ' создание HEX окна Sub CreateChildProc(hwnd As hwnd) Dim As WNDCLASSEX wc With wc .cbSize=SizeOf(WNDCLASSEX) .lpfnWndProc=@HexWndProc .hInstance=Hinst .hCursor=LoadCursor(0,IDC_IBEAM) .hbrBackground=Cast(HBRUSH,COLOR_SCROLLBAR) .lpszClassName=StrPtr(szHexClass) End With If RegisterClassEx(@wc)=0 Then Print "Register error, press any key" Sleep End Endif childHwnd = CreateWindowEx( WS_EX_CLIENTEDGE,szHexClass,"",_ WS_VSCROLL Or WS_CHILD Or WS_VISIBLE,0,25,794,552,hwnd,Cast(HMENU,1),Hinst,0) End Sub ' оконная процедура главного окна Function wndproc(hwnd As HWND, msg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer Select Case msg Case WM_CREATE CreateChildProc(hwnd) ' Создаем свое дочернее окно для вывода HEX значений CreateWindowEx(0,"Button","Открыть файл",_ WS_VISIBLE Or WS_CHILD,10,1,100,20,hwnd,Cast(HMENU,2),Hinst,0) ' Создаем обычную кнопку Case WM_COMMAND If lparam <> 0 Then ' если сообщения от контролов Select Case Loword(wParam) Case 2 ? FileOpen("Открыть","C:\","All files (*.*)"+Chr(0)+"*.*"+Chr(0) ,"") End Select Endif Case WM_DESTROY DeleteObject(hFontCourierNew) PostQuitMessage(0)' выходим End Select Return DefWindowProc(hwnd,msg,wparam,lparam) End Function ' оконная процедура своего окна, на котором будем рисовать HEX данные Function HexWndProc(hwnd As HWND, msg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer Select Case msg Case WM_PAINT ' если требуется рисование 'Return 0 Case WM_VSCROLL ' если произошло событие скроллинга End Select Return DefWindowProc(hwnd,msg,wparam,lparam) End Function ' настройки главного окна With wc .cbSize=SizeOf(WNDCLASSEX) .style=CS_HREDRAW Or CS_VREDRAW .lpfnWndProc=@wndproc .hInstance=Hinst .hIcon=LoadIcon(0,IDI_QUESTION) .hCursor=LoadCursor(0,IDC_ARROW) .hbrBackground=Cast(HBRUSH,COLOR_WINDOW) .lpszClassName=StrPtr(sNameClass) .hIconSm=.hIcon End With ' регистрация окна If RegisterClassEx(@wc)=0 Then Print "Register error, press any key" Sleep End Endif ' Загрузка шрифтов hFontCourierNew = LoadFontProc("Courier new",11,0,0,0,0,0,DEFAULT_CHARSET) ' создаем главное окно mainHwnd = CreateWindowEx(0,sNameClass,"HexViewer",_ WS_VISIBLE Or WS_OVERLAPPED Or WS_CAPTION Or WS_SYSMENU Or WS_MINIMIZEBOX,100,100,800,610,0,0,Hinst,0) ' главный цикл While GetMessage(@msg,0,0,0) TranslateMessage(@msg) DispatchMessage(@msg) Wend
Это пока все , что касается GUI . Если в голову взбредет еще что-то,
напишу позже. В следующей статье я рассчитываю написать о рисовании в окне HEX
Viewer.