рефераты
Главная

Рефераты по рекламе

Рефераты по физике

Рефераты по философии

Рефераты по финансам

Рефераты по химии

Рефераты по хозяйственному праву

Рефераты по цифровым устройствам

Рефераты по экологическому праву

Рефераты по экономико-математическому моделированию

Рефераты по экономической географии

Рефераты по экономической теории

Рефераты по этике

Рефераты по юриспруденции

Рефераты по языковедению

Рефераты по юридическим наукам

Рефераты по истории

Рефераты по компьютерным наукам

Рефераты по медицинским наукам

Рефераты по финансовым наукам

Рефераты по управленческим наукам

Психология и педагогика

Промышленность производство

Биология и химия

Языкознание филология

Издательское дело и полиграфия

Рефераты по краеведению и этнографии

Рефераты по религии и мифологии

Рефераты по медицине

Рефераты по сексологии

Рефераты по информатике программированию

Краткое содержание произведений

Реферат: Создание в среде Borland C++ Builder dll, совместимой с Visual C++

Реферат: Создание в среде Borland C++ Builder dll, совместимой с Visual C++

Роман Мананников

Проблемы взаимодействия

Сложность использования dll, созданной с помощью Borland C++ Builder (далее BCB), в проектах, разрабатываемых в средах Microsoft, обусловлена тремя основными проблемами . Во-первых, Borland и Microsoft придерживаются разных соглашений о наименовании (naming convention) функции в dll. В зависимости от того, как объявлена экспортируемая функция, ее имя может быть дополнено компилятором определенными символами. Так, при использовании такого соглашения о вызове (calling convention), как __cdecl, BCB перед именем функции добавляет символ подчеркивания. Visual C++ (далее VC), в свою очередь, при экспорте функции как __stdcall добавит к ее имени помимо подчеркивания также информацию о списке аргументов (символ @ плюс размер списка аргументов в байтах).

ПРИМЕЧАНИЕ

Использование соглашения __stdcall означает, что вызываемая функция сама удалит из стека свои аргументы. Соглашение __cdecl, наоборот, обязывает очищать стек вызывающую функцию. Объявление функции как __cdecl приведет к некоторому (незначительному) увеличению размера конечного исполняемого файла, поскольку каждый раз после вызова этой функции требуется код по очистке стека, с другой стороны, именно из-за очистки стека вызывающей функцией допускается передача переменного числа параметров. В стек параметры и в том, и в другом случае помещаются справа налево.

В таблице 1 приведены возможные варианты наименований для экспортируемой функции MyFunction, объявленной следующим образом:

extern ”C” void __declspec(dllexport) <calling convention> MyFunction(int Param);

в зависимости от соглашения о вызове (<calling convention>) и компилятора.

Соглашение о вызове VC++ C++ Builder
__stdcall _MyFunction@4 MyFunction
__cdecl MyFunction _MyFunction

Таблица 1. Наименования функций в зависимости от соглашения о вызове и компилятора.

Во-вторых, объектные двоичные файлы (.obj и .lib), создаваемые BCB, несовместимы с объектными файлами VC, и, следовательно, не могут быть прилинкованы к VC-проекту. Это означает, что при желании использовать неявное связывание (linking) c dll необходимо каким-то образом создать .lib-файл (библиотеку импорта) формата, которого придерживается Microsoft.

ПРИМЕЧАНИЕ

Следует отметить, что до появления 32-разрядной версии Visual C++ 1.0 компиляторы Microsoft использовали спецификацию Intel OMF (Object Module Format – формат объектного модуля). Все последующие компиляторы от Microsoft создают объектные файлы в формате COFF (Common Object File Format – стандартный формат объектного файла). Основной конкурент Microsoft на рынке компиляторов – Borland – решила отказаться от формата объектных файлов COFF и продолжает придерживаться формата OMF Intel. Отсюда и несовместимость двоичных объектных файлов.

В-третьих, классы и функции-методы классов, экспортируемые из BCB dll, не могут быть использованы в проекте на VC. Причина этого кроется в том, что компиляторы искажают (mangle) имена как обычных функций, так и функций-методов класса (не путайте с разными соглашениями о наименованиях). Искажение вносится для поддержки полиморфизма, то есть для того, чтобы различать функции с одинаковым именем, но разными наборами передаваемых им параметров. Если для обычных функций искажения можно избежать, используя перед определением функции директиву extern ”С” (но при этом, во-первых, на передний план выходит первая проблема – разные соглашения о наименовании функций в dll, а во-вторых, из двух и более функций с одинаковым именем директиву extern ”С” можно использовать только для одной из них, в противном случае возникнут ошибки при компиляции), то для функций-методов класса искажения имени неизбежны. Компиляторы Borland и Microsoft, как вы уже, вероятно, догадались, используют различные схемы внесения искажений. В результате VC-приложения попросту не видят классы и методы классов, экспортируемые библиотеками, скомпилированными в BCB.

ПРИМЕЧАНИЕ

