В 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."
Рекомендации
Используйте Int64: Для большинства случаев, особенно с устаревшими функциями вроде RtlConvertLongToLargeInteger, предпочтительно использовать Int64 как возвращаемый тип, как рекомендует Microsoft.
Ассемблерные обёртки: Если необходимо сохранить семантику структуры, создайте ассемблерную обёртку.
Избегайте устаревших функций: По возможности используйте современные альтернативы, так как многие из этих функций считаются устаревшими.
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
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.