9

Is it possible to get the list of members (properties, functions, procedures) for a COM object via the OleVariant type?

For example,

var
  wscript: Olevariant;
begin
  wscript := CreateOleObject("WScript.Shell");
  ...
end;

and I am particularly interested in getting a list of functions such as WScript.Echo, WScript.Quit etc.

I want to have this feature because it is good to implement the code auto completion.

justyy
  • 5,831
  • 4
  • 40
  • 73
  • 1
    Only to a subset of those objects implementing Automation interfaces (but that is quite a rich subset). See http://en.wikipedia.org/wiki/OLE_Automation and http://en.wikipedia.org/wiki/IDispatch – Arioch 'The Oct 30 '13 at 12:21
  • `IDispatch` offers conversion from Name to ID. Not sure how you get the list of names. – David Heffernan Oct 30 '13 at 12:22
  • Might be interesting: http://delphihaven.wordpress.com/generic-idispatch-proxies-for-active-scripting/ Generic IDispatch Proxy Classes for Active Scripting (Delphi 2010+) – Arioch 'The Nov 05 '13 at 20:12

2 Answers2

8

You can use the GetTypeInfo method and the ITypeInfo interface.

Try this sample code (is not complete but you can use it as starting point)

{$APPTYPE CONSOLE}

uses
  SysUtils,
  ActiveX,
  ComObj,
  Variants;

//http://spec.winprog.org/typeinfo/
//http://spec.winprog.org/typeinf2/
//http://spec.winprog.org/typeinf3/

function GetTypeStr(tdesc : TTypeDesc; Context : ActiveX.ITypeinfo):string;
var
  tinfo    : ActiveX.ITypeInfo;
  bstrName : WideString;
begin
   case tdesc.vt of
     VT_PTR   : Result:=GetTypeStr(tdesc.ptdesc^,Context);
     VT_ARRAY : Result:=Format('Array of %s',[GetTypeStr(tdesc.padesc^.tdescElem,Context)]);
     VT_USERDEFINED : begin
                        context.GetRefTypeInfo(tdesc.hreftype, tinfo);
                        tinfo.GetDocumentation(-1, @bstrName, nil, nil, nil);
                        Result:=bstrName;
                      end
   else
     Result:=VarTypeAsText(tdesc.vt);
   end;
end;


//http://msdn.microsoft.com/en-us/magazine/dd347981.aspx
Procedure InspectCOMOnbject(const ClassName: string);
Var
  ComObject     : OleVariant;
  Dispatch      : IDispatch;
  Count         : Integer;
  i,j,k         : Integer;
  Typeinfo      : ActiveX.ITypeinfo;
  ptypeattr     : ActiveX.PTypeAttr;
  pfuncdesc     : ActiveX.PFuncDesc;//http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.vswizard.tagfuncdesc.aspx
  rgbstrNames   : TBStrList;
  cNames        : Integer;
  bstrName      : WideString;
  bstrDocString : WideString;
  sValue        : string;
  sinvkind      : string;
