Delphi 创建建具有“反射调用”功能的类

有的时候你会希望自己定义的一个Class在运行期能增加一个property、function,或者一个方法能完全改变他的行为,又或者你希望通过方法名来调用方法(类似JAVA)。

这样的效果可以利用AutoObject的后绑定特性来实现。

第一步、模拟实现一个AutoObject。

其实关键部分是要实现IDispatch的GetIDsOfNames(通过函数(属性)名称获取函数ID),和Invoke(调用指定DispID的方法)。完成之后,TRefClass便有了后帮定特性。可以赋值给Variant变量。

第二步、用一个List 保存增加的属性名称、方法名称、方法指针。以便GetIDsOfNames能找到指定的方法。

第三步、对已知方法调用、属性赋值处理比较简单。对动态增加的方法,由于参数未知,和参数的传递方式不同,处理起来相对困难(下面的例子支持简单参数类型Stdcall。

  TRefClass = class(TInterfacedObject, IDispatch)
  private
    function CallFunction(ProcAddress: Pointer; var Params: TDispParams): variant;
  protected
    FList: TCustomList;
    function GetFieldValue(DataField: String): Variant; virtual;
    procedure SetFieldValue(DataField: String; Value: Variant); virtual;
    function ClassMapp: variant;
    procedure AddProperty(Attribute: String; Default: variant);
    function GetProperty(Attribute: String): variant;
    function FindProperty(DataField: String): boolean;
    procedure AddProcedure(ProName: string; Address: Pointer; OfObject: TObject);

    {IDispatch}
    function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
    function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
    function GetIDsOfNames(const IID: TGUID; Names: Pointer;
      NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; virtual; stdcall;
    function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
      Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; virtual; stdcall;

  public
    constructor Create(AOwner: TComponent); virtual;
    destructor Destroy; override;
    class function CreateAsAutoObject(AOwner: TComponent): variant;
  end;
 implementation
procedure TRefClass .AddProcedure(ProName: string; Address: Pointer;
  OfObject: TObject);
var
  pMethod: ^TSetPropertyProc;
begin
  pMethod := AllocMem(SizeOf(TSetPropertyProc));
  TMethod(pMethod^).Data := OfObject;
  TMethod(pMethod^).Code := Address;

  FList.Add(ProName, PointerToVariant(pMethod));
end;

procedure TRefClass .AddProperty(Attribute: String; Default: variant);
begin
  FList.Add(Attribute, Default);
end;
function TRefClass .CallFunction(ProcAddress: Pointer;
  var Params: TDispParams): variant;

  function GetParamAdrs(Value: Variant): Pointer;
  var
    s: PString;
    k: PInteger;
    F: PExtended;
  begin
    case TVarData(Value).VType of
      varInteger, varSmallint, varSingle:
        begin
          New(k);
          K^ := Value;
          Result := K;
        end;
      varOleStr, varString:
        begin
          New(S);
          s^ := Value;
          Result := S;
        end;
      varDouble, varCurrency:
        begin
          New(F);
          F^ := Value;
          Result := F;
        end;
      else
        Result := nil;
    end;
  end;
var
  i: Integer;
  pParams: array of Pointer;
  p: TMethod;
  pCount: Integer;
begin
  pCount := Params.cArgs;
  SetLength(pParams, pCount);
  FillChar(pParams[0], pCount*Sizeof(Pointer), 0);
  p := TMethod(ProcAddress^);

  for i:=pCount-1 downto 0 do
  begin
    pParams[pCount-1-i] := GetParamAdrs(Variant(DispParams(Params).rgvarg^[i]));
  end;

  asm
//    push eax
//    push ecx
//    push edx
//    //push p.Data
    cmp pCount, 1
    JB @exec
    JE @One
    cmp pCount, 2
    JE @two
    @ThreeUp:
      CLD
      mov ecx, pCount
      sub ecx, 2
      mov edx, 4
      add edx, 4
    @loop:
      mov eax, [pParams]
      mov eax, [eax]+edx
      mov eax, [eax]
      push eax
      add edx, 4
      Loop @loop
    @Two:
      mov ecx, [pParams]
      mov ecx, [ecx]+4
      mov ecx, [ecx]
    @One:
      mov edx, [pParams]
      mov edx, [edx]
      mov edx, [edx]
    @exec:
      mov eax, p.Data
      test eax, eax
      je @1
      jne @call
      @1:
        mov eax, edx
        mov edx, ecx
        pop ecx
        jmp @call
      @call:
        call P.Code
//    pop edx
//    pop ecx
//    pop eax
  end;

  for i:=0 to pCount-1 do
    Dispose(pParams[i]);
end;

function TRefClass .ClassMapp: variant;
begin
  result := Self as IDispatch;
end;

constructor TRefClass .Create(AOwner: TComponent);
begin
  inherited Create;
  FOwner := AOwner;
  FList := TCustomList.Create();
end;

class function TRefClass .CreateAsAutoObject(AOwner: TComponent): variant;
begin
  result := Create(AOwner).ClassMapp;
end;

destructor TRefClass .Destroy;
begin
  FList.free;
  inherited;
end;

function TRefClass .FindProperty(DataField: String): boolean;
begin
  result := false;
end;

function TRefClass .GetFieldValue(DataField: String): Variant;
var
  P: PPropInfo;
  V: variant;
  TypeData: PTypeData;
  PFunction: ^TGetPropertyFunction;
  k: integer;
begin
  V := FList[DataField];
  if TVarData(V).Reserved1=1 then
  begin
    P := VariantToPointer(V);
    case P^.PropType^.Kind of
      tkInteger, tkChar, tkWChar, tkClass:
        result := GetOrdProp(FOwner, P);
      tkEnumeration:
        begin
          TypeData := GetTypeData(P^.PropType^);
          if TypeData^.BaseType^ = TypeInfo(Boolean) then
            Result := Boolean(GetOrdProp(FOwner, P))
          else
            Result := GetOrdProp(FOwner, P);
        end;
      tkFloat:
        Result := GetFloatProp(FOwner, P);
      tkString, tkLString, tkWString:
        Result := GetStrProp(FOwner, P);
      tkSet:
        Result := GetSetProp(FOwner, P);
      tkMethod:
        Result := P^.PropType^.Name;
      tkVariant:
        Result := GetVariantProp(FOwner, P);
      tkInt64:
        Result := GetInt64Prop(FOwner, P) + 0.0;
      tkDynArray,
      tkArray,
      tkRecord,
      tkInterface:;
    end;
  end
  else
  begin
    k := FList.IndexOf(Format('Get%s', [DataField]));
    if k<>-1 then
    begin
      PFunction := VariantToPointer(FList.Items[k]);
      V := PFunction^;
    end;
    result := V;
  end;
end;

function TRefClass .GetIDsOfNames(const IID: TGUID; Names: Pointer;
  NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
const
  FUNCLIST: array[1..4] of String =
    ('AddProperty', 'GetProperty', 'FindProperty', 'AddProcedure');
var
  S: String;
  DispID: integer;
  i: integer;
begin
  s := WideString(POleStrList(Names)^[0]);
  DispID := 0;
  for i := 1 to 4 do
    if CompareText(S, FUNCLIST[i])=0 then
    begin
      DispID := -1*i;
      break;
    end;
  if DispID = 0 then
  begin
    DispID := FList.IndexOf(S);

    if DispID = -1 then
    begin
      result := E_NOTIMPL;
      exit;
    end;
  end;
  PDispIdList(DispIDs)^[0] := DispID;
  result := S_OK;
end;

function TRefClass .GetProperty(Attribute: String): variant;
begin
  result := GetFieldValue(Attribute);
end;

function TRefClass .GetTypeInfo(Index, LocaleID: Integer;
  out TypeInfo): HResult;
begin
  result := S_OK;
end;

function TRefClass .GetTypeInfoCount(out Count: Integer): HResult;
begin
  result := S_OK;
end;

function TRefClass .Invoke(DispID: Integer; const IID: TGUID;
  LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
  ArgErr: Pointer): HResult;
Var
  V: Variant;
  P: Pointer;
begin
  case DispID of
    -1:
      begin
        if PDispParams(@Params)^.cArgs=1 then
          AddProperty(OleVariant(PDispParams(@Params).rgvarg^[0]), NULL)
        else
          AddProperty(OleVariant(PDispParams(@Params).rgvarg^[1]), OleVariant(PDispParams(@Params).rgvarg^[0]))
      end;
    -2:
      V := FindProperty(OleVariant(PDispParams(@Params).rgvarg^[0]));
    -3:
      V := GetProperty(OleVariant(PDispParams(@Params).rgvarg^[0]));
    -4:
      if PDispParams(@Params)^.cArgs=2 then
        AddProcedure(OleVariant(PDispParams(@Params).rgvarg^[1]),
          VariantToPointer(OleVariant(PDispParams(@Params).rgvarg^[0])), nil)
      else if PDispParams(@Params)^.cArgs=3 then
        AddProcedure(OleVariant(PDispParams(@Params).rgvarg^[2]),
          VariantToPointer(OleVariant(PDispParams(@Params).rgvarg^[1])),
          VariantToPointer(OleVariant(PDispParams(@Params).rgvarg^[0])));
    else
    begin
      if (Flags and DISPATCH_PROPERTYGET) = DISPATCH_PROPERTYGET then
        V := GetFieldValue(FList.Names[DispID])
      else if (Flags and DISPATCH_PROPERTYPUT) = DISPATCH_PROPERTYPUT then
        SetFieldValue(FList.Names[DispID], OleVariant(PDispParams(@Params).rgvarg^[0]))
      else
      begin
        V := FList.Items[DispID];
        P := VariantToPointer(V);
        V := CallFunction(P, TDispParams(Params));
      end;
    end;
  end;
  if assigned(VarResult) then
    PVariant(VarResult)^ := V;
  result := S_OK;
end;

procedure TRefClass .SetFieldValue(DataField: String; Value: Variant);
  function RangedValue(const AMin, AMax: Int64): Int64;
  begin
    Result := Trunc(Value);
    if Result < AMin then
      Result := AMin;
    if Result > AMax then
      Result := AMax;
  end;
var
  PropInfo: PPropInfo;
  TypeData: PTypeData;
  V: variant;
  PProcedure: ^TSetPropertyProc;
  k: integer;
begin
  V := FList[DataField];

  if TVarData(V).Reserved1=1 then
  begin
    PropInfo := VariantToPointer(V);

    if PropInfo <> nil then
    begin
      case PropInfo.PropType^^.Kind of
        tkInteger, tkChar, tkWChar:
          begin
            TypeData := GetTypeData(PropInfo^.PropType^);
            SetOrdProp(FOwner, PropInfo, RangedValue(TypeData^.MinValue, TypeData^.MaxValue));
          end;
        tkEnumeration:
          if VarType(Value) = varString then
            SetEnumProp(FOwner, PropInfo, VarToStr(Value))
          else
          begin
            TypeData := GetTypeData(PropInfo^.PropType^);
            SetOrdProp(FOwner, PropInfo, RangedValue(TypeData^.MinValue, TypeData^.MaxValue));
          end;
        tkSet:
          if VarType(Value) = varInteger then
            SetOrdProp(FOwner, PropInfo, Value)
          else
            SetSetProp(FOwner, PropInfo, VarToStr(Value));
        tkFloat:
          SetFloatProp(FOwner, PropInfo, Value);
        tkString, tkLString, tkWString:
          SetStrProp(FOwner, PropInfo, VarToStr(Value));
        tkVariant:
          SetVariantProp(FOwner, PropInfo, Value);
        tkInt64:
          begin
            TypeData := GetTypeData(PropInfo^.PropType^);
            SetInt64Prop(FOwner, PropInfo, RangedValue(TypeData^.MinInt64Value, TypeData^.MaxInt64Value));
          end;
      else
      end;
    end;
  end
  else
  begin
    k := FList.IndexOf(Format('Set%s', [DataField]));
    if k<>-1 then
    begin
      PProcedure := VariantToPointer(FList.Items[k]);
      PProcedure^(Value);
    end;
    FList.Add(DataField, Value);
  end;
end;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值