От редакции: В частности, разновидностями полиморфизма времени компиляции являются перегрузка (ad-hoc полиморфизм) и шаблоны функций (параметрический полиморфизм).

Эти три проблемы осложняют использование BCB dll из приложений, созданных на VC, но все-таки это возможно. Ниже описаны три способа создания dll совместимой с VC и дальнейшего успешного использования этой dll.

Алгоритмы создания VC-совместимой dll и ее использование

Два из описанных в этом разделе алгоритмов применяют неявное связывание с dll, один – явную загрузку dll. Опишем сначала самый простой способ – использование BCB dll из проекта VC посредством ее явной загрузки в процессе выполнения программы.

Алгоритм с явной загрузкой dll

Применяя данную технику, нам не придется создавать совместимые с VC библиотеки импорта (.lib). Вместо этого добавится ряд действий по загрузке и выгрузке dll в приложении, ее использующем.

Создадим BCB dll (New -> DLL Wizard -> C++ -> Use VCL -> OK), экспортирующую для простоты всего две функции. Одна из функций будет вычислять сумму двух чисел и не будет использовать VCL-классы, а другая будет создавать окно и выводить в VCL-компонент TStringGrid элементы массива, переданного в качестве одного из аргументов.

ПРИМЕЧАНИЕ

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

Листинг 1 - Компилятор Borland C++ Builder 5

ExplicitDll.h

#ifndef _EXPLICITDLL_

#define _EXPLICITDLL_

extern "C"

{

 int __declspec(dllexport) __cdecl SumFunc(int a, int b);

 HWND __declspec(dllexport) __stdcall ViewStringGridWnd(int Count,double* Values);

}

#endif

Ключевое слово __declspec с атрибутом dllexport помечает функцию как экспортируемую, имя функции добавляется в таблицу экспорта dll. Таблица экспорта любого PE-файла (.exe или .dll) состоит из трех массивов: массива имен функций (а точнее, массива указателей на строки, содержащие имена функций), массива порядковых номеров функций и массива относительных виртуальных адресов (RVA) функций. Массив имен функций упорядочен в алфавитном порядке, ему соответствует массив порядковых номеров функций. Порядковый номер после некоторых преобразований превращается в индекс элемента из массива относительных виртуальных адресов функций. При экспорте функции по имени имеет место следующая последовательность действий: по известному имени функции определяется ее индекс в массиве имен функций, далее по полученному индексу из массива порядковых номеров определяется порядковый номер функции, затем из порядкового номера, с учетом базового порядкового номера экспорта функций для данного PE-файла, вычисляется индекс, по которому из массива адресов извлекается искомый RVA функции. Помимо экспорта по имени возможен экспорт функций по их порядковым номерам (ordinal). В этом случае последовательность действий для получения индекса элемента из массива относительных виртуальных адресов сводится только к преобразованию порядкового номера функции. Для экспорта функций по номеру используется .def-файл с секцией EXPORTS, где за каждой функцией будет закреплен порядковый номер. При этом в тексте самой dll функции как экспортируемые не помечаются. Подробнее о таблице экспорта можно прочитать в статье по адресу Создание в среде Borland C++ Builder dll, совместимой с Visual C++http://www.rsdn.ru/article/baseserv/pe_coff.xml.

ExplicitDll.cpp

#include <vcl.h>

#include <grids.hpp>

#include "ExplicitDll.h"

int __cdecl SumFunc(int a, int b)

{

 return a + b;

}

HWND __stdcall ViewStringGridWnd(int Count, double* Values)

{

 try

 {

 // создаем VCL-форму, на которой будет отображен StringGrid,

 // и задаем ее основные параметры

 TForm* GridForm = new TForm((TComponent *)NULL);

 GridForm->Caption = "Grid Form";

 GridForm->Width = 300;

 GridForm->Height = 300;

 // создаем компонент StringGrid и устанавливаем его размеры

 TStringGrid *Grid = new TStringGrid(GridForm);

 Grid->ColCount = Count + 1;

 Grid->RowCount = Count + 1;

 // заполняем StringGrid значениями

 if (Values != NULL)

 for (int i = 0; i < Count; i++)

 Grid->Cells[i + 1][i + 1] = Values[i];

 // задаем параметры отображения StringGrid в родительском окне

 Grid->Parent = GridForm;

 Grid->Align = alClient;

 // показываем VCL-форму

 GridForm->Show();

 // возвращаем хэндл VCL-окна клиентскому приложению,

 // дабы оно могло это окно при необходимости закрыть

 return GridForm->Handle;

 }

 catch(...)

 {

 return NULL;

 }

}

#pragma argsused

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)

{

 return 1;

}

Проанализируем сформированные компилятором наименования экспортируемых функций. Воспользовавшись утилитой impdef.exe, поставляемой совместно с C++Builder (находится в каталоге $(BCB)Bin, синтаксис командной строки – impdef.exe ExplicitDll.def ExplicitDll.dll), получим следующий .def-файл

