Тотальная неудачница и убийца жёстких дисков.
#post-id: 8025-19-14
#original-date: 16.07.2022 Sat
#original-time: 7:14 PM
#original-day: 8025
#original-host: WinXP Home SP3 (Build 2600)

Пытаюсь совладать с информацией о версии файла и прихожу во всё большее изумление.

Во-первых, я таки разобралась, что там с парами в «\VarFileInfo\Translation». Там язык и кодировка, как следует из инструкции.

Язык – это типа для того чтобы найти нужный: русский, английский, японский там. Его можно узнать из функций GetUserDefaultLangID() и GetSystemDefaultLangID(), которые вернут язык по улмочанию для пользоваетеля и системы. Либо можно узнать язык для текущего потока через функцию GetThreadLocale(), которая возвращает идентификатор локали, у которого в верхних двух байтах – идентификатор сортировки, а в нижних – как раз идентификатор языка. Думаю, что последний способ предпочитетельнее, поскольку локаль потока может отличаться по той ии иной причине от пользовательской, например, при запуске какой-то программы через AppLocale.

/* В свою очередь идентификатор языка состоит из языка и подязыка, типа как все эти en-us и en-gb, но нам это уже не интересно. */

А кодировка – это актуальная кодировка данных в ресурсах, тоесть, формат, в котором данные записаны. Тоесть вот эти все cp866, windows-1251 и даже 1200 для UTF-16.

Тоесть, каждая пара как бы говорит, что вот есть строки для русского языка, записанные в windows-1251, есть строки для английского языка, записанные в UTF-16, и есть строки для японского, записанные в shift_jis. А программа (тот же Проводник, показывая версию файла в свойствах) уже выбирает нужный ей язык и пускается во все тяжкие.

Хочу отметить, что это я тут использую все эти «windows-1251» и «en-us», но на деле там всё – числовые коды. Тоесть 1251 и 0x409.

Вооот.

Для того чтобы начать работать с версией, нужно сделать GetFileVersionInfoSize(), выделить буфер и передать его в GetFileVersionInfo(), в который будет записан специально отформатированный блок с информацией об искомом файле.

Кстати, обратите внимание на эти милые рудименты от Win16:

lpdwHandle
Pointer to a variable that the function sets to zero.


Потом можно будет сделать VerQueryValue() с данным блоком, чтобы получить интересующие нас значения.

Так, запрос «\» возвращает нам структуру, в которой цифрами, а не ёбаной строкой «1, 12, 866 (спешл билд)», указана версия файла, а так же дополнительные флаги, которые пожелал указать разработчик. И я плюю в лицо всем девелоперам, которые не утруждают себя этой информацией, рассчитывая, что пользователю более чем достаточно посмотреть в абаутбокс. Или в ридми. Или в двочиный дамп файла. Или посмотреть историю на сайте и прикинуть версию по размеру и дате скачки.

Запрос «\VarFileInfo\Translation» возвращает нам все языки, для которых есть строки, как я указала выше. Тоесть, получив массив из этого раздела, мы можем распарсить его и узнать, что строки версии в файле присутствуют в трёх языках, как, скажем, у сетапов моих программ.

Забавное. Для каждого языка набор строк может различаться. Скажем, на русском у нас может быть полный набор, а на японском мы указываем только внутреннее имя и копирайт. Если свойства такого файла посмотреть в русской и японской винде, можно заметить, что Проводник находит нужный язык, а другие не трогает. Поэтому в свойствах будут только те строки, которые Проводник нашёл для текущего языка.

А теперь – жесть. По по идее, ничто не мешает нам взять все языки и для каждого прочитать строки. Но очень быстро окажется, что функция VerQueryValue() будет сообщать, что нет такого ресурса при запросе всех языков кроме одного. Если сохранить на диск буфер, полученный функцией GetFileVersionInfo(), окажется, что в нём присутствуют строки только для одного языка.

И я не уверена, какой выбирается.