begin
  ComObject     := CreateOleObject(ClassName);
  Dispatch      := IUnknown(ComObject) as IDispatch;
  OleCheck(Dispatch.GetTypeInfoCount(Count));
  for i := 0 to Count-1 do
    begin
       OleCheck(Dispatch.GetTypeInfo(i,0,Typeinfo));
       OleCheck(Typeinfo.GetTypeAttr(ptypeattr));
       try
        case ptypeattr^.typekind of
         TKIND_INTERFACE,
         TKIND_DISPATCH :
          begin
            for j:=0 to ptypeattr^.cFuncs-1 do
            begin
               OleCheck(Typeinfo.GetFuncDesc(j, pfuncdesc));
               try
                 OleCheck(Typeinfo.GetNames(pfuncdesc.memid, @rgbstrNames, pfuncdesc.cParams + 1, cNames));
                 OleCheck(Typeinfo.GetDocumentation(pfuncdesc.memid,@bstrName,@bstrDocString,nil,nil));

                 if 1=1 then //pfuncdesc.elemdescFunc.tdesc.vt<>$0018 then
                 begin
                   //pfuncdesc.elemdescFunc.paramdesc
                   case pfuncdesc.invkind of
                    INVOKE_FUNC           : if pfuncdesc.elemdescFunc.tdesc.vt = VT_VOID then sinvkind :='procedure' else sinvkind :='function';
                    INVOKE_PROPERTYGET    : sinvkind :='get property';
                    INVOKE_PROPERTYPUT    : sinvkind :='put property';
                    INVOKE_PROPERTYPUTREF : sinvkind :='ref property';
                   else
                     sinvkind :='unknow';
                   end;


                    {
                   if bstrDocString<>'' then
                    Writeln(Format('// %s',[bstrDocString]));
                     }
                    if pfuncdesc.cParams<=1 then
                    begin
                       if pfuncdesc.elemdescFunc.tdesc.vt = VT_VOID then
                        Writeln(Format('%s %s;',[sinvkind,bstrName]))
                       else
                        Writeln(Format('%s %s : %s;',[sinvkind,bstrName,GetTypeStr(pfuncdesc.elemdescFunc.tdesc, Typeinfo)]));
                    end
                    else
                    begin
                      sValue:='';
                      for k := 1 to pfuncdesc.cParams do
                      begin
                        //Writeln(Format('%s : %d',[rgbstrNames[k], pfuncdesc.lprgelemdescParam[k-1].tdesc.vt]));
                        sValue:= sValue + Format('%s : %s',[rgbstrNames[k], GetTypeStr(pfuncdesc.lprgelemdescParam[k-1].tdesc,Typeinfo)]);
                        if k<pfuncdesc.cParams then
                          sValue:=sValue+';';
                      end;

                      if pfuncdesc.elemdescFunc.tdesc.vt = VT_VOID then
                        Writeln(Format('%s %s (%s);',[sinvkind, bstrName, sValue]))
                      else
                        Writeln(Format('%s %s (%s) : %s;',[sinvkind, bstrName,SValue,GetTypeStr(pfuncdesc.elemdescFunc.tdesc, Typeinfo)]))
                    end;
                      //Writeln(pfuncdesc.elemdescFunc.tdesc.vt);
                 end;
               finally
                 Typeinfo.ReleaseFuncDesc(pfuncdesc);
               end;
            end;
          end;
        end;
       finally
          Typeinfo.ReleaseTypeAttr(ptypeattr);
       end;
    end;
end;



begin
 try
    CoInitialize(nil);
    try
      //InspectCOMOnbject('WbemScripting.SWbemLocator');
      InspectCOMOnbject('Excel.Application');
      //InspectCOMOnbject('Schedule.Service');
      //InspectCOMOnbject('WScript.Shell');
    finally
      CoUninitialize;
    end;
 except
    on E:EOleException do
        Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit');
 Readln;
end.
RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • what doest "pfuncdesc.elemdescFunc.tdesc.vt<>$0018" mean? – justyy Nov 01 '13 at 16:44
  • Is for check if the method return a value or is empty (void). you can check get the possible values from here http://msdn.microsoft.com/en-us/library/gg164263.aspx – RRUZ Nov 01 '13 at 19:09
  • strange, why this piece of code returns only 3 functions for WScript.Shell , where is the rest of APIs ? – justyy Nov 04 '13 at 14:33
  • I think that there are some memory leaks in calls to `GetDocumentation`. Output string parameters should be pointers to `BSTR`; BSTRs can then be assigned to Delphi string/widestring variables, and finally must be passed to `SysFreeString`. BSTRs returned by `GetNames` in *rgbstrNames* must be freed also. – JRL Mar 09 '14 at 18:25
2

The object may implement IDispatchEx, then you can use GetNextDispID to enumerate all members, and GetMemberName and GetMemberProperties to discover some (minimal) information about each member.

Or, the object may implement IDispatch, and in particular IDispatch::GetTypeInfo, then you can (with some difficulty) extract information about its members from ITypeInfo object.

Before you ask, I have no idea how to do any of this in Delphi.

If all else fails, you can read the documentation.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85