ExplicitDll.def

LIBRARY EXPLICITDLL.DLL

EXPORTS

 ViewStringGridWnd @1 ; ViewStringGridWnd

 _SumFunc @2 ; _SumFunc

 ___CPPdebugHook @3 ; ___CPPdebugHook

Поскольку в данном примере экспортируемая функция ViewStringGridWnd использует соглашение __stdcall, ее имя осталось неизменным (см. таблицу 1), следовательно, для вызова этой функции VC-приложение воспользуется именем ViewStringGridWnd (например, при вызове GetProcAddress), а вот для вызова функции SumFunc использовать придется имя _SumFunc. Очевидно, что осуществлять вызов функции, пользуясь ее измененным именем, неудобно само по себе, а тем более, если dll пишет один программист, а работает с ней другой. Для того чтобы при использовании __cdecl-соглашения экспортируемые функции можно было использовать с их истинными именами (без символов подчеркивания), необходимо об этом позаботиться заранее, то есть на этапе создания самой dll. Для этого создается .def-файл (это можно сделать в любом текстовом редакторе), в котором определяется секция EXPORTS, содержащая псевдоним (alias) для каждой экспортируемой __cdecl-функции. В нашем случае он будет выглядеть следующим образом

ExplicitDllAlias.def

EXPORTS

 ; VC funcname = BCB funcname

 SumFunc = _SumFunc

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

Созданный .def-файл добавляется (Project -> Add to Project) к проекту dll. После компиляции, проанализировав dll c помощью impdef.exe, получим следующее

ExplicitDll.def

libRARY EXPLICITDLL.DLL

EXPORTS

 SumFunc @4 ; SumFunc

 ViewStringGridWnd @2 ; ViewStringGridWnd

 _SumFunc @1 ; _SumFunc

 ___CPPdebugHook @3 ; ___CPPdebugHook

Имеем на одну экспортируемую функцию больше, но при этом реальное количество функций в dll осталось неизменным, а функция с именем SumFunc (функция-псевдоним) является ссылкой на свой оригинал, то есть на функцию, экспортируемую под именем _SumFunc.

ПРИМЕЧАНИЕ

Более правильным будет сказать, что функция-псевдоним попросту добавляется в таблицу экспорта dll: ее имя SumFunc добавляется в массив имен функций, а в массив порядковых номеров добавляется присвоенный ей порядковый номер. Однако соответствующий функции-псевдониму RVA в массиве относительных виртуальных адресов будет равен RVA функции с именем _SumFunc. Убедиться в этом можно последовательно вызывая GetProcAddress для имен функций SumFunc и _SumFunc и анализируя возвращаемый адрес (можно, разумеется, воспользоваться различными программами, позволяющими просмотреть содержимое исполняемого файла). В обоих случаях адрес функции будет одинаков.

Таким образом, с помощью .def-файла псевдонимов при экспорте функций, определенных как __cdecl, мы избавляем пользователей от необходимости вызова функций по их измененным именам, хотя и такая возможность остается.

ПРЕДУПРЕЖДЕНИЕ

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

В результате изложенного выше мы получили dll, экспортирующую функции с именами SumFunc и ViewStringGridWnd. При этом их названия не зависят от того, какое соглашение о вызове использовалось при объявлении этих функций. Теперь рассмотрим пример использования нашей dll в приложении VC. Создадим в среде Visual C++ 6.0 (или Visual C++ 7.0) простое MFC-приложение, которое будет представлять собой обычное диалоговое окно (File -> New -> MFC AppWizard(exe) -> Dialog based -> Finish). Добавим к исходному диалогу две кнопки: кнопку “SumFunc” и кнопку “ViewStringGridWnd”. Затем для каждой кнопки создадим обработчик события BN_CLICKED: OnSumFunc() и OnViewStringGridWnd() соответственно. Нам также понадобятся обработчики сообщений для событий формы WM_CREATE и WM_DESTROY. Полный рабочий код этого приложения находится в примерах к статье, здесь же будет приведена только часть, демонстрирующая работу с нашей dll, поскольку оставшаяся часть кода генерируется средой разработки.

Листинг 2 - Компилятор Visual C++ 6.0

UsingExplicitDLLDlg.cpp

// код, генерируемый средой разработки

// хэндл тестируемой DLL

HINSTANCE hDll = NULL;

// тип указателя на функцию ViewStringGridWnd

typedef HWND (__stdcall *ViewStringGridWndProcAddr) (int Count, double* Values);

// хэндл окна с VCL-компонентом StringGrid

HWND hGrid = NULL;

// тип указателя на функцию SumFunc

typedef int (__cdecl *SumFuncProcAddr) (int a, int b);

// код, генерируемый средой разработки

// обработчик нажатия кнопки SumFunc

void CUsingExplicitDLLDlg::OnSumFunc()

