Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Hello, World!
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  21:35[Войти] | [Зарегистрироваться]

Об использовании в Delphi классов, созданных в MS VC++

Роман Гордон
дата публикации 15-08-2006 04:02

Об использовании в Delphi классов, созданных в MS VC++

(Экспорт открытых методов чужого класса в свою программу)

1. Требования

Предполагается: знание Delphi на уровне использования DLL, а также написания собственных; знание С++ на уровне написания простейшего приложения в среде MS VC++.

Желательно: общее понимание соглашений о вызове функций; общее представление о способах передачи параметров и возврата значения.
Используемые инструменты: Borland Delphi 6, MS VC++ 6.0

2. Обоснование

Необходимость использования чужого кода в своей программе возникает регулярно. Вставка готовых удачных решений позволяет не изобретать велосипед заново. В хороших случаях чужой код написан на том же языке, что и свой, либо решение оформлено в виде DLL или компонента. Однако, бывают случаи похуже. Например, приобретается PCI-плата расширения с программным обеспечением для нее, а это ПО оказывается файлами исходного кода на С или С++, в то время как проект уже начат на Delphi, и, кроме того, в команде разработчиков С++ знают плохо.

3. Варианты решения

В принципе, можно весь проект писать на С++. Если такая возможность есть – не исключено, что это лучший выход. Но пользовательский интерфейс в Delphi разрабатывается быстрее, чем в MS VC++ (не только мое мнение, но хорошую цитату не нашел), кроме того, в группе могут плохо знать С++. И если даже С++ знают хорошо, но проект уже начат на Delphi, переписывать готовое – значит, тратить неоплачиваемое время.

Можно переписать код С++ на Delphi. Для этого требуется время, и, возможно, немалое, а главное – знание С++ на уровне существенно выше начального («читаю со словарем»). При этом, многие языковые конструкции С++ не имеют прямых аналогов в Delphi, и их перенос чреват появлением ошибок, в том числе, совершенно дурацких, и потому трудноотлавливаемых. В частности, прекрасный пример из обсуждения статьи «ЯП, ОПП и т.д. и т.п. в свете безопасности программирования»:

for(;P('\n'),R-;P('|')) for(e=C;e-;P('_'+(*u++/8)%2))P('| '+(*u/4)%2);

Можно попробовать засечь время и перевести это на Pascal. Станет примерно понятно, сколько времени уйдет на перевод класса, где подобные конструкции не единичны.

Можно воспользоваться интерфейсами и технологией СОМ (пожалуй, точнее – моделью СОМ и технологией ActiveX). Но – вот цитата из [1], глава « Модель многокомпонентных объектов»:
«И еще одно замечание: не думайте, что путь будет легким. Крейг Брокшмидт говорил, что перед тем, как он начал понимать эти концепции, у него был « шесть месяцев туман в голове.» Минимальная предпосылка – исчерпывающее знание языка С++.» Конец цитаты. И, хотя «модель СОМ предоставляет унифицированный, открытый, объектно-ориентированный протокол связи между программами » (цитата оттуда же), она требует такой квалификации от программиста, которая редко встречается в среде непрофессионалов.

Можно реализовать код, который необходимо использовать, в виде DLL. Один из существенных плюсов DLL – неважно, на каком языке она написана, если соблюдены некоторые условия, такие, как соглашение о вызове и совместимость типов.

С учетом того, что в группе разработчиков в основном о С++ поверхностные представления, а СОМ – незнакомая аббревиатура, и, при этом, срок сдачи проекта – традиционно – вчера, ничего лучше варианта с DLL у нас придумать не получилось.

4. Немного теории

Передать, точнее, экспортировать несколько функций из DLL – не проблема. Приводим типы, соглашения о вызовах, заполняем список экспортируемых функций – и всё (в основном). Об этом написано немало, например, в [2], в параграфе «Использование DLL, разработанных в С++».

Экспортировать класс несколько сложнее. Даже если и DLL, и основная программа написаны на Delphi, возникают проблемы с распределением памяти, которые решаются использованием модуля ShаreMem, первым в списке uses как проекта, так и DLL [2, 3]. Причем, этот модуль можно, в принципе, заменить самодельным менеджером памяти [там же, 3]. Но как использовать ShаreMem, если DLL написана на другом языке, или написать собственный менеджер для двух языков? Наверное, можно и так, но, напоминаю, срок сдачи проекта – вчера. Если есть и другие возражения, часто время – определяющий фактор.

