Что такое файлы ресурсов в visual studio

Обновлено: 04.07.2024

Рано или поздно любой человек, программирующий под Windows (имеется в виду платформа Win32), сталкивается с необходимостью работы с ресурсами. Когда настанет этот момент - в первую очередь, зависит от среды разработки и библиотек, которыми программист пользуется. Некоторые ориентированы на работу непосредственно с ресурсами с самого начала, другие же позволяют в большинстве случаев обойтись и без них. Однако когда возникает необходимость отредактировать какой-то ресурс, как раз последние среды разработки не могут похвастаться удобными редакторами. В этой статье, как вы, наверное, уже догадались, мы поговорим о ресурсах. И о том, как с ними можно работать с максимальным комфортом.


Что такое ресурсы

Если вы уже достаточно программировали под Win32, то не можете не знать, что такое ресурсы. Однако у начинающих программистов этот термин нередко вызывает вопросы, поэтому, думаю, будет нелишним напомнить (или объяснить), что мы имеем в виду, когда говорим "ресурс".

Ресурсы - это данные, помещённые в специально отведённую для них область исполняемого файла. Как известно, исполняемые файлы могут содержать не только программный код, но и данные. В ОС такие файлы Windows разбиты на секции. Если данные находятся в секции, отведённой для ресурсов, значит, это ресурсы. Чем ресурсы отличаются от любых остальных данных внутри EXE-файла? Собственно, только тем, что система предоставляет программисту специальные функции для работы с ними.

Традиционно выделяют разные типы ресурсов. К стандартным относятся значки (иконки), курсоры для мыши, bitmap-картинки, строки и таблицы строк, комбинации "горячих" клавиш (accelerators), меню, диалоговые окна и информация о версии приложения. Существует также специальный тип ресурсов под названием RCDATA, позволяющий добавить в программу любые данные, не подходящие под приведенную выше классификацию, и работать с ними тоже как с обычными ресурсами.


Для чего они нужны

"Классическое" программирование под Win32, давно ставшее анахронизмом, предусматривает такой подход к созданию приложений, при котором ресурсы используются более чем интенсивно. Однако в большинстве сред разработки, с которыми поставляются фирменные библиотеки для создания пользовательского интерфейса, ресурсы задействованы не совсем так, как предлагали создатели операционной системы. Например, в Borland Delphi и C++ Builder все формы (окна) и всё, что на них, включая меню и таблицы "горячих" клавиш, находятся в секции RCDATA в виде специального текстового описания формы.

Собственно, в связи с этим может возникнуть один довольно здравый вопрос: а в чём вообще смысл использования ресурсов? Ведь можно прекрасно создать меню и диалоговые окна прямо по ходу работы приложения, с помощью стандартных функций Win32 API. Зачем же усложнять всё, создавая сначала ресурсы, а уже после вызывая их из приложения? Ответ довольно прост. Вынесение всех описанных выше видов ресурсов в отдельную часть исполняемого файла облегчает задачу по их изменению в уже скомпилированной программе. Согласитесь, изменить содержимое ресурса с помощью специального редактора намного легче, чем её дизассемблировать и менять ассемблерный код. Однако тут же возникает ещё один вопрос, тоже вполне логичный. Зачем может понадобиться изменение ресурсов? Самый простой пример - локализация программы. Для перевода интерфейса программы на другой язык достаточно воспользоваться редактором ресурсов, причём сделать это может даже человек, достаточно далёкий от программирования. А пользователи любят локализованные программы.


Инструментарий

Для работы с ресурсами, естественно, нужен специальный инструментарий. Его можно разделить на несколько видов: WYSIWYG 1 -редакторы ресурсов, компиляторы ресурсов и редакторы ресурсов, которые работают с исполняемыми файлами.

До того, как ресурс попадёт в исполняемый файл, он может пребывать в двух состояниях: в виде скрипта или в виде скомпилированного файла ресурсов. Файлы со скриптами - текстовые файлы с расширением RC, имеющие внутри себя текст на языке Resource Script. Компиляторы ресурсов преобразуют скрипты в двоичный формат, с которым умеют работать компоновщики исполняемых файлов. Компилятор ресурсов есть практически в каждом пакете для программирования. Например, в GNU Compiler Collection для Windows этот компилятор называется Windres.exe.

С WYSIWYG-редакторами работать, конечно же, на порядок проще, чем писать скрипты в блокноте. Картинки в них рисовать, естественно, не так удобно, как в Adobe Photoshop, но в плане редактирования меню, диалогов, да и иконок с курсорами всё не так уж и плохо. В них ресурсы можно редактировать, как правило, и в виде скриптов, и в скомпилированном двоичном виде. Таких программ, к счастью тоже немало. Наиболее хорошо знаком программистам, пожалуй, редактор ресурсов из Microsoft Visual Studio. Есть также Borland Resource Workshop, которому сто лет в обед, а в последних версиях сред разработки от Borland его уже не видно и не слышно.

Программы, которые умеют работать с ресурсами в исполняемых файлах, как правило, тоже имеют некоторые зачатки WYSIWYG. Дальше всех в этом направлении продвинулся Resource Hacker, но и остальные тоже не так уж плохи.


Resource Builder от SiComponents

А сейчас я хотел бы рассказать о программе, которая в приведенной классификации попадает сразу во все три категории и имеет как WYSIWYG-дизайнер, так и компилятор ресурсов. Умеет при этом работать и со скриптами, и с двоичными файлами ресурсов, и с исполняемыми файлами. Название программы, как вы уже догадались, - ResourceBuilder, а найти в интернете её можно по адресу www.resource-builder.com или www.sicomponents.com. Программа, правда, платная, но это, пожалуй, тот самый случай, когда продукт действительно заслуживает сотни "вечнозелёных", которую за него просят разработчики.

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


