Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

Функции Windows API, возвращающие структуры в Delphi и Pascal: особенности и решения

Delphi , ОС и Железо , Windows

 

Введение

В Windows API существует небольшое количество функций, которые возвращают структуры напрямую, а не через параметры. Одним из таких примеров является функция RtlConvertLongToLargeInteger из библиотеки ntdll.dll. Эта статья исследует особенности работы с такими функциями в Delphi и Free Pascal (FPC), а также предлагает решения для корректного взаимодействия с ними.

Проблема возврата структур в Windows API

В 32-битных версиях Windows некоторые функции API возвращают структуры данных нестандартным способом. Например, функция RtlConvertLongToLargeInteger объявлена следующим образом:

function RtlConvertLongToLargeInteger(
  In32bitSigned: Longint
): TLARGE_INTEGER; stdcall; external ntdll;

В языке C эта функция возвращает 64-битное значение LARGE_INTEGER в паре регистров eax:edx. Однако FPC по умолчанию ожидает, что возвращаемое значение будет размещено в стеке, а не в регистрах, что может привести к повреждению стека и ошибкам доступа (Access Violation).

Структура TLARGE_INTEGER

TLARGE_INTEGER (или LARGE_INTEGER в C) - это объединение (union), которое может быть представлено несколькими способами:

type
  TLARGE_INTEGER = record
    case Integer of
      0: (
        LowPart: DWORD;
        HighPart: Longint);
      1: (
        QuadPart: Int64);
  end;

Хотя в большинстве случаев QuadPart (как Int64) может использоваться для доступа к полному 64-битному значению, важно понимать, что это именно структура, а не просто Int64.

Решения проблемы

1. Использование Int64 как возвращаемого типа

Самый простой способ обойти проблему - объявить функцию с возвращаемым типом Int64:

function RtlConvertLongToLargeIntegerB(
  In32bitSigned: Longint
): Int64; stdcall; external ntdll name 'RtlConvertLongToLargeInteger';

Это решение предложено самой Microsoft в документации к функции. После получения значения его можно преобразовать в TLARGE_INTEGER:

var
  LargeInt: TLARGE_INTEGER;
  Temp: Int64;
begin
  Temp := RtlConvertLongToLargeIntegerB(High(Longint));
  LargeInt.QuadPart := Temp;

  // Теперь можно использовать LargeInt.LowPart и LargeInt.HighPart
end;

2. Использование ассемблерной обёртки

Для случаев, когда важно сохранить семантику структуры, можно создать ассемблерную обёртку:

function RtlConvertLongToLargeIntegerAsm(In32bitSigned: Longint): TLARGE_INTEGER;
asm
  // Вызов оригинальной функции
  call RtlConvertLongToLargeInteger
  // Результат уже в eax:edx
  // Заполняем структуру TLARGE_INTEGER
  mov [Result].TLARGE_INTEGER.LowPart, eax
  mov [Result].TLARGE_INTEGER.HighPart, edx
end;

3. Изменение соглашения о вызове

Некоторые разработчики пробуют использовать соглашение cdecl, но это не решает проблему, так как FPC и Delphi всё равно будут ожидать возврата структуры в стеке.

Особенности работы компиляторов

Free Pascal

FPC следует соглашениям GCC для совместимости с инструментами вроде binutils и GDB. Для 32-битных Windows FPC возвращает структуры в стеке, за исключением очень маленьких структур (1, 2 или 4 байта).

Delphi

Delphi также возвращает структуры через скрытый параметр-указатель, а не через регистры, что не соответствует соглашениям Microsoft для 32-битных Windows.

Microsoft Visual C++

MSVC возвращает 64-битные структуры в паре регистров eax:edx, как указано в документации Microsoft:

"On x86 platforms, all arguments are widened to 32 bits when they are passed. Return values are also widened to 32 bits and returned in the EAX register, except for 8-byte structures, which are returned in the EDX:EAX register pair."

Рекомендации

  1. Используйте Int64: Для большинства случаев, особенно с устаревшими функциями вроде RtlConvertLongToLargeInteger, предпочтительно использовать Int64 как возвращаемый тип, как рекомендует Microsoft.

  2. Ассемблерные обёртки: Если необходимо сохранить семантику структуры, создайте ассемблерную обёртку.

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

  4. 64-битные приложения: В 64-битном коде эта проблема не возникает, так как соглашения о вызовах унифицированы.

Пример полного решения

{$APPTYPE CONSOLE}
{$TYPEDADDRESS ON}
{$LONGSTRINGS OFF}

program StructReturnExample;

uses
  Windows;

type
  TLARGE_INTEGER = record
    case Integer of
      0: (
        LowPart: DWORD;
        HighPart: Longint);
      1: (
        QuadPart: Int64);
  end;

const
  ntdll = 'ntdll.dll';

// Правильное объявление с Int64
function RtlConvertLongToLargeInteger_Int64(
  In32bitSigned: Longint
): Int64; stdcall; external ntdll name 'RtlConvertLongToLargeInteger';

// Ассемблерная обёртка для сохранения структуры
function RtlConvertLongToLargeInteger_Asm(In32bitSigned: Longint): TLARGE_INTEGER;
asm
  push ebx
  mov ebx, eax  // Сохраняем параметр
  mov eax, ebx  // Восстанавливаем параметр
  call RtlConvertLongToLargeInteger_Int64
  mov [Result].TLARGE_INTEGER.LowPart, eax
  mov [Result].TLARGE_INTEGER.HighPart, edx
  pop ebx
end;

const
  LONG_VALUE = High(Longint);

var
  LargeInteger: TLARGE_INTEGER;
  TempInt64: Int64;
begin
  if IsDebuggerPresent then asm int 3 end;

  Writeln;
  Writeln('Converting: ', LONG_VALUE);
  Writeln;

  // Использование Int64
  TempInt64 := RtlConvertLongToLargeInteger_Int64(LONG_VALUE);
  LargeInteger.QuadPart := TempInt64;
  Writeln('Using Int64 wrapper:');
  Writeln('  LowPart: ', LargeInteger.LowPart);
  Writeln('  HighPart: ', LargeInteger.HighPart);
  Writeln('  QuadPart: ', LargeInteger.QuadPart);
  Writeln;

  // Использование ассемблерной обёртки
  LargeInteger := RtlConvertLongToLargeInteger_Asm(LONG_VALUE);
  Writeln('Using assembler wrapper:');
  Writeln('  LowPart: ', LargeInteger.LowPart);
  Writeln('  HighPart: ', LargeInteger.HighPart);
  Writeln('  QuadPart: ', LargeInteger.QuadPart);

  Writeln;
  Writeln('Press ENTER to exit...');
  Readln;
end.

Заключение

Работа с функциями Windows API, которые возвращают структуры, требует особого внимания в Delphi и Free Pascal. Хотя современные компиляторы предлагают различные способы решения этой проблемы, наиболее надёжным подходом остаётся следование рекомендациям Microsoft и использование Int64 для 64-битных возвращаемых значений. Для случаев, когда важна семантика структуры, ассемблерные обёртки предоставляют необходимое решение.

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

Создано по материалам из источника по ссылке.

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


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




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


:: Главная :: Windows ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 20:14:06
2025-08-29 02:57:49/0.0059840679168701/0