Hex Viewer (Draw)

В этой статье, начнем писать отрисовку данных. Сразу определимся, что окно как бы будет разделено на три области:

  1. Адрес
  2. Hex данные
  3. AСSII данные

Для начала, неплохо бы эти области залить разными цветами. Не будем мудрить, просто нарисуем три разноцветных прямоугольника. Делать это будем в процедуре PaintHexProc

Сперва нужные переменные:

Dim As HDC hdc,hdc2 ' контексты устройства
Dim As PAINTSTRUCT ps ' структура, содержащая инфу для рисования
Dim As HBITMAP hbitmap ' битмап для рисования
Dim As HBRUSH  brush ' хендл кисти
Dim As HPEN pen ' хендл пера

Далее макрос рисования прямоугольников. В макросе три вводных значения (цвет заливки, координата x , ширина):

#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


Собственно само рисование:

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 символов

BitBlt(hdc2,0,0,794,552,hdc,0,0,SRCCOPY) ' копируем содержимое контекста в другой контекст
DeleteObject(hbitmap) ' удаляем битмап
DeleteDC(hDC) ' удаляем совместимый контекст
EndPaint(childHwnd, @ps) ' заканчиваем рисование


Перед тем как отрисовывать текст, было неплохо иметь буфер, откуда этот текст можно взять. Пока у нас нет HEX данных из файла, мы для наших опытов временно создадим массив с рандомными значениями. Потом когда информация будет поступать из файла, мы этот массив удалим.

Dim Shared As Byte TempDIM(10000)
' ......
For i As Integer = 0 To 10000
    TempDIM(i) = Rnd*256
Next


Так же нам понадобится небольшой буфер размером 416 байт, как раз столько, сколько помещается в нашей области рисования:

Dim Shared As Ubyte Ptr pbBytesBuffer ' буфер , ограниченный в размере (416 байт),
'используется для копирования в него части информации, которая отображается в видимой части окна
pbBytesBuffer = Callocate(416) ' выделим память для буфера


Создадим процедуру копирования из реального буфера (в последствии из файла) в наш небольшой буфер pbBytesBuffer:

'заполнение буфера pbBytesBuffer данными
Sub CopyBytesProc()
    ' пока мы копируем с нулевого адреса (пока нет прокрутки)
    ' далее мы эту процедуру перепишем
    CopyMemory( pbBytesBuffer ,@TempDIM(0) , 416)
End Sub


Создадим еще 2 глобальные переменные. Одна будет содержать реальный размер файла. Другая - смещение в самом начале видимой части окна. По сути вторая переменная нам будет нужна тогда, когда будет работать скроллинг, но в коде мы ее пристроим прямо сейчас.

Dim Shared As Ulongint ullSizeFile ' размер файла
ullSizeFile = 10000
Dim Shared As Ulongint ullNullOffsetView 'смещение в самом начале видимой части окна


В процедуре рисования создадим еще 2 локальные переменные. Одна будет содержать высоту букв в пикселях. Другая смещение в буфере.

Dim As Integer offset ' смещение
Dim As Integer y = 21 ' высота букв


Напишем макрос для рисования текста:

#MACRO mDrawText()
    SelectObject(hdc, hFontCourierNEW) ' свой шрифт в контекст
    TextOut(hdc,10,y*i+5,Hex(i*16+ullNullOffsetView,11), 11) ' рисуем строку - адрес
    TextOut(hdc,142,y*i+3,sHex, 47)' рисуем строку - HEX
    TextOut(hdc,611,y*i+3,sString, 16)' рисуем строку - ASCII
#EndMacro


Перед отрисовкой текста, скопируем данные в наш буфер. И установим режим прозрачности для вывода текста:

CopyBytesProc() ' копируем данные в буфер, из которого будем читать информацию и рисовать
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 ullNullOffsetView+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


Немного поясню.

Этот участок:

If ullNullOffsetView+offset = ullSizeFile Then ' если конец буфера
    If x>0 Then ' если прошла хоть одна итерация
        mDrawText() ' вызываем макрос
    Endif
    Exit For, For ' выход из обоих циклов
Endif


дорисовывает последнюю строку в файле. Дело в том, что последняя строка при рисовании может быть и не полной (т.е. не во всю длину). Вот как раз для этого и внедрен данный участок.

Остальное думаю должно быть понятно.

Весь код полностью:

#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
Dim Shared As Byte TempDIM(10000)
Dim Shared As Ulongint ullSizeFile ' размер файла
ullSizeFile = 10000
Dim Shared As Ulongint ullNullOffsetView 'смещение в самом начале видимой части окна
Dim Shared As Ubyte Ptr pbBytesBuffer ' буфер , ограниченный в размере (416 байт),
'используется для копирования в него части информации, которая отображается в видимой части окна
pbBytesBuffer = Callocate(416) ' выделим память для буфера

' загрузка шрифта
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

'заполнение буфера pbBytesBuffer данными
Sub CopyBytesProc()
    ' пока мы копируем с нулевого адреса
    ' далее мы эту процедуру перепишем
    CopyMemory( pbBytesBuffer ,@TempDIM(0) , 416)
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+ullNullOffsetView,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 символов

    CopyBytesProc() ' копируем данные в буфер, из которого будем читать информацию и рисовать
    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 ullNullOffsetView+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

' оконная процедура своего окна, на котором будем рисовать 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 ' если произошло событие скроллинга
    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)

For i As Integer = 0 To 10000
    TempDIM(i) = Rnd*256
Next

' главный цикл
While GetMessage(@msg,0,0,0)
    TranslateMessage(@msg)
    DispatchMessage(@msg)
Wend


На этом думаю, данную статью можно закончить. А в следующей уже решить вопрос с прокруткой, что даст возможность читать любой участок буфера в пользовательском режиме.

 

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