Разработчики Resource Builder'а утверждают, что это единственный WYSIWYG-редактор ресурсов, поддерживающий Unicode-ресурсы, в том числе и RC-скрипты в этой кодировке. Кроме того, программа поддерживает и 16-битные, и 32-битные скомпилированные файлы ресурсов, аналогично и исполняемые файлы. Также присутствует поддержка изменения ресурсов без перекомпиляции исполняемого файла, которая, по существу, является стандартной и реализована во многих программах, однако это ничуть не умаляет полезности данной функции. Что интересно, Resource Builder умеет работать и с ресурсами компонентов Delphi/C++ Builder.

Графический редактор, встроенный в Resource Builder, по своим возможностям просто-таки близнец Paint'а. В то же время, в отличие от многих других редакторов, он поддерживает редактирование изображений любого размера и с любой глубиной цвета. В справке написано, что программа умеет работать не только со стандартными BMP-картинками, но и с изображениями в формате JPEG. Однако с такими ресурсами нельзя будет работать теми же системными средствами, что и с BMP, поэтому лично мне эта возможность не кажется такой уж полезной.

Зато интересная особенность Resource Builder'а - поддержка работы с формами Delphi и C++ Builder. С ними можно работать в текстовом виде, причём редактор имеет подсветку синтаксиса для них. А если у вас на компьютере установлены Delphi 6 или 7, или же C++ Builder 6, то можно воспользоваться их средствами для визуального редактирования форм. Если при этом указать все дополнительные компонентные библиотеки (BPL), то работа с формами будет совсем комфортной.

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

Вместе с Resource Builder поставляется и консольный компилятор ресурсных скриптов, поэтому те, кто хочет компилировать ресурсы из командной строки, также будут иметь возможность это сделать. Языковые возможности компилятора полностью совпадают с возможностями визуального редактора ресурсов, так что все ресурсные скрипты, созданные с помощью Resource Builder'а, полностью с ним совместимы.

Ресурсы в программе можно поместить в специальную ресурсную DLL-библиотеку, причём для того, чтобы сделать это, не требуется никаких сторонних инструментов. Одним движением руки в программу или в ресурсный файл можно добавить XP Manifest (специальный ресурс, обеспечивающий поддержку визуальных стилей Windows XP). Для удобства пользователя в Resource Builder'е имеется поддержка многоязычного интерфейса, причём русский язык включён в дистрибутив пробной версии программы. Также стоит отметить присутствие "Мастера настроек", который можно в любой момент вызвать из меню "Инструменты". С его помощью можно быстро настроить самое важное, не вникая в детали всех настроек, которых в Resource Builder'е не так уж и мало.

Таким образом, в качестве подведения итогов хочу сказать, что Resource Builder - самый качественный из встреченных мною инструментов для работы с ресурсами. В этом он может дать фору даже такому монстру, как Visual Studio. А уж о таких программах, как Borland Resource Workshop или Borland Image Editor, и говорить нечего. Поэтому рекомендую его всем, кто страдает от недостатков того редактора ресурсов, которым пользуется.

Вадим СТАНКЕВИЧ


1 WYSIWYG - What You See Is What You Get, т.е. в редакторе всё выглядит так же, как будет выглядеть при работе программы

Создайте текстовый файл, содержащий строковые ресурсы. Для преобразования текстового файла в двоичный файл ресурсов (RESOURCES-файл) можно использовать генератор файлов ресурсов (resgen.exe). Затем можно внедрить двоичный файл ресурсов в исполняемый файл приложения или библиотеку приложения с помощью компилятора языка или во вспомогательную сборку с помощью компоновщика сборок (Al.exe). Дополнительные сведения см. в разделе Ресурсы в текстовых файлах.

Создайте XML-файл ресурсов (RESX-файл), который содержит строки, изображения или данные объектов. Для преобразования RESX-файла в двоичный файл ресурсов (RESOURCES-файл) можно использовать генератор файлов ресурсов (resgen.exe). Затем двоичный файл ресурсов можно внедрить в исполняемый файл приложения или библиотеку приложения с помощью компилятора языка или во вспомогательную сборку с помощью компоновщика сборок (Al.exe). Дополнительные сведения см. в разделе Ресурсы в RESX-файлах.

Создайте XML-файл ресурсов (RESX-файл) программным способом с помощью типов в пространстве имен System.Resources. Можно создать RESX-файл, перечислить его ресурсы и извлечь конкретные ресурсы по имени. Дополнительные сведения см. в разделе Работа с RESX-файлами программным способом.

Создайте двоичный файл ресурсов (RESOURCES-файл) программным способом. Затем этот файл можно внедрить в исполняемый файл приложения или библиотеку приложения с помощью компилятора языка или во вспомогательную сборку с помощью компоновщика сборок (Al.exe). Дополнительные сведения см. в разделе Ресурсы в RESOURCES-файлах.

Создайте файл ресурсов в Visual Studio и включите этот файл в проект. В Visual Studio есть редактор ресурсов,с помощью которого можно добавлять, удалять и изменять ресурсы. Во время компиляции файл ресурсов автоматически преобразуется в двоичный RESOURCES-файл и внедряется в сборку приложения или вспомогательную сборку. Дополнительные сведения см. в разделе Файлы ресурсов в Visual Studio.

Ресурсы в формате текстовых файлов