Можно создавать экземпляр класса при загрузке DLL, ликвидировать при выгрузке (используя события DLL_PROCESS_ATTACH/DETACH), а для методов класса (функций- членов, раз уж класс на С++) написать однострочные функции-обертки, не являющиеся членами, а просто вызывающие соответствующие функции-члены. Некрасиво, и много лишней работы. Попробуем все же экспортировать класс.

В [2], сказано: «Библиотеки DLL не могут экспортировать классы и объекты, если только вы не используете специализированную технологию Microsoft под названием СОМ или какую-либо другую усовершенствованную технологию». Впрочем, там же есть замечание: «На самом деле объекты могут быть переданы из DLL в вызывающую программу в случае, если эти объекты спроектированы для использования в качестве интерфейсов или чистых абстрактных классов». Кроме этого замечания, в [2] об экспорте объектов почти всё, но уже хорошо, что есть шанс «сделать это по-быстрому».

И, наконец, в [4] находим параграф «Экспорт объектов из DLL». Там сказано: «К объекту и его методам можно получить доступ, даже если этот объект содержится внутри DLL. Но к определению такого объекта внутри DLL и его использованию предъявляются определенные требования. Иллюстрируемый здесь подход применяется в весьма специфических ситуациях, и, как правило, такого же эффекта можно достичь путем применения пакетов или интерфейсов». Наша ситуация вполне специфическая; пакеты здесь неприменимы, так как они все же для использования с Delphi, про использование интерфейсов и СОМ уже сказано, а использовать интерфейсы без СОМ вне Delphi, судя по [2], нельзя.

И, пожалуй, самое важное из [4]:
«На экспорт объектов из DLL накладываются следующие ограничения:
  1. Вызывающее приложение может использовать лишь те методы объекта, которые были объявлены как виртуальные.
  2. Экземпляры объектов должны создаваться внутри DLL.
  3. Экспортируемый объект должен быть определен как в DLL, так и в вызывающем приложении с помощью методов, определенных в том же порядке.
  4. Из объекта, содержащегося вутри DLL, нельзя создать объект-потомок.
Здесь перечислены лишь основные ограничения, но возможны и некоторые другие. »

Далее там рассказывается о работе с DLL, написанной в Delphi, но полученной информации достаточно для работы с DLL, создаваемой в MS VC++.

Мастер MS VC++ позволяет создать обычную (regular) DLL и DLL-расширение (extension). Обычная DLL может экспортировать только С-функции и не способна экспортировать С++-классы, функции-члены или переопределенные функции [1]. Стало быть, надо использовать DLL-расширение. Мастер создаст заготовку, затем в каталог проекта надо будет скопировать два файла – заголовочный и файл кода (*.h и *.cpp), содержащие класс, с экземпляром которого предстоит поработать. Затем подключить их к проекту DLL и немного доработать в соответствии с указанными ограничениями.

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

Далее в файл .срр проекта DLL нужно добавить функции создания и ликвидации объекта. Пример в [4] обходится без функции ликвидации, видимо, потому, что в приведенном там примере и DLL и импортирующее приложение написаны на Delphi, так что можно освободить память методом Free, который есть у всех наследников TObject и отсутствует у объектов С++, не имеющих общего класса-предка. Функция создания объекта должна просто вызывать конструктор, передать ему полученные от приложения параметры и вернуть указатель на созданный объект. Функция ликвидации принимает указатель на объект и вызывает деструктор. И обязятельно вписать эти функции в список экспортируемых.

И всё! Пятнадцать минут работы, при минимальном знании С++. Остальное в Delphi.

В импортирующей программе необходимо объявить класс, содержащий виртуальные открытые функции в том же порядке. Также необходимо объявить сложные структуры данных, используемые в DLL и передаваемые через ее границу в любом направлении. Имеются в виду структуры С++, они же записи Паскаля. И, конечно же, нужно объявить импортируемые функции создания и уничтожения класса. Теперь для создания экземпляра класса вызывается соответствующая функция DLL, когда объект перестает быть нужным – снова вызывается соответствующая функция DLL, а методы вызываются традиционно – «Объект.Метод(Параметры)». При этом обзывать методы в Delphi можно как угодно, важен лишь порядок их следования и списки параметров.

Если в С++ функция-член возвращает значение, в Delphi соответствующий метод должен быть тоже функцией. Если же функция-член возвращает void, в Delphi соответствующий метод – процедура.

