Автор: Оригинал: Дата: Распространение: |
Семенов Олег http://www.oldi.ru/review/xls/xls.htm август 2002 GPL |
Документ описывает формат файла MS Exel различных версий, и делится на две логические части: описание формата OLE2 файла и описание формата BIFF файла. Описание собрано на основе проработки отдельных статей и ... (censored. Some companies don't like debugging).
Документ основывается на разрозненном англоязычном материале. Описание OLE2 основано на документе "DIG2000 file format proposal" и личных опытах работы с OLE2 файлами. Описание BIFF основано на документации Microsoft к формату BIFF и трудах OpenOffice проекта с дополнениями автора. Основное внимание уделено форматам BIFF5, BIFF7 как наиболее универсальные в плане чтения разными версиями Excel, но рассмотрены и другие форматы. К сожалению, полной документации нет даже у Microsoft (некоторые функции помечены как требующие документации). Данный документ не претендует на полное описание форматов Excel. Главная цель документа - дать описание основных функций, которые позволят читателю создать полноценный файл Excel, написать программу чтения или записи формата Excel. Документ также будет полезен людям, работающим с любыми форматами, использующими OLE2 контейнер (MS Office документы, OpenOffice документы и т.п.)
Отмазка:
Здесь уместно процитировать предисловие к главе "A: Structured Storage" из статьи "DIG2000 file format proposal":
Бинарный формат OLE2 файла является собственностью Microsoft. Мы надеемся, что они не лицензируют и не взимают плату за создание третьими сторонами независимых реализаций программ чтения и записи файлов OLE2.
Содержание:1.1. Версии форматов Excel
Формат файла Excel называется BIFF (Binary Interchange File Firmat).
Верия Excel | Версия BIFF | Тип документа | Формат файла |
---|---|---|---|
Excel 2.1 | BIFF2 | Worksheet | Stream |
Excel 3.0 | BIFF3 | Worksheet | Stream |
Excel 4.0 | BIFF4 | Worksheet or Workbook | Stream |
Excel 5.0 | BIFF5 | Workbook | OLE2 Storage |
Excel 7.0 (Excel 95) | BIFF7 | Workbook | OLE2 Storage |
Excel 8.0 (Excel 97) | BIFF8 | Workbook | OLE2 Storage |
Excel 9.0 (Excel 2000) | BIFF8 | Workbook | OLE2 Storage |
Excel 10.0 (Excel XP) | BIFF8 | Workbook | OLE2 Storage |
С версии Excel 5.0 BIFF файл инкапсулируется в OLE2 контейнер. Этот контейнер представляет собой настоящую файловую систему. И заслуживает отдельного разговора. С формата OLE2 контейнера и начнем рассмотрение формата Excel.
Замечание: если Вы пишите в среде MS Windows, то здесь реализованы интерфейсы для работы с OLE2 контейнером: IStream и IStorage.
Что бы не нарушать никаких авторских прав, в разных источниках формат OLE имеет различные названия: LAOLA, POIFS, но фактически они описывают одну и ту же структуру файловой системы.
OLE файл состоит из так называемых виртуальных потоков. Виртуальный поток - это данные, которые читаются как линейный поток, хотя их физическое расположение в файле может быть фрагментировано. Это могут быть данные пользователя или структуры, контролирующие работу файла. Кстати, файл сам по себе так же можно считать виртуальным потоком на диске.
OLE файл построен как файловая система. Все пространство файла разбито на сектора. Размер сектора определяется при создании файла и как правило равен 512 байтам. Виртуальный поток состоит из последовательности секторов. Сектора нумеруются от -1 (Header) с шагом 1. Почти все переменные ссылаются на номер сектора, а не на смещение.
Файл имеет различные типы секторов: FAT, Directory, MiniFAT, DIF, Storage. Отдельный тип сектора - заголовок (Header). Основное отличие его от всех секторов, это то, что заголовок всегда 512 байт и всегда начинается со смещения 0. Остальные сектора могут иметь другой размер (определенный при создании) и располагаться в любом месте файла.
Для простоты, при создании Excel файла можно исключить мини-потоки и MiniFAT, как это сделано, например, в библиотеке Spreadsheet-WriteExcel для Perl. Мини поток в обычном файле Exel используется в основном для внедренных объектов. Если таковых нет, то можно обойтись и без мини-потока. А если файл небольшой (менее 7 мб), то исключить и DIF сектора.
Для описания данных в структуре файла вводятся новые типы данных:
typedef unsigned long |
ULONG; |
// 4 bytes |
Все данные, содержащие более одного байта, хранятся, используя Intel (Little-Endian) нотацию. Это значит, что менее значимый байт хранится первым, а наиболее значимый байт - последним. Например, 32-битное значение 12345678H преобразуется в 78H 56H 34H 12H
Самый первый сектор в файле - заголовок (Header), имеющий совершенно определенную структуру.
Смещ. HEX |
Тип | Размер байт |
Переменная | Значение, как в файле | Описание |
---|---|---|---|---|---|
000 | BYTE | 8 | _abSig[8]; | D0CF11E0 A1B11AE1 | Идентификатор. Всегда постоянная (0 x E011CFD0, 0 x E11AB1A1), в старых версиях (beta 2, до 92 года) имел значение {0E11FC0D D0CF11E0} |
008 | CLSID | 16 | _clid; | 00000000 00000000 00000000 00000000 |
Class ID. Устанавливается WriteClassStg, считывается GetClassFile/ReadClassStg. Для Excel как правило = 0 |
018 | USHORT | 2 | _uMinorVersion; | 3E00 | Младшее значение версии формата. Для Excel константа 3EH |
01A | USHORT | 2 | _uDllVersion; | 0300 | Старшее значение версии Dll/формата. Для Excel константа 3H |
01С | USHORT | 2 | _uByteOrder; | FEFF | 0 x FFFE говорит, что используется Intel нотация |
01E | USHORT | 2 | _uSectorShift; | 0900 | Размер сектора. Обычно равно 9, что указывает на размер 512 байт (29) |
020 | USHORT | 2 | _uMiniSectorShift; | 0600 | Размер мини-сектора. Обычно равно 6, что указывает на размер 64 байт (26) |
022 | USHORT | 2 | _usReserved; | 0000 | Зарезервировано, должно быть равно 0 |
024 | ULONG | 4 | _ulReserved1; | 00000000 | Зарезервировано, должно быть равно 0 |
028 | ULONG | 4 | _ulReserved2; | 00000000 | Зарезервировано, должно быть равно 0 |
02C | FSINDEX | 4 | _csectFat; | Число секторов, в которых размещается FAT. Если файл <7Мб, то равно 1, если больше, то больше 1 и появляется DIF сектор. | |
030 | SECT | 4 | _sectDirStart; | Номер первого сектора, в котором размещается Property Set Storage (еще называют FAT Directory или Root Directory Entry) | |
034 | DFSIGNATURE | 4 | _signature; | 00000000 | Подпись для транзакций. Excel не использует транзакции. Должно быть равно 0. |
038 | ULONG | 4 | _ulMiniSectorCutoff; | 00100000 | Максимальный размер мини-потока. Обычно 4096 |
03С | SECT | 4 | _sectMiniFatStart; | Первый сектор мини-FAT. Если 0 х FFFFFFFE (-2), то мини-поток отсутствует. | |
040 | FSINDEX | 4 | _csectMiniFat; | Число секторов в цепочке мини-FAT. 0, если мини-потока нет | |
044 | SECT | 4 | _sectDifStart; | Первый сектор в DIF цепочке. Если файл <7Мб, то DIF цепочка отсутствует и значение равно 0 x FFFFFFFE (-2) | |
048 | FSINDEX | 4 | _csectDif; | число секторов в DIF цепочке.0, если файл <7Мб | |
04С | SECT | 436 | _sectFat[109]; | Номера первых 109 секторов, в которых располагается FAT. Если файл <7Мб, то сектор один, остальные значение заполняются 0 х FFFFFFFF (-1). |
Пример простого файла Excel:
Размер сектора 512 байт. Размер мини-сектора 64 байта. FAT расположен в секторе 24 (18H, смещение 00003200) и он единственный. Root Directory Entry располагается в 25 (19H, смещение 00003400) секторе. Мини-потоки отсутствуют.
В некоторых источниках FAT называют Big Block Depot (BBD). Каждый сектор в файле представлен в FAT, включая и пустые сектора. FAT представляет собой виртуальный поток из одного или более FAT секторов. В простейшем случае FAT состоит из одного сектора.
Каждый виртуальный поток файла представлен в FAT цепочкой из номеров блоков, в которых расположен виртуальный поток. Есть специальные значения:
В этом примере блоки идут подряд, но это не обязательно.
Как читать FAT:
FAT состоит из блоков по 4 байта, пронумерованных с 0. В блоке указан номер блока FAT или, что то же самое, номер следующего принадлежащего потоку блока. Итак, в блоке FAT0 значение 1. Это значит, что поток начинается с блока 0 и следующий блок 1 (блок файла и блок FAT). В блоке 1 значение 2, и т.д. до блока 7. В нем значение -2 (0 x FFFFFFFE), что означает конец цепочки. В результате, первый поток содержит блоки файла {0,1,2,3,4,5,6,7}. В нашем примере присутствуют еще 2 потока: {8,9,10,11,12,13,14,15}, {16,17,18,19,20,21,22,23}.
Следующее значение, расположенное в блоке 24, это -3 (0 x FFFFFFFD). Оно означает, что в блоке 24 файла находится сам FAT.
Осторожно, не пропустите следующее значение в блоке 25, это -2 (0 x FFFFFFFE). Оно означает, что в этом блоке что-то располагается. А из заголовка нам известно, что здесь располагается Property Set Storage. Property Set Storage - обычный виртуальный поток, который обычным образом отображается в FAT.
Остальные значение равны -1 (0 x FFFFFFFF). Это значит, что дальше пустые сектора и ими забит остаток FAT до 512 байт.
Поскольку данные располагаются в секторах определенного размера, то возможен вариант, когда данных много меньше величины сектора и будет попусту тратиться свободное пространство. Для таких случаев предусмотрены мини-сектора, а для их организации - мини-FAT.
Расположение мини-FAT можно узнать из заголовка. В переменной _sectMiniFatStart хранится номер сектора с мини-FAT, а в переменной _csectMiniFat - число занимаемых мини-FAT секторов. Мини-FAT - это такой же виртуальный поток, как и все остальные, поэтому он представлен в обычном FAT, и если он занимает несколько секторов, то не составит труда восстановить цепочку секторов мини-FAT по обычному FAT.
Мини-FAT организован точно так же, как и обычный FAT, с той лишь разницей, что вместо работы с целым файлом, идет работа с мини-потоком как единым целым (хотя физически он может быть фрагментирован как тот же файл в дисковой файловой системе), и указываются не основные сектора (с размером _uSectorShift), а мини-сектора (с размером _uMiniSectorShift). Причем мини-сектора нумеруются и указываются относительно целого мини-потока.
Как собрать воедино мини-поток будет показано ниже, так как для этого необходимо рассмотреть структуру Property Set Storage.
DIF сектора (Double Indirect Fat) служат для организации целого FAT из нескольких FAT секторов. Для оптимизации, первые 109 секторов FAT представлены в заголовке и никакого DIF сектора в небольшом (<7 Мб) файле нет.
DIF сектор - это как бы виртуальное продолжение заголовка (перечисления секторов, в которых располагается FAT). Первые 109 секторов указываются в заголоовке, остальные - в DIF секторах.
Есть ли в файле DIF сектора и сколько их, известно из заголовка (_sectDifStart - первый сектор в DIF цепочке, _csectDif - число секторов DIF). Если DIF сектор не один, то последнне значение в секторе - указатель на блок, в котором расположен следующий DIF сектор.
Таким образом, что бы найти FAT большого файла, необходимо сначало собрать воедино цепочку FAT, прочитав номера секторов FAT из заголовка и пройдясь по всей DIF цепочке, а потом пройтись по всей цепочке FAT, собрав таким образом весь FAT.
Может возникнуть небольшая путаница с названиями Property Set Storage и Property Set. Вроде одно относится к другому, но Property Set Storage на самом деле гораздо шире, чем просто контейнер для Property Set. В некоторых источниках его называют Directory chain - цепь директорий или попросту дерево каталогов. Это более правильное название. Как и в FAT DOS системы, здесь есть файлы и каталоги.
Каждый виртуальный поток файла имеет запись в дереве каталогов. Первый сектор дерева каталогов - Root Directory Entry (корень). Корень может содержать как потоки (Stream), так и контейнеры (Storage). Имя Root Directory Entry обычно содержит строку "Root Entry" в Unicod'e, но в некоторых версиях содержится только первая буква "R". Эта строка всегда игнорируется, поскольку Root Entry известна по своей позиции и идентификатору SID 0 (он нигде не указан, а отсчитывается по подряд идущим записям в дереве каталогов, начиная с нуля) лучше, чем по своему имени. Новые разработки должны записывать имя "Root Entry", но поддерживать работу и со старыми версиями, в которых записано "R".
Каждый уровень иерархии (набор ветвей) представлен как красное/черное дерево. Родитель этого набора ветвей имеет указатель на вершину этого дерева. Дерево должно отвечать следующим требованиям, чтобы быть правильным:
С помощью красных узлов можно организовать как бы дерево в дереве.
Простейшая реализация, когда все узлы - черные. В этом случае получается бинарное дерево. Все сказанное про цвета относится к спецификации OLE2. Если Вас интересует только Excel, без дополнительных объектов типа MS Equation, то используйте бинарное дерево и не задумывайтесь о цвете. Он используется в основном для внешних объектов. Например, если вставляется в таблицу документ MS Word, то узел Word Document будет красным и содержать дерево относящееся только к внедренному документу Word.
Некорневые записи могут быть как потоковыми (STGTY_STREAM) - виртуальные файлы, так и контейнерными (STGTY_STORAGE) элементами - виртуальные директории. Контейнерные элементы имеют _clsid, _time[], и _sidChild значения, потоковые элементы могут и не иметь. Потоковые элементы имеют установленные _sectStart и _ulSize поля, в то время как эти поля в контейнерных элементах могут быть равны 0 (за исключением корня).
Чтобы определить физическое расположение потока (файла) для потокового элемента, необходимо сначало определить, какой FAT (обычный или мини) используется для потока. Поток, у которого значение поля _ulSize меньше, чем _ulMiniSectorCutoff располагается в мини-потоке и _startSect используется как индекс в MiniFat (который начинается с _sectMiniFatStart). Поток, чей _ulSize больше, чем _ulMiniSectorCutoff располагается в стандартном FAT, его _startSect используется как индекс в стандартном FAT.
typedef enum tagSTGTY {
STGTY_INVALID = 0,
STGTY_STORAGE = 1,
STGTY_STREAM = 2,
STGTY_LOCKBYTES = 3,
STGTY_PROPERTY = 4,
STGTY_ROOT = 5,
} STGTY ;
typedef enum tagDECOLOR {
DE_RED = 0,
DE_BLACK = 1,
} DECOLOR ;
Смещ. HEX |
Тип | Размер байт |
Переменная | Значение, как в файле | Описание |
---|---|---|---|---|---|
000 | BYTE | 64 | _ab[32*sizeof(WCHAR)]; | Название элемента в Unicode, оканчивающегося нулем. Остаток места заполняются нулями | |
040 | WORD | 2 | _cb; | Длина элемента в байтах, включая завершающий ноль | |
042 | BYTE | 1 | _mse | Тип объекта. Значение берется из перечисления STGTY | |
043 | BYTE | 1 | _bflags; | 01 | Значение берется из перечисления DECOLOR |
044 | SID | 4 | _sidLeftSib; | SID левой ветви этой записи в дереве каталогов. Т.е. ID предыдущего элемента | |
048 | SID | 4 | _sidRightSib; | SID правой ветви этой записи в дереве каталогов. Т.е. ID следующего элемента | |
04C | SID | 4 | _sidChild; | SID первого ребенка, выполняющего роль корня для всех детей этого элемента (если _mse = STGTY_STORAGE) | |
050 | GUID | 16 | _clsId; | CLSID этого контейнера (если _mse = STGTY_STORAGE) | |
060 | DWORD | 4 | _dwUserFlags; | Пользовательский флаг контейнера (если _mse = STGTY_STORAGE) | |
064 | TIME | 16 | _T_time[2]; | Время создания/изменения (Timestamp). Первые 8 байт - время создания, вторые 8 байт - время изменения | |
074 | SECT | 4 | _sectStart; | Первый сектор потока (если _mse = STGTY_STREAM). Если _mse = STGTY_ROOT, здесь может быть значение первого сектора мини-потока | |
078 | ULONG | 4 | _ulSize; | Размер потока в байтах (если _mse = STGTY_STREAM). Если _mse = STGTY_ROOT, здесь может быть размер мини-потока | |
07C | DFPROPTYPE | 2 | _dptPropType; | Зарезервировано на будущее. Должно быть равно 0 |
Посмотрим на пример. Здесь 4 элемента в дереве каталогов. Нумеруются они подряд начиная с 0, т.е. Root Entry имеет SID 0, Book - SID 1, Summary Information - SID 2, Document Summary - SID 3.
_ab | = "R o o t E n t r y " | |
_cb | = 16H (22) | Включает завершающий 0. Каждый символ занимает 2 байта (и 0 тоже). |
_mse | = 05 | STGTY_ROOT. Показывает, что данная запись - корневая |
_bflsgs | = 01 | DE_BLACK. Ветвь черная. |
_sidLeftSib | = FFFFFFFF | -1. Нет предыдущего |
_sidRightSib | = FFFFFFFF | -1. Нет следующего |
_sidChild | = 02000000 | Ребенок SID 2 (Summary Information) |
_clsId | = 1008 0200 0000 0000 С000 0000 0000 0046 |
Class ID. Если не знаете, заполняйте нулями |
_dwUserFlags | = 00000000 | Для STGTY_ROOT недоступен |
_T_time[2] | = 0000 0000 0000 0000 0000 0000 0000 0000 |
Первые 8 байт - время создания, вторые 8 байт - время изменения. Все нули - время не установлено |
_sectStart | = FFFFFFFF | Здесь может быть значение первого сектора мини-потока. -1, значит мини-поток отсутствует. |
_ulSize | = 00000000 | Здесь может быть размер мини-потока в байтах. 0 - мини-потока нет. |
_dptPropType | = 0000 | Всегда 0 |
_ab | = "B o o k " | |
_cb | = 0AH (10) | Включает завершающий 0. Каждый символ занимает 2 байта (и 0 тоже). |
_mse | = 02 | STGTY_STREAM. Показывает, что данная запись - поток. |
_bflsgs | = 01 | DE_BLACK. Ветвь черная. |
_sidLeftSib | = FFFFFFFF | -1. Нет предыдущего |
_sidRightSib | = FFFFFFFF | -1. Нет следующего (Почему?) |
_sidChild | = FFFFFFFF | Нет детей. Для потока не доступно. |
_clsId | = 0000 0000 0000 0000 0000 0000 0000 0000 |
Class ID. Если не знаете, заполняйте нулями |
_dwUserFlags | = 00000000 | Не установлен |
_T_time[2] | = 0000 0000 0000 0000 0000 0000 0000 0000 |
Первые 8 байт - время создания, вторые 8 байт - время изменения. Все нули - время не установлено |
_sectStart | = 00000000 | Может показаться, что ничего не установлено. На самом деле установлен 0, т.е. поток начинается с нулевого блока. |
_ulSize | = 00100000 | 4096 байт. Размер потока. |
_dptPropType | = 0000 | Всегда 0 |
_ab | = 05H" S u m m a r y |
Название начинается с 05H - это специальные имена, указывающие, что данный поток Property Set и, что он отвечает определенному формату, который будет описан ниже |
_cb | = 28H (40) | Включает завершающий 0. Каждый символ занимает 2 байта (и 0 тоже). |
_mse | = 02 | STGTY_STREAM. Показывает, что данная запись - поток. |
_bflsgs | = 01 | DE_BLACK. Ветвь черная. |
_sidLeftSib | = 01000000 | Предыдущий элемент с SID 1 - Book |
_sidRightSib | = 03000000 | Следующий элемент с SID 3 - Document Summary |
_sidChild | = FFFFFFFF | Нет детей. Для потока не доступно. |
_clsId | = 0000 0000 0000 0000 0000 0000 0000 0000 |
Class ID. Если не знаете, заполняйте нулями |
_dwUserFlags | = 00000000 | Не установлен |
_T_time[2] | = 0000 0000 0000 0000 0000 0000 0000 0000 |
Первые 8 байт - время создания, вторые 8 байт - время изменения. Все нули - время не установлено |
_sectStart | = 08000000 | Поток начинается с блока 8. |
_ulSize | = 00100000 | 4096 байт. Размер потока. |
_dptPropType | = 0000 | Всегда 0 |
_ab | = 05H" D o c u m e n t |
Название начинается с 05H - это специальные имена, указывающие, что данный поток Property Set и, что он отвечает определенному формату, который будет описан ниже |
_cb | = 38H (56) | Включает завершающий 0. Каждый символ занимает 2 байта (и 0 тоже). |
_mse | = 02 | STGTY_STREAM. Показывает, что данная запись - поток. |
_bflsgs | = 01 | DE_BLACK. Ветвь черная. |
_sidLeftSib | = FFFFFFFF | -1. Нет предыдущего (Почему?) |
_sidRightSib | = FFFFFFFF | -1. Нет следующего |
_sidChild | = FFFFFFFF | Нет детей. Для потока не доступно. |
_clsId | = 0000 0000 0000 0000 0000 0000 0000 0000 |
Class ID. Если не знаете, заполняйте нулями |
_dwUserFlags | = 00000000 | Не установлен |
_T_time[2] | = 0000 0000 0000 0000 0000 0000 0000 0000 |
Первые 8 байт - время создания, вторые 8 байт - время изменения. Все нули - время не установлено |
_sectStart | = 08000000 | Поток начинается с блока 8. |
_ulSize | = 00100000 | 4096 байт. Размер потока. |
_dptPropType | = 0000 | Всегда 0 |
Все потоки должны принадлежать корню (поскольку каталогов нет), но судя по ссылкам (_sidLeftSib, _sidRightSib, _sidChild) - это дерево вида:
Root Entry | |
||
Book <-- | Summary Information | --> Document Summary Information |
Т.е. _sidChild показывает с какой ветвью связан каталог, а уже от этой ветви идут связи вправо и влево (_sidLeftSib, _sidRightSib).
Почему же только Summary Information имеет ссылки на соседние ветви? В принципе, Book имеет правого соседа, а Document Summary Information - левого, но эти ссылки для обхода дерева ничего не дают полезного, поэтому -1. На самом деле, если в этих местах будет ссылка, то она будет идти не на того соседа, с которого пришли, а совсем на другого. То есть, каждый элемент фактически может иметь 2 соседа справа и два соседа слева.
Часто в файле Excel можно встретить потоки (первые два обязательны):
и контейнеры:
Во всех потоках записи располагаются последовательно, они никогда не включаются в другие записи. Исключение составляет BIFF8: поток объекта Escher разделен и включается в несколько записей MSODRAWING.
Как найти данные мини-потока? Для этого нужно обратиться к записи Root Entry в Property Set Storage. Со смещением 074H в переменной _sectStart указан номер первого сектора, в котором находятся данные мини-потока. Что бы определить весь мини-поток, необходимо проследить цепочку в основном FAT, начиная с сектора _sectStart.
Данные в мини-потоке разбиты на блоки размером _uMiniSectorShift и нумеруются с 0. Цепочки данных в мини-потоке описываются мини-FAT'ом, используя эту нумерацию.
Примерный алгоритм чтения файла следующий:
1. Читаем заголовок.
2. Читаем FAT
3. Если есть мини-FAT, читаем его
3. Читаем Property Set Storage
4. Читаем необходимые нам данные (на примере записи "Book")
Наборы свойств (Property Sets) необходимы для хранения небольшой информации (вроде авторства и т.п.). Они располагаются в файле как отдельная запись, поэтому очень удобны для передачи данных другим приложениям. Например, какой-нибудь информационной программе нужно быстро узнать авторов всех расположеных на диске файлов. Она читает каждый файл, обращается в Property Set Storage, находит необходимую запись, и читает только необходимую для нее информацию (я опустил, что еще надо прочитать заголовок, FAT и т.п., т.е. то, что в любом случае надо делать), не трогая остальное.
Все Наборы свойств представлены в Property Set Storage, причем их имена начинаются с символа 0 x 05, что говорит о том, что данные будут в определенном формате, описанном ниже.
Наборы свойств имеют заголовок, пару Format ID/смещение и секцию (теоретически спецификация OLE2 допускает более, чем одну секцию, но это не поддерживается). Секция также имеет некоторое деление. Лучше понять формат Набора свойств поможет таблица:
Property Set Header
|
|||||||||
Пара Format ID/ Смещение
|
|||||||||
Секция
|
Размер одного Набора свойств лимитирован 256 кб. Все данные находятся в Intel нотации. В заголовке может смутить часть "порядок байт", но она не используется и включена для будущих реализаций.
Property Set Header
typedef struct PROPERTYSETHEADER {
WORD wByteOrder; //
Всегда 0 х FFFE
WORD wFormat; //
0 показывает, что формат правильный и принадлежит Property Set
DWORD dwOSver; //
Старшее слово показывает операционную систему: 0 - Windows, 1 - Macintosh, 2
- Windows 32 bit, 3 - Unix. Младшее слово показывает номер версии операционной
системы
CLSID clsid; //
Идентификатор класса, который показывает или дает доступ к Набору свойств
DWORD reserved; //
Зарезервировано. Всегда 1.
} PROPERTYSETHEADER;
Пара Format ID/ Смещение
typedef struct FORMATIDOFFSET {
FMTID fmtid; //
Имя секции
DWORD dwOffset; //
Смещение в байтах от начала всего Набора свойств
} FORMATIDOFFSET;
Секция
Каждая секция состоит из
typedef struct PROPERTYSECTIONHEADER
{
DWORD cbSection;
// Размер секции в байтах
DWORD cProreties; //
Колличество Свойств в наборе
PROPERTYIDOFFSET rgprop[]; //
Перечисление адресов Свойств
} PROPERTYSECTIONHEADER;
typedef struct PROPERTYIDOFFSET {
DWORD
propid; //
Название Свойства
DWORD dwOffset; //
Смещение в байтах от начала секции до Свойства
} PROPERTYIDOFFSET;
typedef struct SERIALIZEDPROPERTYVALUE
{
DWORD
dwType; //
Тип Свойства
BYTE rgb[]; //
Собственно само значение Свойства
} SERIALIZEDPROPERTYVALUE;
Наиболее полная информация собрана в статье Charlie Kindel "OLE Property Set Exposed" из библиотеки MSDN.