В текстовых файлах (TXT или RESTEXT) можно сохранять только строковые ресурсы. Для нестроковых ресурсов используйте RESX-файлы или создавайте их программными средствами. Текстовые файлы, содержащие строковые ресурсы, имеют следующий формат.

Форматы TXT- и RESTEXT-файлов ресурсов идентичны. Расширение файла RESTEX служит для того, чтобы текстовые файлы сразу опознавались как файлы ресурсов на основе текста.

Строковые ресурсы представляются в виде пар имя/значение, где имя — строка, определяющая ресурс, а значение — строка ресурса, которая возвращается при передаче имени методу извлечения ресурсов, например, ResourceManager.GetString. Имя и значение должны быть разделены знаком равенства (=). Пример:

Не следует использовать файлы ресурсов для хранения паролей, конфиденциальной информации или личных данных.

В текстовых файлах допускаются пустые строки (то есть, ресурсы, значение которых равно String.Empty). Пример:

Любые пустые строки в текстовых файлах считаются содержащими пробелы и игнорируются.

В следующем примере определяются два строковых ресурса с именами OKButton и CancelButton .

Если в текстовом файле содержатся дубликаты имен, генератор файлов ресурсов (resgen.exe) отображает предупреждение и игнорирует второе имя.

Значение не может содержать символы новой строки. Но с помощью escape-символов языка С можно указать символы новой строки ( \n ) и табуляции ( \t ). Также можно включить символ обратной косой черты, предварив его escape-символом (например, "\\"). Также допускаются пустые строки.

Сохраняйте ресурсы в формате текстового файла с кодировкой UTF-8 или UTF-16 с прямым или обратным порядком байтов. Но генератор файлов ресурсов (resgen.exe), преобразующий TXT-файл в RESOURCES-файл, по умолчанию обрабатывает файлы в кодировке UTF-8. Если вы хотите, чтобы программа Resgen.exe могла работать с файлом в кодировке UTF-16, необходимо указать метку порядка байтов Юникода (U+FEFF) в начале файла.

В следующем примере используется файл ресурсов в текстовом формате GreetingResources.txt для простого консольного приложения "Hello World". В этом текстовом файле определены две строки — prompt и greeting , которые предлагают пользователю ввести свое имя и отображают приветствие.

Текстовый файл преобразуется в RESOURCES-файл с помощью следующей команды:

Если вы используете Visual Basic и файл с исходным кодом называется Greeting.vb, используйте следующую команду для создания исполняемого файла, содержащего внедренный RESOURCES-файл:

Ресурсы в RESX-файлах

В отличие от текстовых файлов, в которых могут храниться только строковые ресурсы, в XML-файлах ресурсов (RESX) могут храниться строки, двоичные данные (такие как изображения, значки и аудиоклипы) и программные объекты. RESX-файл содержит стандартный заголовок, который описывает формат записей ресурсов и включает сведения о версии XML, которые используются для анализа данных. За заголовком XML следуют данные в файле ресурсов. Каждый элемент данных состоит из пары "имя-значение", заключенной в тег data . Атрибут name этого тега определяет имя ресурса, а вложенный тег value содержит значение ресурса. Для строковых данных тег value содержит строку.

Например, следующий тег data определяет строковый ресурс с именем prompt и значением "Enter your name:".

Не следует использовать файлы ресурсов для хранения паролей, конфиденциальной информации или личных данных.

Для объектов ресурсов тег data содержит атрибут type , указывающий тип данных ресурса. Для объектов, состоящих из двоичных данных, тег data также включает атрибут mimetype , который указывает тип base64 двоичных данных.

Во всех RESX-файлах для создания и анализа двоичных данных заданного типа используется форматтер двоичной сериализации. В результате, если формат двоичной сериализации для объекта изменится недопустимым образом, RESX-файл может стать недействительным.

В следующем примере показана часть RESX-файла, в которой содержится ресурс Int32 и растровое изображение.

Так как RESX-файлы должны представлять собой XML-код с правильным, заранее определенным форматом, с ними не рекомендуется работать вручную, особенно если они содержат нестроковые ресурсы. Вместо этого в Visual Studio предусмотрен прозрачный интерфейс для создания RESX-файлов и управления ими. Дополнительные сведения см. в разделе Файлы ресурсов в Visual Studio. Создавать RESX-файлы и управлять ими можно также программно. Дополнительные сведения см. в разделе Работа с RESX-файлами программным способом.

Ресурсы в RESOURCES-файлах

Для программного создания двоичного файла ресурсов (RESOURCES-файла) непосредственно из кода можно использовать класс System.Resources.ResourceWriter. Для создания RESOURCES-файла из текстового файла или RESX-файла можно также использовать генератор файлов ресурсов (resgen.exe). Помимо строковых данных, RESOURCES-файл может содержать двоичные данные (массивы байтов) и данные объектов. Для программного создания RESOURCES-файла необходимо выполнить следующие действия.

Создайте объект ResourceWriter с уникальным именем файла. Это можно сделать, указав имя файла или файловый поток для конструктора класса ResourceWriter.

Вызовите одну из перегрузок метода ResourceWriter.AddResource для каждого именованного ресурса, который требуется добавить в файл. Ресурсом может быть строка, объект или коллекция двоичных данных (массив байтов).

Вызовете метод ResourceWriter.Close, чтобы записать ресурсы в файл и закрыть объект ResourceWriter.

Не следует использовать файлы ресурсов для хранения паролей, конфиденциальной информации или личных данных.

