21 апреля 2015 г.

Задачка №18А

Что не так с этим кодом?

procedure TForm1.Button1Click(Sender: TObject);
var
  Wnd: HWND;

  function EnumWindowsProc(const AWnd: HWND; const AParam: LPARAM): BOOL; stdcall;
  begin
    if AWnd = Wnd then
      Caption := 'OK';
    Result := True;
  end;

begin
  Wnd := Handle;
  EnumWindows(@EnumWindowsProc, 0);
end;
Не обращайте внимание на его бессмысленность, вопрос не в этом, а в корректности кода.

Это предварительная задачка, подготовка к реальной задачке, вторая часть будет опубликована через неделю (и, да, на этот раз - действительно через неделю, т.к. я написал и вторую часть и ответ и поставил их на авто-публикацию; к сожалению, это означает, что ответ не будет включать комментарии к ответам на задачку, если только я не обновлю его до авто-публикации).

Бонус: возможно, вы уже догадались, что будет во второй части?

Читать далее: часть 2.

13 комментариев :

  1. Ну, EnumWindows - это API функция винды, и когда она вызывает callback функцию EnumWindowsProc, она не передаёт указатель Self, поэтому "Caption := 'OK'" должен будет вызвать исключение.
    Ну и, переменная WND в теле этой процедуры то-же будет не видна - ведь она вызывается через callback функцию.

    Точнее так: из callback функции EnumWindowsProc недоступны данные процедуры, вызвавшей EnumWindows - ни указатель Self, ни переменная Wnd.

    ОтветитьУдалить
  2. Задачка очень интересная.

    > не передаёт указатель Self, переменная WND в теле этой процедуры будет не видна...
    В этом случае код бы не компилировался. А он компилируется, и даже работает. Но неправильно.

    ОтветитьУдалить
  3. Да, помню меня это сильно удивило, когда я столкнулся с таким первый раз - компилятор "видит" переменные, а на самом деле эти переменные выходят из области видимости.
    Наверняка следующая часть будет связана со стеком

    ОтветитьУдалить
  4. 1. Self не передается в callback
    2. эта строка не верная "if AWnd = Wnd then" т.к. вместо значения Wnd берется что-то от балды со стека - можно вообще на AV налететь при хорошем везении
    3. ну и это, из-за пункта 1 тоже не верно "Caption := 'OK';"

    ОтветитьУдалить
    Ответы
    1. На самом деле Wnd и Self сохраняются в стеке TForm1.Button1Click, и затем оттуда получаются EnumWindowsProc.
      Просто в данном случае это не работает. Такое вот ограничение вложенной функции, ее можно вызвать только непосредственно из основной функции.

      Удалить
    2. Конечно сохраняется, я даже и спорить не будут. Но что нам это дает, если калбэк не умеет получать эту информацию? :)

      Удалить
  5. var
    Form1: TForm1;
    Wnd: HWND;

    implementation

    {$R *.dfm}

    procedure TForm1.Button1Click(Sender: TObject);
    (*
    область видимости переменной ограничена локальным стеком,
    поэтому Wnd будет не доступна для EnumWindowsProc
    переносим переменную Wnd в объявление глобальных переменных
    var
    Wnd: HWND;
    *)
    function EnumWindowsProc(const AWnd: HWND; const AParam: LPARAM): BOOL; stdcall;
    begin
    if AWnd = Wnd then
    begin
    (* Caption := 'OK'; *) // Свойство Caption окна не доступно при вызове EnumWindowsProc
    SetWindowText(Wnd, 'ОК'); // Изменяем Caption окна например так
    Result := False; // При нахождении окна программы, прерываем выполнение EnumWindows
    end else
    Result := True;
    end;

    begin
    Wnd := Handle;
    EnumWindows(@EnumWindowsProc, 0);
    end;

    ОтветитьУдалить
    Ответы
    1. После чего значение глобальной переменной перезатрет любой сторонний поток, который тоже имеет к ней доступ.
      Вы серьезно? :)

      Удалить
  6. Компилятор рассчитает кадр стека относительно Button1Click. Но в действительности вызов EnumWindowsProc произойдет из другой подпрограммы.
    Но раз есть пользовательский параметр, чего же ним не воспользоваться?
    -------
    procedure TForm1.Button1Click(Sender: TObject);

    function EnumWindowsProc(Wnd: HWND; Param: LPARAM): LongBool; stdcall;
    begin
    if Param = 0 then
    begin
    SetLastError(ERROR_INVALID_PARAMETER);
    Result := false;
    end
    else if TForm(Param).Handle = Wnd then
    begin
    TForm(Param).Caption := 'OK';
    SetLastError(ERROR_SUCCESS);
    Result := false;
    end
    else
    Result := true;
    end;
    begin
    if not EnumWindows(@EnumWindowsProc, LPARAM(Self)) and (GetLastError <> ERROR_SUCCESS) then
    RaiseLastOSError;
    end;

    ОтветитьУдалить
  7. Блин, почему блог только в "Simple" отображается? Где нормальные десктопные шаблоны?

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

      При желании можно вручную использовать несколько динамических шаблонов, но их работу я не проверял.

      Удалить
    2. Этот комментарий был удален автором.

      Удалить
    3. Меня вполне устраивал статичный старый, CodeMonkey. Вполне удобный был.

      Удалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

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