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


Вот собственно и все для данной статьи. Теперь просмотрщик может загружать и просматривать файлы любой длины. А в следующей статье нашему просмотрщику добавим функционал "Перейти по нужному адресу" и какой-нибудь поиск.

содержание | назад | вперед