При работе с датами в Windows API разработчики часто сталкиваются с функцией DosDateTimeToVariantTime, которая преобразует дату и время в формате DOS (16-битные значения) в формат Variant Time (8-байтное значение с плавающей точкой). Однако при использовании этой функции в Delphi и Free Pascal возникают нюансы, связанные с несоответствием типов данных. В этой статье мы разберем проблему и предложим решения для корректной работы с функцией.
Проблема определения функции
В модуле activex.pp (часть Free Pascal) функция объявлена следующим образом:
function DosDateTimeToVariantTime(
wDosDate: ushort;
wDosTime: ushort;
pvtime: pdouble
): longint; stdcall; external oleaut32dll name 'DosDateTimeToVariantTime';
Официальная документация Microsoft показывает аналогичное определение на C:
"The function returns TRUE on success and FALSE otherwise."
Возникает противоречие:
1. В Pascal-реализации используется longint
2. В документации C-объявление использует INT
3. Фактическое поведение соответствует булевому типу (BOOL)
Анализ дизассемблированного кода
Исследование машинного кода функции показывает:
.text:100169FF xor eax, eax
.text:10016A01 jmp short loc_100169EE
Функция явно возвращает 1 при успехе и 0 при ошибке, что соответствует поведению булевого типа.
Почему возникла проблема?
Исторические причины:
1. В старых версиях C/C++ тип BOOL был макросом для longint
2. Значения TRUE и FALSE определялись как 1 и 0 соответственно
3. Windows API сохраняет обратную совместимость, используя INT в заголовках
Как отмечает участник обсуждения Thaddy:
"This stems from C(++) compilers interpreting false as zero and anything else a valid true. Predates compilers with _Bool."
Решения для Pascal-разработчиков
Вариант 1: Сравнение с нулем (рекомендуется)
Самый простой и совместимый способ:
var
DosDate, DosTime: Word;
VTime: Double;
Res: Longint;
begin
// Преобразование даты и времени в DOS-формат
DosDate := ...;
DosTime := ...;
Res := DosDateTimeToVariantTime(DosDate, DosTime, @VTime);
if Res <> 0 then
ShowMessage('Успешное преобразование: ' + FloatToStr(VTime))
else
ShowMessage('Ошибка преобразования');
end;
Вариант 2: Создание обертки с правильным типом
Для повышения семантической корректности:
function CorrectDosDateTimeToVariantTime(
wDosDate: Word;
wDosTime: Word;
out VTime: Double
): Boolean;
var
Res: Longint;
begin
Res := DosDateTimeToVariantTime(wDosDate, wDosTime, @VTime);
Result := Res <> 0;
end;
// Использование:
var
Success: Boolean;
VTime: Double;
begin
Success := CorrectDosDateTimeToVariantTime(DosDate, DosTime, VTime);
if Success then
// Действия при успехе
end;
Альтернативное решение: прямая работа с API
Для полного контроля можно использовать прямое объявление:
function DosDateTimeToVariantTime(
wDosDate: Word;
wDosTime: Word;
pvtime: PDouble
): LongBool; stdcall; external 'oleaut32.dll';
// Использование с правильным типом:
var
VTime: Double;
begin
if DosDateTimeToVariantTime(DosDate, DosTime, @VTime) then
// Успешное выполнение
else
// Обработка ошибки
end;
Важно! При таком подходе необходимо учитывать: 1. Размер типа LongBool (4 байта) 2. Совместимость с существующим кодом 3. Поведение на разных версиях Windows
Рекомендации по работе с Windows API в Pascal
Всегда проверяйте фактическое поведение функций через:
Официальную документацию Microsoft
Дизассемблирование (если необходимо)
Эмпирическое тестирование
Используйте правильные типы сравнений: // Неправильно:
if DosDateTimeToVariantTime(...) then ...
// Правильно:
if DosDateTimeToVariantTime(...) <> 0 then ...
Для критически важных функций создавайте обертки с проверкой ошибок: function SafeDosDateTimeToVariantTime(...): Boolean;
begin
Result := DosDateTimeToVariantTime(...) <> 0;
if not Result then
RaiseLastOSError;
end;
Следите за обновлениями в Pascal-библиотеках:
В FPC/Lazarus проблема исправлена в версии 3.2.2+
В Delphi используйте официальные заголовки из Winapi.Windows
Пример полного преобразования
function FileTimeToVariantTime(const FileTime: TFileTime; out VTime: Double): Boolean;
var
DosDate, DosTime: Word;
LocalFileTime: TFileTime;
begin
Result := False;
if FileTimeToLocalFileTime(FileTime, LocalFileTime) then
if FileTimeToDosDateTime(LocalFileTime, DosDate, DosTime) then
Result := DosDateTimeToVariantTime(DosDate, DosTime, @VTime) <> 0;
end;
// Использование:
var
Ft: TFileTime;
Vt: Double;
begin
GetSystemTimeAsFileTime(Ft);
if FileTimeToVariantTime(Ft, Vt) then
ShowMessage('Variant time: ' + FloatToStr(Vt))
else
ShowMessage('Ошибка преобразования');
end;
Заключение
Проблема с определением DosDateTimeToVariantTime в Pascal-библиотеках демонстрирует важность понимания низкоуровневых особенностей Windows API. Хотя официальные заголовки указывают тип INT, фактическое поведение функции соответствует булевому типу.
Для надежной работы:
1. Используйте сравнение результата с нулем
2. Рассмотрите создание типобезопасных оберток
3. Регулярно обновляйте библиотеки компонентов
4. Проверяйте документацию и фактические реализации
Эти принципы помогут избежать ошибок при работе не только с DosDateTimeToVariantTime, но и с другими API-функциями, где встречаются подобные несоответствия типов.
Особенности преобразования форматов даты и времени в Windows API при работе с Pascal и Delphi, включая решение проблем несоответствия типов данных.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS