Перейти к содержанию

MasterGH

Ветераны
  • Постов

    2 999
  • Зарегистрирован

  • Победитель дней

    129

Сообщения, опубликованные MasterGH

  1. Тема. Поворот камеры в игровом мире (на английском, автор L. Spiro).

    #1: Creating the full view matrix.


    CVector3 vTarget = vEnemyPos - vYourPos;

    // Get the up and right vectors.  Get the right vector by using the world-axis up vector.
    CVector3 vUp( 0.0f, 1.0f, 0.0f );
    CVector3 vRight = vTarget.Cross( vUp );

    // The up vector is the cross of the right and forward.
    vUp = vRight.Cross( vTarget );


    // Normalize all of the vectors.
    vTarget.Normalize();
    vUp.Normalize();
    vRight.Normalize();


    // Create the view matrix.
    mMatrix._11 = vRight.x;
    mMatrix._12 = vRight.y;
    mMatrix._13 = vRight.z;
    mMatrix._14 = 0.0f;

    mMatrix._21 = vUp.x;
    mMatrix._22 = vUp.y;
    mMatrix._23 = vUp.z;
    mMatrix._24 = 0.0f;

    mMatrix._31 = vTarget.x;
    mMatrix._32 = vTarget.y;
    mMatrix._33 = vTarget.z;
    mMatrix._34 = 0.0f;

    mMatrix._41 = vYourPos.x;
    mMatrix._42 = vYourPos.y;
    mMatrix._43 = vYourPos.z;
    mMatrix._44 = 1.0f;
    // Create a forward vector.  Looking at the enemy.

    If the perspective matrix causes the view to look down -Z instead of Z, you will have to invert the target vector using the following as the first line instead:

    CVector3 vTarget = vYourPos - vEnemyPos;

    The matrix is in DirectX format (row-major). Transpose for OpenGL. Y is assumed to be up.

    #2: Just modifying your player’s facing direction.

    You have 2 values representing the direction your player is facing. These are Cartesian coordinates. The values you have are useless. The only thing we need to know is that the player has Cartesian values to describe the direction it is facing.

    Again you need to create a vector to look at the target. But instead of converting it to a matrix we convert it to Cartesian coordinates. As you remember from 3rd grade elementary school, you can convert a directional vector to Cartesian coordinates via the following (assuming Y is up).


       _fHor = ::fatan2( _vVector.z, _vVector.x );
       float fLen = _vVector.Len();
       if ( fLen == 0.0f ) {
          _fVert = 1.5707963267948966192313216916398f; // Half PI.
       }
       else {
          _fVert = ::acosf( _vVector.y / fLen );
       }
    }
    void VectorToCartesian( const CVector3 &_vVector, float &_fHor, float &_fVert ) {

    With that elementary utility function it is pre-school to perform this task.


    CVector3 vTarget = vEnemyPos - vYourPos;

    // Convert to Cartesian to overwrite the RAM in the player block.
    float fH, fV;
    VectorToCartesian( vTarget, fH, fV );
    // Create a forward vector.  Looking at the enemy.

    fH and fV will be the values to overwrite in your player block.

    Again it may cause you to look away from the target, in which case you reverse the first line of code (again).

    Again Y = up.

  2. То, что написано в статье у меня получилось (можно посмотреть на скриншоте)

    post-3-1280205660,75_thumb.jpg

    Теперь попробую осуществить подобное в MHS.

    Я хочу сделать так чтобы скрипты MHS выводили информацию в окно игры, а не только в консоль MHS...

  3. А вообще ничего сложного.

    1. Нужно получить D3DDev: IDirect3DDevice9 как результат из перехваченой CreateDevice9. Затем инициализировать тут же g_Font: ID3DXFont

    2. Перехватить EndScene9 с выполнением

      TextRect := Rect(100,100,100,100);

      g_Font.DrawTextA(nil, PChar('Превед!!! :)'), -1, @TextRect,

        DT_LEFT or DT_NOCLIP, D3DCOLOR_RGBA($00, $ff, $ff, $ff) );

    И это всё для вывода текста, а для рисования нужно поискать необходимые методы.

    Непонятно зачем автору именно для этой задачи надо было ставить хук на Direct3DCreate9. Я думаю это не нужно было.

    ----------

    Та статья хорошо показывает как и что работает.

    Однако, я не хочу лоадер процесса, я не хочу подгрузчик dll и я хочу рисовать в окне игры, когда игра уже запущена. Мне не нужны хуки, т.к. я могу использовать инъекции кода: 

    1) в одну из сигнатур кода, который работает с D3DDev для получения указателя на D3DDev (или аналогичного OpenGL) и инициализации шрифта

    2) в одну из сигнатур EndScene9 для разных DirectX-библиотек или для OpenGL для рисования текста.

    Думаю, можно было сделать и получше.  С теорией всё понятно, можно приступить к практике.

  4.  Что-то я не совсем понял где в твоём примере код, который рисует в видео-контексте игры.  :)

    Но зато я нашёл статью (я, обычно, ищу статьи с известными подходами, чтобы по ним побыстрее сориентироваться чем копаться в документациях). И вот результат:

    shot00004.jpg

    Статья - тут

    Попробую подобное повторить и сделать на MHS.

  5. Цель 1: с помощью MHS нарисовать график и текст в окне некоторой игрушки.

    Цель 2: разобраться как связать события мышки с рисуемыми частями.

    Нужна помощь в поиске информации по технологии рисования в окне чужого процесса (игры) и в перехвате событий: мышки, клавиатуры к примеру.

      :)

  6. Я думаю с телепортом если не всё стало ясно, то почти всё.

    Покрайне мере технология выяснена.

    SER[G]ANT+5 к прокачке за труды исследования телепорта (каждый уровень будет доставаться всё труднее и труднее) Подобные исследования я очень приветствую.

    live_4_ever +2 (за статью о телепорте, за то что интересуешься темой)

    M4K +6 (за выкристаллизованный опыт по расположению данных в структурах, за отличное умение пользоватья сканером памяти (я забыл тебе накинуть очков))

    Чтобы стать Продвинутым, ты должен показать умение писать скрипты на CE.  :)

  7. Тема. Мощные MHS С-скрипты.

    Цель этого туториала, показать как работать со скриптами в MHS, которые активируются по горячим клавишам. Ну а также привлечь внимание читателя к этим скриптам.

    В С- скрипте мы можем привести некоторый адрес игры к заданной структуре и работать с ней уже на C-коде. Очень жаль что нельзя создавать классы и приводить игровые объекты к этим классам (была бы очень мощная штука - поверьте мне).

    Мы познакомимся с функций PrintF(), которая будет выводить в консоль некоторые данные. Кто не знает язык С, тем учить!   :)

    Подопытный кролик будет XP-шный сапёр (для него уже было решение на Дельфи). Мы сделаем одно задание - вывод на консоль информации о расположении мин. Решение этого задания находится в справке, но тем не менее решение я напишу на русском языке. По сравнению с решением на Дельфи, этот скрипт избавит вас от поиска процесса сапёра.

    Итак

    1) Запускаем сапёра. 

    2) Находим адреса кол-ва мин по высоте и по ширине. 

    3) Находим массив в котором располагаются мины.

    4) Tools->Script Editor.

    Создаём новый скрипт (сохраняя его в какой-нибудь файл и компилируем по F5)

    VOID ShowMineSweeperBoard() {
        extern struct BOARDSIZE {
            INT iWidth;
            INT iHeight;
        }               g_bwSize            = { "winmine.exe", 0x5334 };  
        extern BYTE     g_pbBoard[32*32]    = { "winmine.exe", 0x5361 };  

        CHAR * pcRow = Malloc( g_bwSize.iWidth + 1 );                

        PrintF( "%d?%d", g_bwSize.iWidth, g_bwSize.iHeight );    
        for ( INT J = 0; J < g_bwSize.iHeight; J++ ) {
            for ( INT I = 0; I < g_bwSize.iWidth; I++ ) {
                pcRow[I] = (g_pbBoard[(32*J)+I] & 0x80) ? '1' : '0';  
            }
            pcRow[g_bwSize.iWidth] = '\0';            
            PrintF( pcRow );
        }
        Free( pcRow );
    }

    VOID On_HK_23( DWORD dw1, DWORD dw2 ) {
        Clear();
        ShowMineSweeperBoard();
    }

    5) Tools->Hotkeys и вводим там параметры

    post-3-1280128262,74_thumb.png

    6) Нажимаем F2 и видим в консоли ответ: там где находятся единицы там мины.

    post-3-1280128443,62_thumb.png

    Справочная информация:

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

    post-3-1280128719,64_thumb.png

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

    Итак что же даёт скрипт MHS.

    Если вы посмотрите справку, то там вы увидите что есть разное применении С-скрипта. Правила поиска, правила заморозки, множество поддерживаемых функций с памятью, процессами, регистарми, работы с отладчиком и дизассемблером. Область примирения очень обширная. Чтобы сузить эту область применения, наверно, стоит размышлять о том как можно с помощью C-скрипта реализовать тот или иной чит-код. Прежде всего С-кодом можно работать со структурами и ссылками на эти или другие структуры. Поиск в этих структурах данных и составление логических действий. Во всяком случае я полагаю, что область эта ещё не очень изучена нами. Обычно, нам хватает ассемблерного отладочного кода как инъекции....   

  8. Z- координату найти проще всего если подниматься / опускаться в игровом мире на каких-то возвышениях, лестницах и т.п. Обычно координата имеет 4-байта с точкой, а может быть и 8 байт с точкой. Ищу всегда через MHS в блоке памяти в котором находится здоровье героя или структура героя.

    Инъекцию отладочного кода можно сделать в любую часто срабатываемую инструкцию. Если тебя очень беспокоит производительность, то надо выбирать варианты инъекции и тестировать, тестировать...

    Я предполагаю, что вариант который я предложил будет лучшим. Инъекция сохраняющая или восстанавливающая координаты происходит, тогда когда ты нажимаешь на горячую клавишу и эта же инъекция сама исправляет инструкцию "прыга" на инструкцию "оригинальную". Вот и всё, производительность теряться не будет.

  9. Я думаю ты сам сможешь ответить на все вопросы   :ninja:

    Я бы сделал так:

    1. Находим адрес координат Z любым удобным способом.

    2. Находим инструкцию работающую с этой  Z координатой.

    Если инструкция1 работает со всеми координатами, то это на руку.

    Если нет, то координата Z стоит, обычно, рядом с X, Y и не составит особого труда написать скрипт.

    3. Создаём такой скрипт1 - инъекция "Сохранение координат в зарегистрированную метку1 из инструкции1 с последующей отменой этой иньекции". Только не уничтожай метку1.

    5. Создаём скрипт2 - иньекция "Восстановление координат из метки1 с последующей отменой инъекции". Только не уничтожай метку1.

    6. На первый и второй скрипты вешаем хот-кеи.

    Если сделать так как я написал, то это избавит от лишних флагов  :)

  10. Решение 3-го задания (которое объединяет решения первого и второго) имеет следующий вид.

    Скрипт1 - Главный


    /*
    Процесс: TrainMe(hard_1_0).exe
    Чит-код: Скрипт на восттановление указанного кол-ва объектов
    Дата: 7.21.2010
    Автор скрипта: MasterGH(c)
    Версия CE: Cheat Engine 5.6  RUS (v 2.0)

    ------------------
    На _void_RstObj() делалаем два call-а в места:
    1) из кода после добавления объектов
    2) из кода после уменьшения объектов

    */
    alloc(_newmem,2048)
    label(_void_RstObj)
    label(_EAX_GetCountObj)
    label(_void_AddObject)
    label(_void_createobj_eax_)
    label(_ex)
    label(_loop)
    label(_a1)
    label(_a2)

    registersymbol(_void_RstObj)


    _newmem:
    //Восстановить кол-во недостающих объектов
    _void_RstObj:
      pushf
      push eax
      call _EAX_GetCountObj // возвращает eax = кол-во объектов в данный момент
      push ebx
      mov ebx,#1000
      sub ebx,eax
      mov eax,ebx
      pop ebx
      cmp eax, 0    // eax объектов которых не хватает
      jl short _ex      // переход если объектов которых не хватает меньше нуля
      call _void_createobj_eax_
    _ex:
      pop eax
      popf
      ret
      
    // добавление кол-во объектов  
    _void_createobj_eax_:
    _loop:
      pushad
      call _void_AddObject// добавляем объект
      popad
      dec eax
      cmp eax,0
      jg short _loop // пока eax больше нуля
      ret
      
     //Добавление одного объекта:
    _void_AddObject:
      push 4010C0
      push 4
      push 4
      call 0040187c
      add esp,04
      test eax,eax
      je short _a1
      mov [eax],000003e7
      jmp short _a2
    _a1:
      xor eax,eax
    _a2:
      push edi
      lea edi,[esp+08]
      mov [esp+08],eax
      call 00401280
      add esp,0C
      ret
      
    //Получение кол-ва объектов в eax:
    _EAX_GetCountObj:
      mov eax,[4045B0]
      sub eax,[4045AC]
      sar eax,2
      ret
      
    [DISABLE]
    dealloc(_newmem)
    unregistersymbol(_void_RstObj)
    [ENABLE]

    Скрипт2


    aobscan(_faddress,a1xxxxxxxx2bxxxxxxxxxxxxxxxxxx8dxxxxxxxxc1xxxxxxffxxxxxxxxxx83)
    alloc(_newmem,2048)
    label(_returnhere)
    label(_originalcode)

    _newmem:
    call _void_RstObj
    _originalcode:
    mov eax,[4045B0]
    jmp _returnhere

    _faddress:    // 0040118D = About+CD
    jmp _newmem
    _returnhere:

    [DISABLE]
    aobscan(_faddress,2bxxxxxxxxxxxxxxxxxx8dxxxxxxxxc1xxxxxxffxxxxxxxxxx83)

    _faddress-5:
    mov eax,[4045B0]

    dealloc(_newmem)
    //Alt: db A1 B0 45 40 00
    [ENABLE]

    Скрипт3


    aobscan(_faddress,8bxxxxxxxxxx8bxxxxxxxxxx8bxx2bxxc1xxxx85xx0fxxxxxxxxxx83)
    alloc(_newmem,2048)
    label(_returnhere)
    label(_originalcode)

    _newmem:
    call _void_RstObj
    _originalcode:
    mov ecx,[4045B0]
    jmp _returnhere

    _faddress:    // 00401109 = About+49
    jmp _newmem
    nop
    _returnhere:

    [DISABLE]
    aobscan(_faddress,908bxxxxxxxxxx8bxx2bxxc1xxxx85xx0fxxxxxxxxxx83)

    _faddress-5:
    mov ecx,[4045B0]

    dealloc(_newmem)
    //Alt: db 8B 0D B0 45 40 00
    [ENABLE]

    Изучил я скрипты на С-коде в CheatEngine и в MHS. К сожалению, обе программы не поддерживают ассемблерные вставки в C-коде. В связи с чем, как мне показалось, лучше писать автоассемблерскими скриптами, как я написал выше. Конечно, все три скрипта лучше объединить в один, но задача в целом уже решена   8-)

    Спасибо всем, кто принимал участие в решение этого очень сложного трейнми. Если я сделаю ещё трейнми, то уже в отдельной в новой теме.

  11.  Оказывается я дал решение скрипта не для того скомпилированного трейнми. Теперь презалил - ссылка в первом посте. Так что можете скачать и проверить мой скрипт первого решения.

    Второе решение оставляю за вами.

    Следующее задание - задание3

    На примере двух предыдущих решений делаем общий чит-код создающий 1000 объектов, которые не будут уменьшаться. С учётом того, что объекты могут уменьшаться от внешнего влияния (которого в трейнми нет). Это условие характерно, когда в играх начинается новый уровень и объекты создаются с новым уровнем в меньшем количестве.

    Подсказки:

    1) Внедрить прыг в код трейнми создания объектов на функцию1(1000)

    2) Внедрить прыг в код трейнми уменьшения объектов на функцию1(1000)

    Функция1 (int a) //количество гранат

    {

    1) a1= Подсчитать количество гранат

    2) a2= a-a1;     // Вычислить недостатающее количество

    3) функция2(a2);   // Создать недостающее кол-во гранат

    }

    функция2 (int a){}

    Попробуйте сделать это решение как через автоассемблер так и через C-script.

    Если приводить аналогии с игрой, то нужно учитывать, что инструкций создания и уменьшения объектов может быть множество. Надо будет внедрять либо в одну из них, либо в некоторые, либо во все, либо создавать поток в игре, которые через интервал времени будет проверять кол-во объектов и создавать недостающие. 

  12. В геймхакинге применение событий определить можно  - это очень большее "поле" для раздумий. Например в терйнере можно перехватывать событие закрытия окна процесса игры и предпринять в это случае определённые действия. Особое внимание можно уделить событиям мышки и нажатиям клавиш на клавиатуре, т.к. это связано с действиями в игре, а если более конкретно связано с данными по адресам в игре. Перехватывая и инициализируя события мышки и клавиатуры можно что-то интересное сделать. Например, если по адресу такому-то такое-то значение, а событие от клавиатуры(мышки) такое то запустить такой-то чит. Короче говоря можно многое что придумать + даже придумать применении в инструментах по гемхакингу...

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

    Тема События в Дельфи.

    Ниже будут темы которые на мой взгляд неплохо знать, т.е знать о их существовании, а потом лазить по справкам если что. События вещь очень полезная. Представьте что у вас есть доступ ко всей очереди событий происходящий в Windows? Это и нажатие кнопок в каких-то окнах, открытие окон/закрытие и т.п. + даже свои события можно создавать (там ограниченное количество). Ваша программа может обрабатывать эти события вплоть, выбрасывать из очереди сообщений и добавлении свои события...

    Автор: Briculski

    Тема: Добавление события на примере onmouseleave 

    Все потомки TComponent могут посылать сообщения CM_MOUSEENTER и CM_MOUSELEAVE во время вхождения и покидания курсора мыши области компонента. Если вам необходимо, чтобы ваши компоненты обладали реакцией на эти события, необходио написать для них соответствующие обработчики. 

    procedure CMMouseEnter(var msg:TMessage); message CM_MOUSEENTER;

    procedure CMMouseLeave(var msg: TMessage); message CM_MOUSELEAVE;

    ..

    ..

    ..

    procedure MyComponent.CMMouseEnter(var msg:TMessage);

    begin

    inherited;

    {действия на вход мыши в область компонента}

    end;

    procedure MyComponent.CMMouseLeave(var msg: TMessage);

    begin

    inherited;

    {действия на покидание мыши области компонента}

    end;

    Дополнение

    Часто приходится сталкиваться с ситуацией, когда необходимо обработать два важных события для визуальных компонентов:

    MouseEnter - когда событие мыши входит в пределы визуального компонента;

    MouseLeave - когда событие мыши оставляет его пределы.

    Известно, что все Delphi объявляет эти сообщения в виде:

    Cm_MouseEnter;

    Cm_MouseLeave.

    Т.е. все визуальные компоненты, которые порождены от TControl, могут отлавливать эти события. Следующий пример показывает как создать наследника от TLabel и добавить два необходимых события onmouseleave и onmouseenter.

    (*///////////////////////////////////////////////////////*)

    (*// Author: Briculski Serge

    (*// E-Mail: bserge@airport.md

    (*// Date: 26 Apr 2000

    (*///////////////////////////////////////////////////////*)

    unit BS_Label;

    interface

    uses

      Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

      StdCtrls;

    type

      TBS_Label = class(TLabel)

      private

        { Private declarations }

        Fonmouseleave: TNotifyEvent;

        Fonmouseenter: TNotifyEvent;

        procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;

        procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;

      protected

        { Protected declarations }

      public

        { Public declarations }

      published

        { Published declarations }

        property onmouseleave: TNotifyEvent read Fonmouseleave write Fonmouseleave;

        property onmouseenter: TNotifyEvent read Fonmouseenter write Fonmouseenter;

      end;

    procedure Register;

    implementation

    procedure Register;

    begin

      RegisterComponents('Custom', [TBS_Label]);

    end;

    { TBS_Label }

    procedure TBS_Label.CMMouseEnter(var Message: TMessage);

    begin

      if Assigned(Fonmouseenter) then

        Fonmouseenter(Self);

    end;

    procedure TBS_Label.CMMouseLeave(var Message: TMessage);

    begin

      if Assigned(Fonmouseleave) then

        Fonmouseleave(Self);

    end;

    end.

    Тема: "Великолепный метод Perform"

    Автор: Михаил Христосенко

    WEB сайт: http://mihandelphi.narod.ru

     В этой статье я постараюсь показать что можно делать с помощью метода Perform, и какие интересные вещи скрываются в VCL кодах (в частности messages.pas). Метод Perform дает вам возможность посылать сообщения различным компонентам. Все сообщения описаны в файле Messages.pas (настоятельно рекомендую вам его посмотреть!!!). Данный метод надо вызывать по такой схеме: 

    Имя_компонента.Perform(Сообщение, верхний параметр: Integer, нижний параметр: Integer);

    Начнем с самого простого. Попробуем закрыть форму. Для этого поставьте на форму одну кнопку и в обработчике ее события onclick напишите:

    Form1.Perform(WM_CLOSE, 0, 0);

    Теперь попробуем изменить иконку вашей программы. Поставьте на форму компонент Image и загрузите в него какую-нибудь иконку. Будем использовать сообщение WM_SETICON. А поскольку в качестве параметров необходима величина типа Integer, то мы воспользуемся указателем на иконку (handle). Теперь обработчик нажатия кнопки может иметь вид:

    procedure TForm1.Button1Click(Sender: TObject);

    begin

      Form1.Perform(WM_SETICON, 0, image1.Picture.Icon.Handle);

    end;

    Теперь попробуем осуществить программный клик по кнопке 1. Поставьте на форму еще одну кнопку и в ее обработчике события onclick, напишите:

    Button1.Perform(WM_LBUTTONDOWN, 0, 0);

    Button1.Perform(WM_LBUTTONUP, 0, 0);

    А можно и попроще реализовать:

    Button1.Perform(BM_CLICK, 0, 0);

    Объясняю почему два обработчика. Вначале посылается сообщение, что кнопка нажата, затем сообщение - что отпущена, тогда то и происходит клик. Если вы все сделали правильно и ничего с формы не убирали, то иконка вашего приложения вновь поменяется.

    Теперь маленько коснемся компонентов для работы с текстом. Для начала установите на форму компонент Memo. Будем делать с ним разные стандартные вещи: добавлять символы, копировать, вставлять, вырезать, отменять и т.д.

    Начнем с вырезания текста. Обработчик кнопки поменяйте на:

    Memo1.SelectAll;

    Memo1.Perform(WM_CUT, 0, 0);

    С начала выделяется весь текст, а потом вырезается и помещается в буфер. Также можно и копировать текст, только надо изменить сообщение на: WM_COPY. Соответственно, чтобы вставить текст из буфера напишите:

    Memo1.Perform(WM_PASTE, 0, 0);

    Для очистки содержимого Memo, воспользуйтесь сообщением WM_CLEAR с параметрами 0,0. Для того, чтобы отменить введенный текст напишите следующее:

    Memo1.Perform(EM_UNDO, 0, 0);

    Чтобы добавить символ в Memo нужно написать так:

    Memo1.Perform(WM_CHAR, 192, 0);

    где 192, номер символа 'A', этот вызов метода Perform, можно заменить на аналогичный:

    Memo1.Perform(WM_CHAR, LongInt(char('A')), 0);

    Здесь значение символа 'A' как тип Char преобразуется в тип LongInt, а затем добавляется в Memo.

    Теперь будем разбираться с Listbox' ами. Для этого добавьте его на форму а событие onclick кнопки замените на:

    Listbox1.Perform(LB_ADDSTRING, 0, LongInt(Pchar('Эта строка появится в ListBoxe')));

    Если заменить LB_ADDSTRING на LB_INSERTSTRING, то строка будет вставляться в зависимости от первого параметра, который равен 0 в первом случае.

    Для того, чтобы выделить какую-нибудь строку в Listbox'e зная ее имя нужно написать следующий код:

    Listbox1.Perform(LB_SELECTSTRING, 0, LongInt(pchar('текст строки, которую нужно найти')));

    С помощью приведенной выше строчки кода можно реализовать поиск в ListBox'e, наподобие того, как это делается в FontDialog. В текстовом поле ввода вы вводите текст, и наиболее похожий по шрифт выделяется. Чтобы это осуществить поставьте на форму компонент Edit. А в обработчике его события onchange напишите:

    Listbox1.Perform(LB_SELECTSTRING, 0, LongInt(pchar(Edit1.Text)));

    Ну вот кратенький обзор метода Perform подошел к концу. Хочется посоветовать только одного, смотрите файл Messages.pas, ищите новые решения и не бойтесь пробовать, а вдруг сработает!!!

    Тема: "События в Delphi" (из статей Введение в DELPHI)

    Содержание:

    Обзор

    События в Delphi

    Понимание событий

    Обработка сообщений Windows в Delphi

    Обзор

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

    Программирование, ориентированное на события - неотъемлемая черта Windows. Некоторые программные среды для быстрой разработки приложений (RAD) пытаются скрыть от пользователя эту черту совсем, как будто она настолько сложна, что большинство не могут ее понять. Истина заключается в том, что событийное программирование само по себе не так уж сложно. Однако, есть некоторые особенности воплощения данной концепции в Windows, которые в некоторых ситуациях могут вызвать затруднения.

    Delphi предоставляет полный доступ к подструктуре событий, предоставляемой Windows. С другой стороны, Delphi упрощает программирование обработчиков таких событий.

    В данном уроке приводится несколько примеров того, как обрабатывать события в Delphi, дается более детальное объяснение работы системы, ориентированной на события.

    События в Delphi

    Объекты из библиотеки визуальных компонент (VCL) Delphi, равно как и объекты реального мира, имеют свой набор свойств и свое поведение - набор откликов на события, происходящие с ними. Список событий для данного объекта, на которые он реагирует, можно посмотреть, например, в Инспекторе Объектов на странице событий. (На самом деле, на этой странице представлен список свойств, которые имеют тип вроде TMouseMoveEvent и представляют из себя процедуры-обработчики событий. Существует соглашение по названиям данных свойств. Например, ondblclick соответствует двойному щелчку мыши, а onkeyup - событию, когда нажатая клавиша была отпущена.) Среди набора событий для различных объектов из VCL есть как события, портируемые из Windows (MouseMove, KeyDown), так и события, порождаемые непосредственно в программе (DataChange для TDataSource).

    Поведение объекта определяется тем, какие обработчики и для каких событий он имеет. Создание приложения в Delphi состоит из настройки свойств используемых объектов и создания обработчиков событий.

    Простейшие события, на которые иногда нужно реагировать - это, например, события, связанные с мышкой (они есть практически у всех видимых объектов) или событие Click для кнопки TButton. Предположим, что вы хотите перехватить щелчок левой кнопки мыши на форме. Чтобы сделать это - создайте новый проект, в Инспекторе Объектов выберите страницу событий и сделайте двойной щелчок на правой части для свойства onclick. Вы получите заготовку для обработчика данного события:

    procedure TForm1.FormClick(Sender: TObject);

    begin

    end;

    Напишите здесь следующее:

    procedure TForm1.FormClick(Sender: TObject);

    begin

      MessageDlg('Hello', mtInformation, [mbOk], 0);

    end;

    Каждый раз, когда делается щелчок левой кнопки мыши над формой будет появляться окно диалога (см. рис.1).

    post-3-1279435900,99_thumb.gif

    Рис.1: Диалог, появляющийся при щелчке мыши на форме.

    Код, приведенный выше, представляет из себя простейший случай ответа на событие в программе на Delphi. Он настолько прост, что многие программисты могут написать такой код и без понимания того, что они на самом деле отвечают на сообщение о событии, посланное им операционной системой. Хотя программист получает это событие через третьи руки, тем не менее он на него отвечает.

    Опытные программисты в Windows знают, что при возникновении события, операционная система передает вам не только уведомление о нем, но и некоторую связанную с ним информацию. Например, при возникновении события "нажата левая кнопка мыши" программа информируется о том, в каком месте это произошло. Если вы хотите получить доступ к такой информации, то должны вернуться в Инспектор Объектов и создать обработчик события onmousedown:

    procedure TForm1.FormMouseDown(Sender: TObject;

                                   Button: TMouseButton;

                                   Shift: TShiftState;

                                   X, Y: Integer);

    begin

      Canvas.TextOut(X, Y, 'X='+IntToStr(X)+' Y='+IntToStr(Y));

    end;

    Запустите программу, пощелкайте мышкой на форме:

    post-3-1279435937,1_thumb.gif

    Рис.2

    Как видите, в Delphi очень просто отвечать на события. И не только на события, связанные с мышкой. Например, можно создать обработчик для onkeydown (нажата клавиша):

    procedure TForm1.FormKeyDown(Sender: TObject;

                                 var Key: Word;

                            Shift: TShiftState);

    begin

      MessageDlg(Chr(Key), mtInformation, [mbOk], 0);

    end;

    Теперь, когда вы имеете начальные знания о программировании событий в Delphi, самое время вернуться назад и посмотреть на теорию, стоящую за тем кодом, что вы написали. После получения представления о том, как работает система, можно вернуться к среде Delphi и посмотреть, как использовать полностью имеющиеся возможности.

    Понимание событий

    Событийное программирование есть не только в Windows, и данную черту можно реализовать не только в операционной системе. Например, любая DOS программа может быть основана на простом цикле, работающем все время жизни программы в памяти. Ниже вы найдете гипотетический пример, как данный код может выглядеть:

    begin

      InitializeMemory;

      repeat

        CheckForMouseEvent(Events);

        CheckForKeyPress(Events)

        HandleEvents(Events);

      until Done := True;

      DisposeMemory;

    end.

    Это типичный пример программы, ориентированной на события. Она начинается и заканчивается инициализацией и освобождением памяти. В программе присутствует простой цикл repeat..until, который проверяет появление событий от мыши и клавиатуры и затем дает возможность программисту ответить на эти события.

    Переменная Events может быть записью с простой структурой:

    TEvent = record

      X, Y: Integer;

      MouseButton: TButton;

      Key: Word;

    end;

    Тип TButton, указанный выше, можно декларировать так:

    TButton = (lButton, rButton);

    Эти структуры позволяют вам проследить, где находится мышь, каково состояние ее кнопок, и значение нажатой клавиши на клавиатуре. Конечно, это пример очень простой структуры, но заложенные здесь принципы отражают то, что происходит внутри Windows или внутри других систем, ориентированных на события, вроде Turbo Vision. Если бы программа, приведенная выше, была редактором текста, то обработчик HandleEvent для такой программы мог бы иметь вид:

    procedure HandleEvent(Events: TEvent);

    begin

      case Events.Key of

       'A'..'z': Write(Events.Key);

        EnterKey: Write(CarriageReturn);

        EscapeKey: Done := True;

      end;

    end;

    Согласно коду выше, программа будет печатать букву 'a' при нажатии этой клавиши и перейдет на новую строку, если нажата клавиша 'Enter'. Нажатие 'Esc' приведет к завершению программы.

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

    Надеюсь, что приведенный пример дает некоторое понимание работы ориентированной на события системы. Единственное, что осталось пропущенным - почему система Windows так спроектирована.

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

    Если кратко, программист в Windows почти никогда не должен напрямую проверять "железо". Система выполняет эту задачу и передает информацию программе в виде сообщений.

    Когда пользователь щелкает мышкой, операционная система обрабатывает это событие и передает его в окно, которое должно обработать данное событие. Созданное сообщение, в этом случае, пересылается в некую процедуру DefWindowProc окна (default window procedure). DefWindowProc - аналог процедуры HandleEvent из примера, приведенного выше.

    Каждое окно в Windows имеет свою DefWindowProc. Чтобы полностью понять данное утверждение, представьте, что каждая кнопка, каждый ListBox, каждое поле ввода и т.д. на самом деле являются окнами и имеют свою процедуру DefWindowProc. Это очень гибкая и мощная система, но она может заставить программиста писать очень сложный код. Delphi дает возможность быть защищенным от такой структуры программы.

    Почти все, что происходит в Windows принимает форму сообщений и, если вы хотите их использовать в Delphi (в большей мере это необходимо при написании своих собственных компонент), то нужно понять, как эти сообщения работают.

    Если посмотреть на DefWindowProc в справочнике по Windows API, то можно увидеть следующее определение:

    function DefWindowProc(Wnd: HWnd; Msg, wParam: Word;

                           lParam: LongInt);

    Каждое сообщение, посылаемое в окно, состоит из четырех частей: первая часть - handle окна, получающего сообщение, Msg сообщает, что произошло а третья и четвертая части (wParam и lParam) содержат дополнительную информацию о событии. Вместе эти четыре части являются аналогом показанной выше структуры TEvent.

    Вторая часть сообщения имеет длину 16 бит и сообщает, что за событие произошло. Например, если нажата левая кнопка на мыши, то переменная Msg содержит значение WM_LBUTTONDOWN. Существуют десятки различного типа cообщений и они называются вроде WM_GETTEXT, WM_HSCROLL, WM_GETTEXTLENGTH и т.п. Список всех сообщений можно видеть в справочнике по Windows API (on-line help).

    Последние две переменные, длиной 16 и 32 бита, называются wParam и lParam. Они сообщают программисту важную дополнительную информацию о каждом событии. Например при нажатии кнопки мыши, lParam содержит координаты указателя мыши.

    Одна из хитростей заключается в том, как выделить нужную информацию из этих переменных. В большинстве случаев Delphi освобождает вас от необходимости выполнять данную задачу. Например, в обработчике события onmousedown для формы вы просто используете координаты X и Y. Как программисту, вам не нужно прилагать усилия для получения сообщения и связанных с ним параметров. Все, что связано с событиями, представлено в простом и непосредственном виде:

    procedure TForm1.FormMouseDown(Sender: TObject;

                                   Button: TMouseButton;

                                   Shift: TShiftState;

                                   X, Y: Integer);

    Итак, если подвести итог, то должно стать ясным следующее:

    Windows является системой ориентированной на события;

    События в Windows принимают форму сообщений;

    В недрах VCL Delphi сообщения Windows обрабатываются и преобразуются в более простую для программиста форму;

    Обработка событий в Delphi сводится к написанию для каждого объекта своих обработчиков;

    События в программе на Delphi вызываются не только сообщениями Windows, но и внутренними процессами.

    Обработка сообщений Windows в Delphi

    Конечно, нельзя придумать такую библиотеку объектов, которые бы полностью соответствовали потребностям программистов. Всегда возникнет необходимость дополнения или изменения свойств и поведения объектов. В этом случае, так же, как и при создании своих собственных компонент в Delphi, часто требуется обрабатывать сообщения Windows. Поскольку Object Pascal является развитием и продолжением Borland Pascal 7.0, то это выполняется сходным с BP способом.

    Общий синтаксис для декларации обработчика сообщений Windows:

    procedure Handler_Name(var Msg : MessageType);

    message WM_XXXXX;

    Handler_Name обозначает имя метода; Msg - имя передаваемого параметра; MessageType - какой либо тип записи, подходящий для данного сообщения; директива message указывает, что данный метод является обработчиком сообщения; WM_XXXXX - константа или выражение, которое определяет номер обрабатываемого сообщения Windows.

    Рассмотрим обработку сообщений на примере. Например, при нажатии правой кнопки мыши на форме в программе появляется всплывающее меню (pop-up menu, если оно было привязано к этой форме). Программист может захотеть привязать к правой кнопке какое-нибудь другое событие. Это можно сделать так:

    type

       TForm1 = class(TForm)

            PopupMenu1: TPopupMenu;

            MenuItem1: TMenuItem;

            MenuItem2: TMenuItem;

            MenuItem3: TMenuItem;

       private

         { Private declarations }

            procedure WMRButtonDown(var Msg : TWMMouse); message

    WM_RBUTTONDOWN;

       public

         { Public declarations }

       end;

    Подчеркнут код, добавленный в декларацию объекта TForm1 вручную. Далее, в секции implementation нужно написать обработчик:

    procedure TForm1.WMRButtonDown(var Msg : TWMMouse);

    begin

      MessageDlg('Right mouse button click.', mtInformation,

        [mbOK], 0);

    end;

    В данном случае при нажатии правой кнопки мыши будет появляться диалог.

    Вообще-то, у класса TForm уже есть унаследованный от дальнего предка обработчик данного события, который называется точно также и вызывает то самое pop-up меню. Если в новом обработчике сообщения нужно выполнить действия, которые производились в старом, то для этого применяется ключевое слово inherited. Если слегка модифицировать наш обработчик, то после диалога будет появляться pop-up меню:

    procedure TForm1.WMRButtonDown(var Msg : TWMMouse);

    begin

      MessageDlg('Right mouse button click.', mtInformation,

        [mbOK], 0);

      inherited;

    end;

    Однако, есть еще способ обработки всех сообщений, которые получает приложение. Для этого используется свойство OnMessage объекта Application, который автоматически создается при запуске программы. Если определен обработчик события OnMessage, то он получает управление при любом событии, сообщение о котором направлено в программу. Следующий код будет приводить к появлению диалога при двойном щелчке мыши на любом объекте в приложении.

    procedure TForm1.FormCreate(Sender: TObject);

    begin

      Application.OnMessage:=AOM;

    end;

    procedure TForm1.AOM(var Msg: TMsg; var Handled: Boolean);

    begin

       Handled:=False;

       if Msg.Message = WM_LBUTTONDBLCLK then begin

         MessageDlg('Double click.', mtInformation, [mbOK], 0);

         Handled:=True;

       end;

    end;

    Конечно, в обработчике нельзя выполнять операции, требующие длительного времени, поскольку это приведет к замедлению выполнения всего приложения.

    Если найду ещё статьи по событиям, то добавлю. Рихтера приводить не буду, кто желает - тогда сразу к нему книга "Windows для профессионалов"

  13. Основы работы с Win32API (прим. от меня применительно к Дельфи

    Автор: Акулов Николай

    Введение

    Цель этого обзора - помочь человеку перейти от использования средств Delphi к функциям Win API. Предполагается, что читатель уже неплохо владеет Delphi... 

    Продолжение ниже:

    Кроме того, многие авторы книг по Delphi не уделяют достаточно внимания функциям Win API, предназначенным для работы с окнами и графикой, потому что считают, что VCL Delphi достаточно хорошо справляется с этими задачами. Так что часто приходится учиться работе с Win API по книгам по 16-разряд-ному Borland Pascal'ю. Поэтому я буду обращать внимание и на отличие 32-разрядных версий от 16-разрядных. Но я не буду без особой необходимости останавливаться на подробном опи-сании конкретных функций, так как это всё сделано в справочной системе. Я также остановлюсь и на этой самой справочной системе, потому что начинающему программисту может оказаться не очень просто разобраться с ней.

    Что такое Win API

    Win API - это набор функций, предоставляемых операционной системой каждой программе. Эти функции находятся в стандартных динамически компонуемых библиотеках (Dynamic Linked Li-brary, DLL), таких как kernel32.dll, user32.dll, gdi32.dll. Эти файлы находятся в директории Win-dow. Вообще говоря, каждая программа должна самостоятельно заботится о том, чтобы подключить эти библиотеки. DLL могут подключаться к программе статически и дина-мически. В первом случае программа <освобождает> DLL только при завершении, во втором освобождение может произойти в любой момент. Если после освобождения DLL оказывается, что её больше не использует ни одна программа, она выгружается из памяти. Так как стандартные библиотеки используются самой системой, они всегда находятся в памяти, и поэтому использование динамического подключения бессмысленно. Чтобы статически подключить в Delphi некоторую функцию Win API, например, функцию GetWindowDC из модуля user32.dll, надо написать конструкцию вида

    function GetWindowDC(Wnd: HWnd); HDC;

    stdcall; external 'user32.dll' name 'GetWindowDC';

    Такая запись обеспечивает одновременно и статическое подключение библиотеки user32, и декларацию функции GetWindowDC, что позволяет компилятору в дальнейшем работать с ней. Обратите внимание, что все функции Win API написаны в соответствии с моделью вызова stdcall, а в Delphi по умолчанию используется другая модель - register (модель вызова определяет, как функции передаются параметры). Поэтому при импорте функций из стандартных биб-лиотек необходимо явно указывать эту модель. Далее указывается, из какой библиотеки импор-тируется функция и какое название в ней она имеет. Дело в том, что имя функции в библиотеке может не совпадать с тем, под которым она становится известна компилятору. Позже я остановлюсь на тех случаях, когда это используется. Главным недостатком DLL следует считать то, что в них сохраняется информация только об именах функций, но не об их параметрах. По-этому, если при импорте функции указать не те параметры, какие подразумевались автором DLL, то программа будет работать неправильно (вплоть до зависания), а ни компилятор, ни операционная система не смогут указать на ошибку.

    Обычно программа использует довольно большое число функций Win API. Декларировать их все довольно утомительно. К счастью, Delphi избавляет программиста от этой работы: все эти функции уже описаны в соответствующих модулях, достаточно упомянуть их имена в разделе uses. Например, большинство общеупотребительных функций описаны в модулях Windows.dcu и Messages.dcu.

    Как получить справку по функциям Win API

    Для тех, кто решил использовать Win API, самым необходимым инструментом становится ка-кая-либо документация по этим функциям. Их так много, что запомнить все совершенно нере-ально, поэтому работа без справочника под рукой просто невозможна. Наиболее доступным справочником для российского программиста является Win32 Developer's Reference, справочная система фирмы Microsoft, потому что фирма Inprise (тогда ещё Borland), получила лицензию на включение её в состав Delphi. Сам я буду постоянно ссылаться на эту справку, потому что под-робное описание функций займёт слишком много места, да и нет особого смысла описывать то, что описали и без меня.

    Хотя в комплект поставки Delphi и входит эта справочная система, содержащая описание всех функций Win API, получение справки по ним не настолько удобное, как по стандартным функ-циям Delphi. Если набрать в редакторе Delphi имя какой-нибудь функции Win API, поставить курсор в начало этой функции и нажать F1, то откроется справка по ней, как и в случае обыч-ных функций и процедур. Однако функции Win API не появляются в предметном указателе справочной системы. Авторы Delphi объясняют это ограничениями, накладываемыми самой справочной системой (как обычно, всех собак вешают Windows). Они же советуют создать в меню кнопки <Пуск> ярлык к справочной системе Win32 Developer's Reference. Мне остаётся только присоединиться к этому совету и добавить, что ярлык надо создавать к файлу MSTools.hlp, который находится в директории $(Delphi).

    Другая проблема заключается в том, что, так как система Windows написана на Си, все описа-ния функций Win API даны в соответствии с синтаксисом именно этого языка, а не Паскаля. Во-первых, необходимо разобраться с типами. Ниже приведена таблица, какие типы Си чему соответствуют.

    WCHAR = WideChar;

    LPSTR = PChar;

    LPCSTR = PChar;

    LPWSTR = PWideChar;

    LPCWSTR = PWideChar;

    DWORD = Integer;

    BOOL = LongBool;

    PBOOL = ^BOOL;

    PINT = ^Integer;

    PWORD = ^Word;

    PDWORD = ^DWORD;

    LPDWORD = PDWORD;

    UCHAR = Byte;

    PUCHAR = ^Byte;

    SHORT = Smallint;

    UINT = Integer;

    PUINT = ^UINT;

    ULONG = Longint;

    PULONG = ^ULONG;

    LCID = DWORD;

    LANGID = Word;

    int = Integer;

    long = LongInt;

    PVOID = Pointer;

    HANDLE = THandle;

    LPPOINT = TPoint;

    RECT = TRect;

    LPRECT = PRect;

    LPSIZE = PSize;

    BITMAP = TBitmap;

    Все типы, приведённые в первой части таблицы, в целях совместимости описаны в модуле Win-dows.dcu, поэтому их можно использовать наравне с обычными типами Delphi. Кроме этих типов общего назначения существуют ещё специальные. Например, дескриптор окна имеет тип HWND, первый параметр сообщения - тип WPARAM. Эти специальные типы также описаны в Windows.dcu. В некоторых имена типов, встречающихся в справке, и соответствующих им ти-пов из Windows.dcu отличаются только добавлением буквы , как это можно видеть из вто-рой части таблицы. Кстати, не следует путать тип TBitmap, определённый в Windows.dcu, с классом TBitmap, определённым в Graphics.dcu. Зачем разработчикам Delphi потребовалось на-зывать разные типы одним именем, не понятно, тем более что во второй версии Delphi был тип BITMAP, который куда-то исчез в третьей. Зато в четвёртой версии снова появился BITMAP, остался TBitmap, да ещё добавился tagBITMAP, и все эти три типа означают то же самое.

    Теперь о синтаксисе описания самой функции в Си. Оно имеет вид

    <Тип функции> <Имя функции>(<Тип параметра> <Имя параметра>,

    <Тип параметра2> <Имя параметра2>, ...);

    Еще в Си различается верхний и нижний регистр, поэтому идентификаторы HDC, hdc, hDC и т. д. - разные идентификаторы (автор Си очень любил краткость и хотел, чтобы можно было делать не 26, а 52 переменные с именем из одной буквы). Поэтому часто можно встретить, что имя параметра и его тип совпадают с точностью до регистра. К счастью, при описании функции в Delphi мы не обязаны сохранять имена параметров, значение имеют лишь их типы и порядок следования. С учётом всего этого функция, описанная в справке как

    HMETAFILE CopyMetaFile(HMETAFILE hmfSrc, LPCTSTR lpszFile);

    в Delphi имеет вид:

    function CopyMetaFile(hmfSrc: HMETAFILE; lpszFile: LPCTSTR): HMETAFILE;

    или, что то же самое,

    function CopyMetaFile(hmfSrc: HMetaFile; lpszFile: PChar): HMetaFile;

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

    Несколько особняком стоит тип VOID. Если функция имеет такой тип, то в Паскале она описы-вается как процедура. Если вместо параметров у функции в скобках стоит VOID, это означает, что функция не имеет параметров. Например, функция

    VOID CloseLogFile(VOID);

    в Delphi описывается как

    procedure CloseLogFile;

    Не путайте VOID и PVOID. PVOID - это нетипизированный указатель, соответствующий типу Pointer.

    В тех случаях, когда тип параметра является указателем на другой тип (обычно начинается с букв LP), при описании этой функции в Delphi можно пользоваться параметром-переменной, так как в этом случае функции передаётся указатель. Например, функция

    int GetRgnBox(HRGN hrgn, LPRECT lprc);

    в файле Windows.pas описана

    function GetRgnBox(RGN: HRGN; var p2: TRect): Integer;

    И, наконец, если не удаётся понять, как функция, описанная в справке, должна быть переведена на Паскаль, можно попытаться найти описание этой функции в исходных текстах модулей, по-ставляемых вместе с Delphi. Эти модули находятся в директории $(DELPHI). Можно также воспользоваться подсказкой, которая всплывает в редакторе Delphi после того, как будет набрано имя функции.

    Если посмотреть справку, например, по функции GetSystemMetrics, то видно, что эта функция должна иметь один целочисленный параметр. Однако далее в справке предлагается при вызове этой функции подставлять в качестве параметра не числа, а SM_ARRANGE, SM_CLEANBOOT и т. д. Подобная ситуация и со многими другими функциями Win API. Все эти SM_ARRANGE, SM_CLEANBOOT и т. д. являются именами числовых констант. Эти константы описаны в том же модуле, в котором описана функция, использующая их, поэтому можно не выяснять числен-ные значения этих констант, а указывать при вызове функций их имена, например, GetSystem-Metric-s(SM_Arrange); Если по каким-то причинам всё-таки потребовалось выяснить численные значения, то в справочной системе их искать не стоит - их там нет. Я могу только опять отправить к исходным текстам модулей Delphi, в которых эти константы описаны. Так, например, просматривая Windows.pas, можно узнать, что SM_ARRANGE = 56. Кстати, всем, кто решиться самостоятельно просматривать исходники, я очень рекомендую использовать для этого не текстовый редактор, а программу, которая может только показать, но не изменить файл (что-то вроде просмотра по F3 в Norton Commander'е). Так безопаснее. Или же стоит по-думать о резервной копии.

    В описании многих функций Win API вверху можно увидеть три ссылки: QuickInfo, Overview и Group. Первая даёт краткую информацию о функции. Самой полезной частью этой информации является то, для каких версий Windows эта функция реализована. Например, очень полезна функция MaskBlt, однако QuickInfo показывает, что она реализована только в Windows NT. Программа, использующая эту функцию, не будет работать в Windows 95. Иногда напротив на-звания одной из систем стоит слово , которое переводится как <пень>, <обрубок> (например, для функции GetDeviceGammaRamp это слово стоит напротив Windows NT). Это означает, что в данной версии эта функция присутствует (то есть обращение к ней не вызывает ошибки), но ничего не делает. Оставим на совести программистов из Microsoft вопрос, зачем нужны такие пни. Overview - это краткий обзор какой-то большой темы. Например, для любой функции, работающей с растровыми изображениями, обзор будет в двух словах объяснять, за-чем в принципе нужны эти самые растровые изображения. Судя по непроверенным данным, первоначально эти обзоры замышлялись как нечто большее, но потом остановились на таких вот лаконичных фразах. Как бы то ни было, найти в обзоре полезную информацию удаётся крайне редко, поэтому заглядывать туда стоит только если ну совсем ничего не понятно. И, наконец, Group. Эта ссылка приводит к списку всех функций, родственных данной. Например, для функции CreateRectRgn группу будут составлять все функции, имеющие отношение к ре-гионам. Если теперь нажимать на кнопку << (два знака <меньше>) сразу под главным меню окна справки, то будут появляться страницы с кратким описанием возможных применений объ-ектов, с которыми работают функции (в приведённом примере описание возможностей ре-гионов). Чтобы читать их в нормальной последовательности, лучше всего нажать на < столько раз, сколько возможно, а затем пойти в противоположном направлении с помощью кнопки >.

    Иногда в справке можно встретить указания или . К этим замечаниям следует относится критически, так как справка написана для Windows 95, когда ещё не было Windows NT 4.0, описывается версия со старым интерфейсом. Так что то, про что на-писано , может вполне успешно работать и в Windows NT 4.0 и выше, осо-бенно если это <что-то> связано с пользовательским интерфейсом. То же самое относится и к QuickInfo. Такие вещи лучше всего проверять на практике.

    Ещё несколько слов о числовых константах. В справке можно встретить числа вида, например, 0xC56F или 0x3341. Префикс <0x> в Си означает шестнадцатеричное число. В Delphi надо его заменить на <{:content:}gt;, то есть вышеназванные числа должны быть записаны как $C56F и $3341 соот-ветственно.

    Дескрипторы вместо классов

    Программируя в Delphi, мы быстро привыкаем к тому, что каждый объект реализуется экземпляром соответствующего класса. Например, кнопка реализуется экземпляром класса TButton, контекст устройства - классом TCanvas. Но когда создавались первые версии Windows, объ-ектно-ориентированный метод программирования ещё не был общепризнанным, поэтому он не был реализован. Современные версии Windows частично унаследовали этот недостаток, по-этому в большинстве случаев приходится работать по старинке, тем более что DLL могут экс-пор-тировать только функции, но не классы.

    Когда мы создаём некоторый объект в Windows, ему присваивается уникальный 32-разрядный номер, называемый дескриптором. В дальнейшем при работе с этим объектом каждой функции передаётся этот дескриптор. В этом и заключается главное различие между методами класса и функциями Win API. Первые связаны с тем экземпляром класса, через который они вызыва-ются, и поэтому не требуют явного указания на объект. Вторым необходимо такое указание, так как они сами по себе никак не связаны ни с одним объектом.

    Не следует думать, что при работе с Win API следует полностью отказываться от классов Delphi. Эти методы прекрасно работают вместе. Правда, внутренние механизмы Delphi не могут вклю-читься, если изменение объекта происходит через Win API. Например, если спрятать окно не с помощью метода Hide, а с помощью вызова функции Win API ShowWindow(Handle, SW_Hide), не возникнет событие OnHide, потому что оно запускается теми самыми внутренними механиз-мами Delphi. Но такие недоразумения случаются обычно только тогда, когда функциями Win API дублируется то, что можно сделать и с помощью Delphi. Для вызова функций Win API объ-екта, созданного с помощью Delphi, используйте свойство Handle. В нём хранится дескриптор.

    В некоторых случаях класс Delphi инкапсулирует несколько объектов Windows. Например, класс TBitmap включает в себя HBitmap и HPalette - картинку и палитру к ней. Соответственно, он хранит два дескриптора - в свойствах Handle и Palette.

    Все экземпляры классов, созданные в Delphi, должны удаляться. В некоторых случаях это происходит автоматически, в некоторых программист должен сам позаботиться о <выносе му-сора>. Аналогичная ситуация и с объектами, создаваемыми в Win API. Если посмотреть справку по функции, создающей какой-то объект, то там обязательно будет информация о том, какой функцией можно удалить объект и может ли система сделать это автоматически. Во многих случаях совершенно разные объекты могут удаляться одной и той же функцией. Так, функция DeleteObject удаляет косметические карандаши, геометрические карандаши, кисти, шрифты, регионы, растровые изображения и палитры. Обращайте внимание на возможные исключения. Например, регионы не удаляются системой автоматически, однако если вызвать для региона функцию SetWindowRgn, то этот регион переходит в собственность операционной системы. Никакие дальнейшие операции с ним, в том числе и удаление, совершать нельзя

    Формы Delphi и окна Windows

    Принято считать, что класс TForm реализует окно. Это не совсем верно, потому что TForm реализует лишь часть тех объектов, которые принято называть окнами. Например, кнопка - это тоже окно, но реализуется она классом TButton.

    Каждое окно принадлежит к какому-то оконному классу. Не следует путать оконный класс с классами Delphi. Это некий шаблон, определяющий базовые свойства окна. Каждому такому шаблону присваивается имя, уникальное в его области видимости. Классы делятся на локаль-ные (видимые только в приложении, регистрирующем их) и глобальные (видимые вне прило-жения). В 16-разрядных версиях Windows локальный класс, зарегистрированный приложением, был виден всем другим экземплярам этого же приложения. В 32-разрядных версиях различные экземпляры одного приложения стали более самостоятельными, поэтому каждый экземпляр должен заново регистрировать все свои классы. Перед использованием класс необходимо заре-гистрировать ( функция RegisterClassEx). При завершении программы все классы, зарегистриро-ванные в ней, удаляются автоматически, хотя при необходимости их можно удалить и само-стоятельно. Отсюда очевидно, что глобальный оконный класс, доступный всем программам, должен быть зарегистрирован динамической библиотекой, постоянно находящейся в памяти. Как это сделать, можно прочитать в Win32 Developer's Reference, тема WNDCLASS. Если же глобальный класс регистрируется программой обычным образом, то это означает, что он будет доступен не только самой программе, но и всем библиотекам, вызванным ею, но никак не другим программам и не другим экземплярам этой программы. Если наоборот, глобальный класс регистрирует DLL, то он становится доступным всем программам, использующим эту DLL. Но в этом случае класс не удаляется автоматически.

    При создании окна обязательно указывать его класс. Данный класс должен быть к этому мо-менту зарегистрирован. В Delphi имя оконного класса для окон, созданных наследниками TForm, всегда совпадает с именем класса Delphi. Существуют предопределённые классы Win-dows, которые не надо регистрировать. Это 'BUTTON', 'COMBOBOX', 'EDIT', 'LISTBOX', 'MDICLIENT', 'SCROLLBAR' и 'STATIC'. Назначение этих классов понятно из их названий (класс 'STATIC' реализует статические, то есть не реагирующие на мышь и клавиатуру, но имеющие дескриптор элементы, текстовые или графические). Впрочем, можно определить локальный класс с зарезервированным именем, он перекроет глобальный в пределах приложения.

    Кроме имени, класс включает в себя другие параметры, такие как стиль, кисть и т. д. Они подробно перечислены в справке по теме WNDCLASS.

    Кроме класса нужно указать стиль окна и расширенный стиль. Они определяют поведение конкретного окна и не имеют ничего общего со стилем класса. Возможные значения этих стилей перечислены в справке по функциям CreateWindow и CreateWindowEx. Результатом работы этих функций является дескриптор созданного ими окна.

    Функции, создающие окна, требуют указать дескриптор приложения. В Delphi этот дескриптор хранится стразу в двух переменных - MainInstance модуля System и HInstance модуля SysInit. Оба эти модуля автоматически подключаются к любому модулю, созданному в Delphi, так что можно использовать ту или иную переменную по своему вкусу. Кстати, не следует путать авто-матическое подключение этих модулей с автоматической генерацией кода IDE Delphi для под-ключения таких модулей, как Windows, Forms, SysUtils и т. д. В первом случае модули подклю-чаются несмотря на то, что не упомянуты в списке uses. Более того, их упоминание там приве-дёт к ошибке. Во втором случае эти модули явным образом подключаются, просто Delphi автоматически пишет эту часть программы за программиста. Можно написать модуль или даже це-лую программу, которые не будут использовать SysUtils, но нельзя написать такие, которые не будут использовать System.

    Создание окон через Win API требует кропотливой работы. VCL Delphi справляется с этой задачей замечательно, поэтому создавать окна самостоятельно приходится только тогда, когда ис-пользование VCL нежелательно, например, если необходимо написать как можно более ком-пактное приложение. Во всех остальных случаях приходится только слегка подправлять работу VCL. Например, с помощью Win API можно изменить форму окна или убрать из него заголо-вок, оставив рамку. Подобные действия не требуют от программиста создания нового окна, можно воспользоваться тем, что уже создано VCL.

    Другой случай, когда могут понадобиться функции Win API для окон - если приложение должно что-то делать с чужими окнами. Например, хотя бы просто перечислить все окна, от-крытые в данный момент, как это делает WinSight32. Но в этом случае также не приходится самому создавать окна, работа идёт с уже имеющимися.

    Callback функции

    Прежде чем двигаться дальше, необходимо разобраться с тем, что такое callback функции. На русский язык это обычно переводится как функции косвенного вызова. Эти функции в программе описываются, но обычно не вызываются напрямую, хотя ничто не запрещает сделать это. В этом они похожи на те методы класса, которые связаны с событиями. Ничто не мешает вызывать напрямую, например, метод FormCreate, но делать это приходится крайне редко. С другой стороны, даже если этот метод не вызывается явно, он всё равно выполняется, потому что VCL автоматически вызывает его без прямого указания программиста. Еще одно общее свойство - конкретное имя метода при косвенном вызове не важно. Можно изменить его, но если этот метод по-прежнему будет связан с событием OnCreate, он так же будет успешно вы-зываться. Разница заключается только в том, что такие методы вызываются внутренними меха-низмами Delphi, а callback функции - самой системой Windows. Соответственно, на эти функции налагаются следующие требования: во-первых, эти функции должны быть именно функциями, а не методами класса (впрочем, иногда это условие удаётся обойти); во-вторых, эти функции должны быть написаны в соответствии с моделью вызова stdcall. Справочная система предла-гает использовать модель callback, которая в имеющихся версиях Windows совпадает с stdcall. Однако в Delphi такая модель не поддерживается. Что же касается того, как программист сообщает системе о том, что он написал callback функцию, то это в каждом случае по-своему.

    Очень часто функции косвенного вызова используются при перечислении некоторых объектов. В качестве примера рассмотрим перечисление окон с помощью функции EnumWindows. В справке она описана так:

    BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);

    Соответственно, в Windows.pas она имеет вид

    function EnumWindows(lpEnumFunc: TFNWndEnumProc;

    lParam: LPARAM): BOOL; stdcall;

    тип TFNWndEnumProc совпадает с типом Pointer. Здесь должен стоять указатель на callback функцию. Синтаксис этой функции описан так:

    BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);

    Функции с таким именем не существует в Win API. Это так называемый прототип функции, согласно которому следует описывать callback функцию. На самом деле этот прототип предос-тавляет большую свободу, чем это может показаться на первый взгляд. Как я уже сказал выше, имя может быть любым. Любыми могут быть и типы функции и параметров, при условии что новые типы совпадают по размерам с теми, которые указываются. Что касается типа функции и типа первого параметра, то они имеют определённый смысл и менять их тип практически бес-смысленно. Другое дело со вторым параметром. Он предназначен специально для передачи значения, которое программист волен использовать по своему усмотрению, система не имеет на него никаких видов. А программисту может показаться удобнее работать не с типом LPARAM (то есть LongInt), а, например, с указателем или же с массивом из четырёх байт. Лишь бы были именно четыре байта, а не восемь, шестнадцать или ещё какое-то число. Можно даже превратить этот параметр в параметр-переменную, так как при этом функции будут переда-ваться всё те же четыре байта - адрес переменной. Но эти удовольствия для тех, кто хорошо разбирается с тем, как используется стек для передачи параметров при различных моделях вы-зова.

    Как же работает EnumWindows? После вызова эта функция начинает по очереди перебирать все имеющиеся в данный момент окна верхнего уровня, то есть те, у которых нет родителя. Для каждого такого окна вызывается эта самая callback функция, в качестве первого параметра ей передаётся дескриптор данного окна (каждый раз, естественно, новый), в качестве второго - то, что было передано самой функции EnumWindows в качестве второго параметра (каждый раз одно и то же). Что же может делать callback функция с этим дескриптором? А всё, на что у про-граммиста хватит фантазии. Например, можно минимизировать или вообще закрыть все эти окна, хотя не понятно, с чего бы вдруг устраивать такую диверсию. Или можно проверять все эти окна на соответствие какому-то условию, пытаясь найти нужное. А значение, возвращаемое callback функцией, влияет на работу EnumWindows. Если она возвращает False, значит, всё, что нужно, уже сделано, можно не перебирать остальные окна.

    Окончательный код для того случая, когда второй параметр имеет тип Pointer, выглядит так:

    function MyCallbackFunction(Wnd:HWnd; P: Pointer):Bool; stdcall;

    begin

      { что-то делаем }

    end;

    ...

    var

      MyPointer: Pointer;

    ...

    EnumWindows(@MyCallbackFunction, LongInt(MyPointer));

    Что бы мы ни делали с типом второго параметра callback функции, тип соответствующего па-раметра EnumWindows не меняется. Поэтому необходимо явное приведение передаваемого па-раметра к типу LongInt. Обратное преобразование типов при вызове MyCallbackFunction осуще-ствляется автоматически.

    В 16-разрядных версиях Windows вызов callback функций осложнялся тем, что для них необхо-димо было делать специальный код, называемый прологом. Пролог создавался с помощью функции MakeProcInstance, удалялся после завершения с помощью FreeProcInstance. То есть вызов EnumWindows должен был бы выглядеть так:

    var

      MyProcInstnace: TFarProc;

    ...

    MyProcInstance := MakeProcInstance(@MyCallbackFunction, HInstance);

    EnumWindows(MyProcInstance, LongInt(MyPointer));

    FreeProcInstance(MyProcInstance);

    В Delphi этот код будет работоспособным, так как для совместимости MyProcInstance и FreePro-cInstance оставлены. Но они ничего не делают (в чём легко убедиться, просмотрев ис-ходный файл Windows.pas), поэтому можно обойтись и без них. Другой способ, с помощью которого в 16-разрядных версиях можно сделать пролог - описать функцию с директивой export. Эта директива сохранена для совместимости и в Delphi, но в 32-разрядных версиях она также ничего не делает (несмотря на то, что справка, например, по Delphi 3.0 утверждает обрат-ное; в справке по Delphi 4.3 этой ошибки уже нет).

    Сообщения Windows

    Человеку, знакомому с Delphi, должна быть ясна схема событийного управления. Программист пишет только код реакции на какое-либо событие, а дальше программа ждёт, когда система сочтёт, что настало время передать управление этому участку кода. Простые программы в Del-phi состоят исключительно из методов реакции на события вроде OnCreate, onclick, OnClose-Qerry и т. д. Причём событием называется не только событие в обычном смысле этого слова, то есть когда происходит что-то внешнее, но и ситуация, когда событие используется просто для передачи управления основной программе в тех случаях, когда VCL не может сама справиться с какой-то задачей. Примером такого события является, например, TListBox.OnDrawItem. Устанавливая стиль списка в lbOwnerDrawFixed или lbOwnerDrawVariable, программист как бы сообщает VCL, что он не доволен теми средствами рисования элементов списка, которыми она располагает, и что он берёт эту часть задачи на себя. И каждый раз, когда возникает необходимость в рисовании элемента, VCL передаёт управление специально написанному коду. На самом деле разница между двумя типами собы-тий весьма условна. Можно так же сказать, что когда пользователь нажимает клавишу, VCL не знает, что делать, и поэтому передаёт управление обработчику onkeypress.

    Событийное управление не есть изобретение авторов Delphi. Такой подход исповедует сама система Windows. Только здесь события называются сообщениями (message), что, на мой взгляд, даже лучше отражает ситуацию. Windows посылает программе сообщения, связанные либо с тем, что произошло что-то внешнее (мышь, клавиатура...), либо с тем, что самой сис-теме потребовались от программы какие-то действия. Самым распространённым таким дейст-вием является предоставление информации. Например, когда Windows хочет узнать заголовок окна, она посылает этому окну специальное сообщение, в ответ на которое окно должно сооб-щить системе свой заголовок. Ещё бывают сообщения, которые просто уведомляют программу о начале какого-то действия (например, о начале перетаскивания окна) и предоставляют возможность вмешаться. Но это вмешательство необязательно.

    В Delphi для реакции на каждое событие обычно создаётся свой метод. В Windows одна проце-дура, называемая оконной, обрабатывает все сообщения. В языке Си нет понятия <процедура>, поэтому при использовании Паскаля может возникнуть путаница. Дело в том, что то, что называется оконной процедурой, на самом деле является функцией. Тем не менее, я буду использовать общепринятый термин <оконная процедура>. Каждое сообщение имеет свой уни-кальный номер, а оконная процедура обычно целиком состоит из оператора case, и каждому сообщению соответствует своя альтернатива этого оператора. Номера сообщений учить не надо, потому что можно использовать константы, описанные в модуле Messages.dcu. Эти кон-станты начинаются с префикса, указывающего на принадлежность сообщения к какой-то группе. Например, сообщения общего назначения начинаются с WM_: например, WM_Paint, WM_GetTextLength. Сообщения, специфичные, например, для кнопок, начинаются с префикса BM_. Остальные группы сообщений также связаны либо с теми или иными элементами управления, либо со специальными действиями, например, с динамическим обменом данными (dy-namic data exchange, DDE). Обычной программе приходится обрабатывать довольно много сообщений, поэтому оконная процедура бывает, как правило, очень длинной и громоздкой. Оконная процедура описывается программистом как callback функция и указывается при созда-нии оконного класса. Таким образом все окна данного класса имеют одну и ту же оконную процедуру. Впрочем, существует возможность породить так называемый подкласс, то есть новый класс, наследующий все свойства существующего, за исключением оконной процедуры. Несколько подробнее об этом будет сказано далее.

    Кроме номера, каждое сообщение содержит два параметра - WParam и LParam. Буквы и означают и , то есть первый параметр 16-разрядный, а второй - 32-разряд-ный. Однако так было только в старых, 16-разрядных версиях Windows. В 32-разрядных вер-сиях оба параметра 32-разрядные, несмотря на их названия. Конкретный смысл каждого пара-метра зависит от сообщения. В некоторых сообщениях один или оба параметра могут вообще не использоваться, в других - наоборот, двух параметров даже не хватает. В этом случае один из параметров (обычно LParam) содержит указатель на дополнительные данные. После обра-ботки сообщения оконная процедура должна вернуть какое-то значение. Обычно это значение просто сигнализирует, что сообщение не нуж-дается в дополнительной обработке, но в некоторых случаях оно более осмысленно, например, WM_SetIcon должно вернуть дескриптор иконки, которая была установлена ранее. Если про-граммист не хочет обрабатывать сообщение самостоятельно, он должен вызвать для его обра-ботки функцию DefWindowProc.

    Обработка сообщения требует времени, иногда довольно значительного. За это время окну может быть отправлено ещё несколько сообщений. Чтобы они не пропали, Windows организует так называемую очередь сообщений. Очередь сообщений своя для каждой нити. Нить должна сама выбирать сообщения из этой очереди, транслировать их и затем вызывать функцию Dispatch-Message, чтобы направить это сообщение в нужную оконную процедуру. Всё это лучше не писать самому, а оставить на совести VCL, которая прекрасно с этим справляется. При программировании в Delphi обычно требуется либо нестандартная реакция на сообщение, либо отправка сообщения другому окну.

    Отправка сообщения другому окну может осуществляться достаточно разнообразными способами. Можно послать сообщение в очередь, а можно заставить Windows вызвать оконную про-цедуру напрямую, в обход очереди. Можно установить максимальное время ожидания отклика от окна, которому послано сообщение. Все эти функции хорошо описаны в справке (см., на-пример, функцию SendMessage и группу родственных функций).

    Кроме параметров WParam и LParam, каждому сообщению приписывается время возникновения и координаты курсора в момент возникновения. Эти параметры можно узнать с помощью функций GetMessagePos и GetMessageTime.

    Разумеется, что Delphi предоставляет программисту все средства, необходимые для обработки сообщений. Самый простой способ - описать метод для обработки сообщения с директивой message. Это выглядит примерно так

    type

      TSomeForm = class(TForm)

    ...

    procedure WMSomeMessage(var message: TMessage);

    message WM_SomeMessage;

    ...

    procedure TSomeForm.WMSomeMessage;

    begin

      ...

      inherited

    end;

    Стандартная оконная процедура в Delphi устроена так, что она ищет среди методов класса специальные методы для обработки каждого сообщения. Эти методы во многом подобны обыкновенным виртуальным методам. Другими словами, если переопределить такой метод, будет вы-зван именно новый, а не старый вариант. Вообще говоря, в классе-родителе метода для обра-ботки какого-то конкретного сообщения может и не существовать. Это, однако, никак не ска-зывается на синтаксисе (в отличие от обычных виртуальных методов, где приходится писать директиву virtual для вновь созданных и override для перекрытых). Кроме того, при перекрытии методов обработки сообщений не важно имя метода, значение имеет только константа, стоящая после message. Именно поэтому при вызове перекрытого метода для обработки данного сообщения достаточно просто написать inherited, без указания имени метода. Такой способ вызова не приведёт к ошибке даже в том случае, если класс-родитель вообще не имел метода для обработки такого сообщения.

    Тип TMessage сделан специально для обработки сообщений. Это запись, содержащая 32-раз-рядные целые поля Msg, WParam, LParam и Result. Первое поле содержит номер сообщения, два следующих - параметры сообщения, а полю Result метод должен присвоить то значение, кото-рое потом вернёт системе оконная процедура. Именно из-за необходимости передавать значе-ние параметр метода обработки сообщения должен быть переменной. При обработке сообще-ний часто приходится сталкиваться с ситуациями, когда один 32-разрядный параметр используется для передачи двух 16-разрядных значений. Чтобы облегчить программисту работу в таких случаях, тип TMessage описан как вариантная запись, поэтому в нём есть поля WParamLo, WParamHi, LParamLo, LParamHi, ResultLo и ResultHi, имеющие тип Word и дающие доступ к старшему и младшему словам соответствующего параметра.

    Так как параметры WParam и LParam могут иметь совершенно различный смысл для разных сообщений, не всегда удобно представлять их в виде чисел. Иногда предпочтительнее, чтобы они имели тип Pointer, или LongBool, или ещё какой-либо. Поэтому тип TMessage - не единственный тип, который может иметь параметр метода обработки сообщения. Для многих сооб-щений в модуле Messages.dcu описаны собственные типы. Их названия образованы от названия соответствующих сообщений. Например, для сообщения WM_Paint описан тип TWMPaint, для WM_GetText - TWMGetText, и так далее. В этих типах все поля имеют тот тип, который наи-лучшим образом подходит для обработки именно этого сообщения. Кроме того, поля имеют названия, отражающие их назначения, что делает программу более удобной для чтения. Но такие типы описаны не для всех сообщений, поэтому иногда приходится пользоваться универ-сальным TMessage. Кстати, если по каким-то причинам в методе обработки сообщения потре-буется использовать не тот тип, который используется в соответствующем методе класса-роди-теля, никаких проблем не возникнет: в данном случае приведение типов выполняется автома-тически. Узнать, есть ли специальный тип для данного сообщения, можно двумя способами: либо поискать этот тип в Messages.pas, либо просто проверить, <съест> его компилятор или нет.

    Сообщения, определяемые пользователем

    Использование сообщений очень удобно в тех случаях, когда нужно заставить окно выполнить какое-то действие. Поэтому Windows предоставляет возможность программисту создавать свои сообщения, которые могут быть локальными или глобальными. Использование локальных со-общений связано с некоторым риском. Дело в том, что эти сообщения должны посылаться только <своим> окнам, то есть тем, оконные процедуры которых написаны так, чтобы пра-вильно интерпретировать это сообщение. Если послать такое сообщение <чужому> окну, его реакция может быть непредсказуемой, потому что человек, писавший его оконную процедуру, мог использовать сообщение с этим же номером для своих целей. Всё это вовсе не значит, что обмен локальными сообщениями возможен только внутри одной программы: если разные про-граммы написаны так, что они правильно понимают одно и то же локальное сообщение, они могут без каких-либо ограничений обмениваться им. Немного повторюсь: важно только чтобы отправитель и получатель сообщения одинаково понимали его. В справочной системе специально указывается, что недопустимо отправлять такие сообщения окнам классов 'BUTTON', 'EDIT', 'LISTBOX' и 'COMBOBOX'.

    В Windows (и, соответственно, в модуле Messages.dcu) определена специальная константа WM_User, равная $400 (1024). Впрочем, нет гарантии, что в следующих версиях Windows зна-чение этой константы не изменится. Номера стандартных сообщений лежат в диапазоне от 0 до WM_User-1. Для локальных пользовательских сообщений оставлен диапазон от WM_User до $7FFF (32767). Забегая чуть вперёд, скажу, что для глобальных пользовательских сообщений оставлен диапазон от $C000 до $FFFF (от 49152 до 65535).

    Глобальные пользовательские сообщения, называемые также строковыми, предназначены специально для тех случаев, когда локальные сообщения оказываются слишком ненадёжными. Например, может потребоваться написать несколько программ, которые взаимодействуют ме-жду собой. Поиск окон, принадлежащих этим программам, можно осуществлять, посылая всем окнам какое-либо специальное сообщение. Те, которые правильно откликнулись - <свои>. Так как сообщения посылаются всем окнам, <чужие> тоже будут его получать. Нужна гарантия, что они никак не отреагируют на такое сообщение. Для этого существует регистрация сообщений, обеспечивающая уникальный номер каждому, кто в нём нуждается.

    Прежде чем зарегистрировать сообщение, необходимо придумать ему имя (именно поэтому они называются строковыми). Если давать своим сообщениям осмысленные имена, а не что-то вроде WM_MyMessage1, слишком мала вероятность случайного совпадения. Далее это сообще-ние регистрируется функцией RegisterWindowMessage, которая возвращает уникальный номер этого сообщения. Если сообщение с таким именем регистрируется впервые, номер выбирается из числа ещё не занятых. Если же сообщение с таким именем уже было зарегистрировано, то возвращается тот же самый номер, который был присвоен ему при первой регистрации. Таким образом разные программы, регистрирующие сообщения с одинаковыми именами, получат одинаковые номера и смогут понимать друг друга. Для прочих же окон это сообщение не будет иметь никакого смысла.

    Неудобство использования таких сообщений очевидно - их номера определяются только после начала выполнения программы, при компиляции они ещё неизвестны. Поэтому обработка та-ких сообщений описанным ранее методом невозможна - мы не знаем, какой номер писать по-сле слова message. Здесь может помочь виртуальный метод WndProc, имеющийся в классе TControl (и в TForm как в его потомке). Этот метод получает все сообщения, поступающие окну. Если перекрыть этот метод, то ничего не мешает сравнивать внутри него номер пришед-шего и определённого пользователем сообщения. Например, так:

    var

      WM_MyUserDemoMessage: Cardinal;

    ...

    procedure TForm1.FormCreate(Sender: TObject);

    begin

      WM_MyUserDemoMessage := RegisterWndowMessage('WM_MyUserDemoMessage')

    end;

    ...

    procedure TForm1.WndProc(var message: TMessage);

    begin

      if message.Msg = WM_MyUserDemoMessage then

      begin

        ...

      end

      else

        inherited WndProc(message)

    end;

    Метод WndProc <первичнее>, чем методы с директивой message. Он раньше получает сообщения, и он же содержит код, который при необходимости ищет и затем вызывает для каждого сообщения соответствующий метод обработки сообщения. И он же вызывает функцию Win API DefWndProc для стандартной реакции на сообщение. Если при перекрытии не вызывать унасле-дованный метод, то придётся самостоятельно реализовывать эти действия или же подумать, как обойтись без них.

    Диапазон номеров сообщений от $8000 (32768) до $BFFF (49151) пока ничем не занят, но зарезервирован Windows для использования в будущем. Авторы Delphi поступили не совсем кор-ректно, использовав верхнюю часть этого диапазона (с адреса $B000 (45046)) для своих собственных сообщений. Именованные константы для этих сообщений находятся в модуле Controls.dcu и начинаются с префикса CM_. Эти сообщения обычно бесполезны для автора готовых программ, но бывают крайне необходимы при написании своих компонентов. Эти сообщения, к сожалению, никак не упомянуты в справке Delphi, поэтому разбираться с ними приходится по исходным файлам VCL.

    Особые сообщения

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

    Сообщение WM_CopyData используется для передачи блока данных от одного процесса к дру-гому. В 32-разрядных версиях Windows память, выделенная процессу, недоступна для всех ос-тальных процессов. Поэтому просто передать указатель другому процессу нельзя - он не смо-жет получить доступ к этой области памяти. Для сообщения WM_CopyData приходится делать исключение: блок данных временно становится доступным другому процессу. Это требует оп-ределённой синхронности действий от двух процессов, поэтому для отправки этого сообщения можно использовать только SendMessage, прямо вызывающую оконную процедуру. PostMessage использовать нельзя.

    Сообщение WM_Paint предназначено для перерисовки клиентской области окна. Если изобра-жение сложное, перерисовка занимает много времени. Чтобы улучшить быстродействие сис-темы, авторы Windows сделали так, что сообщение WM_Paint пропускает все остальные сооб-щения в очереди, и передаётся окну только тогда, когда в очереди не остаётся никаких других сообщений. Если в очереди оказываются несколько сообщений WM_Paint, они объединяются в одно. Просто так послать сообщение WM_Paint невозможно. Для этого надо сначала объявить, что окно или его часть нуждаются в перерисовке (InvalidateRect, InvalidateRgn).

    При обработке сообщений от клавиатуры можно использовать функцию GetKeyState, которая возвращает состояние любой клавиши (нажата-отпущена) в момент возникновения данного события. Именно в момент возникновения, а не в момент вызова функции.

    Компоненты, влияющие на обработку событий

    Так как стандартные средства Delphi не позволяют использовать все инструменты Win API для работы с окнами, иногда приходится писать компоненты, модифицирующие форму. Мне, на-пример, приходилось писать компоненты, при помещении которых на форму она становится непрямоугольной или полупрозрачной. Очень часто таким компонентам приходится обрабаты-вать те сообщения, которые предназначены форме-хозяину. Delphi даёт возможность компо-ненту перехватить сообщения, хотя, на мой взгляд, механизм перехвата оставляет желать луч-шего, потому что он не допускает возможности взаимодействия нескольких перехватчиков.

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

    Прежде всего, владелец должен быть окном, поэтому в конструкторе компонента надо прове-рить класс владельца. Это очень полезно ещё и потому, что тогда в остальных методах объекта можно обойтись без такой проверки, что делает код более эффективным. Чтобы предотвратить создание компонента, достаточно в его конструкторе возбудить какое-либо исключение

    Метод компонента не может быть оконной процедурой, потому что методу всегда неявно пере-даётся <лишний> параметр Self. Поэтому нужна генерация специального кода входа и выхода для того, чтобы вызывать метод вместо оконной процедуры. Этот код генерируется с помощью специальной функции Delphi, которая создаёт в памяти нужный код и возвращает на него ука-затель. Поэтому компонент должен иметь указатель на этот код (я условно назову этот указа-тель NewWndProc). Сам метод, обрабатывающий события (условно - HookWndProc) должен иметь один параметр-переменную типа TMessage, и может быть как статическим, тик и вирту-альным или динамическим. Кроме того, нужен указатель на старую процедуру, которая была до установки компонента (OldWndProc). Далее, компонент должен содержать два метода для пере-хвата и освобождения, которые выглядят так:

    procedure TMyComponent.HookOwner;

    begin

      if Assigned(Owner) then

      begin

        OldWndProc := Pointer(GetWindowLong(TForm(Owner).Handle, GWL_WndProc));

        NewWndProc := MakeObjectInstance(HookWndProc);

        SetWindowLong(TForm(Owner).Handle, GWL_WndProc, LongInt(NewWndProc))

      end

    end;

    procedure TMyComponent.UnhookOwner;

    begin

      if Assigned(Owner) and Assigned(OldWndProc) then

        SetWindowLong(TForm(Owner).Handle, GWL_WndProc, LongInt(OldWndProc));

      if Assigned(NewWndProc) then

        FreeObjectInstance(NewWndProc);

      NewWndProc := nil;

      OldWndProc := nil

    end;

    Функции Win API GetWindowLong и SetWindowLong предназначены для получения и изменения 32-разрядного значения, связанного с данным окном. В данном случае мы с их помощью рабо-таем с 32-разрядным параметром - адресом оконной процедуры. Изменение адреса оконной процедуры с помощью SetWindowLong и есть то самое порождение оконного подкласса, о ко-тором я писал ранее. Функция MakeObjectInstance - это та самая функция, которая превращает метод в оконную процедуру. FreeObjectInstance освобождает память, выделенную для создания кода входа и выхода функцией MakeObjectInstance.

    Было бы глупо перехватывать сообщения и при этом не иметь возможности вызвать ту окон-ную процедуру, которая была до перехвата. Если необходимо вызвать её для обработки сооб-щения Msg с параметрами WParam и LParam, нужно воспользоваться следующим кодом:

    CallWindowProc(OldWndProc, TForm(Owner).Handle, Msg, WParam, LParam);

    Результатом работы этой функции будет число, возвращаемое оконной процедурой.

    Вызов процедуры HookOwner я обычно помещаю в самый конец конструктора компонента, Un-hookOwner - в самое начало деструктора. Но в некоторых ситуациях VCL Delphi уничтожает окно и вновь создаёт его с новыми свойствами. Это происходит очень быстро, пользователь ничего не замечает. (Такое <пересоздание> формы может потребоваться при изменении во время выполнения свойств FormStyle, BorderStyle и BorderIcons.) Однако VCL ничего не знает о перехвате и поэтому не может корректно удалить его, а уж о восстановлении его потом и речи быть не может. Чтобы избежать такой ситуации, необходимо обрабатывать сообщение CM_RecreateWnd: перед вызовом унаследованного метода для обработки этого события компо-нент должен снять перехват, после - восстановить его.

    Если форма содержит несколько компонентов, перехватывающих сообщения, могут возникнуть конфликты. Снятие и восстановление перехвата через обработку сообщения CM_RecreateWnd безопасно в этом смысле, потому что компоненты обрабатывают это сообщение в порядке, об-ратном порядку создания. Но если приходится удалять компонент-перехватчик, он не исклю-чает себя из цепочки перехватчиков, а просто обрывает её, и все перехватчики, созданные после него, оказываются не у дел. Именно это я и считаю главным недостатком механизма перехвата.

    Если есть необходимость перехватывать сообщения не владельца, а родителя, нужно сделать всё то же самое с точностью до замены Owner на Parent. Но владельца компонент в принципе не может поменять, а вот родителя - вполне. Поэтому нужно ещё перекрыть виртуальный метод SetParent, в котором снимается перехватчик со старого родителя, затем вызывается унаследо-ванный SetParent, затем уже ставится обработчик на нового родителя

    Графические функции Win API

    Та часть Win API, которая служит для работы с графикой, обычно называется GDI (Graphic De-vice Interface). Ключевым в GDI является понятие контекста устройства (Device Context, DC). Кон-текст устройства - это специфический объект, хранящий информацию о возможностях устрой-ства, о способе работы с ним и о разрешённой для изменения области. В Delphi контекст уст-ройства представлен классом TCanvas, свойство Handle которого содержит дескриптор контек-ста устройства. TCanvas универсален в том смысле, что с его помощью рисование в окне, на принтере или в метафайле выглядит одинаково. То же самое справедливо и для контекста уст-ройства. Разница заключается только в том, как получить в разных случаях дескриптор контек-ста.

    Большинство методов класса TCanvas являются <калькой> с соответствующих и, в большинстве случаев, одноимённых функций GDI. Но в некоторых случаях (прежде всего в методах вы-вода текста и рисования многоугольников) параметры методов TCanvas имеют более удобный тип, чем функции GDI. Например, метод TCanvas.Polygon требует в качестве параметра от-крытый массив элементов типа TPoint, а соответствующая функция GDI - указатель на та-кой массив и число элементов в нём. Это означает, что для массива до вызова функции надо выделить память, а потом - освободить её. Ещё нужен код, который заполнит эту область па-мяти нужными значениями. И ни в коем случае нельзя ошибаться в количестве элементов мас-сива. Если зарезервировать память для одного числа точек, а при вызове функции указать дру-гое, программа будет работать неправильно. Но для простых функций работа через GDI ничуть не сложнее, чем через TCanvas.

    Для получения дескриптора контекста устройства существует много функций. Только для того, чтобы получить дескриптор контекста обычного окна, существуют три функции: BeginPaint, GetDC, GetWindowDC и GetDCEx. Первая из них может использоваться только при обработке сообщения WM_Paint. Вторая даёт контекст клиентской области окна. Третья позволяет полу-чить контекст всего окна, вместе с неклиентской частью. Последняя же позволяет получить контекст определённой области клиентской части окна.

    После того, как дескриптор контекста получен, можно воспользоваться преимуществами класса TCanvas. Для этого надо создать экземпляр такого класса, и присвоить его свойству Handle по-лученный дескриптор. Освобождение ресурсов нужно проводить в следующем порядке: сначала свойству Handle присваивается нулевое значение, затем уничтожается экземпляр класса TCanvas, затем с помощью подходящей функции GDI освободить контекст устройства.

    Разумеется, можно вызывать функции GDI при работе через TCanvas. Для этого им просто надо передать в качестве дескриптора контекста Canvas.Handle. Коротко пере-числю те возможности GDI, которые разработчики Delphi почему-то не сочли нужным включать в TCanvas: установка прозрачного фона у текста без изменения кисти; рисование кри-вых Безье; работа с регионами; выравнивание текста по любому углу или по центру; установка собственной координатной системы; получение детальной информации об устройстве; исполь-зование геометрических карандашей; вывод текста под углом к горизонтали.

    Использование кистей, карандашей и шрифтов в GDI принципиально отличается от того, что привычно в Delphi. Класс TCanvas имеет свойства Brush, Pen и Font, изменение атрибутов которых приводит к выбору того или иного карандаша, шрифта, кисти. В GDI эти объекты самостоятельны, должны создаваться, получать свой дескриптор, <выбираться> в нужный кон-текст устройства с помощью функции SelectObject и уничтожаться после использования. При-чём удалять можно только те объекты, которые не выбраны ни в одном контексте. Есть также несколько стандартных объектов, которые не надо ни создавать, ни удалять. Их дескрипторы можно получить с помощью функции GetStockObject. Чтобы продемонстрировать это, приведу фрагмент программы, рисующей на контексте с дескриптором DC две линии - синюю и крас-ную. В этом фрагменте используется то, что функция SelectObject возвращает дескриптор объ-екта, родственного выбираемому, который был выбран ранее. Так, при выборе нового карандаша она вернёт дескриптор того карандаша, который был выбран до этого.

    SelectObject(DC, CreatePen(PS_Solid, 1, RGB(255, 0, 0)));

    MoveToEx(DC, 100, 100, nil);

    LineTo(DC, 200, 200);

    DeleteObject(SelectObject(DC, CreatePen(PS_Solid, 1, RGB(0, 0, 255))));

    MoveToEx(DC, 200, 100, nil);

    LineTo(DC, 100, 200);

    DeleteObject(SelectObject(DC, GetStockObject(Black_Pen)));

    Особым образом следует работать через GDI с растровыми изображениями. Эта тема на-столько сложна, что в таком кратком обзоре не стоит и начинать её. Скажу только, что при ис-пользовании 24-битных изображений лучше не комбинировать Delphi и GDI. Если передать TBitmap.Handle какой-нибудь функции GDII, у этой картинки иногда портятся последние несколько байт. Так как строки в растровом изображении располагаются снизу вверх, то это приводит к порче правого верхнего угла рисунка. Такой глюк я наблюдал в Delphi 3.0, про ос-тальные версии Delphi ничего сказать не могу.

    При переходе на 32-разрядную версию Windows многие функции были исключены из GDI и заменены новыми. Список устаревших функций и соответствующих им новых можно найти в справке в разделе 'Graphics Functions'.

    Ещё одно отличие от 16-разрядных версий заключается в том, что ранее дескрипторы графических объектов были глобальными, то есть объект, созданный одной программой, можно было использовать в другой, если эти программы могли передавать друг другу дескрипторы. В 32-разрядных версиях дескрипторы объектов, созданные одним процессом, не имеют смысла для другого.

    Существует одна проблема при работе с метафайлами в Windows 95 (возможно, эта же проблема есть в Windows 98 и NT, но я не проверял). Метафайл создаётся с помощью функции Cre-ateEnhMetaFile. Она возвращает дескриптор контекста метафайла, который можно использовать для рисования. Затем вызывается CloseEnhMetaFile, закрывающая метафайл для рисования, освобождающая контекст устройства и возвращающая дескриптор метафайла. После использования метафайл удаляется функцией DeleteEnhMetaFile, которая освобождает память, связанную с метафайлом, и его дескриптор. Одна из функций, освобождающих дескрипторы, работает неправильно, и дескриптор не освобождается. Если программе часто приходится создавать и уничтожать метафайлы, это быстро приводит к тому, что все дескрипторы оказываются заняты, и система перестаёт работать корректно. Бороться с этим, пользуясь классами TMetafile и TMetafileCanvas, нельзя, потому что они работают через эти же функции.

    Работа со строками в Win API

    Функции Win API не поддерживают тип string, принятый в Delphi. Они работают со строками, оканчивающимися на #0 (нуль-терминированные строки, null-terminated strings). Это означает, что строкой называется указатель на цепочку символов. Признаком конца такой цепочки явля-ется символ #0. Раньше для таких строк использовали термин ASCIIZ. ASCII - обозначение кодировки, Z - zero. Сейчас кодировка ASCII заменена на ANSI, поэтому этот термин больше не применяется, хотя это те же самые по своей сути строки. Обычно программисту приходится работать с кодировкой ANSI, но это не единственная кодировка, поддерживаемая Windows.

    В Delphi определён тип PChar, содержащий указатель на такую строку. Если один из параметров функции Win API имеет такой тип, то можно либо передать ему строковую константу, заклю-чённую в одинарные кавычки, как если бы это был тип string, либо выражение PChar(S), где S - параметр типа string, возможно, сложное выражение. Ещё один способ - воспользоваться функ-циями модуля SysUtils.dcu для работы с нуль-терминированными строками и самостоятельно сформировать строку типа PChar. При этом надо будет самостоятельно выделять и освобождать память для цепочки символов, что обычно приводит только к лишним проблемам. Обычно го-раздо проще работать с типом string, и лишь при вызове соответствующей функции преобразо-вать его к типу PChar. Для любителей оптимизации кода замечу, что такое преобразование не расходует ни память, ни процессорное время, потому что тип string - сам по себе указатель, он указывает именно на строку, завершающуюся нулём, а дополнительная информация, специфическая для типа string, имеет отрицательное смещение относительно этого указателя. Поэтому выражение PChar(S) не приводит к генерации кода, а лишь разрешает компилятору использовать этот указатель в качестве PChar.

    Получить строку от функции Win API несколько сложнее, чем передать её. Обычно это делается в несколько этапов. Сначала с помощью функций Win API выясняется, какова длина строки. Затем резервируется место для неё. А только затем вызывается та функция, которая копирует строку в приготовленный буфер. Например, для получения заголовка окна нужно использовать функции GetWindowTextLength и GetWindowText. В некоторых случаях можно облегчить себе жизнь, если существует ограничение на максимальную длину строки. Например, атом не может быть длиннее 255-ти символов. Поэтому можно выделить буфер размером 256 символов (один - для завершающего нуля), и сразу копировать туда атом. В любом случае полученная строка будет нуль-терминированной. Чтобы преобразовать её к обычной, используйте функцию StrPas. Или же можно просто выполнить присвоение S := P, где S - типа string, P - PChar.

    Другой тип кодировки, поддерживаемый в Windows, называется Wide. В отличие от ANSI в нём для представления одного символа используется не один, а два байта. Все функции, работающие со строками, написаны в двух модификациях - для ANSI и для Wide. Например, если посмотреть модуль user32, в котором, как утверждает справка, описана функция GetWindowText, то видно, что там нет такой функции. Там есть две другие функции - GetWindowTextA и GetWindowTextW, работающие каждая с соответствующей кодировкой. И это относится ко всем функциям, работающим со строками. К тому имени функции, которое указано в справке, необходимо добавить 'A' или 'W', в зависимости от используемой кодировки.

    Разработчики Delphi при написании Windows.pas использовали маленькую хитрость, помогающую начинающему программисту не запутаться. Вот, например, цитата из этого модуля:

    function GetWindowTextA(hWnd: HWND; lpString: PAnsiChar;

    nMaxCount: Integer): Integer; stdcall;

    function GetWindowTextW(hWnd: HWND; lpString: PWideChar;

    nMaxCount: Integer): Integer; stdcall;

    function GetWindowText(hWnd: HWND; lpString: PChar;

    nMaxCount: Integer): Integer; stdcall;

    { Это написано в интерфейсной части модуля }

    ...

    function GetWindowTextA; external user32 name 'GetWindowTextA';

    function GetWindowTextW; external user32 name 'GetWindowTextW';

    function GetWindowText; external user32 name 'GetWindowTextA';

    { А это - в разделе реализации }

    Видно, что функция GetWindowTextA импортируется дважды - один раз под своим настоящим именем, а второй раз - под именем GetWindowText (это и есть тот случай, когда имя функции в библиотеке и то имя, под которым она становится известна компилятору, не совпадают). Поэтому программисту в Delphi нет разницы, писать или , потому что единственное различие у них - тип параметра lpString. Но из исходного текста всё того же модуля видно, что это на самом деле один и тот же тип. По такой же схеме импортируются и все остальные строковые функции Win API.

    Заключение

    Функции Win API - не такая уж сложная штука. Они часто используют идеологию, не похожую ни на какую другую, но и с этим легко разобраться. Проблема только в том, где и как получить по ним информацию. Будем откровенны: в нашей стране далеко не все, мягко говоря, используют честно купленные программные продукты. Лицензионный Windows сейчас не в диковинку только потому, что его часто устанавливают на новые компьютеры. Лицензионный Delphi приобретают некоторые фирмы. Но много ли людей в России может похвастаться, что они видели документацию по Win API фирмы Microsoft? А эту документацию на русском языке? А ведь авторы западных книг по программированию обычно предполагают, что читателю есть куда заглянуть для справки по этим функциям, и поэтому особенно их не разбирают. Так что нашему программисту доступны следующие пути: по крупицам вытаскивать информацию из тех книг, где Win API упоминается; читать Win32 Develpoer's References; изучать исходные файлы RTL и VCL Delphi; искать информацию в интернете (могу посоветовать сайт http://delphi.vitpc.com). Всё. Если человек не готов часами и даже днями искать информацию о нужной функции, лучше ему не становиться программистом. Главная цель этой статьи - облегчить начало этого поиска. Но дальше человек должен идти сам.

  14.  1. Создание системной кнопки "Знак вопроса" ограничивается не Дельфи, а ОС. Эту стандартную кнопку можно создать только в диалоге при отсутствии кнопок "минимизации" и "максимизации".

    Есть решение в создании этой кнопки не как стандартного элемента ОС через отрисовку этой кнопки. В этом случае я даже думаю, удобнее рисовать полностью собственное меню.

    "Изобретать велосипед" ненужно - в интернете можно найти различные способы создания "своего" системного меню.

    2. В  фразе "этот блокнот открылся" ты, наверно, подразумеваешь открытие текста в твоей программе. Если это так, то на форму размести компонент TMemo или TRichEdit, например, с TopenDialog (пример с Tmemo здесь).

    Цитата из этого примера:

    if openDialog1.Execute then Memo1.Lines.LoadFromFile(OpenDialog1.FileName);

    Если имеем дело с TrichEdit, то с ним аналогичная ситуация.

    Для того чтобы открыть единственный файл, то вместо OpenDialog1.FileName нужно задать его имя. Если файла не оказалось об этом будет быть оповещение. Либо будет исключение, которое можно перехватить try/exept (смотри справку) и вызвать сообщение raise exeption.create('Файла нет'). 

  15. У кого получилось найти адрес жизни а то у меня не получается :(

    Судя по скрипту Sergant-а ниже, искать нужно тип 4 байта с точкой. Примени этот скрипт, возможно тебе искать даже не придётся.

    [ENABLE] 
    //Your health don't decreases only from the shots. In other cases you can die.
    aobscan(_faddress,0fxxxxxxxxxxxx0fxxxxxxxxxx8bxxe8xxxxxxxx84xx0fxxxxxxxxxxf3xxxxxx)
    alloc(_newmem,2048)
    label(_returnhere)
    label(_originalcode)

    _newmem:
    mov dword ptr [esi+000001d0],433E0000
    _originalcode:
    comiss xmm0,[esi+000001d0]
    jmp _returnhere

    _faddress:    // 005BE7B7 = JustCause2_Game.exe+1BE7B7
    jmp _newmem
    nop
    nop
    _returnhere:
      
    [DISABLE]
    aobscan(_faddress,90900fxxxxxxxxxx8bxxe8xxxxxxxx84xx0fxxxxxxxxxxf3xxxxxx)

    _faddress-5:
    comiss xmm0,[esi+000001d0]

    dealloc(_newmem)

  16.  Мне понадобилась статья по созданию компонента календаря с привязкой к dataset -базе данных. Поскольку этот компонент по работе, то я его приводить не буду, но статью обязательно приведу пока она не исчезла с рунета.

    Создание собственного компонента в связке с базой данной позволит удобно встраивать управление базой данных в различных программах. На пример моего компонента его можно будет встроить  в любую новую разрабатываемую программу на Дельфи. Возьмём тот же cheatEngine. На форме достаточно будет разместить компонент календаря и на нём фиксировать события, которые будут выделяться определённым цветом. Например мы не доламали игрушку в различные дни, а зафиксировать заметки как-то нужно. Для этого создадим пользователя под именем "Игра "Название игры" " и на сегодняшней дате поставим логи регистров с бряками. База данных может иметь различный формат экспортироваться и импортироваться....

    Я долго время знал Дельфи и не любил возиться с базами данных тем более с визуальными компонентами, теперь я иного мнения :) Ну лучше конечно писать базы данных на C#3.0 с технологией LINQ - более гибкая штука.

    Итак сама статья про создание компонента БД.

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

    Постановка задачи

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

    Реализация

    Наиболее правильным, с точки зрения иерархии vcl, методом решения первого пункта поставленной задачи, будет создание нового компонента, в качестве базового класса которого мы выберем tcustomcontrol. Этот класс является базовым для создания компонентов-надстроек над визуальными объектами windows, и предоставляет методы для отрисовки объектов разных форм. Если же у вас нет необходимости наследовать все особенности поведения объектов windows то можете в качестве базового класса использовать tgraphiccontrol, наследники которого отрисовываются быстрее, поскольку не должны следить за уймой Виндовских служебных сообщений.

    Сам компонент tcustomcontrol определен в модуле controls.pas следующим образом:


    private
    fcanvas: tcanvas;
    procedure wmpaint(var message: twmpaint); message wm_paint;
    protected
    procedure paint; virtual;
    procedure paintwindow(dc: hdc); override;
    property canvas: tcanvas read fcanvas;
    public
    constructor create(aowner: tcomponent); override;
    destructor destroy; override;
    end;
    tcustomcontrol = class(twincontrol)

    Здесь самым интересным для нас является метод paint и свойство canvas. Посредством этих двух членов класса tcustomcontrol мы и будет рисовать нашу кнопку.

    Кроме этого мы немножко расширим функциональность нашего компонента и придадим ему возможность устанавливать цвет темного и светлого участка своей границы, а также ее толщину, и наконец определим свойство flat которое отвечает за функциональность аналогичного свойства стандартных компонентов delphi.

    Исходя из вышесказанного прототип нашего компонента (tellipsebutton) будет выглядеть следующим образом:


    private
    fdarkcolor,flightcolor,fbackcolor:tcolor;
    fsize:integer;
    fpushed:boolean;
    rgn:hrgn;
    fflat:boolean;
    fdrawflat:boolean;
    fonmouseenter,fonmouseleave:tnotifyevent;
    { private declarations }
    protected
    procedure setdarkcolor(value:tcolor);
    procedure setlightcolor(value:tcolor);
    procedure setsize(size:integer);
    procedure setbackcolor(value:tcolor);
    procedure dblclick;override;
    procedure drawflat;dynamic;
    procedure drawnormal;dynamic;
    procedure drawpushed;dynamic;
    procedure wmlbuttondown(var message:twmmouse);message wm_lbuttondown;
    procedure wmlbuttonup(var message:twmmouse);message wm_lbuttonup;
    procedure wmmousemove(var message:twmmousemove);message wm_mousemove;
    procedure cmmouseenter(var message:tmessage);message cm_mouseenter;
    procedure cmmouseleave(var message:tmessage);message cm_mouseleave;
    procedure cmtextchanged(var message:tmessage);message cm_textchanged;
    procedure setflat(value:boolean);
    procedure domouseenter;
    procedure domouseleave;
    { protected declarations }
    public
    constructor create(aowner:tcomponent);override;
    procedure afterconstruction;override;
    destructor destory;virtual;
    procedure repaint;override;
    procedure paint;override;
    { public declarations }
    property canvas;
    published
    property darkcolor:tcolor read fdarkcolor write setdarkcolor default clblack;
    property lightcolor:tcolor read flightcolor write setlightcolor default clwhite;
    property backcolor:tcolor read fbackcolor write setbackcolor default clbtnface;
    property size:integer read fsize write setsize;
    property flat:boolean read fflat write setflat;
    property caption;
    {events}
    property onclick;
    property ondblclick;
    property onmousemove;
    property onmousedown;
    property onmouseup;
    property onmouseenter:tnotifyevent read fonmouseenter write fonmouseenter;
    property onmouseleave:tnotifyevent read fonmouseleave write fonmouseleave;
    { published declarations }
    end;
    tellipsebutton = class(tcustomcontrol)

    Как видим, здесь помимо базовых конструктора create и метода afterconstruction переопределены и методы paint и repaint.

    Вся функциональность этого компонента в основном заключена в динамических методах drawflat, drawnormal, drawpushed которые отвечают за рисование компонента соответственно в режиме flat, в нормальном приподнятом режиме и в нажатом режиме.

    Собственно рисование делается с помощью метода canvas.arc, который рисует часть эллипса заданным цветом. Таким образом мы рисуем одну половину темным цветом а другую – светлым и получаем эффект выпуклости. Поменяв местами цвета мы достигаем эффекта «нажатия» для нашей кнопки. Ну а использовав в качестве цвета фона – средний между темным и светлым цветами границы – мы получаем ефект flat:


    var x,y:integer;
    begin
    canvas.lock;
    try
    inherited paint;
    canvas.brush.color:=backcolor;
    canvas.pen.color:=clgray;
    canvas.arc(0,0,width,height,0,height,width,0);
    canvas.brush.style:=bsclear;
    canvas.ellipse(clientrect);
    canvas.font.size:=5;
    x:=self.clientwidth-canvas.textwidth(caption);
    x:=x div 2;
    y:=self.clientheight-canvas.textheight(caption);
    y:=y div 2;
    canvas.textrect(self.clientrect,x,y,caption);
    finally
    canvas.unlock;
    end;
    end;

    procedure tellipsebutton.drawnormal;
    var i:integer;x,y:integer;
    begin
    canvas.lock;
    try
    inherited paint;
    canvas.brush.style:=bsclear;
    canvas.brush.color:=backcolor;
    canvas.pen.color:=darkcolor;
    canvas.arc(0,0,width,height,0,height,width,0);
    for i:=0 to fsize do
    canvas.arc(i,i,width-i,height-i,i,height-i,width-i,i);
    canvas.pen.color:=lightcolor;
    canvas.arc(0,0,width,height,width,0,0,height);
    for i:=0 to fsize do
    canvas.arc(i,i,width-i,height-i,width-i,i,i,height-i);
    canvas.brush.style:=bsclear;
    canvas.font.size:=5;
    x:=self.clientwidth-canvas.textwidth(caption);
    x:=x div 2;
    y:=self.clientheight-canvas.textheight(caption);
    y:=y div 2;
    canvas.textrect(self.clientrect,x,y,caption);
    finally
    canvas.unlock;
    end;
    end;

    procedure tellipsebutton.drawpushed;
    var i:integer;x,y:integer;
    begin
    canvas.lock;
    try
    inherited paint;
    canvas.brush.style:=bsclear;
    canvas.brush.color:=backcolor;
    canvas.pen.color:=lightcolor;
    canvas.arc(0,0,width,height,0,height,width,0);
    for i:=0 to fsize do
    canvas.arc(i,i,width-i,height-i,i,height-i,width-i,i);
    canvas.pen.color:=darkcolor;
    canvas.arc(0,0,width,height,width,0,0,height);
    for i:=0 to fsize do
    canvas.arc(i,i,width-i,height-i,width-i,i,i,height-i);
    canvas.brush.style:=bsclear;
    canvas.font.size:=5;
    x:=self.clientwidth-canvas.textwidth(caption);
    x:=x div 2;
    y:=self.clientheight-canvas.textheight(caption);
    y:=y div 2;
    canvas.textrect(self.clientrect,x,y,caption);
    finally
    canvas.unlock;
    end;
    end;
    procedure tellipsebutton.drawflat;

    Теперь, оснастив наш компонент необходимыми функциями мы можем приступить к его «причесыванию», т.е. написанию рутинных методов по присвоению значений свойствам и отладке. Первым делом здесь надо реализовать реакцию компонента на события мыши. Это мы делаем посредством методов wmlbuttondown, wmlbuttonup, wmmousemove.


    begin
    inherited;
    paint;
    end;

    procedure tellipsebutton.wmlbuttonup;
    begin
    inherited;
    paint;
    end;
    procedure tellipsebutton.wmmousemove;
    begin
    inherited;
    if csclicked in controlstate then
    begin
    if ptinrect(clientrect,smallpointtopoint(message.pos)) then
    begin
    if not fpushed then drawpushed;
    fpushed:=true;
    end else
    begin
    if fpushed then drawnormal;
    fpushed:=false;
    end
    end;
    end;
    procedure tellipsebutton.wmlbuttondown;

    Здесь также мы реализуем функциональность свойства flat. (в wmmousemove).

    Кроме этого мы используем методы cmmouseenter, cmmouseleave для вызова соответствующих обработчиков событий.

    А также реализовываем метод cmtextchanged для правильного отображения текста кнопки:


    begin
    invalidate;
    end;
    procedure tellipsebutton.cmtextchanged;

    Теперь же дело только за методами paint и repaint, которые мы реализовываем следующим образом:


    begin
    if not fflat then
    begin
    if not (csclicked in controlstate) then
    drawnormal else drawpushed;
    end else
    if fdrawflat then drawflat else
    if not (csclicked in controlstate) then drawnormal else drawpushed;
    end;

    procedure tellipsebutton.repaint;
    begin
    inherited;
    paint;
    end;
    procedure tellipsebutton.paint;

    Теперь наш компонент готов к испытаниям. И перед тем как его регистрировать и кидать на палитру компонентов настоятельно рекомендую Вам проверить его функциональность в runtime режиме. В противном же случае вы рискуете повесить всю ide delphi при добавлении компонента на форму.

    Проверка компонента

    Проверка компонента в runtime режиме не вызовет осложнений даже у новичка. Всего-то лишь надо:

    -создать новое приложение

    -в секции uses разместить ссылку на модуль с вашим компонентом (ellipsebutton.pas)

    -объявить переменную типа tellipsebutton

    -создать компонент, заполнить все его свойства и показать.



    interface

    uses
    windows, messages, sysutils, variants, classes, graphics, controls, forms,
    dialogs, mycontrols;

    type
    tform1 = class(tform)
    ellipsebutton1: tellipsebutton;
    procedure formcreate(sender:tobject);
    procedure formdestroy(sender:tobject);
    private
    { private declarations }
    public
    { public declarations }
    end;

    var
    form1: tform1;

    implementation

    {$r *.dfm}
    procedure tform1.formcreate(sender:tobject);
    begin
    ellipsebutton1:=tellipsebutton.create(self);
    ellipsebutton1.parent:=self;
    ellipsebutton1.setbounds(10,10,100,100);
    ellipsebutton1.visible:=true;
    end;

    procedure tform1.formdestroy(sender:tobject);
    begin
    ellipsebutton1.free;
    end;

    end.
    unit main;

    После такой, наглядной проверки и отладки вы можете спокойно регистрировать ваш компонент:


    begin
    registercomponents('usable', [tellipsebutton]);
    end;
    procedure register;

    И использовать уже в ваших приложениях для быстрого создания эллипсоидных кнопок.

    Итоги

    Теперь, обладая, мастерством рисования, и зная методику написания визуальных компонентов для delphi вы можете преспокойно написать любой замысловатый элемент интерфейса и даже продавать его как отдельный программный продукт за немаленькие деньги.

    Автор: Михаил Продан

  17. Внимание. Решение первой задачи.

    Создание 10000 объектов после нажатия на активацию скрипта и нажатия на кнопку создания объекта. Хотя можно было помучить и сделать обновления текста, но посчитал это тратой времени.


    alloc(_newmem,1024)
    registersymbol(_newmem)
    label(_loop)
    label(_a1)
    label(_a2)
    createthread(_newmem)

    _newmem:
    pushad
    pushf
    push 4010C0
    push 4
    _loop:
    push 04
    call 0040187c
    add esp,04
    test eax,eax
    je short _a1
    mov [eax],000003e7
    jmp short _a2
    _a1:
    xor eax,eax
    _a2:
    push edi
    lea edi,[esp+08]
    mov [esp+08],eax
    call 00401280

    add esp,04
    inc ebx
    cmp ebx,#10000
    jne short _loop

    add esp,08
    popf
    popad
    call GetCurrentThread
    ret

    [DISABLE]
    dealloc(_newmem)
    unregistersymbol(_newmem)
    [ENABLE]

    Да и отмечу ключевые элементы (над ними пришлось попариться в отладке)

    [ENABLE]

    alloc(_newmem,1024)

    registersymbol(_newmem)

    label(_loop)

    label(_a1)

    label(_a2)

    createthread(_newmem)

    _newmem:

    pushad

    pushf

    push 4010C0 // подглянул в стек, так нужно

    push 4

     _loop:// прототип оригинального кода

    push 04

    call 0040187c

    add esp,04

    test eax,eax

    je short _a1

    mov [eax],000003e7

    jmp short _a2

    _a1:

    xor eax,eax

    _a2:

    push edi

    lea edi,[esp+08]

    mov [esp+08],eax

    call 00401280

    add esp,04 // эта фишка обязательно, смотреть стек

    inc ebx

    cmp ebx,#10000

    jne short _loop

    add esp,08 // режем стек

    popf

    popad

     call GetCurrentThread // выходим из потока

    ret

    [DISABLE]

    dealloc(_newmem)

    unregistersymbol(_newmem)

    Создавая этот скрипт я обратил внимание, что нужно восполнить данные в стеке и вовремя их отсекать. pushad и pushf оставил специально для подстраховки и вы так делайте (действительно ли они там нужны).

    Ну и в конце после проверок и вырезания ненужных инстуркций получил:


    alloc(_newmem,1024)
    registersymbol(_newmem)
    label(_loop)
    label(_a1)
    label(_a2)
    createthread(_newmem)

    _newmem:
    push 4010C0
    push 4
    _loop:
    push 04
    call 0040187c
    add esp,04
    test eax,eax
    je short _a1
    mov [eax],000003e7
    jmp short _a2
    _a1:
    xor eax,eax
    _a2:
    push edi
    lea edi,[esp+08]
    mov [esp+08],eax
    call 00401280

    add esp,04
    inc ebx
    cmp ebx,#10000
    jne short _loop

    add esp,08
    ret

    [DISABLE]
    dealloc(_newmem)
    unregistersymbol(_newmem)
    [ENABLE]

    Решение второй задачи будет и пока остаётся за вами. У вас уже есть пример решения первой задачи. Вот теперь попробуйте решить вторую. Затем будет трейнми2 который я сделаю по предполагаемому коду из реальной игры. 

  18. Что-то никто не решил задачу, даю ещё подсказки



    class Myobj
    {
        int _id;
    public:
        Myobj(int id){_id=id;}
    };

    vector <Myobj*> v;

    // Обработчик сообщений
    INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
    {
        UNREFERENCED_PARAMETER(lParam);

        int wmId, wmEvent;
        wchar_t istr[32];

        switch (message)
        {
        case WM_INITDIALOG:
            return (INT_PTR)TRUE;

        case WM_COMMAND:
            wmId    = LOWORD(wParam);
            wmEvent = HIWORD(wParam);
            // Разобрать выбор в меню:
            switch (wmId)
            {
            case IDC_BUTTON1:
                v.push_back(new Myobj(999));
                _itow_s(v.size(), istr, 10);
                SetDlgItemText(hDlg,IDC_STATIC2,istr);
                break;
            case IDC_BUTTON2:
                if (v.size()<=0) break;
                v.pop_back();
                _itow_s(v.size(), istr, 10);
                SetDlgItemText(hDlg,IDC_STATIC2,istr );
            break;
                case WM_DESTROY:
                EndDialog(hDlg, LOWORD(wParam));
                return (INT_PTR)FALSE;
            break;
            }
        }
        return (INT_PTR)FALSE;
    }

    ===============================


    {
      signed int result; // eax@7
      void *v5; // eax@8
      void *v6; // [sp+4h] [bp-48h]@1
      wchar_t String; // [sp+8h] [bp-44h]@7
      unsigned int v8; // [sp+48h] [bp-4h]@1

      v8 = (unsigned int)&v6 ^ dword_404018;
      if ( a2 == 272 )
      {
        result = 1;
      }
      else
      {
        if ( a2 != 273 )
          return 0;
        if ( a3 != 2 )
        {
          if ( a3 == 1001 )
          {
            v5 = operator new(4u);
            if ( v5 )
              *(_DWORD *)v5 = 999;
            else
              v5 = 0;
            v6 = v5;
            sub_401280();
            itow_s((signed int)(dword_4045B0 - Src) >> 2, &String, 0x20u, 10);
            SetDlgItemTextW(hDlg, -1, &String);
          }
          else
          {
            if ( a3 == 1002 && (signed int)(dword_4045B0 - Src) >> 2 )
            {
              dword_4045B0 = (char *)dword_4045B0 - 4;
              itow_s((signed int)(dword_4045B0 - Src) >> 2, &String, 0x20u, 10);
              SetDlgItemTextW(hDlg, -1, &String);
              return 0;
            }
          }
          return 0;
        }
        EndDialog(hDlg, 2);
        result = 0;
      }
      return result;
    }

    signed int __stdcall DialogFunc(HWND hDlg, int a2, unsigned __int16 a3, int a4)

    ==============================


    .text:004010C0 DialogFunc      proc near               ; DATA XREF: wWinMain(x,x,x,x)+7o
    .text:004010C0
    .text:004010C0 var_48          = dword ptr -48h
    .text:004010C0 String          = word ptr -44h
    .text:004010C0 var_4           = dword ptr -4
    .text:004010C0 hDlg            = dword ptr  4
    .text:004010C0 arg_4           = dword ptr  8
    .text:004010C0 arg_8           = word ptr  0Ch
    .text:004010C0
    .text:004010C0                 sub     esp, 48h
    .text:004010C3                 mov     eax, dword_404018
    .text:004010C8                 xor     eax, esp
    .text:004010CA                 mov     [esp+48h+var_4], eax
    .text:004010CE                 mov     eax, [esp+48h+arg_4]
    .text:004010D2                 sub     eax, 110h
    .text:004010D7                 push    esi
    .text:004010D8                 mov     esi, [esp+4Ch+hDlg]
    .text:004010DC                 jz      loc_4011EE
    .text:004010E2                 sub     eax, 1
    .text:004010E5                 jnz     loc_4011BD
    .text:004010EB                 movzx   eax, [esp+4Ch+arg_8]
    .text:004010F0                 sub     eax, 2
    .text:004010F3                 jz      loc_4011D1
    .text:004010F9                 sub     eax, 3E7h
    .text:004010FE                 jz      short loc_401167
    .text:00401100                 sub     eax, 1
    .text:00401103                 jnz     loc_4011BD
    .text:00401109                 mov     ecx, dword_4045B0
    .text:0040110F                 mov     edx, Src
    .text:00401115                 mov     eax, ecx
    .text:00401117                 sub     eax, edx
    .text:00401119                 sar     eax, 2
    .text:0040111C                 test    eax, eax
    .text:0040111E                 jbe     loc_4011BD
    .text:00401124                 sub     ecx, 4
    .text:00401127                 push    0Ah             ; Radix
    .text:00401129                 mov     dword_4045B0, ecx
    .text:0040112F                 push    20h             ; SizeInWords
    .text:00401131                 lea     eax, [esp+54h+String]
    .text:00401135                 sub     ecx, edx
    .text:00401137                 push    eax             ; DstBuf
    .text:00401138                 sar     ecx, 2
    .text:0040113B                 push    ecx             ; Val
    .text:0040113C                 call    ds:_itow_s
    .text:00401142                 add     esp, 10h
    .text:00401145                 lea     ecx, [esp+4Ch+String]
    .text:00401149                 push    ecx             ; lpString
    .text:0040114A                 push    0FFFFFFFFh      ; nIDDlgItem
    .text:0040114C                 push    esi             ; hDlg
    .text:0040114D                 call    ds:SetDlgItemTextW
    .text:00401153                 xor     eax, eax
    .text:00401155                 pop     esi
    .text:00401156                 mov     ecx, [esp+48h+var_4]
    .text:0040115A                 xor     ecx, esp
    .text:0040115C                 call    sub_4017AA
    .text:00401161                 add     esp, 48h
    .text:00401164                 retn    10h
    .text:00401167 ; ---------------------------------------------------------------------------
    .text:00401167
    .text:00401167 loc_401167:                             ; CODE XREF: DialogFunc+3Ej
    .text:00401167                 push    4               ; unsigned int
    .text:00401169                 call    ??2@YAPAXI@Z    ; operator new(uint)
    .text:0040116E                 add     esp, 4
    .text:00401171                 test    eax, eax
    .text:00401173                 jz      short loc_40117D
    .text:00401175                 mov     dword ptr [eax], 3E7h
    .text:0040117B                 jmp     short loc_40117F
    .text:0040117D ; ---------------------------------------------------------------------------
    .text:0040117D
    .text:0040117D loc_40117D:                             ; CODE XREF: DialogFunc+B3j
    .text:0040117D                 xor     eax, eax
    .text:0040117F
    .text:0040117F loc_40117F:                             ; CODE XREF: DialogFunc+BBj
    .text:0040117F                 push    edi
    .text:00401180                 lea     edi, [esp+50h+var_48]
    .text:00401184                 mov     [esp+50h+var_48], eax
    .text:00401188                 call    sub_401280
    .text:0040118D                 mov     eax, dword_4045B0
    .text:00401192                 sub     eax, Src
    .text:00401198                 push    0Ah             ; Radix
    .text:0040119A                 push    20h             ; SizeInWords
    .text:0040119C                 lea     edx, [esp+58h+String]
    .text:004011A0                 push    edx             ; DstBuf
    .text:004011A1                 sar     eax, 2
    .text:004011A4                 push    eax             ; Val
    .text:004011A5                 call    ds:_itow_s
    .text:004011AB                 add     esp, 10h
    .text:004011AE                 lea     ecx, [esp+50h+String]
    .text:004011B2                 push    ecx             ; lpString
    .text:004011B3                 push    0FFFFFFFFh      ; nIDDlgItem
    .text:004011B5                 push    esi             ; hDlg
    .text:004011B6                 call    ds:SetDlgItemTextW
    .text:004011BC                 pop     edi
    .text:004011BD
    .text:004011BD loc_4011BD:                             ; CODE XREF: DialogFunc+25j
    .text:004011BD                                         ; DialogFunc+43j ...
    .text:004011BD                 xor     eax, eax
    .text:004011BF                 pop     esi
    .text:004011C0                 mov     ecx, [esp+48h+var_4]
    .text:004011C4                 xor     ecx, esp
    .text:004011C6                 call    sub_4017AA
    .text:004011CB                 add     esp, 48h
    .text:004011CE                 retn    10h
    .text:004011D1 ; ---------------------------------------------------------------------------
    .text:004011D1
    .text:004011D1 loc_4011D1:                             ; CODE XREF: DialogFunc+33j
    .text:004011D1                 push    2               ; nResult
    .text:004011D3                 push    esi             ; hDlg
    .text:004011D4                 call    ds:EndDialog
    .text:004011DA                 xor     eax, eax
    .text:004011DC                 pop     esi
    .text:004011DD                 mov     ecx, [esp+48h+var_4]
    .text:004011E1                 xor     ecx, esp
    .text:004011E3                 call    sub_4017AA
    .text:004011E8                 add     esp, 48h
    .text:004011EB                 retn    10h
    .text:004011EE ; ---------------------------------------------------------------------------
    .text:004011EE
    .text:004011EE loc_4011EE:                             ; CODE XREF: DialogFunc+1Cj
    .text:004011EE                 mov     ecx, [esp+4Ch+var_4]
    .text:004011F2                 pop     esi
    .text:004011F3                 xor     ecx, esp
    .text:004011F5                 mov     eax, 1
    .text:004011FA                 call    sub_4017AA
    .text:004011FF                 add     esp, 48h
    .text:00401202                 retn    10h
    .text:00401202 DialogFunc      endp
    .text:00401202
    .text:00401202 ; ---------------------------------------------------------------------------
    <p>.text:00401205                 align 10h
    .text:004010C0 ; BOOL __stdcall DialogFunc(HWND, UINT, WPARAM, LPARAM)

    Нужно зациклить:

    v.push_back(new Myobj(999));

    Также в догонку даю ещё задание.

    Когда нажимаем на кнопку уменьшения объектов, то при уменьшении объектов нужно их восполнить.

    Т.е. уменьшили на 1, то сделать нужно +1. Сделать на скриптах CE.

    Итак два задания.

    1. В первом посте. Сделать 10000 объектов при помощи скриптов CE.

    2. Восполнить разрушенный объект при помощи скриптов CE.

    Т.е. мы нажали активировать чит-код (галочку скрипта) и получаем 10000 объектов. Нажали вторую - объекты не уменьшаются.

  19. Там ничего не поксорено, нет никакого шифрования. И всё очень довольно просто решается.

    Необходимо мыслить логически (эта хоть и сложная, но самая простая задача по объектам). Вам нужно 10000 объектов. Объект создаётся, когда кликаем на кнопку складывания. По кнопке конечно же кликать 10000 раз не следует, но можно циклически выполнить кусок кода конструктора и добавление этого объекта в массив. Этот кусок кода сидит на видном месте и выполняется когда кликаем на кнопку складывания.

    В скритах CE можно создать поток который с нужными регистрами циклически выполнит этот кусок кода. Также можно это попробовать сделать на Сишном скрипте CE.

    Я написал этот трейнми на VC++2008 с шаблоном vector, который применяется в реальных играх. В этом я точно уверен. Дальше будет ещё сложнее трейнми в котором будет динамическое привидение объекта класса предка к одному из нескольких родительских.... на подобии в игре Снайпер Гхост Варриор. И тогда вы сможете попробовать взломать гранаты уже в реальной игре, потренировавшись на трейнми.

    Подожду до воскресения перед тем как дам решение, может быть кто-то взломает трейнми   :)

  20. post-3-1278316606,34_thumb.png

    TrainMe(hard_1_0).rar

    Задание.

    Сделать количество объектов равное 10000. При нажатии кнопки на плюс должно стать 10001. Если нажали на минус, то должно быть 10000-1=9999.

     

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

    Подсказка и ограничение.

    Вам нужно найти конструктор объектов и создать 10000 объектов. Они будут подсчитываться при нажатиях на кнопки. Помните, что цель именно такая, а не хак отображения текста. По описаниям ваших действий можно будет судить правильно ли вы сделали. Сам этот трейнми я обманул, так что это реально.

    Подобный приём используется всё чаще и чаще в играх. Это количество: ножей, стрел, патронов в Сталкере, гранаты и т.п. которые имеют объектный тип. Также объектный тип 100% имеют управляемые единицы в стратегиях (ваши юниты, ваши игроки которыми вы управляете). В каждом из приведённых случаев обман может быть гораздо сложнее чем в этом трейнми, но это основа основ взлома объектных типов. 

    В создании, тестировании трейнми принимали участие: MasterGH и Xipho.  

     

    СЛЕДУЮЩИЕ ЗАДАНИЯ ВЫПОЛНЕНЫ.

    Для этого трейнми было 3 задания.

    Их можно было делать на любом инструменте. Предпочитаемый CheatEngine.

    1.Сделать чит-код добавляющий объекты в количестве до 10000.

    При следующем нажатии на кнопу вычитания объектов должно быть 9999 или при нажатии на плюс 10001.

    2.Восполнить разрушенный объект при помощи скриптов CE.

    Т.е. когда мы нажимаем на кнопку вычитания объектов, то разрушенный объект восполняется одним.

    3.Сделать так чтобы при вычитание объектов или при их создании, их количество держалось на отметке не меньше 1000 объектов.

    Поскольку последнее задание объединяет первые два, то напишу только последнее.

    Скрипт1 - Главный

     

    Скрытый текст

    /*
    Процесс: TrainMe(hard_1_0).exe
    Чит-код: Скрипт на восттановление указанного кол-ва объектов
    Дата: 7.21.2010
    Автор скрипта: MasterGH(c)
    Версия CE: Cheat Engine 5.6  RUS (v 2.0)

    ------------------
    На _void_RstObj() делалаем два call-а в места:
    1) из кода после добавления объектов
    2) из кода после уменьшения объектов

    */
    alloc(_newmem,2048)
    label(_void_RstObj)
    label(_EAX_GetCountObj)
    label(_void_AddObject)
    label(_void_createobj_eax_)
    label(_ex)
    label(_loop)
    label(_a1)
    label(_a2)

    registersymbol(_void_RstObj)


    _newmem:
    //Восстановить кол-во недостающих объектов
    _void_RstObj:
       pushf
       push eax
       call _EAX_GetCountObj // возвращает eax = кол-во объектов в данный момент
       push ebx
       mov ebx,#1000
       sub ebx,eax
       mov eax,ebx
       pop ebx
       cmp eax, 0    // eax объектов которых не хватает
       jl short _ex      // переход если объектов которых не хватает меньше нуля
       call _void_createobj_eax_
    _ex:
       pop eax
       popf
       ret
      
    // добавление кол-во объектов  
    _void_createobj_eax_:
    _loop:
       pushad
       call _void_AddObject// добавляем объект
       popad
       dec eax
       cmp eax,0
       jg short _loop // пока eax больше нуля
       ret
      
    //Добавление 1-го объекта:
    _void_AddObject:
       push 4010C0
       push 4
       push 4
       call 0040187c
       add esp,04
       test eax,eax
       je short _a1
       mov [eax],000003e7
       jmp short _a2
    _a1:
       xor eax,eax
    _a2:
       push edi
       lea edi,[esp+08]
       mov [esp+08],eax
       call 00401280
       add esp,0C
       ret
      
    //Получение кол-ва объектов в eax:
    _EAX_GetCountObj:
       mov eax,[4045B0]
       sub eax,[4045AC]
       sar eax,2
       ret
        
    [DISABLE]
    dealloc(_newmem)
    unregistersymbol(_void_RstObj)
    
    [ENABLE]

     

    Скрипт2

     

    Скрытый текст
    aobscan(_faddress,a1xxxxxxxx2bxxxxxxxxxxxxxxxxxx8dxxxxxxxxc1xxxxxxffxxxxxxxxxx83) alloc(_newmem,2048) label(_returnhere) label(_originalcode) _newmem: call _void_RstObj _originalcode: mov eax,[4045B0] jmp _returnhere _faddress:    // 0040118D = About+CD jmp _newmem _returnhere: [DISABLE] aobscan(_faddress,2bxxxxxxxxxxxxxxxxxx8dxxxxxxxxc1xxxxxxffxxxxxxxxxx83) _faddress-5: mov eax,[4045B0] dealloc(_newmem) //Alt: db A1 B0 45 40 00
    
    
    [ENABLE]

     

    Скрипт3

     

    Скрытый текст
    aobscan(_faddress,8bxxxxxxxxxx8bxxxxxxxxxx8bxx2bxxc1xxxx85xx0fxxxxxxxxxx83) alloc(_newmem,2048) label(_returnhere) label(_originalcode) _newmem: call _void_RstObj _originalcode: mov ecx,[4045B0] jmp _returnhere _faddress:    // 00401109 = About+49 jmp _newmem nop _returnhere: [DISABLE] aobscan(_faddress,908bxxxxxxxxxx8bxx2bxxc1xxxx85xx0fxxxxxxxxxx83) _faddress-5: mov ecx,[4045B0] dealloc(_newmem) //Alt: db 8B 0D B0 45 40 00  
    
    
    
    [ENABLE]

     

    Как всё это было сделано вы можете узнать прочитав посты ниже. Конечно, может быть будет что-то не понятно, но лучше всего взять отладчик и попытаться по моим скриптам повторить это. А затем попытаться повторить без моих скриптов.

  21. Не помню была ли у нас эта новость. Вчера на офсайте обновился Cheat Engine до 5.6.1

    June 30 2010:10 year anniversary and 5.6.1 release

    Well, it's been a decade now that the first version of Cheat engine saw the light

    To celebrate that fact I've released ce 5.6.1 , which is mainly a bugfix version but also has some small usability improvements, I also wanted to put out a beta version of CE 6 today but since it has an annoying bug that makes the foundlist hard to use I'll spend some more time on that first.

    link: Cheat Engine 5.6.1

    Fixes:

    Fixed bug where ce would crash(close) on XP systems on certain type of games when opening a process

    Fixed the error at "Same as first scan" when using the float type

    Pointer rescan for value now accepts more than 8 characters

    Fixed pointerscan for value not supporting more than 1 thread

    Fixed showing the fpu in the kernelmode debugger

    Fixed Decreased/Increased by %

    Dissect Data now detects non-exact floating point values again

    Disabled executing the aobscan when assigning a script to the table

    Some disassembler fixes

    Again some more DPI fixes

    The byte-editor in the hexeditor window is now properly aligned at the proper height

    The XMM registers are now shown for kernelmode debugging as well

    Implemented the stop button for kernelmode "Find what addresses this code accesses"

    Fixed error message when pressing enter and no address is selected in the addresslist

    Fixed samememory regions so the last byte is now included as well

    Fixed the simple-copy/paste option in settings (it now has effect)

    Fixed bug where pressing alt made a lot of controls invisible in vista and later

    Fixed the pointerscan bug that caused out of memory problems for the scan (Was already secretly fixed and released for 5.6 one week after the official release, but still mentioning it)

    Changes:

    Rightclicking on registers(multiple locations) saves them to the clipboard

    The values of dissect data can now be saved to disk

    Auto Assembler script entries have a small <script> text you can click for easier script editing

    The commonmodulelist.txt has been updated with some extra dll's that have nothing to do with the game (thanks to psych)

    AutoAssembler: You can't move the cursor beyond the end of a line anymore

    Pointerscan: Pointerscanner now tells you that pressing stop is a stupid thing to do

    Pointerscan: doubleclicking an pointer with invalid base now adds it anyhow. (In case the base becomes valid again later)

    Several extra window positions are now saved when the option to save the position is enabled (included pointerscan and autoassembler)

    CE now notifies you if it thinks there's not enough dispace left

    Default pointer option is now set back to insert instead of append

    Added a "Same as first scan" hotkey option

    When opening a new process and chosing to disable everything now also disables entries in advanced options

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.