В следующем примере программным способом создается RESOURCES-файл с именем CarResources.resources, в котором хранятся шесть строк, значок и два объекта, определяемые приложением (два объекта Automobile ). Класс Automobile , определенный и созданный в этом примере, отмечен атрибутом SerializableAttribute, который позволяет ему сохраняться модулем форматирования при двоичной сериализации.

После создания RESOURCES-файла его можно внедрить в исполняемый файл среды выполнения или библиотеку, используя параметр /resource компилятора языка, или во вспомогательную сборку с помощью компоновщик сборок (Al.exe).

Файлы ресурсов в Visual Studio

При добавлении файла ресурсов в проект Visual Studio среда Visual Studio создает RESX-файл в каталоге проекта. В Visual Studio имеются редакторы ресурсов, позволяющие добавлять строки, изображения и двоичные объекты. Так как редакторы предназначены для обработки только статических данных, их нельзя использовать для хранения программных объектов; данные объектов необходимо записывать в RESX- или RESOURCES-файл программным способом. Дополнительные сведения см. в статье Работа с RESX-файлами программным способом и разделе Ресурсы в RESOURCES-файлах.

При добавлении локализованных ресурсов указывайте для них то же имя корневого файла, что и для основного файла ресурсов. Также в имени файла необходимо указать язык и региональные параметры. Например, при добавлении файла ресурсов с именем Resources.resx можно также создать файлы ресурсов с именами Resources.en-US.resx и Resources.fr-FR.resx, чтобы сохранить локализованные ресурсы для английского (США) и французского (Франция) языков и региональных параметров соответственно. Следует также указать язык и региональные параметры по умолчанию для приложения. Это язык и региональные параметры, ресурсы которых используются в том случае, если для конкретного языка и региональных параметров никаких локализованных ресурсов обнаружить не удается. Чтобы задать язык и региональные параметры по умолчанию, в обозревателе решений Visual Studio щелкните правой кнопкой мыши имя проекта, выберите "Приложение", щелкните Сведения о сборке и в списке Нейтральный язык выберите соответствующий язык и региональные параметры.

Во время компиляции среда Visual Studio сначала преобразует RESX-файлы проекта в двоичные файлы ресурсов (RESOURCES) и сохраняет их в подкаталоге каталога obj проекта. Visual Studio внедряет любые файлы ресурсов, не содержащие локализованные ресурсы, в основную сборку, созданную проектом. Если в каких-либо файлах ресурсов есть локализованные ресурсы, Visual Studio внедряет их в отдельные вспомогательные сборки для каждого локализованного языка и региональных параметров. Затем Visual Studio сохраняет каждую вспомогательную сборку в каталоге, имя которого соответствует локализованному языку и региональным параметрам. Например, локализованные ресурсы английского языка (США) сохраняются во вспомогательной сборке в подкаталоге en-US.

Прежде чем посмотреть, как применять ресурсы для локализации приложений, сначала ознакомимся с методами создания и чтения ресурсов без принятия во внимание языковых аспектов.

Создание файлов ресурсов

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

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

Ниже показан пример создания простой таблицы строк:

Утилита Resgen.exe

Для создания из ***.txt файла ресурсов можно воспользоваться специальной утилитой генерации файлов ресурсов Resgen.exe. Например, ввод следующей команды:

приведет к созданию файла MyResources.resources. Сгенерированный этой утилитой файл ресурсов далее можно либо добавить в сборку как внешний файл, либо вставить в сборку DLL или ЕХЕ. Утилита Resgen также поддерживает возможность создания файлов ресурсов в формате XML с расширением .resX. Применяется она очень просто:

Выполнение этой команды приведет к созданию XML-файла ресурсов по имени MyResources.resX.

Утилита Resgen поддерживает строго типизированные ресурсы. Строго типизированный ресурс представляется в виде класса, который получает доступ к ресурсам. Для создания такого класса в утилите Resgen предусмотрена опция /str:

После опции /str должен быть указан язык, пространство имен, имя класса и имя файла исходного кода, причем именно в таком порядке.

Класс ResourceWriter

Вместо использования для создания файлов ресурсов утилиты Resgen можно написать специальную, позволяющую это делать программу. Класс ResourceWriter из пространства имен System.Resources служит для создания бинарных файлов ресурсов, а класс ResXResourceWriter — для создания файлов ресурсов на базе XML. Оба эти класса поддерживают возможность добавления изображений и любых других сериализуемых объектов. В случае применения класса ResXResourceWriter потребуется сослаться на сборку System.Windows.Forms.

В следующем примере кода демонстрируется создание объекта ResXResourceWriter по имени rw в файле Demo.resx. После создания экземпляра с помощью метода AddResource() класса ResXResourceWriter можно приступать к добавлению набора ресурсов общим объемом до 2 Гбайт. Первый аргумент в AddResource() позволяет указывать имя ресурса, а второй — значение. Ресурс изображения можно добавлять за счет применения экземпляра класса Image. Чтобы можно было использовать класс Image, необходимо сослаться на сборку System.Drawing, а также добавить директиву using для открытия пространства имен System.Drawing.

Здесь объект Image создается за счет открытия файла logo.jpg, поэтому потребуется либо скопировать этот файл изображения в каталог исполняемой программы, либо указать полный путь к нему в аргументе метода ImageToFile(). Оператор using указывает, что ресурс изображения должен автоматически уничтожаться в конце блока using.

Далее в объект ResXResourceWriter добавляются простые строковые ресурсы. В конце метод Close() класса ResXResourceWriter автоматически вызывает ResXResourceWriter.Generate() для осуществления записи ресурсов в файл Demo.resx:

Запуск этой небольшой программы приведет к созданию файла ресурсов Demo.resx с изображением logo.jpg внутри.

Использование файлов ресурсов

Добавьте в этот проект созданный ранее файл ресурсов Demo.resx, открыв в окне Solution Explorer контекстное меню и выбрав в нем пункт Add --> Add Existing Item (Добавить --> Добавить существующий элемент). По умолчанию для свойства Build Action (Действие при компоновке) этого ресурса будет установлено значение Embedded Resource (Встраиваемый ресурс), указывающее, что этот ресурс должен встраиваться в выходную сборку.

Далее в параметрах проекта (за счет выбора Application --> Assembly information (Приложение --> Информация о сборке)) следует установить в качестве значения параметра Neutral Language (Нейтральный язык) основной язык:

Установка нейтрального языка

Изменение значения этого параметра приведет к добавлению в файл assemblyinfо.cs атрибута [NeutralResourceLanguageAttribute], как показано ниже:

Установка значения для данного атрибута улучшит производительность ResourceManager, поскольку позволит ему быстрее отыскивать ресурсы для en-US, а также использовать их в качестве варианта по умолчанию. В этом атрибуте можно также указать место размещения используемого по умолчанию ресурса за счет применения второго параметра в конструкторе. С помощью перечисления UltimateResourceFallbackLocation можно указать, что он должен размещаться в главной сборке (значение MainAssembly) или же в подчиненной (значение Satellite).

После компоновки проекта можно просмотреть сгенерированную сборку утилитой ildasm и увидеть в манифесте атрибут, .mresource. Атрибут .mresource объявляет имя для ресурса в сборке. Если .mresource объявлен public (как в данном примере), это означает, что ресурс может экспортироваться из сборки и использоваться в классах других сборок. Если же .mresource объявлен private, это значит, что ресурс экспортироваться не может и доступен только в пределах данной сборки.

Для получения доступа к встроенному ресурсу используется класс ResourceManager, который находится в пространстве имен System.Resources. Конструктору этого класса в качестве аргумента можно передать имя сборки, в которой содержатся ресурсы.

В рассматриваемом примере ресурсы встроены в исполняемую сборку, поэтому во втором аргументе конструктору должен быть передан результат выполнения метода Assembly.GetExecutingAssembly(). В первом аргументе передается корневое имя ресурсов, состоящее из названия пространства имен и имени файла ресурсов, но без расширения resources. Как было показано ранее, это имя можно отобразить с помощью утилиты ildasm и просто удалить из него расширение resources. Имя можно также получить и программно с применением метода GetManifestResourceNames() класса System.Reflection.Assembly:

Для создания строго типизированного ресурса в редакторе управляемых ресурсов (Managed Resources Editor) можно изменить значение параметра Access Modifier (Модификатор доступа) с No Code Generation (Не генерировать никакой код) на Public (Общедоступный) или Internal (Внутренний). В случае установки значения Public генерируемый класс снабжается модификатором доступа public и тогда к нему возможен доступ из других сборок. При установке значения Internal генерируемый класс получает модификатор доступа internal и доступ к нему может осуществляться только изнутри сборки, в которой он находится.

Пространство имен System.Resources

Давайте кратко пройдемся по всем классам, которые содержатся в пространстве имен System.Resources и позволяют работать с ресурсами.

Класс ResourceManager

Может использоваться для получения ресурсов, относящихся к текущей культуре, из сборок или файлов ресурсов. С помощью ResourceManager можно получать сразу целый набор ресурсов для определенной культуры в виде экземпляра ResourceSet.

Класс ResourceSet

Позволяет представлять набор ресурсов для определенной культуры. При создании экземпляр ResourceSet он производит перечисление по классу, реализуя интерфейс IResourceReader, и сохраняет все ресурсы в HashTable.

Интерфейс IResourceReader

Используется в ResourceSet для перечисления ресурсов. Класс ResourceReader реализует этот интерфейс.

Класс ResourceWriter

Применяется для создания файла ресурсов и реализует интерфейс IResourceWriter.

Классы ResXResourceSet, ResXResourceReader и ResXResourceWriter

Похожи на классы ResourceSet, ResourceReader и ResourceWriter, но служат для создания не бинарного файла ресурсов, а не XML-файла .resx. Вместо того чтобы встраивать ресурс в XML-файл, они позволяют добавлять на него ссылку с помощью ResXFileRef.

Пространство имен System.Resources.Tools

Содержит класс StronglyTypedResourceBuilder, который можно использовать для создания класса из ресурса.

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

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

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

Кроме того, в ресурсы записываются данные, хранение которых в коде программы по каким-то причинам неудобно, например, битовые и векторные изображения.

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

Ресурсы хранятся в виде потоков (stream) или внешних файлов (которые в программе также выглядят как потоки).

Физически ресурсы могут находиться:

  • непосредственно в исполняемом файле (главном файле сборки);
  • в отдельном файле, принадлежащем той же сборке;
  • в другой сборке.

Именно в манифесте определяется, где конкретно находятся ресурсы. С точки зрения программиста ресурсы выглядят как некоторый поток данных, определение формата и интерпретация содержания которого возлагаются на программиста. Где бы ни находился ресурс (в той же сборке или отдельном файле), программный доступ к нему осуществляется одним-единственным способом – в виде потока (System.IO.Stream).

Работа с ресурсами в приложениях

Ресурсы в приложение могут быть добавлены несколькими способами:

Программное добавление ресурсов

