Hex Viewer (Load file)
В данной статье напишем часть кода, которая будет отвечать за загрузку файлов. Как я и писал в одной из прошлых статей, для загрузки будем использовать мэпирование. Не сказать что это лучшее решение, но вот так захотелось. Хотя после можно легко переписать код загрузки с помощью простых файловых операций (ReadFile, WriteFile...), если вам например понадобится читать\писать напрямую информацию на физический жесткий диск или флешку. Мэпирование работает только с файлами.
Во первых , для скроллинга внесем парочку определений:
#DEFINE iMax_scrolling_16 &h7fffl #DEFINE iMax_scrolling_32 &h7fffffffl
Вспоминаем прошлую статью, а в ней пропорцию и параметр nMax. Две эти
константы нужны для определения максимальной величины прокрутки, которую мы
задаем в параметр sInfo.nMax структуры SCROLLINFO. Более подробно описано в
прошлой статье.
Вот эти строчки из кода убираем:
Dim Shared As Byte TempDIM(10000) ullSizeFile = 10000
Массив TempDIM нам более не потребуется , а переменная ullSizeFile теперь
будет зависеть от размера реального файла. Поэтому присвоение значения мы
сделаем уже после загрузки файла и получения его размеров.
Кроме того, у нас появятся новые глобальные переменные:
Dim Shared As Ubyte Ptr pbFileBytesBuffer ' указатель на буфер мэпированного файла Dim Shared As HANDLE hFileMap , hFile ' хендлы для файлов Dim Shared As Integer iFileOpenFlag ' флаг , если произошла загрузка файла Dim Shared As Ulongint ullClaster,ullBackupClaster ' текущий кластер, бекап кластера Dim Shared As Ulongint ullRealOffset ' реальное смещение от начала файла Dim Shared As Integer iMax_scrolling ' максимальное значение для прокручивания
pbFileBytesBuffer - указатель на буфер мэпированного файла. Данный
указатель возвращает функция MapViewOfFile на основе нужного нам смещения в
файле. А освобождает данный указатель функция UnmapViewOfFile.
hFileMap , hFile - хендлы , возвращаемые функциями CreateFileMapping и CreateFile соответственно.
iFileOpenFlag - флаг , являющийся сигналом для того, чтобы определить загружен ли какой либо файл в редактор.
ullClaster - текущий кластер. Попробую объяснить попроще. Функция MapViewOfFile может получить указатель на весь файл, но только до определенного размера. Так на моем компьютере я смог получить указатель на весь файл в 700 мб. Если ей указать больший размер , то функция возвращает ошибку. На более старых компьютерах со скромным железом , этот размер может сократится до десятков мегабайт , а может и меньше. Поэтому правильнее считывать данные порционно или как я называю кластерами. Но это еще не все. Функция MapViewOfFile требует , чтобы адрес смещения в файле для буфера (pbFileBytesBuffer) был кратным гранулярности выделения памяти в системе. Узнать какова величина гранулярности, можно с помощью функции GetSystemInfo. Но мы не будем вызывать GetSystemInfo, поскольку и так известно , что на системах x86 данное число равно 4096 байт. Я решил, что в данной программе один кластер будет равен 65536 байт. Почему такой? Да просто так захотелось, главное что данное число кратно 4096. То есть мы будем получать адреса начала буфера pbFileBytesBuffer в файле через каждые 65536 байта по необходимости.
Более наглядно:
Пусть размер файла 150000 байт
1 кластер----------------------2 кластер--------------------------------3
кластер----------------
0 смещение-----------------65536
смещение--------------------131072 смещение--------149999 смещение (конец
файла)
Чтобы прочитать весь файл , требуется 3 раза получить указатель , вызвав MapViewOfFile. Первый раз передать функции 0 смещение, второй раз передать 65536 смещение, третий раз передать 131072 смещение. Кроме того потребуется передать кол-во байт для чтения функции MapViewOfFile. Она определит предел буфера. Как видно из схемы выше, два буфера будут содержать по 65536 байт , а последний 18927 байт. В первых двух случаях придется передать функции размер буфера чуть больше чем 65536 байт, чтобы не было проблемы пограничного значения. Дело в том, что наш буфер , из которого читается информация для рисования равен 416 байт. Представим себе, что наша прокрутка остановилась на определенном значении, при котором адрес начала буфера отрисовки будет равен 65502. Адрес начала отрисовки вроде как находится в 1 кластере, но данные разделились на два кластера. Итого: 65502+416 = 65918. А наш буфер заканчивается на смещении 65536. Получается, что мы вылезем за рамки нашего буфера и получим вылет программы. При прокрутке самое последнее значение смещения в первом кластере равно 65520, ведь строка равна 16 символам. А со смещения 65536 уже начнется второй кластер с новым указателем. Значит нам нужно считывать не меньше 65520+416 = 65936 байт. В программе я округлил до 66000 байт.
ullBackupClaster - бекап кластера для ullClaster. Переменная нужна для отсеивания лишних вызовов MapViewOfFile. Позиция кластера (ullClaster) целиком зависит от прокрутки. При каждой прокручиваемой области , мы высчитываем кластер. И если он равен текущему, то есть данные лежат в рамках текущего кластера, то нам незачем лишний раз получать указатель. То есть данная переменная является чем-то вроде триггера.
ullRealOffset - является смещением от начала файла. Именно его мы видим в самом верху нашего окошка. Но не стоит путать с переменной ullNullOffsetView. У нас ullNullOffsetView будет смещением в рамках буфера pbFileBytesBuffer, который как я писал выше, у нас будет размером не более 66000 байт.
iMax_scrolling - данная переменная будет содержать одно из двух значений, определенных выше (iMax_scrolling_16 или iMax_scrolling_32).
Будем разбирать функции , работающие с файлом и по ходу дела вносить
правки и дополнения в разные участки кода (в том числе совершенствуя прокрутку).
Для работы с файлом я определил 4 процедуры. Можно было обойтись и тремя, но так
показалось удобнее, хоть и чуточку расточительнее в плане размера конечного EXE
файла.
CreateMapping - открывает файл для мэпирования. Так же получает длину
файла
CloseMapping - закрывает мэпированный файл и очищает различные
переменные
GetPtrMapFile - получает указатель на начало промэпированного
файла
GetPtrMapFileAnyPosition - получает указатель на различные участки
промэпированного файла
Да, последние две функции можно было соединить в одну, но ничего страшного.
Код CreateMapping:
'Функция открывает файл для мэпирования. Так же получает длину файла Function CreateMapping(szFileName As Zstring Ptr) As Integer Dim li As ULARGE_INTEGER ' li - структура разделения ULONGINT на младшие и старшие 4 байта Dim As Uinteger iFileOffsetHigh,iFileOffsetLow ' старшие и младшие частички ULONGINT hFile = CreateFile(szFileName, GENERIC_READ Or GENERIC_WRITE, FILE_SHARE_READ Or FILE_SHARE_WRITE, 0, OPEN_ALWAYS, 0, 0) If hFile <> INVALID_HANDLE_VALUE Then iFileOffsetLow = GetFileSize(hFile, @iFileOffsetHigh) ' получаем размеры файла li.LowPart = iFileOffsetLow li.HighPart = iFileOffsetHigh ullSizeFile = li.QuadPart If ullSizeFile < &h1900000000 Then ' размер меньше 100ГБ iMax_scrolling = iMax_scrolling_32 Else iMax_scrolling = iMax_scrolling_16 Endif hFileMap = CreateFileMapping(hFile, 0, PAGE_READWRITE, li.HighPart , li.LowPart, 0) ' проекция файла в память If hFileMap <> 0 Then If ullSizeFile>416 Then ' если развер файла больше кол-ва символов области прокрутки ullMaxCount = (ullSizeFile-416)\16+1 ' высчитываем Else ullMaxCount = 0 ' иначе равно 0 Endif Return TRUE Else Return FALSE Endif Else Return FALSE Endif End Function
Для получения и установки старших и младших частей типа ULONGINT , будем
пользоваться структурой ULARGE_INTEGER
Dim li As ULARGE_INTEGER ' li - структура разделения ULONGINT на младшие и старшие 4 байта Dim As Uinteger iFileOffsetHigh,iFileOffsetLow ' старшие и младшие частички Ulongint
Дело в том, что смещения при больших файлах у нас могут быть 64х-битные,
а функции WinAPI сделаны с упором на совместимость с 32х-битной версией, и даже
с 16х-битной. Так WinAPI GetFileSize размер возвращает в двух половинках. WinAPI
CreateFileMapping получает размер файла тоже в двух половинках. Однако нам нужен
размер одним 64х-битным числом для своих различных расчетов.
Первым делом нужно открыть файл при помощи функции CreateFile. Не смотрите на флаги, они не совсем корректные для нашей программы. Ведь у нас просмотрщик (без возможности редактирования), поэтому флагов типа GENERIC_WRITE и OPEN_ALWAYS быть не должно. Я писал этот код с упором на будущее (а вдруг когда-нибудь просмотрщик перерастет в редактор :) ). Да и нашей программе это ни коим образом не повредит. Ведь файл мы открываем из стандартного диалога получения файлов, поэтому создать новый файл ну никак не получится. Запись же в файл тем более не получится, поскольку для записи функцию MapViewOfFile нужно вызывать с флагом FILE_MAP_WRITE , а у нас везде стоит FILE_MAP_READ.
И так мы открыли файл, далее проверяем хендл на валидность и получаем размер файла:
If hFile <> INVALID_HANDLE_VALUE Then iFileOffsetLow = GetFileSize(hFile, @iFileOffsetHigh) ' получаем размеры файла li.LowPart = iFileOffsetLow li.HighPart = iFileOffsetHigh ullSizeFile = li.QuadPart
Как я и говорил, функция GetFileSize получает размер в двух половинках.
Эти половинки мы объеденяем в структуре, чтобы получить 64х-битное число. Его мы
и записываем в переменную ullSizeFile.
Далее в зависимости от размера файла, мы устанавливаем максимальное значение для параметра sInfo.nMax структуры SCROLLINFO. Я не буду здесь останавливаться, просто вспоминаем прошлую статью:
If ullSizeFile < &h1900000000 Then ' размер меньше 100ГБ iMax_scrolling = iMax_scrolling_32 Else iMax_scrolling = iMax_scrolling_16 Endif
Далее функция CreateFileMapping проецирует файл в память:
hFileMap = CreateFileMapping(hFile, 0, PAGE_READWRITE, li.HighPart , li.LowPart, 0) ' проекция файла в память
Здесь так же флаг PAGE_READWRITE не совсем корректный для нашей
программы, но нам главное чтобы флаги функций CreateFile ,
CreateFileMapping и MapViewOfFile не входили в противоречие. Так же видно, что
размер для функции задается 2 половинками 64х-битного числа (li.HighPart ,
li.LowPart) .
После проверки хендла на валидность, мы получаем максимальное кол-во прокручиваемых областей:
If hFileMap <> 0 Then If ullSizeFile>416 Then ' если развер файла больше кол-ва символов области прокрутки ullMaxCount = (ullSizeFile-416)\16+1 ' высчитываем Else ullMaxCount = 0 ' иначе равно 0 Endif Return TRUE Else Return FALSE Endif
Данный код:
If ullSizeFile>416 Then ' если развер файла больше кол-ва символов области прокрутки ullMaxCount = (ullSizeFile-416)\16+1 ' высчитываем Else ullMaxCount = 0 ' иначе равно 0 Endif
я удалил из процедуры CreateChildProc и перенес в эту процедуру. Ведь в прошлых статьях размер у нас был по сути константным, а теперь зависит от размера файла.
Наша функция CreateMapping возвращает TRUE в случае успеха, и FALSE если что-то пошло не так.
Код процедуры CloseMapping выглядит так:
'Процедура закрывает мэпированный файл Sub CloseMapping() UnmapViewOfFile(pbFileBytesBuffer) CloseHandle(hFileMap) CloseHandle(hFile) ' На всякий случай очищаем все переменные pbFileBytesBuffer = 0 hFileMap = 0 hFile = 0 ullClaster = 0 ullRealOffset = 0 iFileOpenFlag = FALSE End Sub
Здесь по сути освобождаются все хендлы и очищаются переменные.
Процедура GetPtrMapFile выглядит так:
' Функция дает указатель на начало промэпированного файла. Function GetPtrMapFile() As Integer If ullSizeFile > 66000 Then ' если размер файла больше чем 66000 pbFileBytesBuffer = MapViewOfFile(hFileMap,FILE_MAP_READ, 0,0,66000) ' читаем часть файла Else pbFileBytesBuffer = MapViewOfFile(hFileMap,FILE_MAP_READ, 0,0,0) ' читаем весь файл Endif If pbFileBytesBuffer = FALSE Then Return FALSE Else Return TRUE Endif End Function
В реальности, здесь просто получаем указатель с помощью MapViewOfFile.
Если размер уж совсем маленький (меньше ~64кб) , то получаем указатель на файл
целиком. В любом другом случае, получаем указатель на нулевое смещение файла, с
размером буфера 66000 байт. Функция GetPtrMapFile возвращает TRUE в случае
успеха, и FALSE если что-то пошло не так.
Функция GetPtrMapFileAnyPosition выглядит так:
' Функция дает указатель на различные участки промэпированного файла. Function GetPtrMapFileAnyPosition() As Integer ullRealOffset = ullVScrollPos*16 ' реальное смещение ullClaster = ullRealOffset\65536 ' позиция кластера для MapViewOfFile Dim li As ULARGE_INTEGER li.QuadPart = 65536*ullClaster ' адрес посылаемый в MapViewOfFile , с которого начнется кластер If ullBackupClaster <> ullClaster Then ' если позиция кластера изменилась UnmapViewOfFile(pbFileBytesBuffer) ' удаляем мэпирование старого буфера If (li.QuadPart + 66000) < ullSizeFile Then ' если размер файл больше чем (адрес + буфер из 66000 байт) pbFileBytesBuffer = MapViewOfFile(hFileMap,FILE_MAP_READ, li.HighPart,li.LowPart,66000) ' просто читаем 66000 байт Else pbFileBytesBuffer = MapViewOfFile(hFileMap,FILE_MAP_READ, li.HighPart,li.LowPart,ullSizeFile-65536*ullClaster) ' читаем остатки байт Endif Endif ullNullOffsetView = ullRealOffset - li.QuadPart ' получаем начальное смещение в видимой части окна ullBackupClaster = ullClaster ' сохраняем позицию кластера If pbFileBytesBuffer = FALSE Then Return FALSE Else Return TRUE Endif End Function
В функции первым делом в зависимости от положения прокрутки получаем смещение в файле относительно нуля:
ullRealOffset = ullVScrollPos*16 ' реальное смещение
Далее получаем новое значение кластера, просто поделив смещение в файле
на число 65536:
ullClaster = ullRealOffset\65536 ' позиция кластера для MapViewOfFile
И высчитываем начальный адрес смещения буфера pbFileBytesBuffer для
функции MapViewOfFile. Делаем это так, чтобы адрес был кратным числу 4096
(в общем по правилам гранулярности выделения памяти в системе):
Dim li As ULARGE_INTEGER li.QuadPart = 65536*ullClaster ' адрес посылаемый в MapViewOfFile , с которого начнется кластер
Проверяем поменялась ли позиция кластера:
If ullBackupClaster <> ullClaster Then ' если позиция кластера изменилась
Если поменялась , то освобождаем указатель на прошлый кластер:
UnmapViewOfFile(pbFileBytesBuffer) ' удаляем мэпирование старого буфера
Дальше просто проверяем условие , при котором может быть достигнут конец
файла, чтобы нам не вылезти за его пределы при чтении. И получаем указатель на
новый кластер:
If (li.QuadPart + 66000) < ullSizeFile Then ' если размер файл больше чем (адрес + буфер из 66000 байт) pbFileBytesBuffer = MapViewOfFile(hFileMap,FILE_MAP_READ, li.HighPart,li.LowPart,66000) ' просто читаем 66000 байт Else pbFileBytesBuffer = MapViewOfFile(hFileMap,FILE_MAP_READ, li.HighPart,li.LowPart,ullSizeFile-65536*ullClaster) ' читаем остатки байт Endif
В этой же процедуре высчитываем смещение в рамках буфера
pbFileBytesBuffer и сохраняем его в переменную ullNullOffsetView:
ullNullOffsetView = ullRealOffset - li.QuadPart ' получаем начальное смещение в видимой части окна
И сохраняем значение текущего кластера:
ullBackupClaster = ullClaster ' сохраняем позицию кластера
Функция GetPtrMapFileAnyPosition возвращает TRUE в случае успеха, и FALSE
если что-то пошло не так.
Теперь работу этих функций надо как то объединить и заставить работать на благо нашей программы. Сделаем мы это в новой процедуре onCommandOpenFile:
' Обработка кнопки "Выбрать файл" Sub onCommandOpenFile() filename = FileOpen("Открыть","C:\","All files (*.*)"+Chr(0)+"*.*"+Chr(0) ,"") ' диалог выбора файла If filename <> "" Then If iFileOpenFlag = TRUE Then ' если что-то уже загружено SetScrollPos(childHwnd,SB_VERT,0,1)' прокрутку на начало CloseMapping() ' освобождаем все хендлы Endif If CreateMapping(filename) Then ' получаем проекцию файла If GetPtrMapFile() = FALSE Then ' получаем указатель MessageBox(0,"Не удалось получить указатель для файла","Ошибка!",MB_ICONERROR) Else iFileOpenFlag = TRUE ' файл загружен SendMessage(childHwnd,WM_VSCROLL,SB_THUMBTRACK,0) ' установим прокрутку на нуль InvalidateRect(childHwnd,0,false) ' обновляем окно Endif Else MessageBox(0,"Не удалось получить доступ к файлу","Ошибка!",MB_ICONERROR) Endif Else MessageBox(0,"Не удалось получить путь к файлу","Ошибка!",MB_ICONERROR) Endif End Sub
В этой процедуре вызываем стандартный диалог выбора файлов и проверяем
возвращаемое значение:
filename = FileOpen("Открыть","C:\","All files (*.*)"+Chr(0)+"*.*"+Chr(0) ,"") ' диалог выбора файла If filename <> "" Then
Если что-то не так, то выводим сообщение о неудаче:
MessageBox(0,"Не удалось получить путь к файлу","Ошибка!",MB_ICONERROR)
Если все нормально, то сначала проверяем загружен ли уже какой либо файл в редактор. Если загружен, то сначала сдвигаем прокрутку на нуль, во избежание ошибок в программе и освобождаем все ресурсы, полученные при прошлой загрузке файла:
If iFileOpenFlag = TRUE Then ' если что-то уже загружено SetScrollPos(childHwnd,SB_VERT,0,1)' прокрутку на начало CloseMapping() ' освобождаем все хендлы Endif
Далее проецируем файл в память:
If CreateMapping(filename) Then ' получаем проекцию файла
Если операция успешна, то получаем указатель на буфер при помощи функции
GetPtrMapFile():
If GetPtrMapFile() = FALSE Then ' получаем указатель MessageBox(0,"Не удалось получить указатель для файла","Ошибка!",MB_ICONERROR) Else iFileOpenFlag = TRUE ' файл загружен SendMessage(childHwnd,WM_VSCROLL,SB_THUMBTRACK,0) ' установим прокрутку на нуль InvalidateRect(childHwnd,0,false) ' обновляем окно Endif
Если функция GetPtrMapFile отработает правильно, устанавливаем флаг
iFileOpenFlag . Посылаем сообщение SB_THUMBTRACK (для чего это нужно ,
читаем прошлую статью) . И заставляем перерисоваться окошко при помощи
InvalidateRect. Кстати строку:
SendMessage(childHwnd,WM_VSCROLL,SB_THUMBTRACK,0) ' установим прокрутку на нуль
я так же перенес из процедуры CreateChildProc.
Теперь перенесемся в нашу оконную процедуру wndproc и немного подправим там код:
............... Case WM_COMMAND If lparam <> 0 Then ' если не от контролов Select Case Loword(wParam) Case 2 onCommandOpenFile() End Select Endif Case WM_DESTROY If iFileOpenFlag = TRUE Then CloseMapping() Endif ...............
В событии WM_COMMAND , если сообщение пришло от нашей кнопки, мы поставим
обработчик на процедуру onCommandOpenFile() . Таким образом мы связали нажатие
кнопки и загрузку файла.
Так же в событии WM_DESTROY , если файл был
загружен, поставим вызов CloseMapping() для закрытия всех хендлов.
Теперь кое какие правки в целом по коду. В процедуру CopyBytesProc внесем некоторые изменения:
'заполнение буфера pbBytesBuffer данными Sub CopyBytesProc() Dim As Ulongint ullTempSize If (ullRealOffset + 416) < ullSizeFile Then ullTempSize = 416 Else ullTempSize = ullSizeFile - ullRealOffset Endif CopyMemory( pbBytesBuffer ,pbFileBytesBuffer+ullNullOffsetView , ullTempSize) End Sub
Теперь размер копируемых байт для буфера pbBytesBuffer рассчитывается при
участии переменной ullRealOffset. А переменная ullNullOffsetView используется
только как смещение для pbFileBytesBuffer.
Далее уберем следующую строчку из процедуры PaintHexProc(неправильно в процедуре рисования вызывать лишний код):
CopyBytesProc() ' копируем данные в буфер, из которого будем читать информацию и рисовать
и поместим ее в новую процедуру SetScrollProc.
Так же в процедуре PaintHexProc заменим строчку:
If ullNullOffsetView+offset = ullSizeFile Then ' если конец буфера
на:
If ullRealOffset+offset = ullSizeFile Then ' если конец файла
Подправим код в процедуре GetScrollinfoProc:
' получение информации о скроллинге Sub GetScrollinfoProc(hwnd As hwnd) Dim info As SCROLLINFO info.cbSize = Sizeof(SCROLLINFO) info.fMask = SIF_TRACKPOS GetScrollInfo(hwnd, SB_VERT, @info) ' получаем инфу о скроллинге от системы If ullMaxCount<iMax_scrolling Then ' если максимальная прокрутка меньше iMax_scrolling ullVScrollPos = info.nTrackPos ' тогда просто присваиваем системную инфу Else ' высчитываем процентное соотношение позиции скроллинга ullVScrollPos = info.nTrackPos ullVScrollPos = (ullVScrollPos/iMax_scrolling)*ullMaxCount Endif End Sub
В ней мы просто заменим константу 7fffffff на переменную iMax_scrolling,
значение которой у нас варьируется в зависимости от размера файла.
Создадим новую процедуру для скроллинга SetScrollProc. До этого код с вызовом WINAPI SetScrollInfo находился в процедуре ScrollProc. Однако принудительный скроллинг нам будет нужен не только как ответ на движение ползунка. Он понадобится при переходе на нужный адрес или при поиске значений. Значит рационально данный код вынести в отдельную процедуру:
' скроллинг Sub SetScrollProc() Dim As Integer vs Dim As SCROLLINFO sInfo sInfo.cbSize = Sizeof(SCROLLINFO) sInfo.fMask = SIF_ALL ' маска для любых действий со скролом sInfo.nMin = 0 sInfo.nMax = Iif (ullMaxCount>iMax_scrolling,iMax_scrolling,ullMaxCount) ' ставим максимальное значение скроллинга vs = Iif(ullMaxCount>iMax_scrolling,(ullVScrollPos/ullMaxCount)*iMax_scrolling,ullVScrollPos) ' высчитываем реальное смещение в скроле sInfo.nPos = vs sInfo.nPage = 1 If GetPtrMapFileAnyPosition() = FALSE Then ' получаем новый указатель мэп-файла MessageBox(0,"Не удалось получить указатель для файла","Ошибка!",MB_ICONERROR) Else CopyBytesProc() ' копируем данные в буфер, из которого будем читать информацию и рисовать Endif SetScrollInfo (childhwnd, SB_VERT,@sInfo, TRUE) InvalidateRect(childhwnd,0,false) End Sub
Здесь мы так же заменили константу 7fffffff на переменную
iMax_scrolling . Так же отсюда происходит вызов функции
GetPtrMapFileAnyPosition() и процедуры CopyBytesProc() . Все логично: происходит
скроллинг, значит может меняться кластер. И соотвественно нужно произвести
копирование данных для рисования.
А в процедуру ScrollProc запишем ссылку на вызов SetScrollProc()
Окончательный код выглядит так:
#INCLUDE "windows.bi" #INCLUDE "win/commctrl.bi" #INCLUDE "win/commdlg.bi" InitCommonControls() #DEFINE iMax_scrolling_16 &h7fffl #DEFINE iMax_scrolling_32 &h7fffffffl Declare Function HexWndProc(hwnd As HWND, msg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer Declare Function CreateMapping(szFileName As Zstring Ptr) As Integer Declare Sub CloseMapping() Declare Function GetPtrMapFile() As Integer Declare Function GetPtrMapFileAnyPosition() As Integer Declare Sub CopyBytesProc() Declare Sub SetScrollProc() ' Переменные 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 ' окно HEX Dim Shared ofn As OPENFILENAME Dim Shared filename As Zstring * 512 Dim Shared As Ulongint ullSizeFile ' размер файла Dim Shared As Ulongint ullNullOffsetView 'смещение в самом начале видимой части окна Dim Shared As Ubyte Ptr pbBytesBuffer ' буфер , ограниченный в размере (416 байт), 'используется для копирования в него части информации, которая отображается в видимой части окна pbBytesBuffer = Callocate(416) ' выделим память для буфера Dim Shared As Ulongint ullVScrollPos,ullMaxCount ' позиция скрола, кол-во прокручиваемых областей Dim Shared As Ubyte Ptr pbFileBytesBuffer ' указатель на буфер мэпированного файла Dim Shared As HANDLE hFileMap , hFile ' хендлы для файлов Dim Shared As Integer iFileOpenFlag ' флаг , если произошла загрузка файла Dim Shared As Ulongint ullClaster,ullBackupClaster ' текущий кластер, бекап кластера Dim Shared As Ulongint ullRealOffset ' реальное смещение от начала файла Dim Shared As Integer iMax_scrolling ' максимальное значение для прокручивания ' загрузка шрифта 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 ' Обработка кнопки "Выбрать файл" Sub onCommandOpenFile() filename = FileOpen("Открыть","C:\","All files (*.*)"+Chr(0)+"*.*"+Chr(0) ,"") ' диалог выбора файла If filename <> "" Then If iFileOpenFlag = TRUE Then ' если что-то уже загружено SetScrollPos(childHwnd,SB_VERT,0,1)' прокрутку на начало CloseMapping() ' освобождаем все хендлы Endif If CreateMapping(filename) Then ' получаем проекцию файла If GetPtrMapFile() = FALSE Then ' получаем указатель MessageBox(0,"Не удалось получить указатель для файла","Ошибка!",MB_ICONERROR) Else iFileOpenFlag = TRUE ' файл загружен SendMessage(childHwnd,WM_VSCROLL,SB_THUMBTRACK,0) ' установим прокрутку на нуль InvalidateRect(childHwnd,0,false) ' обновляем окно Endif Else MessageBox(0,"Не удалось получить доступ к файлу","Ошибка!",MB_ICONERROR) Endif Else MessageBox(0,"Не удалось получить путь к файлу","Ошибка!",MB_ICONERROR) Endif End Sub ' оконная процедура главного окна Function wndproc(hwnd As HWND, msg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer Select Case msg Case WM_CREATE CreateWindowEx(0,"Button","Открыть файл",_ WS_VISIBLE Or WS_CHILD,10,1,100,20,hwnd,Cast(HMENU,2),Hinst,0) ' Кнопка открыть файл CreateChildProc(hwnd) ' Создаем свое дочернее окно для вывода HEX значений Case WM_COMMAND If lparam <> 0 Then ' если не от контролов Select Case Loword(wParam) Case 2 onCommandOpenFile() End Select Endif Case WM_DESTROY If iFileOpenFlag = TRUE Then CloseMapping() Endif DeleteObject(hFontCourierNew) PostQuitMessage(0)' выходим End Select Return DefWindowProc(hwnd,msg,wparam,lparam) End Function 'заполнение буфера pbBytesBuffer данными Sub CopyBytesProc() Dim As Ulongint ullTempSize If (ullRealOffset + 416) < ullSizeFile Then ullTempSize = 416 Else ullTempSize = ullSizeFile - ullRealOffset Endif CopyMemory( pbBytesBuffer ,pbFileBytesBuffer+ullNullOffsetView , ullTempSize) End Sub ' рисование на окне childHwnd Sub PaintHexProc() Dim As HDC hdc,hdc2 ' контексты устройства Dim As PAINTSTRUCT ps ' структура, содержащая инфу для рисования Dim As HBITMAP hbitmap ' битмап для рисования Dim As HBRUSH brush ' хендл кисти Dim As HPEN pen ' хендл пера Dim As Integer offset ' смещение Dim As Integer y = 21 ' высота букв #MACRO mRectangle(col,x,w) ' макрос рисования прямоугольников brush=CreateSolidBrush(col) ' создаем кисть SelectObject(hdc,brush) ' кисть в контекст pen= CreatePen(PS_SOLID ,1,Cast(COLORREF,col)) ' создаем перо SelectObject(hDC,Cast(HGDIOBJ,pen)) ' перо в контекст Rectangle(hDC,x,0,w,552) ' рисуем прямоугольник DeleteObject(brush) ' удаляем кисть DeleteObject(pen) ' удаляем перо #EndMacro #MACRO mDrawText() SelectObject(hdc, hFontCourierNEW) ' свой шрифт в контекст TextOut(hdc,10,y*i+5,Hex(i*16+ullRealOffset,11), 11) ' рисуем строку - адрес TextOut(hdc,142,y*i+3,sHex, 47)' рисуем строку - HEX TextOut(hdc,611,y*i+3,sString, 16)' рисуем строку - ASCII #EndMacro hdc2 = BeginPaint(childHwnd, @ps) ' начинаем рисование, получаем контекст hdc = CreateCompatibleDC(hdc2) 'создаем совместимый контекст hbitmap = CreateCompatibleBitmap(hdc2,794,552) ' создаем совместимый битмап SelectObject(hdc,hbitmap) ' битмап в контекст mRectangle(&hFFE9E9,0,109) ' рисуем прямоугольник для адресов mRectangle(&hf0f0f0,110,595) 'рисуем прямоугольник для Hex символов mRectangle(&hE9FFE9,596,794) ' рисуем прямоугольник для ASCII символов SetBkMode(hdc,1) ' режим прозрачности для текста For i As Integer = 0 To 25 ' цикл по числу видимых строк Dim As ZString*50 sHex ' хранит HEX символы Dim As ZString*16 sString ' хранит ASCII символы For x As Integer = 0 To 15 ' цикл по числу столбцов If ullRealOffset+offset = ullSizeFile Then ' если конец файла If x>0 Then ' если прошла хоть одна итерация mDrawText() ' вызываем макрос Endif Exit For, For ' выход из обоих циклов Endif sHex &= (Hex(pbBytesBuffer[offset],2)& " ") ' собираем строку HEX Dim As Ubyte btempSimbol = pbBytesBuffer[offset]' получаем ASCCI код Select Case btempSimbol ' отсеиваем непечатываемые символы Case &h0 To &h1f,&hAD,&h98,&h7f btempSimbol = &h2E ' заменяем их точками End Select sString[x] = btempSimbol ' собираем строку ASCII offset +=1 ' увеличиваем смещение Next mDrawText()' вызываем макрос Next BitBlt(hdc2,0,0,794,552,hdc,0,0,SRCCOPY) ' копируем содержимое контекста в другой контекст DeleteObject(hbitmap) ' удаляем битмап DeleteDC(hDC) ' удаляем совместимый контекст EndPaint(childHwnd, @ps) ' заканчиваем рисование End Sub ' получение информации о скроллинге Sub GetScrollinfoProc(hwnd As hwnd) Dim info As SCROLLINFO info.cbSize = Sizeof(SCROLLINFO) info.fMask = SIF_TRACKPOS GetScrollInfo(hwnd, SB_VERT, @info) ' получаем инфу о скроллинге от системы If ullMaxCount<iMax_scrolling Then ' если максимальная прокрутка меньше iMax_scrolling ullVScrollPos = info.nTrackPos ' тогда просто присваиваем системную инфу Else ' высчитываем процентное соотношение позиции скроллинга ullVScrollPos = info.nTrackPos ullVScrollPos = (ullVScrollPos/iMax_scrolling)*ullMaxCount Endif End Sub ' скроллинг Sub SetScrollProc() Dim As Integer vs Dim As SCROLLINFO sInfo sInfo.cbSize = Sizeof(SCROLLINFO) sInfo.fMask = SIF_ALL ' маска для любых действий со скролом sInfo.nMin = 0 sInfo.nMax = Iif (ullMaxCount>iMax_scrolling,iMax_scrolling,ullMaxCount) ' ставим максимальное значение скроллинга vs = Iif(ullMaxCount>iMax_scrolling,(ullVScrollPos/ullMaxCount)*iMax_scrolling,ullVScrollPos) ' высчитываем реальное смещение в скроле sInfo.nPos = vs sInfo.nPage = 1 If GetPtrMapFileAnyPosition() = FALSE Then ' получаем новый указатель мэп-файла MessageBox(0,"Не удалось получить указатель для файла","Ошибка!",MB_ICONERROR) Else CopyBytesProc() ' копируем данные в буфер, из которого будем читать информацию и рисовать Endif SetScrollInfo (childhwnd, SB_VERT,@sInfo, TRUE) InvalidateRect(childhwnd,0,false) End Sub ' обработка информации о скроллинге Sub ScrollProc(hwnd As HWND, param As Integer) Select Case param Case SB_TOP ullVScrollPos = 0 ' на начало страницы Case SB_BOTTOM ullVScrollPos = ullMaxCount 'на конец страницы Case SB_LINEUP If(ullVScrollPos > 0) Then ullVScrollPos-=1 'сдвигаемся ближе к началу страницы на 1 Endif Case SB_LINEDOWN If(ullVScrollPos < ullMaxCount) Then ullVScrollPos+=1 'сдвигаемся ближе к концу страницы на 1 Endif Case SB_PAGEUP If(ullVScrollPos > 10) Then ullVScrollPos-=10 'сдвигаемся ближе к началу страницы на 10 Endif Case SB_PAGEDOWN If(ullVScrollPos < (ullMaxCount-10)) Then ullVScrollPos+=10 'сдвигаемся ближе к концу страницы на 10 Endif Case SB_THUMBPOSITION,SB_THUMBTRACK ' если пользовательское перемещение на неопределенное кол-во GetScrollinfoProc(hwnd) ' получаем информацию из системы и высчитываем End Select SetScrollProc()' скроллим 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 ' если требуется рисование PaintHexProc() Return 0 Case WM_VSCROLL ' если произошло событие скроллинга If iFileOpenFlag = TRUE Then ScrollProc(hwnd,Loword(wparam)) ' обрабатываем Return 0 Endif End Select Return DefWindowProc(hwnd,msg,wparam,lparam) End Function 'Функция открывает файл для мэпирования. Так же получает длину файла Function CreateMapping(szFileName As Zstring Ptr) As Integer Dim li As ULARGE_INTEGER ' li - структура разделения ULONGINT на младшие и старшие 4 байта Dim As Uinteger iFileOffsetHigh,iFileOffsetLow ' старшие и младшие частички ULONGINT hFile = CreateFile(szFileName, GENERIC_READ Or GENERIC_WRITE, FILE_SHARE_READ Or FILE_SHARE_WRITE, 0, OPEN_ALWAYS, 0, 0) If hFile <> INVALID_HANDLE_VALUE Then iFileOffsetLow = GetFileSize(hFile, @iFileOffsetHigh) ' получаем размеры файла li.LowPart = iFileOffsetLow li.HighPart = iFileOffsetHigh ullSizeFile = li.QuadPart If ullSizeFile < &h1900000000 Then ' размер меньше 100ГБ iMax_scrolling = iMax_scrolling_32 Else iMax_scrolling = iMax_scrolling_16 Endif hFileMap = CreateFileMapping(hFile, 0, PAGE_READWRITE, li.HighPart , li.LowPart, 0) ' проекция файла в память If hFileMap <> 0 Then If ullSizeFile>416 Then ' если развер файла больше кол-ва символов области прокрутки ullMaxCount = (ullSizeFile-416)\16+1 ' высчитываем Else ullMaxCount = 0 ' иначе равно 0 Endif Return TRUE Else Return FALSE Endif Else Return FALSE Endif End Function 'Процедура закрывает мэпированный файл Sub CloseMapping() UnmapViewOfFile(pbFileBytesBuffer) CloseHandle(hFileMap) CloseHandle(hFile) ' На всякий случай очищаем все переменные pbFileBytesBuffer = 0 hFileMap = 0 hFile = 0 ullClaster = 0 ullRealOffset = 0 iFileOpenFlag = FALSE End Sub ' Функция дает указатель на начало промэпированного файла. Function GetPtrMapFile() As Integer If ullSizeFile > 66000 Then ' если размер файла больше чем 66000 pbFileBytesBuffer = MapViewOfFile(hFileMap,FILE_MAP_READ, 0,0,66000) ' читаем часть файла Else pbFileBytesBuffer = MapViewOfFile(hFileMap,FILE_MAP_READ, 0,0,0) ' читаем весь файл Endif If pbFileBytesBuffer = FALSE Then Return FALSE Else Return TRUE Endif End Function ' Функция дает указатель на различные участки промэпированного файла. Function GetPtrMapFileAnyPosition() As Integer ullRealOffset = ullVScrollPos*16 ' реальное смещение ullClaster = ullRealOffset\65536 ' позиция кластера для MapViewOfFile Dim li As ULARGE_INTEGER li.QuadPart = 65536*ullClaster ' адрес посылаемый в MapViewOfFile , с которого начнется кластер If ullBackupClaster <> ullClaster Then ' если позиция кластера изменилась UnmapViewOfFile(pbFileBytesBuffer) ' удаляем мэпирование старого буфера If (li.QuadPart + 66000) < ullSizeFile Then ' если размер файл больше чем (адрес + буфер из 66000 байт) pbFileBytesBuffer = MapViewOfFile(hFileMap,FILE_MAP_READ, li.HighPart,li.LowPart,66000) ' просто читаем 66000 байт Else pbFileBytesBuffer = MapViewOfFile(hFileMap,FILE_MAP_READ, li.HighPart,li.LowPart,ullSizeFile-65536*ullClaster) ' читаем остатки байт Endif Endif ullNullOffsetView = ullRealOffset - li.QuadPart ' получаем начальное смещение в видимой части окна ullBackupClaster = ullClaster ' сохраняем позицию кластера If pbFileBytesBuffer = FALSE Then Return FALSE Else Return TRUE Endif 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
Вот собственно и все для данной статьи. Теперь просмотрщик может
загружать и просматривать файлы любой длины. А в следующей статье нашему
просмотрщику добавим функционал "Перейти по нужному адресу" и какой-нибудь
поиск.
содержание | назад | вперед