Созданный COM-компонент становится недействительным после выхода из метода (но не из его области действия)

#delphi #com #delphi-xe2 #reference-counting

#delphi #com #delphi-xe2 #подсчет ссылок

Вопрос:

В настоящее время я тестирую два внешних COM-компонента. У меня большая проблема с одним из них, но я действительно не могу найти причину такого поведения. Позвольте мне привести несколько примеров.

 const
  CLASS_SomeClas: TGUID = '{SomeGUID}';
type

ISomeInterface = interface(IDispatch)
  ['{SomeGUID}']
  function SomeMethod(const AInput: WideString): WideString; safecall;
end;

TWrappingClass = class(TObject)
strict private
  FInstance: ISomeInterface;
  procedure CreateInstance;
public
  procedure DoYourActualJob;
end;

procedure TWrappingClass.CreateInstance;
begin
  FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
  dbg(FInstance._AddRef); // Debugs 3
  dbg(FInstance._AddRef); // Debugs 4
  dbg(FInstance.Release); // Debugs 3
  dbg(FInstance._AddRef); // Debugs 4
  FInstance.SomeMethod(''); //Runs as expected
end;

procedure TWrappingClass.DoYourActualJob;
begin
  CreateInstance;
  dbg(FInstance._AddRef); //Debugs -1!
  FInstance.SomeMethod(''); //AV
end;
  

Как указано в примере, экземпляр становится недействительным после выхода из CreateInstance метода. Компонент предназначен для работы со многими последовательными вызовами SomeMethod и он действительно работает при вызове внутри одного метода.
Может ли кто-нибудь подсказать мне, что там на самом деле происходит, почему мой экземпляр становится недействительным? Это проблема с моим кодом, с Delphi или с кодом компонента? Когда я меняю реализацию TWrappingClass на другого поставщика (то есть я меняю оба ISomeInterface и CLASS_SomeClass ), тогда все работает нормально.

РЕДАКТИРОВАТЬ: поведение не меняется, когда я даже не вызываю SomeMethod . То есть после того, как я ухожу, CreateInstance вызов _AddRef возвращает -1. Компонент, который я тестирую, находится здесь, CADEditorX Вероятно, мне не разрешено присоединять OCX, не нарушая его лицензию.

Комментарии:

1. Есть ли у нас какой-либо способ воспроизвести это? Похоже, что в коде Q. Проблем нет.

2. Единственный возможный способ, которым это могло произойти, — это если FInstance.SomeMethod() уменьшается внутреннее значение refcount экземпляра до точки, в которой экземпляр освобождает себя. Это означает, что COM-объект, вероятно, неисправен из-за нарушения семантики refcount.

3. @MichalSzczygiel: если вы можете воспроизвести проблему, используя другой COM-объект (в Win32 API их полно!), или даже вообще без использования какого-либо внешнего COM-объекта, просто напишите в своем коде свой собственный объект с интерфейсом, тогда это ошибка компилятора. Но если вы можете воспроизвести проблему, используя только этот конкретный COM-объект и никакой другой, тогда виноват COM-объект, и вам нужно обратиться к поставщику. Учитывая, что Delphi изначально поддерживает пересчет COM и интерфейса более десяти лет без проблем, это вряд ли может быть ошибкой компилятора.

4. Легко представить ошибку, которая могла бы вызвать это. Плохо написанный компонент может неправильно реализовать подсчет ссылок. Он может просто уничтожить себя при каждом Release вызове, независимо от того, сколько ссылок было взято.

5. Проведите эксперимент с компилятором C . Также выполните пошаговое выполнение программы Delphi в отладчике процессора. Необходимо сделать это, чтобы получить неявные локальные данные. Я думаю, если вы не можете согласиться с тем, что проблема не в Delphi, то вы напрасно тратите здесь свое время. Все, что вы можете сделать, это отправить отчет о контроле качества.

Ответ №1:

В вопросе вы четко указываете, что ошибочное поведение происходит только с одним конкретным COM-объектом. Учитывая этот факт и то, что подсчет ссылок COM в Delphi, как известно, работает правильно, единственный разумный вывод заключается в том, что ошибка заключается в этом конкретном COM-объекте.

Ваш единственный выход — связаться с поставщиком этого COM-объекта и отправить им отчет об ошибке.


Одна вещь, на которую следует обратить внимание с целью возможного обхода, — это то, как вы создаете объект. Вы используете CreateComObject . При этом получает идентификатор класса и возвращает IUnknown . Он вызывает, CoCreateInstance передавая идентификатор класса и запрашивая IUnknown интерфейс. Затем вам нужно запросить свой интерфейс, ISomeInterface . Итак, ваш код выглядит следующим образом:

 var
  iunk: IUnknown;
  intf: ISomeInteface;
....
CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, 
  IUnknown, iunk);
iunk.QueryInterface(ISomeInterface, intf);
  

Тот факт, что у вас есть две переменные интерфейса, одна IUnknown и one ISomeInterface , объясняет, почему вы видите количество ссылок, которое вы делаете. Теперь вы можете подумать, что у вас есть только одна переменная интерфейса, но это не так. Их два, только один из них является неявным локальным. Вы можете убедиться в этом, просмотрев скомпилированный код и выполнив его в отладчике.

Этот код:

 procedure TWrappingClass.CreateInstance;
begin
  FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
end;
  

компилируется так, как если бы это было так (игнорируя проверку ошибок):

 procedure TWrappingClass.CreateInstance;
var
  iunk: IUnknown;
begin
  iunk := CreateComObject(CLASS_SomeClass);
  try
    FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
  finally
    iunk := nil;
  end;
end;
  

Возможно, COM-компонент не может обработать вызов, Release выполненный в его IUnknown интерфейсе.

Итак, вы могли бы попытаться обойти это, используя CoCreateInstance вместо CreateComObject . Передайте ISomeInterface в качестве riid параметра.

 OleCheck(CoCreateInstance(CLASS_SomeClass, nil, CLSCTX_INPROC_SERVER 
  or CLSCTX_LOCAL_SERVER, ISomeInterface, FInstance));
  

Комментарии:

1. Вопрос: Получит ли приведение com-объекта к интерфейсу ссылку на что-то другое, кроме самого объекта? Кажется, я припоминаю, что если бы вы выполняли делегирование интерфейса в Delphi, приведение объекта к делегированному интерфейсу увеличило бы количество ссылок для интерфейса, но ни капли не заботило бы объект в игре, что означало, что у вас была ссылка на что-то, что больше не было в игре. Это просто глупый вопрос, хотя, поскольку прошло много лет с тех пор, как я много работал над Delphi, я просто помню, что был в том же положении, приводя объект к интерфейсу и теряя фактический объект.

2. @Lasse Это COM, а не Delphi. За кулисами, как бы это ни было реализовано, все интерфейсы, к которым запрашивается конкретный IUnknown, должны иметь общий срок службы.

3. Спасибо. Теперь я могу подтвердить, что это действительно какая-то неприятная ошибка в компоненте. После получения экземпляра с помощью CoCreateInstance IID_ISgCADEditor все работает нормально. Но когда я использую CoCreateInstance с IID_IUnknown , а затем QueryInterface для IID_ISgCADEditor , тогда он сломан. Извините, что не поверил вам с самого начала. Но теперь у меня есть обходной путь, и у меня есть реальные аргументы поставщику, а не просто «у меня это не работает». К сожалению, я могу дать вам 1 только один раз.

4. Хорошо. Я рад, что не сошел с ума!