30 апреля 2015 г.

Задачка №18В

Это третья часть задачки №18. Что не так с этим кодом?

function EnumWindowsProc(const AWnd: HWND; const AParam: LPARAM): Boolean; stdcall;
var
  Wnd: HWND;
begin
  Wnd := HWND(AParam);
  if AWnd = Wnd then
    Result := True
  else
    Result := False;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Wnd: HWND;
begin
  Wnd := Handle;
  EnumWindows(@EnumWindowsProc, LPARAM(Wnd));
end;

Ответ будет (автоматически) опубликован через две недели.

P.S. _Rouse, специально для тебя ;)

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

  1. Возможно дело в типа, возвращаемом EnumWindowsProc? Boolean 1-байтный, в то время как BOOL 4-байтный. Не испортит ли это стек?

    ОтветитьУдалить
  2. И еще есть разница в регистрах, куда ложится результат EnumWindowsProc. Для BOOL это будет eax, а для Boolean - другой. Как вариант - EnumWindowsProc вычитает значение не из того регистра

    ОтветитьУдалить
  3. ну да. Если для BOOL будет eax , то для Boolean - это al. Илья, это часть одного регистра.
    Проблема в том, что старшие биты не будут инициализированы. А вызывающая подпрограмма проверяет результат на равенство нулю, то есть: равно нулю или не равно.
    Наглядный листинг:
    mov eax, 0FFFFFFFFh
    mov al, 0 ; Result := false, инициализируется 1 байт при типе Boolean
    ; значение в регистре будет таким eax = 0FFFFFF00h
    ; а теперь проверочка
    cmp eax, 0 ; if Result = 0 then

    В подпрограмме EnumWindowsProc что будет в регистре eax, в момент присваивания результата, неизвестно. И в 99,99% случаях вызывающая подпрограмма получит ненулевое значение.

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

      Удалить
    2. Хотя, нет, это уже явная подсказка.
      Тогда просто упомяну, что сравнение идет через
      test eax, eax
      jz

      Удалить
    3. Не суть, какой инструкцией результат будет проверен - логическим умножением или инструкцией сравнения. Внимание на самом значении.
      Я успел увидеть подсказку)). Но аномалий с ней, с подсказкой, не заметил. Это касается win32. У меня еще не возникало задач, которые вынудили бы установить 64-битную систему. Выходит в ней и закавыка. Дождусь ответ от знатоков win64.
      А в win32 этот код работает по той причине, что кодогенератор создает такой код: xor eax, eax для выражения Result := false.
      Внутри же EnumWindows результат не задействуется, но возвращается в Button1Click через переменную fSuccess:

      BOOL PseudoEnumWindows(..., WNDENUMPROC lpfn, ...)
      {
      UINT i;
      UINT cHwnd;
      HWND *phwndT;
      HWND *phwndFirst;
      BOOL fSuccess = TRUE;

      if ((cHwnd = BuildHwndList(..., &phwndFirst) == -1) {
      return FALSE;
      }

      phwndT = phwndFirst;
      for (i = 0; i < cHwnd; i++) {
      if (!(fSuccess = (*lpfn)(*phwndT, lParam)))
      break;
      phwndT++;
      }

      FinalizeHwndList;

      return fSuccess;
      }


      P.S.: Теги "bold, italic и href" - хорошо. Но очень не хватает "pre и code".

      Удалить
    4. >> Не суть, какой инструкцией результат будет проверен - логическим умножением или инструкцией сравнения.
      Смотря как стравнивать :)
      test eax, eax
      jz
      и
      cmp eax, 1
      jnz
      по сути идентичны, но дадут разное поведение на 32 битном регистре :)

      Удалить
    5. Ой, пардон - что-то странное сказал.

      Конечно же:

      test eax, eax
      sbb eax, eax
      inc eax

      и

      cmp eax, 1
      sbb eax, eax
      inc eax

      в EAX результат

      Удалить
    6. ЗЫ: это с учетом разницы между BOOL и Boolean (особенно второй вариант подвержен ошибке)

      Удалить
    7. Я понял, что Вы имели в виду. Вы правы. Постами выше я руководствовался, исходя из справки, что результат может принимать два значения "нуль" и "не нуль". Отсюда происходит проверка на нулевое значение: cmp eax, 0 и test eax, eax , обе установят флаг нуля. Написав cmp eax, 0 я хотел подчеркнуть именно сравнение с нулем.

      И эта заковыка даже на win32 демонстрирует это поведение - не обнуляется весь регистр, как это происходит в случае с явным идентификатором FALSE :
      function EnumWindowsProc(const AWnd: HWND; const AParam: LPARAM): Boolean; stdcall;
      begin
      Result := not (AWnd = HWND(AParam));
      end;

      Удалить
    8. Абсолютно верно :)
      Вы практически подошли к концу решения задачи, но... :)

      Удалить
  4. Думаю, что скажу банальность - но разве не логично компилятору делать код, проверяющий на True и False простой проверкой младшего бита в младшем разряде регистра?
    Ведь тогда не будет проблемы, когда результат то в eax, то в ax, то в al...

    ОтветитьУдалить
    Ответы
    1. Вообще-то процессору вообще проще обнулять регистр целиком и сравнивать на равенство полного регистра с нулём, а не выделять из регистра байты и оперировать с ними.

      Собственно, при включенной оптимизации Delphi компилирует именно такой код, поэтому Boolean выше будет, фактически, скомпилирован в BOOL. При выключенной оптимизации компилятор Delphi компилирует буквальный код.

      Проблема в том, что мы не контролируем вызывающий нас код (код Windows), а там может быть что угодно - как и сравнение младшего байта (навряд ли), так и сравнение полного регистра (скорее всего). Компилятору C вообще не нужно волноваться о "результат то в eax, то в ax, то в al".

      Более того, не всегда даже выполняется "не-ноль - это истина" - иногда функции Windows требуют конкретного значения для True.

      Удалить

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

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

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

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

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

Примечание. Отправлять комментарии могут только участники этого блога.