Как определить, есть ли свойство у потомка IInterface?

У меня много интерфейсов в результате импорта библиотеки типов. Итак, интерфейсы такие:

  ISomeCollection = dispinterface
    ['{6592E851-3D65-4D04-B5F3-B137667B816A}']
    procedure Remove(Identifier: OleVariant); dispid 2;
    function Add(Name: OleVariant; DatabaseType_ID: OleVariant): ERSModel; dispid 3;
    property _NewEnum: IUnknown readonly dispid -4;
    property Item[Identifier: OleVariant]: ERSModel readonly dispid 4;
    property _Item[Identifier: OleVariant]: ERSModel readonly dispid 0; default;
    property Count: Integer readonly dispid 1;
  end;

_NewEnum — это идиома для использования оператора цикла Visual Basic for-each (это точно так же, как for-in в Delphi) коллекции COM-объектов — несмотря на объявление IUnknown, на самом деле это интерфейс IEnumVARIANT. Поскольку это единственный способ перечислить элементы коллекции, я обошел его с помощью:

{This class have just this class function} 
class function TVariantUtils.GetAs<T>(pModeloOleVar: OleVariant): T;
begin
  Result := (T(IUnknown(pModeloOleVar)));
end;

Использовать:

var 
  EnumColecction: IEnumVariant;
  // TEnumeratorObjects: This is a generic class to implement an enumerator over
  // an IEnumVARIANT interface
  ListOfSubObjects: TEnumaretorObjects; 
begin
  ...
  EnumCollection := TVariantUtils.GetAs<IEnumVariant>(Object.SomeCollection._NewEnum);
  ListOfSubObects := TEnumeratorObjects<ItemofSomeCollection>.Create(EnumCollection);
  ...
End;

Конструктор получает параметр IEnumVariant. Я хочу создать конструктор, который получает IInterface и определяет, имеет ли ISomeCollection свойство _NewEnum типа IUnknown, и выполняет приведенный выше код один раз. Я не знаю имя или GUID интерфейса во время компиляции.

Замечания: тег delphi-xe связан с тем, что я хочу знать механизм, даже если он работает только на Delphi XE (даже если мне нужно купить Starter Edition только для этого). Я использую D2010.

EDIT: Моя попытка использовать RTTI (он компилируется, но не работает):

constructor TEnumeratorVariant<T>.Create(pEnumeraVariante: IInterface);
var
  EnumVar: IEnumVariant;
  Contexto: TRttiContext;
  InfoTipo: TRttiType ;
  PropInfo: TRttiProperty;
  pTipo: PTypeInfo;
begin
  Contexto.Create;
  pTipo := TypeInfo(pEnumeraVariante);
  InfoTipo := Contexto.GetType(TypInfo(pEnumeraVariante));
  PropInfo := InfoTipo.GetProperty('_NewEnum');
  if Assigned(PropInfo) then
  begin
    Supports(PropInfo.GetValue(pEnumeraVariante), IEnumVariant, EnumVar);
    Create(EnumVar);
  end;
  Contexto.Free;
  PropInfo.Free;
  InfoTipo.Free;
end;