Если в С++ параметр передается по значению, то и в Delphi тоже, если же по ссылке (то есть как указатель), то в Delphi такой параметр должен быть объявлен с ключевым словом var.

Чуть подробнее о параметрах и их типах. Практически везде, где говорится о DLL, упоминается, что, если хотите обеспечить совместимость DLL с программами на других языках, необходимо обеспечить совместимость типов. То есть, стремиться использовать стандартные типы ОС Windоws. Такие типы, как string или file вообще не совместимы с С++, с TDateTime можно поэкспериментировать, вообще-то, он соответствует стандарту, принятому в OLE-автоматизации ([3]). Опять же, [3] заявляет о соответствии типов single и double Delphi с float и double в С++ соответственно. Хотя в [5] есть такой совет со ссылкой на News Group: «Если вы создаете DLL не с помощью Delphi, то избегайте чисел с плавающей точкой в возвращаемом значении. Вместо этого используйте var-параметр (указатель или ссылочный параметр в С++) Причина кроется в том, что Borland и Microsoft применяют различные способы возврата чисел с плавающей точкой. Borland С++ и Delphi могут использовать один и тот же метод».

Таблица соответствия типов Delphi и С++:
Тип С++БайтовТип Delphi
int?(4)integer
unsigned int?(4)cardinal
char, __int8 1 shortint
short, __int162smallint
long, __int32 (int)4 longint (integer)
__int648int64
unsigned char1byte
unsigned short2word
unsigned long4longword
float4single
double8double
char * PChar

5. Немного практики

Для примера будет использован несложный и бесполезный класс на С++, состряпанный на ходу. В MS VC++ создадим проект, используя MFC AppWizard(exe), без использования представления «Документ-вид», на основе диалога, и обзовем его «example_exe». Добавим два новых файла – example.cpp и example.h.

Файл example.h:
//*****************************************************************************
// традиционный финт ушами во избежание
// повторного включения файла .h
#if !defined(EXAMPLE__INCLUDED)
#define EXAMPLE__INCLUDED
// введем парочку структур для демонстрации работы с ними
typedef struct
{
  int n;
  int i;
  short j;
  char k;
}struct_1;

typedef struct
{
  int n2;
  short a[3];
}struct_2;

// Класс-пример. Ничего полезного, просто демонстрация.
class CExample
{
private:
  int Field;
  CString Name;
  void Show(CString str);
public:
  // конструктор и деструктор, как полагается
  CExample(int F, CString N);
  ~CExample();
  // просто сообщение
  void Message(CString str, int Digit);

  // «процедура» и «функция»
  void Proc(int * Digit);
  int Func(int Number);

  // работа с закрытым полем
  void SetF(int F);
  int GetF();

  // работа со структурами
  struct_2 * Struct1to2(struct_1 s1);
};
#endif //if !defined(EXAMPLE__INCLUDED)
//*****************************************************************************

В классе есть пара закрытых полей, закрытая функция-член, набор открытых функций. Конструктор принимает два параметра. Строковый параметр будем интерпретировать, как имя объекта. Функция Message нужна для отображения на экране хоть каких-то сообщений, демонстрирующих, что что-то происходит. Proc имитирует процедуру, то есть, не возвращает значения, зато изменяет что-то в программе, в нашем случае, переданный параметр. Func и есть функция, то есть, ничего не изменяет, зато вычисляет некоторое значение и возвращает его. Плюс здесь же установщик и считыватель закрытого поля, а также простенькая демонстрация работы со структурами.

Файл example.срр:
//*****************************************************************************
#include "stdafx.h"
#include "Example.h"

// конструктор инициализирует два закрытых поля
// и выдает сообщение об успешном создании
// при помощи закрытой функции
CExample::CExample(int F, CString N)
{
  this->Field = F;
  this->Name = N;
  this->Show(N + " Created successfully");
}

// деструктор только сообщcellpadding ает о самоликвидации
CExample::~CExample()
{
  this->Show("Deleted successfully");
}

// закрытая функция, по сути – оболочка MessageBox'а
// заголовком бокса будет имя класса
void CExample::Show(CString str)
{
  ::MessageBox(NULL, str, this->Name, MB_OK);
}

// открытая функция, выводит строку и число в десятичном виде.
void CExample::Message(CString str, int Digit)
{
  str.Format(str + " %d", Digit);
  this->Show(str);
}

// "процедура" не возвращает значение, зато изменяет параметр
void CExample::Proc(int * Digit)
{
  *Digit *= 2;
}

