Как реализовать определенный метод в библиотеке типов COM Delphi, чтобы он мог возвращать другой COM-объект? - PullRequest
0 голосов
/ 27 мая 2019

Я разрабатываю COM Dll из существующего Delphi устаревшего кода, который будет использоваться в C#.NET. Я добавил надлежащие интерфейсы к Type Library, и COM-сервер успешно зарегистрирован, и объекты можно увидеть и создать из среды C#. Однако некоторые из существующих классов имеют собственные конструкторы, которые необходимы. Итак, я добавил класс Helper (также как COM object), который содержит методы, которые должны создавать и возвращать другие COM-объекты, но возвращаемый тип несовместим с созданным объектом, и программа вылетает.

Я использую Мастер COM-объектов , и код в значительной степени генерируется самой Delphi. Например, определяя интерфейс ISomeObject, генерируется соответствующий ему класс CoClass с именем SomeObject и класс Delphi TSomeObject. TSomeObject реализует ISomeObject включает реализацию свойств и методов. Я собираюсь создать экземпляр TSomeObject, используя объект Helper, и использовать его в среде C#.

Код C#, который создает объект Helper и его метод, выглядит следующим образом:

    Helper helper = new Helper;
    SomeObject = helper.CreateSomeObject(Param1, Param2);

Когда я добавляю метод с типом возвращаемого значения, установленным на SomeObject, к интерфейсу IHelper в Type Library, генерируется следующий код (без тела).

function THelper.CreateSomeObject(Param1, Param2): SomeObject
begin
    Result := TSomeProject.Create(Param1, Param2); //This line is not generated by COM Object Wizard
end;

Приведенный выше код вылетает с ошибкой из-за несовместимости.

Во время отладки я понял, что тип Result равен Pointer as ISomeObject. Я пытался Type Cast выводить от TSomeProject.Create до SomeProject, используя операнд as, который не работал.

Вопрос в том, как я могу вернуть экземпляр TSomeObject через метод с типом возвращаемого значения SomeObject.

1 Ответ

1 голос
/ 27 мая 2019

Измените сигнатуру вашего метода, чтобы она возвращала интерфейс, а не экземпляр класса SomeObject.Вы можете сделать это в редакторе библиотеки типов следующим образом: enter image description here

Сгенерированный код будет:

function THelper.CreateSomeObject: ISomeObject;
begin

end;

Edit 1

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

Поэтому я попытался воссоздать ваш сценарий самостоятельно в Delphi 7.(самая старая версия Delphi, которую я установил).Я создал COM-сервер (проект ActiveX Library в Delphi), аналогичный приведенному выше.Моя реализация метода CreateSomeObject была:

function THelper.CreateSomeObject: ISomeObject;
begin
  Result := TSomeObject.Create;
end;

Реализация метода TSomeObject.HelloWorld не важна.Затем я зарегистрировал сервер через функцию IDE Run > Register ActiveX Server.После этого я создал пример консольного приложения в Delphi, импортировал библиотеку типов (Project > Import Type Library) и добавил несколько строк кода в основную программу:

uses
  ActiveX, COMTest_TLB;

var
  _Helper: IHelper;
  _SomeObject: ISomeObject;
begin
  CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
  _Helper := CoHelper.Create;
  _SomeObject := _Helper.CreateSomeObject;
  _SomeObject.HelloWorld;
end.

Консольное приложение завершилось без сбоев или неожиданного результата.Все идет нормально.Затем я создал образец консольного приложения C # .NET (.NET 4.5.2) со ссылкой на мою библиотеку COMTest:

using System;
using COMTest;

class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        var helper = new Helper();
        var someObject = helper.CreateSomeObject();
        someObject.HelloWorld();
    }
}

И действительно, приложение зависало с AccessViolationException.Я быстро настроил отладку COM-сервера, настроив хост-приложение на консольное приложение .NET и включив символы удаленной отладки в настройках компоновщика проекта (я уверен, что вы уже поняли это).Создание экземпляра TSomeObject прошло гладко, но присвоение Result не удалось.

Существует некоторая магия компилятора при назначении значения переменной управляемого типа (в данном случае - интерфейс).Он начинается с очистки пункта назначения, который в основном является вызовом _Release, если пункт назначения не является nil.И, к моему удивлению, в случае клиента консольного приложения .NET это не так!Поэтому я изменил реализацию так:

function THelper.CreateSomeObject: ISomeObject;
begin
  Pointer(Result) := nil;
  Result := TSomeObject.Create;
end;

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

Здесь вы можете найти некоторые замечательные ресурсы, связанные с разработкой COM в Delphi.http://www.techvanguards.com/com/

Отказ от ответственности: я никоим образом не связан с этим сайтом, я просто нахожу его невероятно полезным.

Редактировать 2

Методы интерфейса COM должны возвращать HRESULT по соглашению.Это механизм по умолчанию в COM для сообщения об ошибках.Возврат дополнительных значений из метода должен осуществляться через параметры с модификатором [Out].В качестве альтернативы параметр может быть помечен модификатором [Out, RetVal] (обычно последним), чтобы указать возвращаемое значение метода.Обратите внимание, что параметры [Out] передаются по ссылке, которую необходимо указать с помощью дополнительного символа звездочки (*), добавляемого к имени типа в редакторе библиотеки типов.Таким образом, ISomeObject* становится [Out] ISomeObject**.

. Delphi поддерживает safecall соглашение о вызовах и, таким образом, может исключить HRESULT возвращаемое значение из сигнатуры вашего метода, обернув любое необработанное исключение в вашем методе и передав егообратно в регистр EAX , позволяя параметру [Out, RetVal] стать возвращаемым значением.Но это поддерживается только для двойных интерфейсов (см. Вкладку Flags интерфейса), которые получены из IDispatch.Чтобы избежать реализации IDispatch методов, вы можете преобразовать свой легкий COM-объект в объект автоматизации (TAutoObject), если вы этого еще не сделали.Этого можно достичь, выбрав опцию «Объект автоматизации» вместо «COM-объект» при добавлении нового элемента в вашу библиотеку ActiveX.

Так выглядит определение метода при преобразовании в safecall: Type library editor

А вот сгенерированный код с тривиальной реализацией:

type
  THelper = class(TAutoObject, IHelper)
  protected
    function CreateSomeObject: ISomeObject; safecall;
  end;

function THelper.CreateSomeObject: ISomeObject;
begin
  Result := TSomeObject.Create;
end;
...