{

 // указатель на функцию SumFunc

 SumFuncProcAddr ProcAddr = NULL;

 if( hDll != NULL )

 {

 // получение адреса функции

 ProcAddr = (SumFuncProcAddr) GetProcAddress(hDll, "SumFunc");

 if( ProcAddr != NULL )

 {

 // вызов функции

 int result = (ProcAddr)(5, 6);

 // отображение результата в заголовке диалога

 char str[10];

 this->SetWindowText(itoa(result, str ,10));

 }

 }

}

// обработчик нажатия кнопки ViewStringGridWnd

void CUsingExplicitDLLDlg::OnViewStringGridWnd()

{

 // указатель на функцию ViewStringGridWnd

 ViewStringGridWndProcAddr ProcAddr = NULL;

 if( hDll != NULL )

 {

 // получение адреса функции

 ProcAddr = (ViewStringGridWndProcAddr) GetProcAddress(hDll,

 "ViewStringGridWnd");

 if( ProcAddr != NULL )

 {

 // инициализация аргументов

 const int count = 5;

 double Values[count] = {2.14, 3.56, 6.8, 8, 5.6564};

 // закрываем ранее созданное окно, чтобы они не плодились

 if( hGrid != NULL )

 ::SendMessage(hGrid, WM_CLOSE, 0, 0);

 // вызов функции

 hGrid = (ProcAddr)(count, Values);

 }

 }

}

// обработчик события окна WM_DESTROY

void CUsingExplicitDLLDlg::OnDestroy()

{

 CDialog::OnDestroy();

 

 // закрываем окно с компонентом StringGrid, если оно было создано

 if( hGrid != NULL )

 ::SendMessage(hGrid, WM_CLOSE, 0, 0);

 // выгрузка dll из памяти

 FreeLibrary( hDll );

}

// обработчик события окна WM_CREATE

int CUsingExplicitDLLDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

 if (CDialog::OnCreate(lpCreateStruct) == -1)

 return -1;

 

 // загрузка dll в память

 hDll = LoadLibrary("ExplicitDll.dll");

 

 return 0;

}

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

ПРИМЕЧАНИЕ

Следует отметить, что использование экспортируемых unmanaged-функций из управляемого кода (managed code) в .NET осуществляется исключительно посредством явной загрузки dll. К процессу вызова функции в этом случае помимо стандартных шагов (таких как загрузка dll в память посредством LoadLibrary, получение адреса требуемой функции с помощью GetProcAddress и непосредственно вызов), добавляется также процесс маршалинга (marshaling), то есть процесс преобразования типов данных .NET в их аналоги в традиционном двоичном коде (при проталкивании аргументов в стек) и обратно (при анализе возвращаемого значения). Для указания, что метод импортируется из dll, используется атрибут DllImport, параметры которого содержат информацию, необходимую для вызова LoadLibrary и GetProcAddress.

Таким образом, для вызова экспортируемой функции из dll, скомпилированной в BCB, необходимо выполнить следующую последовательность действийя:

Объявить экспортируемые функции либо как __cdecl, либо как __stdcall. Если используется только соглашение __stdcall, пропускаем пункт 3.

Поместить объявления функций в блок extern ”С”. Не экспортировать классы и функции-члены классов, поскольку это все равно не удастся.

Если экспортируются функции с соглашением о вызове __cdecl, то добавить к проекту .def-файл с псевдонимами для каждой такой функции.

Откомпилировать dll.

Создать клиентский (то есть использующий BCB библиотеку) VC-проект.

Скопировать созданную BCB dll в папку с клиентским VC-приложением.

Загрузить dll из клиентского приложения в память при помощи LoadLibrary.

Получить адрес требуемой функции с помощью GetProcAddress и присвоить его указателю на функцию.

Вызвать функцию с помощью указателя на нее.

По окончании использования выгрузить dll из памяти с помощью FreeLibrary.

Алгоритм с неявным связыванием для экспорта (импорта) __cdecl-функций

Как следует из названия раздела, данный способ предназначен для экспорта (а на клиентской стороне – для импорта) функций с __cdecl-соглашением о вызове. Чтобы воспользоваться неявным связыванием, прежде всего, необходимо создать объектный .lib-файл (библиотеку импорта), содержащий ссылку на dll и перечень находящихся в dll функций. Данный объектный файл можно создать по .def-файлу экспорта библиотеки с помощью утилиты lib.exe. При этом полученный .lib-файл будет в нужном нам формате COFF, поскольку компилятор VC придерживается именно этой спецификации (утилита lib.exe поставляется совместно с VC и умеет создавать библиотеки импорта только по .def-файлу). Готовый .lib-файл прилинковывается к клиентскому проекту.

При неявном связывании приложение не подозревает, что использует dll, поэтому функции, вызываемые из динамической библиотеки, как и любые другие, должны быть объявлены в тексте клиентской программы. Для объявления функций воспользуемся исходным заголовочным файлом BCB dll, но функции в нем должны быть помечены уже не как __declspec(dllexport), а как __declspec(dllimport), то есть как импортируемые извне, поскольку по отношению к клиентскому приложению эти функции являются именно импортируемыми.

Исходный текст dll на этот раз будет выглядеть следующим образом:

Листинг 3 - Компилятор Borland C++ Builder 5

ImplicitLinking_cdecl.h

#ifndef _IMPLICITDLL_

#define _IMPLICITDLL_

// если макрос-идентификатор _DLLEXPORT_ был определен ранее,

// то макрос _DECLARATOR_ пометит функцию как экспортируемую,

// в противном случае функция будет помечена как импортируемая.

// Данная конструкция из директив препроцессора позволяет

// воспользоваться заголовочным файлом библиотеки как на этапе

// создания DLL, так и на этапе ее использования, а именно, при

// неявном связывании.

#ifdef _DLLEXPORT_

 #define _DECLARATOR_ __declspec(dllexport)

#else

 #define _DECLARATOR_ __declspec(dllimport)

#endif

extern "C"

{

 int _DECLARATOR_ __cdecl SumFunc(int a, int b);

 HWND _DECLARATOR_ __cdecl ViewStringGridWnd(int Count, double* Values);

}

#endif

ImplicitLinking_cdecl.cpp

#include <vcl.h>

#include <grids.hpp>

// определение _DLLEXPORT_, дабы вместо макроса _DECLARATOR_

// в заголовочном файле было подставлено __declspec(dllexport),

// и функции были объявлены как экспортируемые

#define _DLLEXPORT_

#include "ImplicitLinking_cdecl.h"

int __cdecl SumFunc( int a, int b )

{ // тело функции такое же как в предыдущем разделе

}

HWND __cdecl ViewStringGridWnd( int Count, double* Values )

{ // тело функции такое же как в предыдущем разделе

}

#pragma argsused

int WINAPI DllEntryPoint(HINSTANCE hinst,

 unsigned long reason,

 void* lpReserved)

{

 return 1;

}

Основная возникающая при этом проблема заключается в том, что, согласно таблице 1, функции с __cdecl-соглашением о вызове будут экспортироваться с символом подчеркивания, следовательно, .lib-файл, созданный по .def-файлу экспорта библиотеки, будет содержать измененные имена функций. С другой стороны, во-первых, компилятор VC будет ожидать неизмененных наименований __cdecl-функций, потому что сам VC, экспортируя функции с __cdecl-соглашением о вызове, ничего к их наименованию не добавляет, а во-вторых, заголовочный файл BCB dll, подключаемый к клиентскому приложению, содержит объявления функций с их реальными (без символа подчеркивания) именами. В результате этого, если в тексте клиентского приложения встретится хотя бы один вызов нашей функции, то VC при связывании попытается найти описание этой импортируемой функции в добавленной к проекту библиотеке импорта (.lib-файле), с тем, чтобы добавить соответствующую запись в таблицу импорта приложения. Но из-за несоответствия имен функций в заголовочном и объектном файлах линковщик, естественно, в .lib-файле ничего не найдет, о чем не замедлит выдать сообщение (например, такое - error LNK2001: unresolved external symbol __imp__SumFunc).

ПРИМЕЧАНИЕ

Таблица импорта любого PE-файла содержит массив структур IMAGE_IMPORT_DESCRIPTOR. Каждая такая структура соответствует одной из dll, с которой неявно связан PE-файл. Структура IMAGE_IMPORT_DESCRIPTOR среди прочих полей содержит поле с RVA строки-наименования dll, которой она соответствует, и два поля с RVA массивов двойных слов, предназначенных для хранения информации об импортируемых функциях. При запуске приложения загрузчик PE-файлов заполняет один из этих массивов (так называемую таблицу адресов импорта) адресами импортируемых функций, загрузив перед этим dll, в которой эти функции находятся. Адрес импортируемой функции вычисляется как сумма адреса, по которому была загружена экспортирующая данную функцию dll, и смещения (RVA) самой функции относительно начала dll.

Описанную выше проблему несоответствия заголовочного и объектного файлов можно решить двумя способами – снова воспользоваться рассмотренным в предыдущем разделе .def-файлом с псевдонимами или использовать в заголовочном файле нашей библиотеки директиву препроцессора #define.

Использование псевдонимов

Следуя этому способу, создаем и добавляем к проекту BCB dll следующий .def-файл:

Создание в среде Borland C++ Builder dll, совместимой с Visual C++ImplicitLinkingAliases.def

EXPORTS

 ; MSVC name = Borland name

 SumFunc = _SumFunc

 ViewStringGridWnd = _ViewStringGridWnd

После компиляции наша dll будет экспортировать функции

ImplicitLinking_cdecl.def

libRARY IMPLICITLINKING_CDECL.DLL

EXPORTS

 SumFunc @4 ; SumFunc

 ViewStringGridWnd @5 ; ViewStringGridWnd

 _SumFunc @1 ; _SumFunc

 _ViewStringGridWnd @2 ; _ViewStringGridWnd

 ___CPPdebugHook @3 ; ___CPPdebugHook

