0
votes

I have a function that returns TArray<TValue> given a TArgList.

function GetParameters(Args: TArgList): TArray<TValue>;
var
  Parameters: TArray<TValue>;
  I: Integer;

    function ArgParam(Index: Integer): OleVariant;
    begin
        Result := OleVariant(Args.Arguments[Args.Count - Index - 1]);
    end;

begin
  Parameters := nil;
  for I := 0 to Args.Count - 1 do
  begin
    SetLength(Parameters, Length(Parameters) + 1);
    Parameters[High(Parameters)] := TValue.FromVariant(ArgParam(I));
  end;
  Result := Parameters;
end;

The result of this function is then passed to invoke an RTTI Method.

procedure ExecMethod(MethodName: string; Args: TArray<TValue>);
var
  Method: TRttiMethod;
  Ctx: TRttiContext;
  vType: TRttiType;
  I: Integer;
begin
  vType := nil;
  Ctx := TRttiContext.Create;
  try
    vType := Ctx.GetType(FControl.ClassInfo);

    for Method in vType.GetMethods do
    begin
      if SameText(Method.Name, MethodName) then
            break;
    end;

    if Assigned(Method) then
      Method.Invoke(FControl, Args);
  finally
    Ctx.Free;
    vType.Free;
  end;
end;

This works fine on simple types e.g. int, string or boolean etc. However, when one of the argument is an object/component. That's where I'm hitting a wall. Somehow, I need to convert or type cast an argument if the method expects object/component there.

Parameters[High(Parameters)] := TValue.FromVariant(ArgParam(Args, I));

How do I convert it to TValue ? Tried TValue.From<TObject>, TValue.From<Pointer> but I'm still getting invalid typecast or access violation.

Thanks for the replies.

Addendum

TArgList = record
   Arguments: PVariantArgList;
   Count: Integer;
end;

PVariantArgList is declared in ActiveX

The Invoke signature is :

function Invoke(DispID: Integer; const IID: TGUID;
  LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
  ArgErr: Pointer): HResult;
var
    DispParams: PDispParams;
  **ArgList: TArgList;**
begin
    DispParams := @Params;
    ArgList.Count := DispParams.cArgs;
    ArgList.Arguments := DispParams.rgvarg;
    Result := DISP_E_BADINDEX;
    try
      if Flags and DISPATCH_METHOD = DISPATCH_METHOD then
      begin
        Dec(DispId, BaseMethodDispid);
        if (DispId > -1) and (DispId < FMethods.Count) then
        begin
          Result := S_OK;
          ExecMethod(FMethods[DispId], GetParameters(ArgList));
        end
      end
    .
    .
    .

FMethods is simply a string list of methods. Where DispID is the index in that list of the method name.

1
Please show a minimum, complete and verifiable example.Dsm
What is TArgList?Stefan Glienke
The type(s) of the elements of the "Arguments" array property of the TArgList type may be key to this question. What type are they?Dave Olson
Also, calling SetLength() inside the loop is overkill, you should call it one time before entering the loop. Also, your code does not account for method overloads. You can't go off of name alone, you should enumerate the methods until you find one whose parameter list matches the values being passed in.Remy Lebeau
@Dave it's an IDispatch argument.Rick

1 Answers

0
votes

Assuming that Args.Arguments points to a valid array of variants and that the variants carry OLE automation types, then your GetParameters function should work with no problems.

So, Args.Arguments might not point to the right place since the way it's being derived looks suspicious:

function Invoke(DispID: Integer; const IID: TGUID;
  LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
  ArgErr: Pointer): HResult;
var
    DispParams: PDispParams;
  **ArgList: TArgList;**
begin
    DispParams := @Params;
    ArgList.Count := DispParams.cArgs;
    ArgList.Arguments := DispParams.rgvarg;

"var Params" is untyped and you manufacture a pointer to TDispParams with "@Params". So, ArgList.Arguments is totally dependent on what is being passed to "Invoke" (which you didn't reveal to us.)

Now, maybe Args.Arguments does point to a valid list of variants. You say that the variant argument that's giving you problems holds an "IDispatch", which is a valid OLE automatable type so your function should handle them; but you show an example where you are trying to cast an olevariant using TValue.From<TObject>. That will fail because an olevariant cannot carry an object instance, only interfaces (usually IUnknown or IDispatch). I assume you are getting "invalid variant type" exceptions from "TValue.FromVariant(ArgParam(I));". My suspicion is that those are not olevariants but Delphi variants containing objects (not interfaces). If that is the case you might try something like this:

function GetParameters_usingVariantCracking(Args: TArgList): TArray<TValue>;

var
  Parameters: TArray<TValue>;
  I: Integer;

    function ArgParam(Index: Integer) : TValue;

    var
       ArgumentType : TVarType;
       AnObject : TObject;

    begin
       ArgumentType := TVarData(Args.Arguments[Args.Count - Index - 1]).VType;
       if (ArgumentType = varObject) then
          Result := TObject(TVarData(Args.Arguments[Args.Count - Index - 1]).VPointer)
       else
          Result := TValue.FromVariant(Args.Arguments[Args.Count - Index - 1])
    end;

begin
  Parameters := nil;
  SetLength(Parameters, Args.Count);
  for I := 0 to Args.Count - 1 do
    Parameters[High(Parameters)] := ArgParam(I);

  Result := Parameters;
end;

Read more on the difference between olevariant and variant: Variant Types (Delphi)