Тоесть, если в версии только один язык, то попадает в блок только он. Если несколько, то попадает тот, который совпадает с языком системы (или потока?). А если совпадений нет? Я пока не проверяла, но, чувствую, дойдёт и до этого.

Более того! Как мы выяснили, для строк можно узнать кодировку данных. Однако в блоке все строки хранятся в – ТАДАМ! – UTF-16. Тоесть, эта информация для нас, по идее, носит чисто факультативный фарактер.

Более того. VerQueryValue() последним параметром возвращает размер данных в буфере. Для «\», например, это размер VS_FIXEDFILEINFO в байтах. Для «\VarFileInfo\Translation» – это размер массива в байтах. А для строк... А хрен его знает :}

Документация говорит, что этот параметр «points to a buffer that receives the length, in characters, of the version-information value». Character в Win32 API, обычно, это байт для ANSI строки и два байта для UNICODE строки. Но в случае этой функции тут возвращается значение, отличное от длины полученной строки.

Например, для русской строки длиной 49 символов в windows-1251 возвращается 50. Для английской строки длиной 22 символа в 1200 (тот самый юникод) взвращается 46. А для английской строки длиной 43 символа с кодировкой 0 (так указывает NSIS) возвращается 44.

Очевидно, параметр мало того что возвращает именно байты, так ещё и с паддингом. При этом строки завершаются нулём, что в данных условиях безмерно радует.

А теперь неочевидная жесть. По идее, указатель из VerQueryValue() возвращает адрес где-то в блоке, который мы передаём этой функции. Но. В блоке все строки харанятся в UTF-16. Если мы вызываем VerQueryValueA(), то строка возаращается в ANSI. Внимание, вопрос: куда указывает указатель?

В старой документации (времён NT4 и Win95) про это не сказано ни слова. В той, что сейчас на сайте MS, сказано, что:

When this method returns, contains the address of a pointer to the requested version information in the buffer pointed to by pBlock. The memory pointed to by lplpBuffer is freed when the associated pBlock memory is freed.

И это есть пиздёж, поскольку в блоке информация в UTF-16, в нам прилетает ANSI. Тоесть, перед возвратом функции, система конвертирует строки, возаращает указатель на них и дальше неизвесно что с ними делает и неизвестно когда их освобождает.

Ну и на закуску. Старая документация говорит, что эти функции не работают с Win16 бинарниками. Новая говорит, что подделживаются и Win32, и Win16, и даже Win64 екзешники. Я пока не проверяла, но тоже странно.

#upd(16.07.2022 - 8:08 PM):

Провела эксперимент, и выяснила, что, если в версии нет языка потока, то в блок попадает первый язык из массива «\VarFileInfo\Translation». Миленько. Можно сразу выкидывать код, который перебирает языки и ищет нужный нам.

#upd(16.07.2022 - 8:43 PM):

Провела ещё один эксперимент, чтобы посмотреть, куда указывает указатель с ANSI строкой. Неожиданно оказалось, что куда-то в буфер, который мы передаём VerQueryValue().

Чего, блять?

Присмотрелась к дампу буфера. Оказалось, что там после данных идёт жирный кусок с нулями. Ну и, конечно же, при сохранении того же буфера уже после вызова VerQueryValue(), среди этих нулей оказались наши ANSI строки.

И теперь понятно, что имелось в виду в документации, что память будет освобождена при освобождени буфера. Конечно же она будет освобождена, ведь функция использует наш же собственный буфер. Считаю, что это – элегантное решение, но о нём нужно говорить в документации.

#upd(17.07.2022 - 1:11 AM):

Провела эксперименты в Windows 98. Что характерно, там проблемы те же самые, только блок данных выглядит немного иначе. Там нет пустоты в конце блока, зато все строки – в ANSI и только часть из них продублирована в конце файла в UTF-16.

#upd(17.07.2022 - 2:57 AM):

Оказалось, что по крайней мере в Windows XP функции вполне себе читают Win16 программы.

#upd(17.07.2022 - 3:05 AM):

В x64 Нанами та же история. Win16 приложения не запускаются, но версия очень даже читается. Хотя, Проводник не показывает версию.