Добавить ресурс программным способом можно при помощи API, предназначенного для создания сборок, например, System.Reflection.Emit. Разумеется, к этому моменту ресурсы уже должны быть созданы. Если в ресурсы необходимо поместить данные из уже существующего файла, то никаких проблем не возникнет. Если же ресурсы нужно разместить в формате .resources, придется использовать класс ResourceWriter, входящий в пространство имён System.Resources. К формату .resources мы ещё вернёмся и обсудим его более подробно, а пока поговорим о создании ресурсов при помощи ResourceWriter.

У класса ResourceWriter есть два конструктора:

После создания экземпляра класса ResourceWriter для добавления ресурса необходимо воспользоваться одним из методов:

Первым аргументом методов является имя добавляемого ресурса, а вторым – непосредственно добавляемый ресурс.

Имя ресурса не может быть NULL или пустой строкой, и не должно совпадать с именами других ресурсов.

В ресурсы могут добавляться системные типы (в состав которых входят примитивы (например, Int32), String, DateTime, TimeSpan, Decimal), а также произвольные объекты, поддерживающие сериализацию. Здесь следует уточнить, что объекты не добавляются , а сериализуются . Отсюда и вытекает одно из требований – для того, чтобы объект можно было разместить в ресурсах, он должен быть сериализуемым . Таким образом, «тело» ресурса представляет собой ни что иное, как сериализованное представление объекта того или иного типа.

Вот пример использования класса ResourceWriter:

Он создает в текущем каталоге файл с именем MyRes.resource, в который записывает строку с именем ресурса "str1" и структуру. Перед записью структуры производится боксинг. Использование оператора using позволяет не беспокоиться о вызове метода Close или Generate(), а также защититься от проблем, связанных с исключениями.

Добавление ресурсов из командной строки

Для включения ресурсов в исполняемый файл в компиляторе csc используется опция /resource (сокращённая форма /res), за которой через двоеточие должно быть указано имя файла ресурсов. Следующий пример создает приложение Form1.exe, включая в него в качестве ресурсов файлы Bitmap2.bmp и MyStrings.resources:

В этом случае оба ресурса будут размещены в разделе управляемых (managed) ресурсов исполняемого файла.

Если же возникает необходимость подключить внешний файл ресурсов, не включённый в исполняемый файл, можно воспользоваться опцией /linkresource (сокращённая форма /linkres).

Например, чтобы приложение могло использовать MyStrings.resources как внешний файл, необходимо выдать команду:

Чтобы добавить управляемые (managed) ресурсы в файл, создаваемый при помощи компилятора MC++, необходимо воспользоваться опцией компоновщика /ASSEMBLYRESOURCE.

Чтобы создать строковые ресурсы, необходимо создать файл со строками следующего формата:

Утилита resgen, получив файл такого формата в качестве входного, создаёт другой файл, по умолчанию имеющий расширение .resources. Этот файл может быть добавлен в состав проекта.

Для примера был создан файл MyStrings.txt следующего содержания:

После выполнения команды

будет создан файл MyStrings.resources, который можно включить в проект.

В некотором смысле простейшим видом ресурса является файл как таковой, как единица хранения, причём тип файла не играет роли. Это может быть файл с картинкой, с какой-то закодированной информацией, и так далее… Добавление содержимого файла в состав ресурсов является, возможно, самым простым способом добавления ресурсов в исполняемый файл. Посмотрим, как это можно сделать.

Допустим, на винчестере уже приготовлен файл с именем Bitmap1.bmp, содержащий какую-то картинку (имя файла и характер информации в нём могут быть любыми). Чтобы добавить содержимое файла Bitmap1.bmp в исполняемый файл в качестве ресурса, необходимо произвести следующие действия:

В окне Solution Explorer’а выделить элемент, соответствующий разрабатываемому проекту, после чего нажать правую кнопку мыши.

В появившемся popup-меню выбрать элемент «Add», после чего в очередном popup-меню выбрать элемент «Add existing item».

В появившемся диалоговом окне выбрать файл, который должен быть добавлен в проект.

После выполнения всех этих действий файл появится в дереве проекта (см. рисунок 1):

В контекстном меню добавленного файла выбрать пункт «Properties», что, в свою очередь, приведёт к появлению окна «Properties», в котором можно будет откорректировать свойства файла.

Выбрать в свойстве «Build Action» значение «Embedded Resource» (см. рисунок 2):

После этих действий и компиляции содержимое файла Bitmap1.bmp будет включёно в состав ресурсов. Таким способом в состав ресурсов может быть включено любое (в разумных пределах, естественно) количество любых файлов. Понятно, что при этом никаких действий с содержимым файла производиться не будет, в разделе управляемых ресурсов появится копия содержимого файла.

Как уже говорилось, в состав ресурсов могут быть добавлены и ресурсы формата .resources. Для этого необходимо произвести следующие действия:

  1. В окне Solution Explorer’а выделить элемент, соответствующий разрабатываемому проекту, после чего нажать правую кнопку мыши.
  2. В появившемся popup-меню выбрать элемент «Add», после чего в очередном popup-меню выбрать элемент «Add new item».
  3. В левой части появившегося диалогового окна выбрать категорию «Resources».
  4. В правой части диалогового окна выбрать «Assembly Resource File».
  5. В появившемся grid’е ( см. рисунок 3) ввести все необходимые данные.

В результате этих действий Visual Studio создаст XML-файл, часть содержимого которого можно увидеть на рисунке 4.

Этот файл будет иметь расширение .resx, и при компиляции проекта его содержимое будет преобразовано в .resources и включено в исполняемый файл.

