Создание в среде Borland C++ Builder dll, совместимой с Visual C++ (45828)

Посмотреть архив целиком

Создание в среде 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) MyFunction(int Param);

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

Соглашение о вызове

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 функции как экспортируемые не помечаются. Подробнее о таблице экспорта можно прочитать в статье по адресу http://www.rsdn.ru/article/baseserv/pe_coff.xml.

ExplicitDll.cpp

#include

#include

#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;

}


Случайные файлы

Файл
29435.rtf
240-0317.DOC
43167.rtf
168789.rtf
128906.rtf




Чтобы не видеть здесь видео-рекламу достаточно стать зарегистрированным пользователем.
Чтобы не видеть никакую рекламу на сайте, нужно стать VIP-пользователем.
Это можно сделать совершенно бесплатно. Читайте подробности тут.