Создание и использование только ресурсов в DLL. Консольный редактор ресурсов: редактирование ресурсов в EXE DLL файлах

Электроника 23.05.2019
Электроника

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

Ресурсы могут быть стандартные и определенные пользователем. Данные в стандартном ресурсе описывают иконку, курсор, меню, диалоговое окно, точечный рисунок, расширенный метафайл, шрифт, таблицу горячих клавиш, строки и версию. Определенный пользователем ресурс может содержать любые данные, требуемые приложением (другой.EXE, GIF, MP3 и т.д.).

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

Создание DLL с ресурсами

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

Затем выполнить следующие шаги:

  1. Создайте RC файл, описывающий ресурсы, которые Вы хотите поместить в DLL . Как в примере: (adpdllresources - имя RC файла ASCII ) - один ICON и один GIF добавлен в RC файл:
  2. adpdllresources.rc aboutlogo RCDATA aboutlogo.gif factory ICON FACTORY.ICO
  3. Скомпилируйте RC файл в RES файл при помощи компилятора ресурсов BRCC32
  4. Создайте проект пустой DLL . Сохраните его как adpResources.dpr - после компиляции DLL будет иметь имя adpResources.dll . Полный код проекта DLL будет иметь всего четыре строки в одном файле.
  5. library adpResources; {$R adpdllresources.RES} begin end.
  6. Откомпилируйте Ваш DLL (убедитесь, что adpdllresources.res находится в том же каталоге, что и проект DLL

Как только DLL с ресурсами будет создан, Вы можете использовать его внутри Ваших приложений Delphi. Обратите внимание, что эти ресурсы внутри DLL может использовать любое приложение (не обязательно Delphi).

Как использовать ресурсы из DLL

Чтобы использовать ресурсы из динамической библиотеки, просто загрузите DLL и ресурсы, которые Вы хотите использовать.

Следуйте этим шагам:

  1. Создайте новый проект Delphi. По умолчанию, Delphi добавляет одну форму к проекту. Сохраните проект
  2. Скопируйте DLL с ресурсами (adpResources.dll в папку, где Ваше новое приложение было сохранено
  3. Загрузите ресурс, как показано ниже...

Пример, как загрузить иконку factory и нарисовать ее на холсте Form1 , когда Button1: TButton была нажата).

Procedure TForm1.Button1Click(Sender: TObject); const resICON = "factory"; var h: THandle; Icon: HIcon; begin h:= LoadLibrary("adpResources.DLL"); try if h 0 then begin Icon:= LoadIcon(h, resICON); DrawIcon(Canvas.Handle, 10, 10, Icon); end else begin ShowMessage("Load Resource DLL FAILED!"); end; finally FreeLibrary(h); end; end;

Если Вы добавите поддержку GIF , Вы можете использовать изображение GIF , хранимое в ресурсном DLL , а также его рисовать:

Procedure TForm1.Button2Click(Sender: TObject); const resGIF = "aboutlogo"; var h: THandle; gif: TGifImage; r:TRect; begin h:= LoadLibrary("adpResources.DLL"); try if h 0 then begin gif:= TGIFImage.Create; try gif.LoadFromResourceName(h,resGIF); gif.Paint(Canvas, Form1.ClientRect, ); finally gif.Free; end; end else begin ShowMessage("Load Resource DLL FAILED!"); end; finally FreeLibrary(h); end; end;

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


AutoCAD давно использует формат CUIX в качестве частичных файлов меню, представляющий собой обычный архив, расширение которого переименовано с ZIP на CUIX. Однако в нашей компании используется AutoCAD 2009 SP3, который не умеет работать с CUIX, но использует более старый формат - CUI файлы, содержимое которых представлено в формате XML.

О версиях AutoCAD и MS Visual Studio (небольшое отступление от общей темы)

Для того, чтобы написать управляемый плагин для AutoCAD, можно использовать любую версию MS Visual Studio, в которой имеется возможность компилировать управляемый код под ту версию платформы.NET (2.0, 3.0, 3.5, 4.0, 4.5), которая используется в интересующей нас версии AutoCAD. Целевая версия.NET Framework указана в конфигурационном файле acad.exe.config и при желании мы можем переназначить её.

Для написания неуправляемых ARX плагинов для AutoCAD 2009 так же можно использовать любую версию MS Visual Studio, но лишь при условии, что на компьютере имеется та версия инструментов построения , которая необходима для вашей версии AutoCAD и эти инструменты доступны для использования в вашей IDE. Например, для того, чтобы написать ARX плагин для AutoCAD 2009, требуется та версия инструментов, которая присутствует в MS Visual Studio 2005. По факту можно использовать и более новые версии указанной IDE, при условии, что в настройка проекта будет указана необходимая версия инструментов, которая должна использоваться при сборке проекта:


Для того, чтобы в раскрывающемся списке свойства Platform Toolset присутствовал набор элементов, позволяющий выбрать нужную версию инструментов, эти инструменты должны быть предварительно установлены. Т.е. на компьютере должны быть установлены все те версии MS Visual Studio, чьи инструменты вы хотите использовать для построения. При этом получается, что фактически вы работаете в более новой IDE, используя её преимущества и удобства, а за кулисами код будет компилироваться инструментами той версии, которую вы укажете в настройках своего проекта.

Устанавливать версии MS Visual Studio следует в последовательном порядке, начиная от более старой версии и заканчивая более новой , дабы избежать различного рода конфликтов. Установив очередную версию IDE, не забудьте установить и пакеты её обновлений.

По умолчанию MS Visual Studio 2012 "видит" и отображает в раскрывающемся списке Platform Toolset только инструменты от VS 2012, 2010 и 2008. Для того, чтобы в списке отображались и более старые версии IDE, необходимо выполнить любое (т.е. одно из ) из перечисленных ниже действий:

  • Установить MSI пакет , любезно предоставленный Owen Wengard.
  • Выполнить конфигурацию вручную, как это указано .
Однако, когда речь заходит о создании DLL файл ресурсов, то тут всё гораздо проще: достаточно скомпилировать файл один раз (используя любую версию инструментов), с конфигурацией Release Win32, и затем использовать его на платформах x86\x64 (я компилировал DLL x86 и при этом в Windows 7 x64 мой CUI файл успешно извлекал из него изображения).
Создание DLL файла ресурсов

В MS Visual Studio 2012 создаём новый проект, на основе шаблона Empty Project и назначаем ему нужное имя:


Первым делом сразу устанавливаем конфигурацию в Release Win32:

Теперь в окне Solution Explorer нажимаем правой кнопкой мыши на имени проекта и в появившемся контекстном меню выбираем пункт Properties . Назначаем нужные значения свойствам Target Extension и Configuration Type :



Несмотря на то, что DLL файл собирается для CUI, который будет использоваться в AutoCAD 2009 , в свойстве Platform Toolset указана версия v110 , соответствующая Visual Studio 2012, а не v80 (инструменты Visual Studio 2005), которая была бы необходима для компиляции ARX плагинов под эту версию AutoCAD.


Кроме этого, в настройках проекта нужно изменить ещё одну опцию - No Entry Point :


Произведя обозначенные выше изменения, жмём кнопку Применить и ОК . Теперь свойства нашего, пока ещё пустого проекта настроены должным образом. На вкладке Solution Explorer , из контекстного меню проекта, выбираем пункт Add -> New Item ... и в появившемся диалоговом окне выбираем шаблон файла ресурсов:



Имя файлу можно назначить любое, в нашем примере оставляем Resource.rc . Жмём кнопку Add . Добавленный нами файл будет содержать в себе перечень изображений, которые будут использоваться в наших CUI файлах.

Теперь в каталоге нашего проекта вручную создадим подкаталог, в который скопируем все файлы изображений, которые желаем добавить в DLL файл. Имя подкаталога может быть произвольным, назовём его images . На вкладке Solution Explorer из контекстного меню проекта выбираем пункт Open Folder in File Explorer :



В открывшемся окне Проводника создаём подкаталог images и копируем в него все нужные нам BMP файлы.

В качестве эксперимента добавим BMP файлы разных размеров: 16x16, 24x24, 32x32, 48x48, 64x64 и 128x128. Кнопки на палитрах инструментов должны содержать изображения размером 16x16, а кнопки, размещённые на палитрах Ribbon, имеют размеры поболее. В нашем тестовом CUI файле разместим кнопки с указанными выше разрешениями и там и там, дабы посмотреть результат использования различных размеров.

Можно было бы копировать и файлы в формате PNG, однако AutoCAD 2009 не умеет в CUI файлах использовать изображения такого формата. Теперь возвращаемся к нашей IDE: переключаемся на вкладку Resource View .



Из контекстного меню элемента Resource.rc вызываем пункт Add Resource ...


В диалоговом окне Add Resource в списке Resource type выбираем элемент Bitmap , жмём кнопку Import ... и указываем BMP файлы из созданного нами ранее подкаталога images . Получаем такой результат:


Теперь для каждого элемента значение свойства ID нужно обособить двойными кавычками, чтобы их можно было успешно использовать в наших CUI файлах:


Всё, теперь компилируем наш DLL файл:


Смотрим отчёт о результатах компиляции:


А вот и наш долгожданный DLL:

Полученный DLL файл копируем в тот же каталог, где хранится наш CUI файл и назначаем им одинаковые имена: test.cui и test.dll . Теперь в CUI файле в качестве источника BMP иконок можно использовать наш DLL файл, назначая командам нашего CUI файла (см. свойства Small Image и Large Image ) то значение свойства ID , которое ранее обособляли двойными кавычками:


Значки, указанные в свойстве Small Image будут использоваться на палитрах Toolbar , а те, что указаны для Large Image - используются в Ribbon .

Обратите внимание, что на нашей панели Some toolbar отобразились все BMP изображения кроме последней - той, размеры которой были 128x128 .

Появление в палитре компонентов Borland C++ Builder компонента TCppWebBrowser и компонента TWebBrowser в палитре компонентов Borland Delphi открыло для программистов этих двух сред возможности использования функциональности Internet Explorer"а в прикладных программах. В совокупности со средствами HTML-редакторов, TCppWebBrowser (TWebBrowser) позволяет с легкостью создавать автономные продукты, базируемые на технологии Web-дизайна, отличающиеся не только быстротой и качественностью написания, но и современным интерфейсом. Однако, самым неприятным фактом в использовании данного компонента при написании локального программного продукта, каковым могут быть, например, мультимедийная энциклопедия либо интерактивный учебник, является необходимость наличия отображаемых HTML-страниц, являющихся частью самой программы. Явное их присутствие в открытом виде на дистрибутивах или в уже установленном состоянии на жестких дисках вызывает непреодолимое желание любознательного пользователя заняться их самостоятельным редактированием и некоторое чувство незаконченности процесса создания продукта у самого программиста. Самыми простым и легким способом решения подобной проблемы является помещение служебной информации программы (Web-страниц) в ресурсы DLL.

Широко известен тот факт, что интерфейс Windows насквозь пропитан окнами отображения, аналогичными по функциональности окну, находящемуся в клиентской области Internet Explorer. Тем не менее, в открытом виде это явление практически незаметно глазу обычного пользователя. И уж совсем немногие знают, что некоторые элементы панели управления Windows напрямую выполнены в виде Web-страниц. В частности, элемент панели управления под названием "Установка и удаление программ" исполняется именно в таком виде, начиная с версий Windows ME и Windows 2000. Возникает вполне закономерный вопрос: где же Windows хранит содержимое этих страниц?

Скрупулезное исследование системного каталога даёт ответ на данный вопрос. Web-страницы, используемые интерфейсом Windows, хранятся в виде обычных ресурсов библиотек DLL. К примеру, попытка установления связи в Internet Explorer с недоступным адресом приводит к отображению в окне IE страницы, содержащей соответствующую информацию. Данная страница представлена ресурсом DNSERROR.HTM файла shdoclc.dll, убедиться в чем можно при помощи любого редактора ресурсов (Resource Workshop, Resource Scrutator и т.п.). Таким образом, становится очевидным, что Internet Explorer (а, следовательно, TCppWebBrowser и TWebBrowser аналогично) имеет возможность использования ресурсов, содержащихся в библиотеках DLL.

В прилагаемых к статье архивах содержится весь описанный в ней инструментарий и пример готовой DataBank.dll.

Архив Initial.zip содержит промежуточный объектный файл DataBank.obj будущей библиотеки, файл сценария ресурсов DataBank.rc и компилируемый из него двоичный файл ресурсов DataBank.res. Кроме того, в данный архив включены файлы, непосредственно помещаемые в качестве ресурсов в библиотеку: Header.gif, Return.gif, Back.jpg, Index.htm, Mainpage.htm, Page.htm и Top.htm.

Архив Tools.zip содержит весь инструментарий, необходимый для компиляции и линковки библиотеки DLL. В него входят компилятор ресурсов BRCC32.EXE, линкер ILINK32.EXE, необходимые для их корректной работы файлы UUID.LIB, LNKDFM60.DLL, RLINK32.DLL, RW32CORE.DLL, а также пакетный файл BuildAll.bat, содержащий командные строки для работы с компилятором ресурсов и линкером. Данный инструментарий в обязательно должен входить в поставки Borland C++ Builder и Borland Delphi любых версий, но, возможно, с именами, отличными от имен прилагаемых в архивах файлов.

Архив Results.zip содержит конечный результат работы - библиотеку ресурсов DataBank.dll. Кроме этого, в данный архив включен файл URL.txt с примером адреса головной страницы ресурса данной библиотеки для Internet Explorer , с учетом того, что DataBank.dll предположительно расположен в директории C:\IEnDLL.

Архив DllSymChanger.zip содержит созданную мной программу DllSymChanger.exe, облегчающую процесс замены символов, необходимый для функционирования библиотеки DLL.

Содержимое архивов автономно от наличия или отсутствия на компьютере пользователя установленных версий BCB и Delphi, его работа проверена на машине с абсолютно "свежей" Windows 98 SE.

Вспомнив классическое (не-RAD) программирование с присущим ему "ручным" созданием файла сценария ресурсов, приходим к выводу, что это - как раз то, что нам нужно. Иными словами, нас интересует возможность создания некоторой DLL, в составе которой находились бы ресурсы с требуемыми нам, в конечном счете, HTM-страницами.

В процессе создания исполняемого файла для среды Windows, файлы с исходными текстами приложения компилируются в объектные модули OBJ. Затем редактор связей собирает из объектных модулей промежуточный вариант загрузочного модуля, не содержащий ресурсов. При этом используется файл определения модуля DEF. Текстовый файл сценария ресурсов RC и файлы, непосредственно содержащие требуемые ресурсы, компилируются в двоичный файл RES. На последней стадии формирования загрузочного модуля, его промежуточный вариант собирается с файлом ресурсов для получения окончательного исполняемого файла. Следовательно, для создания DLL ресурсов нам потребуется пройти все эти стадии. Но, чтобы облегчить задачу и всегда пропускать (по нескольким причинам) в будущем первый этап, я самостоятельно создал требуемый объектный файл будущей библиотеки. Сделано это было следующим образом: в BCB6 создаем новый проект DLL, отключаем в мастере создания возможность использования VCL, отключаем в опциях проекта использование пакеджей времени выполнения и использование RTL, и собираем (строим) проект. Из всего, что было создано BCB в каталоге проекта, нас интересует единственный файл. В прилагаемом архиве Initial.zip этот файл носит название DataBank.obj, который, собственно, и являлся бы результатом выполнения первых двух этапов действий, необходимых для создания требуемой DLL. (В случае, если у читателя возникнет желание самостоятельно проверить целесообразность предложенного мной подхода, он может самостоятельно написать текст программы, текст заголовочного файла программы, текст DEF-файла, скопировать в каталог примера H- и LIB-файлы, необходимые для компиляции, и, непосредственно, компилятор кода BCC32.EXE). Название OBJ-файла не имеет никакого значения, так как в дальнейшем из этого файла будет образован конечный файл библиотеки ресурсов с тем именем, которое пользователь может назначить либо при сборке, либо просто переименовать созданный им файл DLL (тем более, что в теле этой библиотеки ее имя нигде не присутствует).

На следующем этапе нам необходимо получить компилированный файл ресурсов, которые будут включены в нашу DLL. Для этого необходимы непосредственно сами ресурсы (HTM-страницы) и файл сценария ресурсов (в нашем случае он носит имя DataBank.rc).

В прилагаемом архиве Initial.zip в виде примера рассмотрено создание DLL с размещенными внутри связанными Web-страницами. Файлы, представляющие эти страницы, носят следующие имена: Index.htm, Mainpage.htm, Page.htm, Top.htm, Header.gif, Return.gif, Back.jpg. Далее следуют три необходимых требования, предъявляемых для правильного функционирования IE внутри создаваемой DLL:

Все файлы, участвующие в работе Web-страниц должны находиться в одной директории; Все ссылки в Web-страницах должны быть относительными; Имя ни одного из файлов, используемых в Web-страницах, не должно содержать символов подчеркивания. Имена файлов должны состоять из набора символов A…Z и 0…9. Имена файлов не должны начинаться с цифр. При несоблюдении первого требования, пользователь просто не сможет разместить файлы внутри DLL в соответствии с их начальным положением. Следовательно, в ссылках HTM-документов изначально будут указаны несуществующие объекты.

При несоблюдении второго требования (при использовании абсолютных ссылок), IE будет пытаться отыскать указанные объекты с использованием абсолютного адреса (в данном случае - пути расположения файла), что в корне неверно. Впрочем, использование относительных ссылок само по себе является нормальной практикой Web-программирования.

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

Итак, после размещения Web-объектов в директории компиляции, мы должны создать файл сценария ресурсов, носящий расширение RC. В примере этот файл имеет название DataBank.rc. Он создается в любом текстовом редакторе и сохраняется в виде обычного текста. Каждая из строк данного файла состоит из имени ресурса, типа ресурса и файла, представляющего данный ресурс. Поскольку в ссылках используемых нами Web-страниц мы указываем имена файлов, содержащих Web-объекты, необходимо каждому ресурсу присвоить имя, соответствующее имени Web-объекта. Вот здесь и возникает основная сложность. Дело в том, что если именам ресурсов мы присвоим имена файлов ресурсов (то есть в имени ресурса поставим в соответствующем месте точку), то при компиляции RES-файла будет получено сообщение об ошибке:

Expecting resource name or resource type name

Для решения данной проблемы, вместо точки, разделяющей "имя" ресурса и его "расширение", мы используем символ подчеркивания - единственный символ, не являющийся цифрой или латинской буквой и разрешенный при создании имени ресурса. Как нетрудно догадаться, конечный файл DLL будет содержать ресурсы с именами, разделенными символом подчеркивания, который, для работоспособности DLL придется заменять точкой. Но об этом - позже.

Вторым элементом строки описания ресурса является имя типа ресурса. Если мы будем применять для назначения нашим ресурсам предопределенный произвольный тип RCDATA, компиляция и сборка DLL пройдут, ресурсы будет корректно вызываться из библиотеки любыми прикладными программами как ресурсы, но IE откажется обрабатывать их таким образом, как нам необходимо. Точно то же самое произойдет, если мы присвоим ресурсам страниц любой другой произвольный тип. То есть, у меня существовала определенная надежда, что IE будет рассматривать типы сгруппированных ресурсов как своеобразные поддиректории, что было бы совсем неплохо для систематизации иерархии взаимосвязей между логическими группами страниц, но разработчики IE, видимо, придерживаются иного мнения на этот счет. Таким образом, если мы присвоим типу ресурса имя, например, PAGEAREA или какое-либо другое, включая и RCDATA, после завершающей обработки библиотеки IE все равно откажется воспринимать Web-страницы в виде Web-страниц. Решение в этой тупиковой ситуации подсказывает изучение структуры системой библиотеки shdoclc.dll. Все страницы, расположенные в его ресурсах, принадлежат двум типам, имена которых являются не строковыми, а целочисленными идентификаторами. Для задания целочисленных типов ресурсов, в строке файла сценария ресурсов необходимо целочисленные значения идентификаторов заключить в круглые скобки. В противном случае, компилятор ресурсов воспримет их как строковые идентификаторы.

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

В конечном виде строка, описывающая присоединение страницы index.htm к ресурсам будущей библиотеки, выглядит так:

index_htm (23) index.htm

После того, как создан файл сценария ресурсов, необходимо провести его компиляцию. Для этого воспользуемся компилятором ресурсов командной строки BRCC32.EXE, входящим в состав BCB6. Взглянув на название файла компилятора, видим, что результатом его работы станет 32-разрядный двоичный файл ресурсов (по крайней мере, если не использовать дополнительных опций командной строки). Теоретически, можно воспользоваться различными 16-разрядными компиляторами (RC.EXE, BRCC.EXE и т.п.) и мы достигнем намеченного результата, но, так как далее нами будет использоваться 32-разрядный линкер, рекомендую пользоваться именно 32-разрядным компилятором ресурсов. Запуск компилятора ресурсов производится из командной строки, которая включает в себя непосредственно имя файла компилятора ресурсов и имя файла сценария ресурсов:

brcc32 DataBank.rc

При работе BRCC32.EXE вызывает библиотеку RW32CORE.DLL. В процессе компиляции на экране появится строка с названием компилятора и строка с указанием принадлежности авторских прав на него. Если компиляция пройдет удачно, никаких дополнительных сообщений не появится, и в директории проекта появится двоичный файл ресурсов DataBank.res. Если в процессе компиляции произойдет ошибка, появится соответствующее сообщение, и двоичный файл ресурсов создан не будет.

Следующим шагом в создании DLL является сборка библиотеки из объектного файла DataBank.obj и двоичного файла ресурсов DataBank.res. Для этой цели воспользуемся линкером ILINK32.EXE, вызывающий в свою очередь библиотеки RLINK32.DLL и LNKDFM60.DLL и использующий файл UUID.LIB. Командная строка вызова линкера с параметрами выглядит следующим образом:

ilink32 DataBank.obj, DataBank.dll, DataBank.res

Будьте внимательны и не ошибитесь с количеством запятых при наборе командной строки. Мы видим, что в командной строке указано имя объектного файла, имя двоичного файла ресурсов и конечное имя библиотеки. Если в качестве создаваемого модуля указать не DataBank.dll, а, например, MMedia.dll, то именно MMedia.dll и будет создан. В процессе сборки выводится строка с названием линкера и строка с указанием принадлежности авторских прав на него. Кроме того, при компиляции именно с этим комплектом служебных файлов, будет выводиться следующее предупреждение:

Warning: Image linked as an executable, but with a .DLL or .BPL extension

Это связано с отсутствием DEF-файла, но, тем не менее, на корректность содержимого библиотеки не влияет. В процессе сборки линкером будут созданы файлы с именем результирующего модуля и расширениями TDS, MAP, ILC, ILD, ILF, ILS которые можно безболезненно удалить.

Библиотека с ресурсами Web-элементов создана.

Если бы мы воспользовались 16-разрядным линкером (например, LINK.EXE), то в результате мы получили бы корректную 16-разрядную DLL, но IE отказался бы работать с ней, выдав соответствующее сообщение.

Предположим, что DataBank.dll имеет следующий путь:

C:\IEnDLL\DataBank.dll

Если мы запустим IE и в строке URL введем

res://C:\IEnDLL\DataBank.dll/INDEX_HTM

Internet Explorer найдет ресурс INDEX_HTM расположенный в DataBank.dll, но не сможет правильно определить ожидаемых от него действий, поскольку имя идентификатора ресурса воспринимается IE как имя файла без расширения.

На этом этапе работа по созданию DLL принимает "шаманский" оборот. Мы сталкиваемся с ситуацией, когда Internet Explorer отказывается корректно воспринимать ресурс без "расширения", а компилятор ресурсов отказывается включать в идентификатор ресурса символ точки. Теперь необходимо вооружиться каким-либо Bin-редактором и "руками" аккуратно заменить символы подчеркивания в заголовке библиотеки точками, не изменяя размера файла. Для этого подходит любой Bin-редактор (UltraEdit, WinHex и т.п.), но я лично воспользовался FAR "по F4". Искать строку "INDEX_HTM" в файле бесполезно, так как еще на этапе компиляции двоичного файла ресурсов из-за его 32-разрядной природы между каждой парой символов идентификатора были помещены символы со значением 0x00. Заменив в заголовке библиотеки соответствующие символы подчеркивания точками и сохранив файл, получаем именно такую DLL, ради которой и были предприняты все вышеперечисленные действия. Для облегчения этого процесса в прилагаемый архив DllSymChanger.zip помещена написанная мной программа DllSymChanger.exe, открывающая файл RC и производящая замены символов подчеркивания точками в одноименном файле DLL, находящемся в той же директории.

Теперь осталось запустить IE и в строке URL ввести

res://C:\IEnDLL\DataBank.dll/INDEX.HTM

Основная задача выполнена полностью.

Стоит заметить, что при выполнении ссылок внутри библиотеки, IE действует как при выполнении ссылок в директории диска.

Таким образом, при создании DLL можно использовать любые Web-объекты, интерпретируемые IE (звук, видео, VRML и т.д.).

Данный предложенный мною способ, открывает большие возможности для программистов в использовании компонента TCppWebBrowser (TWebBrowser) вкупе с достаточной защищенностью служебных данных программного продукта.

Каждый раз, как мне надо собрать новую ресурсную dll, я судорожно начинаю искать нужную информацию. Надоело! Все, что посчитаю важным, закину сюда.

С любезного разрешения Андрея Бушмана почти полностью дублирую . Скрины, взятые у Андрея, дополню (ну просто для себя) скринами с VS2015 CommunityEdition. Собственные комментарии и уточнения буду писать курсивом.
Статья написана для AutoCAD 2009, но прекрасно срабатывает и на других версиях AutoCAD.

Графические изображения, которые используются в CUI файлах, могут браться как из внешних BMP файлов, так и из неуправляемого DLL файла ресурсов, в который эти изображения помещены. Второй способ более удобен в использовании. Здесь рассматривается, как создавать такие DLL файлы и как их использовать в CUI файлах.

AutoCAD давно (начиная с версии 2010) использует формат CUIX в качестве частичных файлов меню, представляющий собой обычный архив, расширение которого переименовано с ZIP на CUIX. Однако в нашей компании используется AutoCAD 2009 SP3, который не умеет работать с CUIX, но использует более старый формат - CUI файлы, содержимое которых представлено в формате XML.

Кроме того, начиная с версии 2015, если не ошибаюсь, в AutoCAD введены два варианта оформления - темная тема и светлая тема. Сделать иконку, которая будет видна и там, и там, очень сложно. А имя картинки менять "на лету" не самое лучшее решение. Подробнее - ниже.

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

В MS Visual Studio 2012 создаём новый проект, на основе шаблона Empty Project и назначаем ему нужное имя:

Теперь в окне Solution Explorer нажимаем правой кнопкой мыши на имени проекта и в появившемся контекстном меню выбираем пункт Properties . Назначаем нужные значения свойствам Target Extension и Configuration Type :

Кроме этого, в настройках проекта нужно изменить ещё одну опцию - No Entry Point:

Произведя обозначенные выше изменения, жмём кнопку Применить и ОК . Теперь свойства нашего, пока ещё пустого проекта настроены должным образом. На вкладке Solution Explorer , из контекстного меню проекта, выбираем пункт Add -> New Item... и в появившемся диалоговом окне выбираем шаблон файла ресурсов:

Имя файлу можно назначить любое, в нашем примере оставляем Resource.rc . Жмём кнопку Add . Добавленный нами файл будет содержать в себе перечень изображений, которые будут использоваться в наших CUI(x) файлах.

Андрей рекомендует картинки, которые будут "загоняться" в dll, помещать в подкаталог \images. Вполне разумная рекомендация, хотя и не критична. Другой вопрос, что настоятельно рекомендую все же помещать ресурсы так, чтобы для них можно было "вычислить" относительный путь. Очень помогает, если разработка ведется не на одном рабочем месте.

В качестве эксперимента добавим BMP файлы разных размеров: 16x16, 24x24, 32x32, 48x48, 64x64 и 128x128. Кнопки на палитрах инструментов должны содержать изображения размером 16x16, а кнопки, размещённые на палитрах Ribbon, имеют размеры поболее. В нашем тестовом CUI файле разместим кнопки с указанными выше разрешениями и там и там, дабы посмотреть результат использования различных размеров.

Уточнение: BMP должен гарантированно подгружаться в VS. Для пользователей GIMP не самые лучшие новости: BMP приходится создавать с глубиной цвета 24 бит и сохранять без сжатия. Если сохранить без сжатия не получается, то через пакетное преобразование с помощью FastStone Image Viewer можно легко добиться нужного результата.

Можно было бы копировать и файлы в формате PNG, однако AutoCAD 2009 не умеет в CUI файлах использовать изображения такого формата.

Уточнение : можно попытаться подключать не *.bmp, а *.ico-файлы. Потребуется, конечно, некоторая дополнительная работа, но это возможно.
В старых версиях для обеспечения "прозрачности" фона следует использовать цвет RGB 192,192,192. Старые версии AutoCAD обрабатывают его как прозрачный.
И вот еще - AutoCAD2018 уже не "понимает" bmp, и требует как раз png

Теперь возвращаемся к нашей IDE: переключаемся на вкладку Resource View .

Из контекстного меню элемента Resource.rc вызываем пункт Add Resource...

В диалоговом окне Add Resource в списке Resource type выбираем элемент Bitmap , жмём кнопку Import... и указываем BMP файлы из созданного нами ранее подкаталога \images. Получаем такой результат (вид абсолютно одинаков что для 2012, что для 2015)

Теперь для каждого элемента значение свойства ID нужно обособить двойными кавычками, чтобы их можно было успешно использовать в наших CUI(x) файлах:

Всё, теперь компилируем наш DLL файл (клавиша F6 или через меню Build -> Build Solution ) .

Конец цитирования, дальнейшие действия по использованию DLL пропишу потом, если вдруг понадобится.

Теперь кое-что из собственного (и не только собственного) опыта.

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

Имя dll должно совпадать с именем cui(x) файла. Крайне желательно положить dll рядом с cui(x) файлом. Теоретически можно ее просто закинуть в пути поддержки, но лично я подобным никогда не занимался - потом замучаешься искать.

По поводу тем оформления, как было обещано, на основе информации на форуме ADN-CIS и справки Autodesk :

  1. В версиях AutoCAD без темы оформления ресурсы забирались из dll, имеющей то же имя, что и файл cui(x)
  2. В версиях AutoCAD с темами оформления необходимо создать отдельный ресурс DLL для каждой темы: светлой и темной. Соответствующий DLL ресурса используется на основе того, какая тема в настоящий момент применена к пользовательскому интерфейсу. По соглашению об именовании файлов DLL требуется использовать то же имя, что и имя файла адаптации, но также необходимо добавить суффикс "_light" для файла DLL, который будет использоваться в качестве светлой темы. Например, если загружен файл CUIx под именем mymenu.cuix , AutoCAD выполняет поиск файла библиотеки ресурсов mymenu_light.dll , если используется светлая тема, и файла mymenu.dll , если используется темная тема.

Последовательность компиляции библиотек с учетом варианта темы оформления отлично описал Дмитрий Загорулькин :

CUIX-файл делал один на версии 2014-2016, проблем с этим не было. Также, сделал два C++ проекта с ресурсными иконочными DLL - один для темной схемы, второй - для светлой. В настройках проектов в разделе "Build Events - Post Build Events" можно настроить события таким образом, чтобы создаваемые dll файлы после сборки сразу копировались в нужные папки под нужным названием. В папку для 2014 версии - только "светлая" dll без суффикса, в папку для версий 2015-2016 - "светлая" с суффиксом "_light", "темная" - без суффикса. Я сперва так делал, а потом настроил то же самое но уже в проекте WIX-инсталлятора. В общем, каким образом ни делай, есть возможность один раз настроить и уже потом не думать откуда и какие файлы нужно скопировать и как назвать - все делается автоматически.
Как AutoCAD определяет, какой DLL подгружать:
- в версиях до 2014 включительно подгружается только та DLL с иконками, название которой совпадает с названием файла CIUX. К примеру, есть файл: "MyTools.cuix", иконки для этого файла ищутся в файле "MyTools.dll".
- в версиях 2015-2016 для темной схемы подгружается DLL, совпадающая с названием CUIX, для светлой - название с суффиксом "_light". Пример: "MyTools.cuix", иконки для темной схемы: "MyTools.dll", для светлой: "MyTools_light.dll".
Еще один неочевидный момент. Можно создавать один ICO файл для иконок 16х16 и 32х32. Для этого внутрь ICO нужно поместить два соответствующих изображения. В настройках кнопки указывается при этом одинаковый ID изображения для большой и маленькой кнопки. AutoCAD сам выберет иконку в зависимости от размера выводимого изображения. Этот прием позволяет сильно сократить количество ICO файлов в ресурсах C++ проекта.

К вопросу об использовании ico-файлов. Тут все просто - читаем статью Использование ресурсной dll для CUIx с прозрачными растрами . Скажу честно - я пока подобным не игрался, но поверю Диме.
===
Добавлено:
Цитата из статьи по поводу подключения ico-файлов:

Достаточно просто добавить новый ico-ресурс при помощи редактора Visual Studio и отредактировать свойства “ICO” ресурса в “RCDATA” как показано ниже:

Создание DLL, содержащей одни лишь ресурсы

Файл ресурсов надо скомпилировать - "rc MyResDll.rc" - и преобразовать линкером в DLL, обязательно указав флаг "/NOENTRY", т. к. эта динамическая библиотека содержит исключительно одни ресурсы и ни строки кода: "link MyRedDll.res /DLL /NOENTRY".

В Visual Studio это сделать еще проще - достаточно кликнуть по папке "Resourses" окна "File View" и добавить новый файл ресурса, который затем можно будет модифицировать визуальным редактором по своему усмотрению.

Для загрузки ресурса из DLL - в принципе, можно воспользоваться уже знакомой нам функцией LoadLibray, и передавать возращенный ею дескриптор LoadString или другой функции, работающей с ресурсами. Однако загрузку динамической библиотеки можно значительно ускорить, если "объяснить" системе, что эта DLL не содержит ничего, кроме ресурсов, и нам достаточно лишь спроецировать ее на адресное пространство процесса, а обо всем остальном мы сумеем позаботиться и самостоятельно.

Вот тут-то и пригодится функция LoadLibraryEx: ее первый аргумент, как и у коллеги LoadLibrary, задает имя динамической библиотеки для загрузки, второй - зарезервирован и должен быть равен нулю, а третий, будучи равным LOAD_LIBRARY_AS_DATAFILE, заставляет функцию делать именно то, что нам нужно - загружать DLL как базу данных (если динамическая библиотека содержит помимо ресурсов еще и код, то загрузка с этим ключом проходит все равно успешно, но функции загруженной DLL не будут доступны - только ресурсы):

#include

#include

h=LoadLibraryEx("MyResDll.dll",0,

LOAD_LIBRARY_AS_DATAFILE);

LoadString(h,1,&buf,99);

printf("%s\n",&buf);

Демонстрация оптимизированной загрузки DLL, не содержащей ничего кроме ресурсов

Эта программа компилируются точно так же, как и предыдущие примеры явной компоновки - и после запуска победно выводит на экране "Hello, Word!", подтверждая, что ресурс "строка" из динамической библиотеки был успешно загружен! Аналогичным способом можно загружать ресурсы из исполняемых файлов; с этой точки зрения они ничем не отличаются от динамических библиотек.

ООП и DLL

#include "stdafx.h"

DWORD ul_reason_for_call,

LPVOID lpReserved

//Наш код.

//Добавляем функцию Add.

if(a>b) res = a; else res = b;

Мастер нам сделал заготовку для нашей dll. Будем развивать ее. Для этого добавим 1 функцию и один класс с методом. Вот код:

#include "stdafx.h"

BOOL APIENTRY DllMain(HANDLE hModule,

DWORD ul_reason_for_call,

LPVOID lpReserved

//Наш код.

//Добавляем функцию Add.

Declspec(dllexport) int Add(int a, int b)

//Добавляем класс MyClass с методом MyMax.

Declspec(dllexport) int MyMax(int a, int b){

if(a>b) res = a; else res = b;

Обратите внимание, что добавляемы функции и методы, которые мы хотим вызывать извне, вы пишем с модификатором __declspec(dllexport). Таким образом мы помечаем так называемые экспортируемые функции.

Компилируем программу. В папке debug проекта должен появится файл firstdll.dll.

Теперь пишем тестовую программу.

#include

//Подключаем необходимый заголовочный файл.

#include "..\firstdll.cpp"

cout<

cout<

Запускаем программу. Как и следовало ожидать, она выведет 22 и 11.

Функция DllMain

Большинство библиотек DLL - просто коллекции практически независимых друг от друга функций, экспортируемых в приложения и используемых в них. Кроме функций, предназначенных для экспортирования, в каждой библиотеке DLL есть функция DllMain. Эта функция предназначена для инициализации и очистки DLL. Она пришла на смену функциям LibMain и WEP, применявшимся в предыдущих версиях Windows. Структура простейшей функции DllMain может выглядеть, например, так:

BOOL WINAPI DllMain (HANDLE hInst,DWORD dwReason, LPVOID IpReserved) {

BOOL bAllWentWell=TRUE;

switch (dwReason) {

case DLL_PROCESS_ATTACH: // Инициализация процесса.

case DLL_THREAD_ATTACH: // Инициализация потока.

case DLL_THREAD_DETACH: // Очистка структур потока.

case DLL_PROCESS_DETACH: // Очистка структур процесса.

if(bAllWentWell) return TRUE;

else return FALSE;

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

При первой загрузке библиотеки DLL процессом вызывается функция DllMain с dwReason, равным DLL_PROCESS_ATTACH. Каждый раз при создании процессом нового потока DllMain вызывается с dwReason, равным DLL_THREAD_ATTACH (кроме первого потока, потому что в этом случае dwReason равен DLL_PROCESS_ATTACH).

По окончании работы процесса с DLL функция DllMain вызывается с параметром dwReason, равным DLL_PROCESS_DETACH. При уничтожении потока (кроме первого) dwReason будет равен DLL_THREAD_DETACH.

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

В состав DLL могут входить ресурсы, не принадлежащие вызывающему эту библиотеку приложению. Если функции DLL работают с ресурсами DLL, было бы, очевидно, полезно сохранить где-нибудь в укромном месте дескриптор hInst и использовать его при загрузке ресурсов из DLL. Указатель IpReserved зарезервирован для внутреннего использования Windows. Следовательно, приложение не должно претендовать на него. Можно лишь проверить его значение. Если библиотека DLL была загружена динамически, оно будет равно NULL. При статической загрузке этот указатель будет ненулевым.

В случае успешного завершения функция DllMain должна возвращать TRUE. В случае возникновения ошибки возвращается FALSE, и дальнейшие действия прекращаются.

Замечание. Если не написать собственной функции DllMain(), компилятор подключит стандартную версию, которая просто возвращает TRUE.

Пример проблемы:

HANDLE hThread; DWORD dwThreadId;

switch (fdwReason)

case DLL_PROCESS_ATTACH:

// DLL проецируется на адресное пространство процесса

// создаем поток для выполнения какой-то работы

hThread = CreateThread(NULL, 0, SomeFunction, NULL, 0, &dwThreadId);

// задерживаем наш поток до завершения нового потока

WaitForSingleObject(hThread, INFINITE);

// доступ к новому потоку больше не нужен

CloseHandle(hThread);

case DLL_THREAD_ATTACH:

// создается еще один поток

case DLL_THREAD_DETACH:

// поток завершается корректно

case DLL_PROCESS_DETACH:

// DLL выгружается из адресного пространства процесса

Функции точки входа должно выполнять только простые задачи инициализации. Они не должны вызвать функцию LoadLibrary или LoadLibraryEx (или функцию, которая вызывает эти функции), потому что это может создать циклы взаимозависимости в порядке загрузки DLL. Это может стать результатом того, что DLL начнет использоваться прежде, чем система выполнит код ее инициализации. Точно так же функция точки входа не должна вызвать функцию FreeLibrary (или функцию, которая ее вызывает), потому что это может привести к тому, что DLL начнет использоваться после того, как система выполнила код завершения ее работы.

Пример от Microsoft. Пошаговое руководство. Создание и использование библиотеки DLL (C++) Visual Studio 2013

В этом пошаговом руководстве описывается создание библиотеки динамической компоновки (DLL) для использования с C++ приложением. Использование библиотеки - хороший способ повторного использования кода. Вместо повторной реализации одних и тех же процедур в каждой программе, вы создаете их один раз и затем ссылаетесь на них из других приложений. Поместив код в библиотеке DLL, вы сэкономите место в каждом приложении, которое ссылается на этод код, а так же сможете обновлять DLL без перекомпиляции всех приложений. Дополнительные сведения о библиотеках DLL см. в разделе DLL в Visual C++.



Рекомендуем почитать

Наверх