Особо необходимо сказать о файлах .resx, содержащих данные о формах, созданных Form Designer’ом. В них, естественно, также хранятся ресурсы. Но! Эти файлы генерируются автоматически. Поэтому в общем случае при добавлении в эти файлы каких-то данных при перекомпиляции проекта эти данные будут утрачены.

Использование ресурсов в приложении

Понятно, что ресурсы создаются для того, чтобы впоследствии извлечь из них данные. Доступ к ресурсам осуществляется при помощи метода GetManifestResourceStream. Скажем, для того, чтобы обратиться к ранее добавленному в ресурсы Bitmap1.bmp, можно использовать следующую последовательность операторов:

Следующий код считывает из ресурсов содержимое ранее добавленного текстового файла 1.txt:

Очевидно, что формат .resource создавался с расчётом на то, что данные из него будут считываться поэлементно, причём поиск отдельного ресурса внутри этого формата будет производиться по имени этого ресурса. Для доступа к ресурсам можно использовать методы класса ResourceManager. Два его основных метода, GetString и GetObject, предназначены для загрузки из ресурсов строк и объектов соответственно. Ниже приведён пример использования объекта класса ResourceManager для загрузки строки, ранее добавленной в .resources:

а участок кода инициализации компонента (в нашем случае pictureBox1) добавляется в код загрузки объекта из ресурса:

После компиляции в ресурс приложения записываются нейтральные к культуре ресурсы, а для разных языков создаются отдельные DLL. Эти DLL помещаются в поддиректории, имена которых соответствуют суффиксам языков. Имена DLL формируются следующим образом: ИмяПриложения.resources.dll, например, Res1.resources.dll.

Если вы хотите, чтобы приложение под, скажем, английской версией Windows работало на русском языке, необходимо свойству Thread.CurrentThread.CurrentUICulture присвоить значение CultureInfo.CurrentCulture. Возможно, такие вещи лучше было бы делать настраиваемыми через пользовательский интерфейс.

Если стоит задача прочитать все содержимое ресурсного файла (или ресурса в формате .resources, находящегося в некоторой сборке), можно использовать класс System.Resources.ResourceReader, реализующий интерфейс IResourceReader. Конструкторы этого класса приведены ниже:

В качестве аргументов передаётся имя файла или указатель на поток, содержащий ресурсы. Поток можно получить способом, аналогичным тому, как это делалось выше для ресурса формата bmp, т.е. если в приложение Res1 добавлен ресурс MyRes.resource, чтобы получить поток, содержащий этот ресурс, можно воспользоваться кодом:

При создании объекта производится считывание всей служебной информации, но не более. Считывание непосредственно ресурсов конструктор не производит. Следующий код последовательно читает все ресурсы, находящиеся в файле MyRes.resource, и выводит на консоль имя, тип и содержимое каждого отдельного ресурса:

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

Физическая организация ресурсов в исполняемом файле.

Чтобы определить, где располагаются ресурсы, необходимо проанализировать таблицу ManifestResource 40), входящую в состав метаданных. Так как нам придётся в дальнейшем работать с ней, приведем список её полей:

Первым делом необходимо определить, где находятся те ресурсы, которые необходимо разобрать. Для этого нужно проанализировать поле Implementation. Если его значение не равно нулю, оно содержит закодированный номер строки в таблицах File или AssemblyRef.

ПРИМЕЧАНИЕ

Определить, с какой таблицей предстоит работать, можно, проанализировав два младших бита поля Implementation. Если их значение равно нулю, ссылка производится на таблицу Files. Если их значение равно единице, ссылка осуществляется на таблицу AssemblyRef. Сдвинув старшие биты поля Implementation вправо на два разряда, получим номер строки в таблице, на которую производится ссылка. Если же значение поля Implementation равно нулю, ресурсы находятся непосредственно в исследуемом файле. Взглянув на таблицу ManifestResource в файле mscorlib.dll, мы увидим следующую картину (см. рисунок 5).


Рисунок 5. Таблица ManifestResource файла mscorlib.dll.

Понятно, что ресурс, данные о котором расположены по смещению 0x17e44a, находится в теле самой библиотеки, а ресурсы со смещениями 0x17e458 и 0x17e466 находятся в других файлах (но не в других сборках). Естественно, эти файлы должны быть перечислены в таблице File. Взглянув на содержание этой таблицы (см. рисунок 6), мы увидим, что в ней присутствуют ссылки на файлы ресурсов.

Отметим, что в данном случае файлы ресурсов не являются сборками. Об этом свидетельствует флаг ContainsNoMetaData поля Flags, сигнализирующий о том, что файл не содержит метаданных.

Как легко догадаться, поле Name таблицы ManifestResource определяет название ресурса. Точнее, не название, а смещение в хипе имён, начиная с которого располагается имя ресурса. И, естественно, значением поля Offset является смещение ресурса относительно того места, начиная с которого ресурсы располагаются в исполняемом файле. Еще раз, в данном случае речь идёт не об RVA, а о смещении.

Итак, можно сказать, что к этому моменту названия ресурсов и «координаты», по которым они находятся, известны. Ничто не мешает построить список ресурсов (см. рисунок 7):


Рисунок 7. Начало списка управляемых ресурсов.

Но для того, чтобы проанализировать, что находится в ресурсах, списка ресурсов недостаточно. Посмотрим, что можно ещё можно «выкачать» из этого списка.

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

Следом за длиной ресурса располагаются непосредственно данные ресурса.


Рисунок 8. Формат хранения данных ресурсов.

Кроме того, скорее всего, в данном случае (рисунок 8) по смещению 0x236с8 находится картинка в формате .jpg, а, скажем, по смещению 0x281a0 – какие-то данные в формате XML, которые были добавлены в состав ресурсов как отдельные файлы. О содержании .resources пока можно только догадываться.

Формат .resources

Обычно ресурсы в формате .resources имеют имя, оканчивающееся на .resources. Но ничто не мешает программисту добавить ресурс любого другого формата, имя которого будет оканчиваться на .resources. Видимо, чтобы увеличить надежность системы, программисты Microsoft ввели так называемое «магическое число» (MagicNumber) 0xBEEFCACE, записываемое в самом начале ресурса. Таким образом, если значение начального двойного слова тела ресурса равно приведённой выше сигнатуре, можно с большой вероятностью сказать, что его формат – .resources.

Формат .resources начинается с заголовка. Его называют ResourceManagerHeader. Этот заголовок состоит из нескольких полей и начинается сигнатурой, о которой мы говорили выше. Непосредственно за «волшебным числом» следует номер версии заголовка, также занимающий четыре байта. Это поле называется HeaderVersionNumber.

Структура ResourceManagerHeader’а показана ниже (см. рисунок 9):


Рисунок 9. Структура ResourceManagerHeader’а.

Пусть читателя не удивляет тот факт, что на скриншоте не показаны типы переменных LenghtOfName и Name. Дело в том, что поле Size является «сжатым» представлением числа и может занимать один, два, три, четыре и так далее байтов. Какой в этом случае у него тип? Кроме того, Size фактически является составляющей Name’а. Какого типа в этом случае Name? Надеемся, что и без определения типов в данном случае информации больше, чем достаточно.

Непосредственно за заголовком менеджера ресурсов следует заголовок набора ресурсов. Из него мы можем «вытащить» побольше информации, чем из заголовка менеджера ресурсов. В частности, из первого двойного слова этого заголовка можно получить версию заголовка. Второе двойное слово (а это уже интересно!) хранит в себе число ресурсов, входящих в разбираемый файл .resources. А третье слово – это число типов, которым принадлежат ресурсы. Например, в хипе ресурсов все ресурсы могут принадлежать одному типу. Другой пример – все ресурсы могут быть разных типов. Пример третий – несколько ресурсов принадлежит одному типу, несколько – другому, и так далее…

За полем, хранящим число типов, следуют поля, в которых записаны названия типов. Каждому названию типов предшествует длина этого типа. Но, наверное, лучше всё это увидеть, чем пытаться представлять (см. рисунок 10).


Рисунок 10. Заголовок набора ресурсов и названия типов ресурсов.

А вот дальше начинается целая детективная история. За названиями типов следует массив хэшей имён ресурсов. В исходниках SSCLI (файл resourcereader.cs) в одном из комментариев написано, «Note that the name hashes array is aligned to 8 bytes so we can use pointers into it on 64 bit machines». Конечно, комментарии не являются технической документацией, однако код для перехода на эту границу восьми байтов, подтверждает написанное:

Декомпилированный Anakrino код практически совпадает с приведённым выше. Другими словами, в этом месте до границы 8 байтов файл должен дополняться некоторой последовательностью символов ‘P’, ‘A’ и ‘D’. Но этот код не может объяснить ту ситуацию, с которой мы столкнулись при анализе одного из файлов (см. рисунок 11).


Рисунок 11 «Выравнивающие» байты.

Видно, что за именем последнего типа ресурсов, заканчивающегося строго на границе восьми байтов, следуют ещё четыре байта, заполненные «выравнивающими» символами. Пришлось допустить, что код «устарел»… :-)

А дальше, как уже говорилось выше, располагается массив хэшей имён ресурсов. Каждый элемент этого массива занимает четыре байта. Ничего интересного с точки зрения анализа ресурсов этот массив не представляет, но из песни, как говорится, слова не выкинешь (см. рисунок 12):


Рисунок 12. Массив хэшей названий ресурсов.

Непосредственно за хэшами следуют названия ресурсов, точнее, даже не названия, а смещения названий. Смещения отсчитываются от начала массива заголовков ресурсов, который начинается сразу же за массивом названий. Опять, наверное, всю эту организацию гораздо легче представить, взглянув на рисунок 13.


Рисунок 13. Хэши названий, смещения заголовков ресурсов, заголовки ресурсов.

Помимо названий ресурсов в заголовках ресурсов хранятся смещения тел ресурсов относительно начала массива тел ресурсов (этот массив можно назвать «Resource Bodies»).

К этому моменту не получен ответ только на один вопрос – а как определить, к какому типу принадлежит тот или иной ресурс?

Для ответа на этот вопрос достаточно взглянуть на рисунок 14:


Рисунок 14. Структура «тел» ресурсов.

«Тело» ресурса начинается с его типа. В данном случае тип ресурса является индексом имени типа в массиве названий типов (см. рисунок 11). Непосредственно за индексом следует «тело» ресурса. В частности, ресурс, располагающийся по смещению 0х280f1, представляет собой строку «EditRateForm».

Вероятно, полезно будет взглянуть на 16-ричное представление ресурса (см. рисунок 16).

Рассматриваемый ресурс занимает место от 0x5b38c до 0x5b4f4 включительно (данные получены при разборе ресурсов). Видно, что в своём «теле» ресурс хранит данные о той библиотеке, в которой определён его тип, и непосредственно название типа. Логично предположить, что в данном случае перед нами – сериализованное представление объекта.

Заключение

Читайте также: