Управляемые (managed) ресурсы в исполняемых файлах .NET
Ресурсы – это некоторые данные, логически связанные с программой, но не являющиеся частью метаданных или кода этой программы.
Ресурсы могут находиться в одном исполняемом файле с программой, в другой сборке или же в другом файле, не являющемся сборкой. Состав ресурсов зависит от конкретных задач.
Главным отличием ресурсов от жестко закодированных данных (констант) является возможность изменения ресурсов без перекомпиляции программы. При этом в программе может содержаться несколько версий одного и того же ресурса, которые подгружаются и используются в зависимости от тех или иных условий.
Кроме того, в ресурсы записываются данные, хранение которых в коде программы по каким-то причинам неудобно, например, битовые и векторные изображения.
В манифесте сборки присутствуют ссылки на ресурсы. Манифестом сборки является часть метаданных, которая определяет основные характеристики данной сборки, а также её зависимости от других сборок и отдельных модулей и файлов.
Ресурсы хранятся в виде потоков (stream) или внешних файлов (которые в программе также выглядят как потоки).
Физически ресурсы могут находиться:
- непосредственно в исполняемом файле (главном файле сборки);
- в отдельном файле, принадлежащем той же сборке;
- в другой сборке.
Именно в манифесте определяется, где конкретно находятся ресурсы. С точки зрения программиста ресурсы выглядят как некоторый поток данных, определение формата и интерпретация содержания которого возлагаются на программиста. Где бы ни находился ресурс (в той же сборке или отдельном файле), программный доступ к нему осуществляется одним-единственным способом – в виде потока (System.IO.Stream).
.NET определяет специальный формат ресурсов, изначально предназначенный для хранения данных дизайнеров, таких, как дизайнеры форм библиотек Windows Forms и ASP.NET. Часто этот формат называется в документации «ресурсом». Это иногда приводит к путанице. Чтобы отличить сами ресурсы от конкретного формата, в этой статье данный формат будет называться .resources, так как файлы в этом формате имеют расширение «.resources».
Работа с ресурсами в приложениях
Ресурсы в приложение могут быть добавлены несколькими способами:
- программно;
- при помощи утилит командной строки;
- при помощи Visual Studio .NET.
Программное добавление ресурсов
Добавить ресурс программным способом можно при помощи API, предназначенного для создания сборок, например, System.Reflection.Emit. Разумеется, к этому моменту ресурсы уже должны быть созданы. Если в ресурсы необходимо поместить данные из уже существующего файла, то никаких проблем не возникнет. Если же ресурсы нужно разместить в формате .resources, придется использовать класс ResourceWriter, входящий в пространство имён System.Resources. К формату .resources мы ещё вернёмся и обсудим его более подробно, а пока поговорим о создании ресурсов при помощи ResourceWriter.
У класса ResourceWriter есть два конструктора:
После создания экземпляра класса ResourceWriter для добавления ресурса необходимо воспользоваться одним из методов:
Первым аргументом методов является имя добавляемого ресурса, а вторым – непосредственно добавляемый ресурс.
Имя ресурса не может быть NULL или пустой строкой, и не должно совпадать с именами других ресурсов.
В ресурсы могут добавляться системные типы (в состав которых входят примитивы (например, Int32), String, DateTime, TimeSpan, Decimal), а также произвольные объекты, поддерживающие сериализацию. Здесь следует уточнить, что объекты не добавляются , а сериализуются . Отсюда и вытекает одно из требований – для того, чтобы объект можно было разместить в ресурсах, он должен быть сериализуемым . Таким образом, «тело» ресурса представляет собой ни что иное, как сериализованное представление объекта того или иного типа.
Эти методы формируют данные в оперативной памяти. Запись в файл производится при помощи метода Generate(), не имеющего параметров. После завершения работы с файлом ресурсов ResourceWriter нужно «закрыть» при помощи методов Close или Dispose, которые вызывают метод Generate(). При работе на C# проще всего создавать экземпляр объекта ResourceWriter внутри оператора using.
Вот пример использования класса ResourceWriter:
Он создает в текущем каталоге файл с именем MyRes.resource, в который записывает строку с именем ресурса «str1» и структуру. Перед записью структуры производится боксинг. Использование оператора using позволяет не беспокоиться о вызове метода Close или Generate(), а также защититься от проблем, связанных с исключениями.
Добавление ресурсов из командной строки
Для включения ресурсов в исполняемый файл в компиляторе csc используется опция /resource (сокращённая форма /res), за которой через двоеточие должно быть указано имя файла ресурсов. Следующий пример создает приложение Form1.exe, включая в него в качестве ресурсов файлы Bitmap2.bmp и MyStrings.resources:
В этом случае оба ресурса будут размещены в разделе управляемых (managed) ресурсов исполняемого файла.
Если же возникает необходимость подключить внешний файл ресурсов, не включённый в исполняемый файл, можно воспользоваться опцией /linkresource (сокращённая форма /linkres).
Например, чтобы приложение могло использовать MyStrings.resources как внешний файл, необходимо выдать команду:
Чтобы добавить управляемые (managed) ресурсы в файл, создаваемый при помощи компилятора MC++, необходимо воспользоваться опцией компоновщика /ASSEMBLYRESOURCE.
Для создания файла .resources из командной строки можно использовать утилиту resgen, которой в качестве входных данных можно указать файл формата .resx или текстовый файл, содержащий набор именованных строк. XML-формат файла .resx довольно сложен. Разумеется, создать файл такого формата вручную можно, но это сложно и неудобно. Поэтому вручную имеет смысл создавать только строковые ресурсы, используя для этого специальный инструмент – программу ResGen, которая поставляется вместе с SDK.. В большинстве случаев .resx-файлы создаются VS.NET автоматически, и предназначены для сериализации данных дизайнеров. Использовать такие файлы без IDE типа VS.NET крайне сложно. Для создания .resources-файлов, содержащих только строки, resgen использует более простой формат входного файла.
Чтобы создать строковые ресурсы, необходимо создать файл со строками следующего формата:
Утилита resgen, получив файл такого формата в качестве входного, создаёт другой файл, по умолчанию имеющий расширение .resources. Этот файл может быть добавлен в состав проекта.
Для примера был создан файл MyStrings.txt следующего содержания:
После выполнения команды
будет создан файл MyStrings.resources, который можно включить в проект.
Единственное, что осталось сказать – если вам нужен набор символов, отличный от ASCII (набор символов 1252), входной файл должен быть сохранен в кодировке Unicode или UTF-8. Это легко сделать с помощью редактора VS.NET или notepad.exe из любой версии ОС семейства NT.
Добавление ресурсов в VS.NET
В некотором смысле простейшим видом ресурса является файл как таковой, как единица хранения, причём тип файла не играет роли. Это может быть файл с картинкой, с какой-то закодированной информацией, и так далее… Добавление содержимого файла в состав ресурсов является, возможно, самым простым способом добавления ресурсов в исполняемый файл. Посмотрим, как это можно сделать.
Допустим, на винчестере уже приготовлен файл с именем Bitmap1.bmp, содержащий какую-то картинку (имя файла и характер информации в нём могут быть любыми). Чтобы добавить содержимое файла Bitmap1.bmp в исполняемый файл в качестве ресурса, необходимо произвести следующие действия:
В окне Solution Explorer’а выделить элемент, соответствующий разрабатываемому проекту, после чего нажать правую кнопку мыши.
В появившемся popup-меню выбрать элемент «Add», после чего в очередном popup-меню выбрать элемент «Add existing item».
В появившемся диалоговом окне выбрать файл, который должен быть добавлен в проект.
После выполнения всех этих действий файл появится в дереве проекта (см. рисунок 1):
В контекстном меню добавленного файла выбрать пункт «Properties», что, в свою очередь, приведёт к появлению окна «Properties», в котором можно будет откорректировать свойства файла.
Выбрать в свойстве «Build Action» значение «Embedded Resource» (см. рисунок 2):
После этих действий и компиляции содержимое файла Bitmap1.bmp будет включёно в состав ресурсов. Таким способом в состав ресурсов может быть включено любое (в разумных пределах, естественно) количество любых файлов. Понятно, что при этом никаких действий с содержимым файла производиться не будет, в разделе управляемых ресурсов появится копия содержимого файла.
Как уже говорилось, в состав ресурсов могут быть добавлены и ресурсы формата .resources. Для этого необходимо произвести следующие действия:
- В окне Solution Explorer’а выделить элемент, соответствующий разрабатываемому проекту, после чего нажать правую кнопку мыши.
- В появившемся popup-меню выбрать элемент «Add», после чего в очередном popup-меню выбрать элемент «Add new item».
- В левой части появившегося диалогового окна выбрать категорию «Resources».
- В правой части диалогового окна выбрать «Assembly Resource File».
- В появившемся grid’е ( см. рисунок 3) ввести все необходимые данные.
В результате этих действий Visual Studio создаст XML-файл, часть содержимого которого можно увидеть на рисунке 4.
Этот файл будет иметь расширение .resx, и при компиляции проекта его содержимое будет преобразовано в .resources и включено в исполняемый файл.
К сожалению, при помощи grid’а (см. рисунок 3) Visual Studio .NET добавить в .resx данные не примитивных типов, например, картинку, практически невозможно, это может быть сделано только при помощи сериализации данных в ресурсы.
Особо необходимо сказать о файлах .resx, содержащих данные о формах, созданных Form Designer’ом. В них, естественно, также хранятся ресурсы. Но! Эти файлы генерируются автоматически. Поэтому в общем случае при добавлении в эти файлы каких-то данных при перекомпиляции проекта эти данные будут утрачены.
Использование ресурсов в приложении
Понятно, что ресурсы создаются для того, чтобы впоследствии извлечь из них данные. Доступ к ресурсам осуществляется при помощи метода GetManifestResourceStream. Скажем, для того, чтобы обратиться к ранее добавленному в ресурсы Bitmap1.bmp, можно использовать следующую последовательность операторов:
Res1 – это название пространства имён, используемого приложением по умолчанию. Его к имени ресурса добавляет Visual Studio.NET.
Следующий код считывает из ресурсов содержимое ранее добавленного текстового файла 1.txt:
В этом отрывке кода подразумевается, что файл добавлялся при помощи утилиты командной строки, а не при помощи VS.NET. Обратите внимание, что данные ресурсов представляются с помощью потока (System.IO.Stream), его необходимо читать с помощью одного из Reader’ов. В частности, для этой цели прекрасно подходит StreamReader. Но по умолчанию StreamReader будет работать в формате UTF-8, поэтому если в файле находится текст в кодировке 1251, необходимо указать её явно.
Очевидно, что формат .resource создавался с расчётом на то, что данные из него будут считываться поэлементно, причём поиск отдельного ресурса внутри этого формата будет производиться по имени этого ресурса. Для доступа к ресурсам можно использовать методы класса ResourceManager. Два его основных метода, GetString и GetObject, предназначены для загрузки из ресурсов строк и объектов соответственно. Ниже приведён пример использования объекта класса ResourceManager для загрузки строки, ранее добавленной в .resources:
Этот метод возвращает не просто набор байтов, а экземпляр класса System.String. Метод GetObject находит ресурс-элемент с указанным именем, создаёт новый экземпляр объекта и загружает в него (с помощью BinaryFormatter’а) состояние объекта. Таким образом, можно сказать, что методы ResourceManager загружают объекты из ресурсов по неким логическим именам. Как говорилось ранее, в первую очередь этот механизм был предназначен для загрузки состояния компонентов и их содержимого в дизайнерах, таких, как Windows Forms и ASP.NET. По умолчанию дизайнеры стараются сериализовать состояние компонентов в код, но бывают случаи, когда сериализация в код невозможна. Например, затруднительно (и неразумно) сериализовать в код содержимое картинок. К тому же для того, чтобы дизайнер мог полностью сериализовать в код все свойства компонента, разработчик компонента должен предпринять некоторые действия (реализовать TypeConverter, преобразующий экземпляр объекта в специальный класс-описание экземпляра – InstanceDescriptor). Если такового не имеется, и сериализуемый класс не является компонентом, дизайнер попытается сериализовать состояние экземпляра BinaryFormatter’ом. Если это удастся, он запишет это состояние в .resx-файл, а в код подставит вызов метода ResourceManager.GetObject. При этом ресурсу будет дано имя вида ИмяКомпонента.ИмяСвойства. Например, компонент pictureBox1 (типа System.Windows.Forms.PictureBox) имеет свойство Image типа System.Drawing.Image. Этот тип не поддерживает сериализацию в код, да ему это не особо и требуется, так как основное его содержимое – это картинка. Поэтому дизайнеры сериализуют объекты такого типа в ресурсы (.resx-файлы). После компиляции .resx-файлы превращаются в ресурсы формата .resources с именами, соответствующими именам форм вида ПространствоИменПринятоеПоУмолчанию.ИмяФормы.resources. При этом в самом начале метода InitializeComponent соответствующей формы добавляется код, инициализирующий экземпляр ResourceManager:
а участок кода инициализации компонента (в нашем случае pictureBox1) добавляется в код загрузки объекта из ресурса:
По умолчанию основное содержимое формы, в том числе и строки, сериализуются в код. Если стоит задача создания многоязычного (поддерживающего локализацию) приложения, в свойствах формы нужно переключить свойство Localizable (по умолчанию оно выставлено в false). После этого все свойства формы будут сериализоваться в ресурсы. Если после этого в свойстве Language переключить язык, то для выбранного языка будет создан свой .resx-файл (а впоследствии и свой экземпляр .resources). Эти экземпляры отличаются суффиксом имени. Например, для английского языка используется суффикс «.en», а для русского – «.ru». Свойства Localizable и Language добавляются к дизайнерам верхнего уровня (root designer-ам) специальным расширенным провайдером – LocalizationExtenderProvider.
После компиляции в ресурс приложения записываются нейтральные к культуре ресурсы, а для разных языков создаются отдельные DLL. Эти DLL помещаются в поддиректории, имена которых соответствуют суффиксам языков. Имена DLL формируются следующим образом: ИмяПриложения.resources.dll, например, Res1.resources.dll.
При этом выбор DLL осуществляется в зависимости от культуры установленной системы, подчёркиваю, установленной системы, а не указанного в региональных установках языка. Язык, указанный в региональных установках, отображается в .NET на свойство Thread.CurrentThread.CurrentCulture или (что то же самое) CultureInfo.CurrentCulture. За язык интерфейса отвечает свойство Thread.CurrentThread.CurrentUICulture.
Если вы хотите, чтобы приложение под, скажем, английской версией Windows работало на русском языке, необходимо свойству Thread.CurrentThread.CurrentUICulture присвоить значение CultureInfo.CurrentCulture. Возможно, такие вещи лучше было бы делать настраиваемыми через пользовательский интерфейс.
Если стоит задача прочитать все содержимое ресурсного файла (или ресурса в формате .resources, находящегося в некоторой сборке), можно использовать класс System.Resources.ResourceReader, реализующий интерфейс IResourceReader. Конструкторы этого класса приведены ниже:
В качестве аргументов передаётся имя файла или указатель на поток, содержащий ресурсы. Поток можно получить способом, аналогичным тому, как это делалось выше для ресурса формата bmp, т.е. если в приложение Res1 добавлен ресурс MyRes.resource, чтобы получить поток, содержащий этот ресурс, можно воспользоваться кодом:
При создании объекта производится считывание всей служебной информации, но не более. Считывание непосредственно ресурсов конструктор не производит. Следующий код последовательно читает все ресурсы, находящиеся в файле MyRes.resource, и выводит на консоль имя, тип и содержимое каждого отдельного ресурса:
На этом краткий рассказ об использовании ресурсов завершён. Ниже мы рассмотрим физическую организацию ресурсов в исполняемом файле, что, возможно, позволит лучше понять работу с ресурсами.
Физическая организация ресурсов в исполняемом файле.
Чтобы определить, где располагаются ресурсы, необходимо проанализировать таблицу ManifestResource 40), входящую в состав метаданных. Так как нам придётся в дальнейшем работать с ней, приведем список её полей:
- Offset
- Flags
- Name
- Implementation.
Первым делом необходимо определить, где находятся те ресурсы, которые необходимо разобрать. Для этого нужно проанализировать поле Implementation. Если его значение не равно нулю, оно содержит закодированный номер строки в таблицах File или AssemblyRef.
| ПРИМЕЧАНИЕ
Подробнее о таблицах см. статью «Физическая организация метаданных в исполняемых файлах .NET» в прошлом номере журнала. Определить, с какой таблицей предстоит работать, можно, проанализировав два младших бита поля 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 находится картинка в формате .png, а, скажем, по смещению 0x281a0 – какие-то данные в формате XML, которые были добавлены в состав ресурсов как отдельные файлы. О содержании .resources пока можно только догадываться. Формат .resourcesОбычно ресурсы в формате .resources имеют имя, оканчивающееся на .resources. Но ничто не мешает программисту добавить ресурс любого другого формата, имя которого будет оканчиваться на .resources. Видимо, чтобы увеличить надежность системы, программисты Microsoft ввели так называемое «магическое число» (MagicNumber) 0xBEEFCACE, записываемое в самом начале ресурса. Таким образом, если значение начального двойного слова тела ресурса равно приведённой выше сигнатуре, можно с большой вероятностью сказать, что его формат – .resources. Формат .resources начинается с заголовка. Его называют ResourceManagerHeader. Этот заголовок состоит из нескольких полей и начинается сигнатурой, о которой мы говорили выше. Непосредственно за «волшебным числом» следует номер версии заголовка, также занимающий четыре байта. Это поле называется HeaderVersionNumber. Четвёртое поле – это имя «считывателя» ресурсов, который должен быть использован системой для чтения ресурсов. Если ресурсы записываются стандартными средствами, система использует «System.Resources.ResourceReader, mscorlib». Поле пятое – название «набора» ресурсов, стандартное название этого поля – System.Resources.RuntimeResource.Set, mscorlib. Третье поле заголовка – это суммарная длина обоих имён. Структура 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 включительно (данные получены при разборе ресурсов). Видно, что в своём «теле» ресурс хранит данные о той библиотеке, в которой определён его тип, и непосредственно название типа. Логично предположить, что в данном случае перед нами – сериализованное представление объекта. ЗаключениеАвторы статьи надеются, что материал, изложенный в статье, поможет начинающим программистам понять основы работы с ресурсами в .NET. Опытные программисты смогут углубить свои знания и работать с ресурсами, используя знания об их внутренней структуре. Как правильно работать с файлом ресурсов пользовательского контрола?В данный момент занимаюсь разработкой под winforms, пишу кастомный контрол. Возник вопрос, где хранить картинки, которые этот контрол использует. Я решил, что буду их хранить в файле ресурсов (.resx) не всего приложения, а только моего контрола. Вопрос, как вы вытаскиваете картинки из ресурсов контрола/формы? Если хранить картинки в файле ресурсов проекта, то тут нет вопросов: Однако, если я использую файл ресурсов контрола/формы, то приходится делать так: Проблема заключается в том, что приходится ручками прописывать название файла картинки. Возможно ли работать с файлом ресурсов контрола/формы, так же, как с файлом ресурсов проекта, то есть без строк? Мое временное решение заключается в том, что я создал статическое ридонли поле, которое хранит название файла картинки: Ресурсы локализованных приложений
Ресурсы, подобные изображениям и строковым таблицам, могут помещаться в файлы ресурсов или подчиненные сборки. Эти ресурсы могут быть очень полезны при локализации приложений, и в .NET предлагается встроенная поддержка для поиска локализованных ресурсов. Прежде чем посмотреть, как применять ресурсы для локализации приложений, сначала ознакомимся с методами создания и чтения ресурсов без принятия во внимание языковых аспектов. Создание файлов ресурсовВ файлах ресурсов могут храниться элементы, подобные изображениям и таблицам строк. В качестве ресурсного файла может быть обычный текстовый файл или файл с расширением .resX, в котором используется XML. В этой статье сначала рассматривается вариант простого текстового файла. Ресурс, включающий в себя таблицу строк, может создаваться в обычном текстовом файле. В этом файле просто производится назначение строк ключам. Под ключом понимается имя, которое может использоваться в программе для получения соответствующего значения. В ключах и значениях допускается использовать пробелы. Ниже показан пример создания простой таблицы строк: Утилита Resgen.exeДля создания из ***.txt файла ресурсов можно воспользоваться специальной утилитой генерации файлов ресурсов Resgen.exe. Например, ввод следующей команды: приведет к созданию файла MyResources.resources. Сгенерированный этой утилитой файл ресурсов далее можно либо добавить в сборку как внешний файл, либо вставить в сборку DLL или ЕХЕ. Утилита Resgen также поддерживает возможность создания файлов ресурсов в формате XML с расширением .resX. Применяется она очень просто: Выполнение этой команды приведет к созданию XML-файла ресурсов по имени MyResources.resX. Утилита Resgen поддерживает строго типизированные ресурсы. Строго типизированный ресурс представляется в виде класса, который получает доступ к ресурсам. Для создания такого класса в утилите Resgen предусмотрена опция /str: После опции /str должен быть указан язык, пространство имен, имя класса и имя файла исходного кода, причем именно в таком порядке. Возможность добавления изображений утилита Resgen не поддерживает. Среди примеров .NET Framework SDK есть и пример ResXGen с обучающими руководствами. В ResXGen можно ссылаться на изображения в файле .resX. Добавлять изображения можно также программно с применением классов ResourceWriter или ResXResourceWriter, как будет показано далее. Класс 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.gif, поэтому потребуется либо скопировать этот файл изображения в каталог исполняемой программы, либо указать полный путь к нему в аргументе метода ImageToFile(). Оператор using указывает, что ресурс изображения должен автоматически уничтожаться в конце блока using. Далее в объект ResXResourceWriter добавляются простые строковые ресурсы. В конце метод Close() класса ResXResourceWriter автоматически вызывает ResXResourceWriter.Generate() для осуществления записи ресурсов в файл Demo.resx: Запуск этой небольшой программы приведет к созданию файла ресурсов Demo.resx с изображением logo.gif внутри. Использование файлов ресурсовДобавлять файлы ресурсов в сборки можно либо с помощью работающего из командной строки C#-компилятора csc.exe с опцией /resource, либо прямо в Visual Studio 2010. Чтобы посмотреть, как работать с файлами ресурсов в Visual Studio 2010, создайте проект консольного приложения по имени ResourceDemo. Добавьте в этот проект созданный ранее файл ресурсов 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, который можно использовать для создания класса из ресурса. |