Таким образом, в таблицу экспорта dll добавляются функции-псевдонимы, имена которых соответствуют функциям, объявленным в заголовочном файле нашей библиотеки. Для полного соответствия (хотя этого можно и не делать) удалим из ImplicitLinking_cdecl.def упоминания обо всех посторонних для приложения-клиента функциях, так как заголовочный файл содержит объявления только двух функций. В результате получим .def-файл готовый для генерации из него объектного .lib-файла:

ImplicitLinking_cdecl.def

libRARY IMPLICITLINKING_CDECL.DLL

EXPORTS

 SumFunc @4 ; SumFunc

 ViewStringGridWnd @5 ; ViewStringGridWnd

ПРИМЕЧАНИЕ

В единственной статье, которую мне удалось найти по данной теме (на сайте bcbdev.com), рекомендовалось, помимо удаления из .def-файла посторонних функций, заменить наименование секции EXPORTS на IMPORTS. Делать этого не следует по той простой причине, что утилита lib.exe (по крайней мере, поставляемая с 6-ой и 7-ой Visual Studio) секцию IMPORTS не поддерживает, поэтому игнорирует все последующие описания функций и создает пустой .lib-файл. Утилита lib.exe находится в каталоге $(VC)Bin, но запустить ее обычно с первого раза не удается, поскольку для работы ей требуется библиотека mspdb60.dll (для lib.exe, поставляемой с Visual Studio 7 – mspdb70.dll). mspdb60.dll лежит в папке $(Microsoft Visual Studio)CommonMSDev98Bin, а mspdb70.dll – в папке $(Microsoft Visual Studio .NET)Common7IDE.

С помощью утилиты lib.exe создадим необходимый для неявного связывания .lib-файл в формате COFF, для этого в командной строке наберем

lib.exe /def:ImplicitLinking_cdecl.def

либо

lib.exe /def:ImplicitLinking_cdecl.def /out:ImplicitLinking_cdecl.lib

Полученный .lib-файл добавим к проекту VC-клиента (Project -> Add To Project -> Files…).

Использование директивы препроцессора #define

Теперь рассмотрим способ, позволяющий добиться одинаковых названий функций в заголовочном и объектном (.lib) файлах с помощью директивы #define. Перепишем заголовочный файл нашей BCB-библиотеки следующим образом

Листинг 4 - Компилятор Borland C++ Builder 5

ImplicitLinking_cdecl.h

#ifndef _IMPLICITDLL_

#define _IMPLICITDLL_

#ifdef _DLLEXPORT_

 #define _DECLARATOR_ __declspec(dllexport)

#else

 #define _DECLARATOR_ __declspec(dllimport)

#endif

extern "C"

{

 // при компиляции в VC к оригинальным наименованиям

 // функций добавятся символы подчеркивания, таким образом

 // имена объявляемых функций совпадут с их именами в таблице

 // экспорта DLL и, следовательно, .lib-файле

 #ifdef _MSC_VER

 #define SumFunc _SumFunc

 #define ViewStringGridWnd _ViewStringGridWnd

 #endif

 int _DECLARATOR_ __cdecl SumFunc(int a, int b);

 HWND _DECLARATOR_ __cdecl ViewStringGridWnd(int Count, double* Values);

}

#endif

При компиляции клиентского VC-приложения в подключенном к проекту заголовочном файле dll (ImplicitLinking_cdecl.h) к наименованию каждой функции с помощью директив #define добавляется символ подчеркивания (макрос _MSC_VER определяется компилятором VC по умолчанию). Поскольку из BCB dll __cdecl-функции экспортируются таким же образом, то есть с добавлением символа подчеркивания, то устанавливается соответствие имен экспортируемых и объявленных функций. Макросы #define распространяют свое влияние и на весь последующий код приложения, что позволяет в тексте программы при вызове импортируемой функции пользоваться ее оригинальным именем, которое при компиляции будет дополнено необходимым магическим символом подчеркивания. Таким образом, мы идем на поводу у фирмы Borland и в клиентском приложении завуалированно используем для вызова функций из нашей dll имена, измененные компилятором BCB. Именно необходимость использования измененных имен (пусть и не в открытую благодаря define-трюку), на мой взгляд, является существенным недостатком этого способа, так как, например, при желании явно (см. раздел “Алгоритм с явной загрузкой dll”) использовать dll придется оперировать измененными именами функций. Не развивая дальше эту тему, скажу, что если BCB dll создается с четким намерением использовать ее в VC-приложениях, то лучше добавлять к проекту библиотеки .def-файл с удобными для пользователей именами-псевдонимами функций.

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

После компиляции dll с помощью impdef.exe получаем .def-файл экспорта, из которого утилитой lib.exe создаем объектный .lib-файл и добавляем его к клиентскому VC-проекту.

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

Листинг 5 - Компилятор Visual C++ 6.0

UsingImplicitLinking_cdeclDlg.cpp

// код, генерируемый средой разработки

// хэндл окна с VCL-компонентом StringGrid

HWND hGrid = NULL;

// подключаем заголовочный файл библиотеки

#include "ImplicitLinking_cdecl.h"

// код, генерируемый средой разработки

void CUsingImplicitLinkng_cdeclDlg::OnSumFunc()

{

 // вызываем функцию SumFunc из dll

 int res = SumFunc(5, 9);

 // выводим результат в заголовок диалогового окна

 char str[10];

 this->SetWindowText(itoa(res, str ,10));

}

void CUsingImplicitLinkng_cdeclDlg::OnViewStringGridWnd()

{

 // инициализация аргументов

 const int count = 5;

 double Values[count] = {2.14, 3.56, 6.8, 8, 5.6564};

 // закрываем ранее созданное окно, чтобы они не «плодились»

 if( hGrid != NULL )

 ::SendMessage(hGrid, WM_CLOSE, 0, 0);

 // вызываем функцию ViewStringGridWnd из dll

 hGrid = ViewStringGridWnd(count, Values);

}

void CUsingImplicitLinkng_cdeclDlg::OnDestroy()

{

 CDialog::OnDestroy();

 

 // закрываем окно с компонентом StringGrid, если оно было создано

 if( hGrid != NULL )

 ::SendMessage(hGrid, WM_CLOSE, 0,0);

}

Основным преимуществом неявной загрузки dll является именно неявность использования dll со стороны клиентского приложения. Другими словами, приложение, вызывая функции, не подозревает, что они могут находиться где-то во внешнем модуле. Результатом является упрощение кода программы. К недостаткам следует отнести тот факт, что dll находится в памяти в течение всей работы программы, неявно ее использующей. Загрузка dll осуществляется при загрузке приложения – загрузчик PE-файлов, просматривая каждую запись в таблице импорта приложения, загружает соответствующую этой записи dll. Следовательно, если используемых библиотек много, загрузка основной программы может затянуться. В случае отсутствия неявно используемой dll приложение вообще не запустится.

Итоговый алгоритм с неявным связыванием для экспорта (импорта) __cdecl-функций состоит из следующей последовательности действий (см. также Демонстрационный проект):

1. Объявить экспортируемые функции как __cdecl.

2. Поместить объявления функций в блок extern ”С”, при этом не экспортировать классы и функции-члены классов.

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

#ifdef _DLLEXPORT_

 #define _DECLARATOR_ __declspec(dllexport)

#else

 #define _DECLARATOR_ __declspec(dllimport)

#endif

и добавить макрос _DECLARATOR_ к объявлению каждой функции, например,

int _DECLARATOR_ __cdecl SumFunc( int a, int b );

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

#ifdef _MSC_VER

 #define FuncName1 _FuncName1

 #define FuncName2 _FuncName2

 #define FuncNameN _FuncNameN

#endif

Если использовался #define-трюк, то пункт 7 нужно будет пропустить.

5. Скомпилировать BCB dll.

6. С помощью impdef.exe создать .def-файл с наименованиями экспортируемых функций.

7. Если в пункте 4 воспользовались псевдонимами, удалить из .def-файла экспорта неиспользуемые наименования функций, оставив только псевдонимы.

8. Создать клиентский VC-проект.

9. Из .def-файла экспорта библиотеки при помощи утилиты lib.exe создать объектный .lib-файл формата COFF и добавить его к клиентскому VC-приложению.

10. Скопировать BCB dll и ее заголовочный файл в папку с клиентским VC-проектом.

11. В клиентском приложении подключить заголовочный файл dll.

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

Алгоритм с неявным связыванием для экспорта (импорта) __stdcall-функций

Как уже упоминалось выше, утилита lib.exe может создавать библиотеку импорта только из .def-файла экспорта, при чем lib.exe при этом никак не взаимодействует с самой dll. Однако .def-файл не содержит никакой информации, касаемой соглашений о вызове, которых придерживаются экспортируемые функции. Следовательно, и lib.exe, работая исключительно с .def-файлом, не сможет уловить, что имеет дело с __stdcall-функциями, и, как результат, не сможет в .lib-файле отобразить функции согласно Microsoft-соглашению о наименовании для __stdcall-функций. Таким образом, учитывая из предыдущего раздела, что для __cdecl-функций lib.exe генерирует вполне работоспособный .lib-файл, приходим к следующему выводу: утилита lib.exe не способна генерировать библиотеки импорта для dll, экспортирующих __stdcall-функции. Людям, пожелавшим или вынужденным (а после прочтения этого раздела думаю только вынужденным) использовать BCB dll с __stdcall-функциями в VC, этот раздел посвящается.

Исходный код BCB dll остался таким же, как в предыдущем разделе (см. Листинг 3), только ключевое слово __cdecl везде необходимо заменить ключевым словом __stdcall.

Известно, что при создании VC dll вместе с ней среда генерирует .lib-файл (библиотеку импорта), который представлен, естественно, в нужном нам формате COFF, и в котором корректно будут отображаться __stdcall-функции. Поэтому создадим (File -> New… -> Win32 Dynamic-Link Library -> OK -> An empty DLL project -> Finish) ложную (dummy) VC dll, которая будет экспортировать тот же набор функций, что и BCB dll. Реализация функций в ложной dll абсолютно не важна, важны исключительно их наименования. Помимо одинаковых наименований экспортируемых функций у ложной и исходной библиотек должны совпадать имена, поскольку .lib-файлы содержат наименования dll. Можно воспользоваться исходными текстами BCBdll, скопировав .h- и .cpp-файлы в директорию к ложной dll, затем добавив их к проекту (Project -> Add To Project -> Files…) и удалив тела всех функций. Если функция возвращает значение, то оставляем оператор return и возвращаем в соответствии с типом все, что угодно (можно 0, NULL и т.д.). Поскольку тела функций будут пустыми, большую часть директив #include с подключаемыми заголовочными файлами также можно удалить. В итоге получим согласно нашему примеру следующий код ложной dll:

Листинг 6 - Компилятор Visual C++ 6.0

ImplicitLinking_stdcallDummy.h

#ifdef _DLLEXPORT_

 #define _DECLARATOR_ __declspec(dllexport)

#else

 #define _DECLARATOR_ __declspec(dllimport)

#endif

extern "C"

{

 int _DECLARATOR_ __stdcall SumFunc(int a, int b);

 HWND _DECLARATOR_ __stdcall ViewStringGridWnd(int Count, double* Values);

}

ImplicitLinking_stdcallDummy.cpp

#define _DLLEXPORT_

#include <windows.h>

#include "ImplicitLinking_stdcallDummy.h"

int __stdcall SumFunc(int a, int b)

{

 return 0;

}

HWND __stdcall ViewStringGridWnd(int Count, double* Values)

{

 return NULL;

}

Согласно таблице 1, VC экспортирует __stdcall-функции, добавляя к их наименованию информацию о списке аргументов и символ подчеркивания. Следовательно, в объектном .lib-файле будут имена, отличные от оригинальных имен функций, объявленных в заголовочном файле, и тем более отличные от наименований функций, экспортируемых из BCB dll, так как __stdcall-функции компилятор BCB экспортирует без изменений. Избавляться от этого несоответствия будем снова посредством .def-файла. Для нашего примера он будет следующим:

DummyDef.def

libRARY ImplicitLinking_stdcall.dll

EXPORTS

 SumFunc

 ViewStringGridWnd

Строка с именем библиотеки (LIBRARY) в .def-файле не обязательна, но если она есть, то имя, указанное в ней, в точности должно совпадать с именами ложной и исходной dll. Добавляем .def-файл к VC-проекту, перекомпилируем и получаем ложную dll и необходимую нам библиотеку импорта, содержащую корректное описание экспортируемых __stdcall-функций. .lib-файл, доставшийся в наследство от ложной dll, должен добавляться (прилинковываться) к любому VC-проекту, который собирается использовать нашу исходную BCB dll.

Пример VC-приложения, импортирующего __stdcall-функции, такой же, как и в предыдущем разделе (см. Листинг 5). Не забудьте в примере подключить (#include) нужный заголовочный файл BCB dll и добавить к проекту нужную библиотеку импорта.

Алгоритм с неявным связыванием для экспорта (импорта) __stdcall-функций (см. также Демонстрационный проект, ImplicitLinkingDll_stdcall.zip):

Объявить экспортируемые функции как __stdcall.

Поместить объявления функций в блок extern ”С”. Не экспортировать классы и функции-члены классов.

Скомпилировать BCB dll.

Поскольку создать корректную библиотеку импорта с помощью утилиты lib.exe не удается, создать ложную VC dll, которая содержит такой же набор функций, как и исходная BCB dll.

Проверить идентичность названий ложной dll и dll исходной, названия должны совпасть.

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

Дабы предотвратить изменение имен функций при экспорте, добавить к VC-проекту ложной библиотеки .def-файл с секцией EXPORTS, в которой просто перечислены оригинальные наименования всех экспортируемых функций.

Скомпилировать ложную dll и получить необходимый .lib-файл с корректным отображением __stdcall-функций.

Создать клиентский VC-проект и добавить к нему полученный .lib-файл.

Скопировать BCB dll и ее заголовочный файл в папку с клиентским VC-проектом.

В клиентском приложении подключить заголовочный файл.

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

Заключение

Как вы могли убедиться, обеспечение успешного взаимодействия BCB dll и клиентского VC-приложения является нетривиальной задачей. Однако такое взаимодействие становится необходимым в случаях, когда использование VCL и C++ Builder-а при разработке отдельных частей приложения является более предпочтительным (например, в силу временных затрат). Используя описанные в статье алгоритмы, вы сможете создавать и успешно использовать BCB dll из VC-проекта.


© 2012 Рефераты, курсовые и дипломные работы.