#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. Хорошо. Я рад, что не сошел с ума!