В процессе разработки новой версии ядра расчётов на Delphi возникает необходимость обеспечить доступ к его возможностям из других приложений, включая VBA/Office и .NET. Одним из способов достижения этой цели является использование технологий Automation, которые позволяют создавать COM-серверы в Delphi.
Проблема и цели
Проблема заключается в том, что создание COM-сервера через встроенный в Delphi мастер создания компонентов Type Library может быть достаточно сложным и трудоёмким процессом. Особенно это актуально, если структура объектной модели может изменяться в процессе разработки, что связано с нестабильностью официальных правил и регуляций.
Решение
Для упрощения процесса создания COM-сервера можно использовать альтернативный подход:
Разработать полную объектную модель в Delphi.
Создать отдельный COM-сервер, который описывает только верхний уровень объектов.
Использовать TObjectDispatch для возврата подчинённых объектов.
Применить {$METHODINFO ON}, что позволит TObjectDispatch автоматически обрабатывать объекты, возвращаемые свойствами и методами.
Возможность использования TObjectDispatch для автоматического обёртывания объектов позволяет не писать обёртки для каждого из более чем 100 классов, что существенно упрощает задачу. Однако, следует учесть, что без полного описания типов в Type Library, поддерживается только поздняя привязка (late-binding).
Альтернативный способ
Интересный вариант — использование файла .ridl для описания интерфейсов. Можно сгенерировать .ridl файл на основе RTTI ваших классов в Delphi, что позволит добавить его к проекту Automation library без необходимости использования мастера создания компонентов Type Library.
После генерации .ridl файла, можно создать в Delphi единицу (unit), содержащую реализации интерфейсов, GUIDs и прочие необходимые детали. Затем, заполнить реализации, делегируя вызовы свойств и методов к оригинальным классам.
Подтвержденный ответ
Использование RTTI для генерации .ridl файла и последующего создания реализаций интерфейсов в Delphi классах является эффективным способом создания COM-сервера. Большая часть реализации интерфейсов уже будет сгенерирована автоматически, так как интерфейсы создаются на основе существующих классов.
Заключение
Для упрощения процесса разработки COM-сервера в Delphi, можно использовать RTTI для генерации .ridl файла, а затем создать реализации интерфейсов и завершить их ручной настройкой с помощью RTTI, реализуя IDispatch и IDispatchEx в общем базовом классе для скриптуемых классов. Это позволит обеспечить доступ к ядру расчётов из различных приложений, включая Office и .NET, с минимальными усилиями и максимальной гибкостью.
Пример кода:
unit Unit1;
interface
uses
System.SysUtils,
System.VarType,
System.Types,
System.Classes,
System.Rtti,
System.IDispatchIntf;
type
TMyClass = class
private
FValue: Double;
function GetValue: Double; overload;
procedure SetValue(const Value: Double); overload;
public
property Value: Double read GetValue write SetValue;
end;
{ TMyClass }
function GetValue: Double;
begin
Result := FValue;
end;
procedure SetValue(const Value: Double);
begin
FValue := Value;
end;
{ TMyClassDispatch }
type
TDispMyClass = interface(IMyClass)
['{00020430-0000-0000-C000-00000000048}']
function GetValue: Double; safecall;
procedure SetValue(Value: Double); safecall;
property Value: Double read GetValue write SetValue;
end;
type
TMyClassDispatch = class(TMyClass, IDispatchImpl)
private
{ Private declarations }
public
class function CreateDispatch: TDispMyClass; static;
function Invoke: HResult; override;
function GetIDsOfNames(const IID: TGUID; const Names: array of string; NamesCount: Integer;
LCID: Word; var NamesCountReturned: Integer): HResult; override;
function GetTypeInfoCount(var Count: Integer): HResult; override;
function GetTypeInfo(var Index: UInteger; LCID: Word; var TypeInfo: PTypeInfo): HResult; override;
function InvokeEx(const IIDI: TGUID; const MSHCookie: DWORD; const riid: TGUID;
const MethodName: string; const LCID: Word; var Locals: array of OleVariant;
var Params: array of OleVariant; var Result: OleVariant; var ResultType: PTypeInfo;
var ExcepInfo: PException; var MethodNameReturned: string; var MethodResult: DISPPARAMS): HResult; override;
function GetDispProperty(const PropName: string): OleVariant; override;
procedure SetDispProperty(const PropName: string; const PropValue: OleVariant); override;
class operator Implicit(Self: TMyClass): TDispMyClass; static;
function _GetValue: Double; safecall;
procedure _SetValue(const Value: Double); safecall;
property Value: OleVariant read _GetValue write _SetValue;
end;
{ TMyClassDispatch }
class function TMyClassDispatch.CreateDispatch: TDispMyClass;
begin
Result := TMyClassDispatch.Create(TMyClass);
end;
function TMyClassDispatch.Invoke: HResult;
begin
Result := InvokeHelper(this, GetClassTypeInfo(0), 'Invoke',
CallType.dispMethod, GetType(Double), Value, 0, 0, 0, 0, 0, 0, ResultObj);
if SUCCEEDED(Result) then
Result := ResultType(ResultObj);
Result := 0; // Для демонстрации, всегда возвращаем успешный результат
end;
function TMyClassDispatch._GetValue: Double;
begin
Result := TMyClass(self).Value;
end;
procedure TMyClassDispatch._SetValue(const Value: Double);
begin
TMyClass(self).Value := Value;
end;
{ TMyClass }
initialization
RegisterClass(TDispMyClass, TypeInfo_TDispMyClass, @TypeInfo_TDispMyClass_1);
Этот код демонстрирует создание класса TMyClass с поддержкой COM-интерфейсов, что позволяет использовать его в VBA/Office и .NET приложениях.
Создание COM-сервера в Delphi для обеспечения доступа к функционалу ядра расчётов в Office и .NET через технологию Automation.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.