person Fabricio Araujo    schedule 09.06.2011    source источник
comment
у делфи есть отражение? Я думаю, что это способ сделать это на других языках.   -  person Alina Danila    schedule 10.06.2011
comment
Да, в Delphi есть похожая концепция под названием RTTI. У моего последнего редактирования есть моя попытка с этим.....   -  person Fabricio Araujo    schedule 10.06.2011
comment
Когда вы говорите, что с RTTI это не работает, что это не делает?   -  person Mason Wheeler    schedule 10.06.2011
comment
@Mason: я не говорю, что это не работает. Я говорю, что то, как я это закодировал, не работает... Поэтому я хочу знать, что такое правильный путь - или даже если правильного пути не существует...   -  person Fabricio Araujo    schedule 10.06.2011
comment
Что вы имеете в виду под не работает? Кроме того, поскольку кажется, что вы имеете дело с двойными интерфейсами COM, вы, вероятно, могли бы использовать стандартный метод COM IDispatch для получения значения свойства с dispid -4.   -  person Ondrej Kelle    schedule 10.06.2011
comment
Ваш исходный код намного сложнее, чем должен быть. Вы звоните _NewEnum и получаете IUnknown. Вы неявно конвертируете его в OleVariant, когда передаете его в GetAs, и в этой функции вы выполняете приведение типа обратно к IUnknown перед приведением (небезопасно) к типу интерфейса, который вам действительно нужен. Delphi поддерживает прямой способ сделать это, начиная с версии 3. Используйте оператор as: EnumCollection := Object.SomeCollection._NewEnum as IEnumVariant.   -  person Rob Kennedy    schedule 10.06.2011
comment
@TOndrej: Если бы я знал, что IID будет легко использовать, IDispatch.GetTypeInfo или что-то в этом роде.   -  person Fabricio Araujo    schedule 10.06.2011
comment
@Rob: Дело не в этом, Роб. Я знаю, что могу это сделать. Чего я не знаю, как это сделать, так это: учитывая потомок IDispatch, переданный в качестве параметра, как определить, есть ли у него свойство _NewEnum.   -  person Fabricio Araujo    schedule 10.06.2011
comment
@Fabricio: вы должны использовать IDispatch.Invoke. О каком ИДН вы говорите?   -  person Ondrej Kelle    schedule 10.06.2011
comment
@TObjdrej: я перепутал GetTypeInfo с GetIdOfnames, что требует знания GUID (IID) IDispatch.   -  person Fabricio Araujo    schedule 10.06.2011
comment
@TOndrej: и Invoke также нужен IID.   -  person Fabricio Araujo    schedule 10.06.2011
comment
@Fabricio: для вызова требуется только IID_NULL.   -  person Ondrej Kelle    schedule 10.06.2011


Ответы (2)


Попробуйте стандартный метод IDispatch (не тестировался, возможно, вам потребуется его настроить):

function GetEnumerator(const Disp: IDispatch): IEnumVariant;
var
  DispParams: TDispParams;
  ExcepInfo: TExcepInfo;
  Status: Integer;
  VarResult: OleVariant;
begin
  Result := nil;
  FillChar(DispParams, SizeOf(DispParams), 0);
  FillChar(ExcepInfo, SizeOf(ExcepInfo), 0);
  Status := Disp.Invoke(DISPID_NEWENUM, GUID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, DispParams, @VarResult, @ExcepInfo, nil);
  if Succeeded(Status) then
    Result := IUnknown(VarResult) as IEnumVariant
  else
    DispatchInvokeError(Status, ExcepInfo);
end;
person Ondrej Kelle    schedule 09.06.2011
comment
Кстати, это именно то, что делает код в комментарии Роба EnumCollection := Object.SomeCollection._NewEnum as IEnumVariant. Но, написав его, как здесь, вы можете изменить его, чтобы не вызывать исключение, а вместо этого возвращать True или False, чтобы указать, существует ли свойство и успешно ли получено его значение. - person Ondrej Kelle; 10.06.2011
comment
Кроме того, если вы настаиваете на доступе к свойству по имени '_NewEnum' вместо его dispid (-4), вы можете сначала вызвать GetIDsOfNames, чтобы получить dispid, а затем продолжить, как описано выше. - person Ondrej Kelle; 10.06.2011
comment
пожалуйста, измените @Result на @VarResult. Большое спасибо, это идеально. - person Fabricio Araujo; 13.06.2011

Вы на правильном пути. RTTI Delphi может найти методы интерфейса, но интерфейс должен генерировать RTTI для этих методов. Он не делает этого по умолчанию; вы должны включить его. Поместите директиву {$M+} в верхнюю часть модуля импорта библиотеки типов, и она должна работать.

person Mason Wheeler    schedule 09.06.2011