// "функция" не изменяет параметр, зато возвращает значение
int CExample::Func(int Number)
{
  int Result;
  Result = Number * 2;
  return Result;
}

// банально присваиваем значение параметра закрытому полю.
void CExample::SetF(int F)
{
  this->Field = F;
}

// еще банальнее...
int CExample::GetF()
{
  return this->Field;
}

// присваиваем значения полей одной структуры полям другой
struct_2 * CExample::Struct1to2(struct_1 s1)
{
  struct_2 * s2 = new struct_2;

  s2->n2 = s1.n * 2;
  s2->a[0] = s1.i;
  s2->a[1] = s1.j;
  s2->a[2] = s1.k;
  return s2;
}
//*****************************************************************************

Для примера более, чем достаточно. Теперь надо посмотреть, как это работает.

В файле Example_exeDlg.h в описании класса CExample_exeDlg где-нибудь в секции public надо вписать
CExample * ex;
то есть, объявить переменную-член, указатель на наш учебно-тренировочный класс, и в конструкторе Example_exeDlg вписать
ex = NULL;
Можно ex сделать и не членом, в принципе, и инициализировать при объявлении. И, конечно, не забыть наверху этого же файла вклеить заголовочный файл класса:
#include "Example.h"

На диалоговую форму накидаем кнопок и создадим их обработчики:

void CExample_exeDlg::OnBtCreate()
{
if (ex == NULL)
ex = new CExample(7, "Example");
}
Если объект еще не создан – создаем и инициализируем пару закрытых полей.

void CExample_exeDlg::OnBtDestroy()
{
delete ex;
ex = NULL;
}
Освобождаем память и устанавливаем указатель в «пусто»

void CExample_exeDlg::OnBtMessage()
{
ex->Message("Any digit - ", 3);
}
Демонстрационное сообщение.

void CExample_exeDlg::OnBtProc()
{
int k = 5;
ex->Message("before K = ", k);
ex->Proc(&k);
ex->Message("after K = ", k);
}
Показываем в последовательных сообщениях, какое значение переменная имела до выполнения процедуры, и какое стала иметь после.

void CExample_exeDlg::OnBtFunc()
{
int k = 5, l;
ex->Message("before K = ", k);
l = ex->Func(k);
ex->Message("after K = ", k);
ex->Message("Result of Func = ", l);
}
Примерно то же самое – значение до выполнения, значение после выполнения и результат выполнения.

void CExample_exeDlg::OnBtGet()
{
ex->Message("", ex->GetF());
}

void CExample_exeDlg::OnBtSet()
{
ex->SetF(ex->GetF() + 1);
}

Эти две – без комментариев. Должно быть так все понятно... Функцию для работы со структурами в этом проекте не буду трогать, не интересно, тут весь фокус, как их передать через границу DLL. Кроме того, не будем возиться с полями ввода, а передадим параметры непосредственно в коде. Наглядность это уменьшает ненамного, а работы меньше. Еще момент – ID кнопок по-умолчанию поменял с BUTTON1 на BT_CREATE и так далее, для наглядности.

Всё! На форме только кнопки, вывод информации через MessageBox. Можно проверить работу.

Сделаем DLL для этого класса. В MS VC++ создадим проект, используя MFC AppWizard(dll), назовем «example_dll». В каталог этого проекта копируем готовые example.cpp и example.h, добавим их к проекту. Будем изменять, в соответствии с выясненными правилами. Начнем с объявления класса:

// Можно использовать AFX_EXT_CLASS, это синонимы.
class AFX_CLASS_EXPORT CExample

Затем из
void Message(CString str, int Digit);
делаем
virtual void __stdcall Message(CString str, int Digit);
и так со всеми открытыми методами, кроме конструктора и деструктора. И на этом бы всё, да CString – несовместимый, опасный тип. Меняем объявление:
virtual void __stdcall Message (char * str, int Digit);
и определение:
void CExample::Message (char* str, int Digit)
{
//добавляем CString:
CString s = str;
//и немного изменяем работу со строкой:
//str.Format(str + " %d", Digit);

s.Format(s + " %d", Digit);
//this->Show(str);
this->Show(s);
}
то есть, приходим к совместимому типу «указатель на нуль-терминальную строку», но, чтобы не потерять функциональность класса CString, объявляем локальную переменную этого класса и используем ее. Осталось еще полторы детали.
Первая деталь – в файле example_dll.cpp в конце пишем:

// вставляем функцию инициализации..
CExample * __stdcall InitExample(int F, char * N)
{
CExample * ex;
// транслируем конструктору принятые параметры
ex = new CExample(F, N);
// и возвращаем указатель на созданный объект
return ex;
}

// ..и ликвидации
void __stdcall DelExample(CExample * ex)
{
delete ex;
}

И половина детали – в файле EXAMPLE_DLL.def в конце дописываем пару строчек, так, чтоб получилось:

;*****************************************************************************
; EXAMPLE_DLL.def : Declares the module parameters for the DLL.

LIBRARY "EXAMPLE_DLL"
DESCRIPTION 'EXAMPLE_DLL Windows Dynamic Link Library'

EXPORTS
; Explicit exports can go here
InitExample
DelExample
;*****************************************************************************

После компиляции DLL готова. Подготовим проект в Delphi, чтобы продемонстрировать ее работу. Создадим проект «Example_Delphi», и в модуле главной формы, перед объявлением класса формы, впишем четыре типа. Два - структуры struct1 и 2:

TRec1 = record
n : integer;
i : integer;
j : smallint;
k : shortint;
end;
TRec2 = record
n2 : integer;
a : array[0..2] of smallint;
end;

Третий – указатель на вторую структуру:

PRec2 = ^TRec2;

А четвертый – наш класс, с которым будем работать:

TExample = class
public
procedure Mess_(str : PChar; Digit : integer); virtual; stdcall; abstract;
procedure Proc(var Digit : integer); virtual; stdcall; abstract;
function Func(Number : integer): integer; virtual; stdcall; abstract;
procedure SetF(F : integer); virtual; stdcall; abstract;
function GetF(): integer; virtual; stdcall; abstract;
function Struct1to2(rec1 : TRec1): PRec2; virtual; stdcall; abstract;
end;

Директивы virtual и stdcall в пояснениях не нуждаются. О них сказано выше. А зачем abstract? Очень просто. Без нее компилятор будет ругаться на неправильное упреждающее объявление функции, ведь описания ее у нас нет, описание – в DLL. Директивы должны идти именно в этом порядке. Иначе компилятору не нравится.

Обратите внимание на первый метод. Остальные названы так же, как и в С++, но слово Message в Delphi зарезервированное, и использовать его не по назначению не стоит. Хорошо, назовем иначе, важно, что она стоит на первом месте среди виртуальных функций, как и в С++, значит, ее найдут по номеру в VMT. Имя роли не играет.

Еще надо добавить объявление экспортируемых функций создания/ликвидации, в конце секции interface:

function InitExample(F: integer; N : PChar) : TExample; stdcall;
external '..\Example_DLL\debug\Example_DLL.dll';
procedure DelExample(ex : TExample); stdcall;
external '..\Example_DLL\debug\Example_DLL.dll';

Здесь предполагается, что DLL лежит там, где и появилась после компиляции, а директории «Example_dll» и «Example_Delphi» имеют общую родительскую. Больше нигде ее искать не будут. Если же указать только имя, приложение будет искать библиотеку в своей папке, папках WINDOWS, SYSTEM32 и прописанных в переменной окружения PATH. Впрочем, это азбука.

Всё, класс можно использовать. Давайте опять наделаем кнопок, а вывод в поле Memo, благо, в Delphi с ним работать быстрее и проще, чем в MS VС++.

Вот обработчики кнопок:
procedure TForm1.Button1Click(Sender: TObject);
begin
  if not Assigned(Self.ex) then
    Self.ex := InitExample(10, 'Ex_Delphi');
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  DelExample(Self.ex);
  Self.ex := nil;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  Self.ex.Mess_(PChar('Некоторая цифра – '), 5);
end;

procedure TForm1.Button4Click(Sender: TObject);
var
  j : integer;
begin
  j := 15;
  Self.Memo1.Lines.Add('j До – ' + IntToStr(j));
  Self.ex.Proc(j);
  Self.Memo1.Lines.Add('j После – ' + IntToStr(j));
end;

procedure TForm1.Button5Click(Sender: TObject);
var
  j : integer;
begin
  j := 20;
  Self.Memo1.Lines.Add('j До – ' + IntToStr(j));
  Self.Memo1.Lines.Add('Результат – ' + IntToStr(Self.ex.Func(j)));
  Self.Memo1.Lines.Add('j После – ' + IntToStr(j));
end;

procedure TForm1.Button6Click(Sender: TObject);
begin
  Self.Memo1.Lines.Add(IntToStr(Self.ex.GetF));
end;

procedure TForm1.Button7Click(Sender: TObject);
begin
  Self.ex.SetF(Self.ex.GetF + 1);
end;

То же самое, что и в С++, и работает так же. Что и требовалось. И добавим кнопку для функции, которая принимает и возвращает структуры. Вот ее обработчик:

procedure TForm1.Button8Click(Sender: TObject);
var
  s1 : TRec1;
  s2 : PRec2;
begin
  // здесь компилятор будет ругаться, но в данном
  // случае это не важно. Просто посмотрим, что
  // до инициализации s2 - это всякая чушь...
  Self.Memo1.Lines.Add('s2 до:' + #9 +
    IntToStr(s2.n2) + #9 +
    IntToStr(s2.a[0]) + #9 +
    IntToStr(s2.a[1]) + #9 +
    IntToStr(s2.a[2]) );

  // инициализация s1
  s1.n := 10;
  s1.i := 1;
  s1.j := 2;
  s1.k := 3;

  // если функция возвращает указатель на запись (структуру) -
  // надо подготовить указатель. Это вам не класс.
  // s2 - типа PRec2, а не TRec2
  s2 := Self.ex.Struct1to2(s1);

  // ... а потом - то, что мы требовали.
  Self.Memo1.Lines.Add('s2 после:' + #9 +
    IntToStr(s2.n2) + #9 +
    IntToStr(s2.a[0]) + #9 +
    IntToStr(s2.a[1]) + #9 +
    IntToStr(s2.a[2]) );
end;

Что делает функция – понятно, тут другая тонкость. Конструктор возвращает (в коде на С++) указатель на класс, а мы присваиваем возвращаемое значение переменной, которая, вроде бы, не указатель. Struct1to2 тоже возвращает указатель – и его надо подготовить. Это объясняется в [3]: «Объект – это динамический экземпляр класса. Объект всегда создается динамически, в «куче», поэтому ссылка на объект фактически является указателем (но при этом не требует обычного оператора разыменования «^»). Когда вы присваиваете переменной ссылку на объект, Delphi копирует только указатель, а не весь объект. Используемый объект должен быть освобожден явно.»

А в С++ структура отличается от класса несколько меньше, и работа с ними почти одинакова.

И еще пара тонкостей. Если в DLL добавить еще виртуальную функцию-член, обязательно в конце, после имеющихся, такая DLL будет совместима со старой программой, где в абстрактном классе эта функция не объявлена. И если изменить имеющуюся функцию, добавив в конец параметров параметр по-умолчанию, такая DLL будет совместима со старой программой, где в абстрактном классе эта функция не имеет такого параметра.

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

6. Заключение

При таком подходе нельзя обращаться к полям данных напрямую. Хотя, это не проблема, можно использовать функции-считыватели и установщики. Нельзя использовать закрытые функции. А зачем их использовать? Для использования есть открытые. Те, кто знакомы с моделью СОМ, могут сказать, что это извращенный вариант нормальной технологии. Но для создания полноценного СОМ-сервера нужно несколько больше знаний. Показанный способ позволяет использовать открытые методы класса, не вдаваясь в подробности реализации класса и не затрачивая много времени. Это дает возможность быстро получить работоспособный вариант программы, и уже потом доводить ее до ума. А то и просто получить удовлетворительный результат.

7. Используемая литература

  1. Круглински Д., Уингоу С., Шеферд Дж.
    Программирование на Microsoft Visual C++ 6.0 для профессионалов /Пер. с англ. – СПб: Питер; М.:Издательско-торговый дом «Русская редакция», 2001 – 864 с.:ил.
  2. Кэнту М.
    Delphi 6 для профессионалов (+СD). – Питер, 2002. – 1088с.: ил.
  3. Лишнер Р.
    Delphi. Справочник. – Пер. с англ. – СПб: Символ-Плюс, 2001. – 640 с., ил.
  4. С. Тейксейра, К. Пачеко
    Delphi 5. Руководство разработчика.
  5. Озеров В.
    Delphi. Советы программистов. – СПб: Символ-Плюс, 2002.-912 с.,ил.


К материалу прилагаются файлы:


Смотрите также материалы по темам:
[Подключение DLL, написанных на других языках]

 Обсуждение материала [ 19-09-2008 02:04 ] 3 сообщения
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования