Delphi 이름별 호출 방법 고급 해결 방안

12299 단어
전첩https://lfzhs.iteye.com/blog/980200
이름에 따라 호출하는 방법은 지금까지 모두가 비교적 주목해 온 기술인 것 같아서 논단에 고전적인 대답이 있다.
type
    TProcedure = procedure(Test: string) of object;

  procedure ExecuteRoutine(Obj: TObject; Name, Param: string);
  var
    PMethod: TMethod;
    AProcedure: TProcedure;
  begin
    PMethod.Data := Pointer(Obj);
    PMethod.Code := Obj.MethodAddress(Name);
    if Assigned(PMethod.Code) then
    begin
      AProcedure := TProcedure(PMethod);
      AProcedure(Param);
    end;
  end;

사용처: 호출 대기 방법은 클래스의published 방법으로 설명하고, Obj는 호출 대기 방법을 가진 클래스의 예이며, Name과 Param은 각각 호출 대기 방법의 이름과 인자입니다.
그러나 이 방법은 매우 큰 한계가 있다.Tprocedure가 성명하면 호출 방법의 매개 변수표도 반드시 정해진다.만약 내가 여러 개의 호출 대기 방법을 정의했고 매개 변수의 개수, 유형, 반환 값이 모두 다르다면, 이 방법도 무능할 것이다.또한:GetProcAddress를 MethodAddress 대신 사용해도 비슷한 효과를 얻을 수 있다. 그러나 우리가 오늘 토론한 것은 호출 클래스의'방법'이다. 그러나 그것이 되돌아오는 것은'방법'이 아니다. 왜냐하면GetProcAddress는 응용 프로그램의 출력(exports)을 얻을 수 있는 과정이나 편지만 얻을 수 있기 때문이다. 이런 과정이나 함수는 어떤 종류에 속하지 않고'방법'이라고 할 수 없기 때문이다.물론 효과는 유사하지만 한계도 유사하다. - (그러면 매개 변수의 개수, 유형이 모두 바뀌면 해결할 수 없나요? (이렇게 되면 본문이 없을 것입니다) 연구를 통해 저는 효과적인 방법을 발견했습니다. Format 함수(주의, DOS 명령이 아니에요, 허허) 다들 낯설지 않으실 거라고 믿습니다.그것에 전송된 매개 변수의 개수와 유형은 모두 가변적이지 않습니까?다음과 같이 선언하면
procedure AProc(Param: array of const);

이렇게 호출할 수 있습니다.
 AProc([123, 'X', True, 'hello'...]);

친구가 말할지도 몰라요. 그럼 간단하잖아요. 그러면 되잖아요.
type
    TProcedure = procedure(Params: array of const) of object;

  procedure ExecuteRoutine(Obj: TObject; Name: string; Params: array of const);
  var
    ...
  begin
    ...
      AProcedure(Params);
    ...
  end;

서두르지 마라, 문제가 방금 생겼는데, 네가 한번 운행해 보겠니?문제 생겼지?(편의를 위해 일시적으로 우리의 ExecuteRoutine 함수라고 부른다. 제어 함수이다. 호출 대기 방법은 호출 대기 방법이라고 약칭한다) 이 형참표의 성명 방법은 확실히 우리의 제어 함수에 적합하지만 호출 대기 방법에 적합하지 않다.왜?왜냐하면 대조 방법의 형삼표는 확실히 그렇지 않기 때문이다.당연하지, 네가 모든 조정 대기 방법의 형삼표를 이렇게 바꾸면 되지 않겠니?얼마나 많은 것을 고쳐야 하는지(대기 함수의 매개 변수표와 내부 실현을 포함하고, 관건은 내부 실현 부분이다)는 말할 것도 없고, 네가 고친 후의 대기 방법의 형참표를 보면 모두 하나의 모양이 된다.그때는 네가 도대체 어떤 파라미터를 보내야 할지 모르겠다.따라서 우리는 가능한 한 조정 방법을 기다리는 형참표를 유지해야 한다.현재 문제는 제어 함수에서 알 수 있는 조정 방법의 주소와 파라미터 목록 (TVarRec에 저장된 그룹) 으로 바뀌었습니다. 호출할 때 파라미터를 어떻게 전송합니까?이것은 몇 가지 예비 지식이 필요하다.우선 들어오는 이 매개 변수표를 살펴보자. 파라메스.그 유형은 Delphi를 가변 개방 수조(Variant open array)라고 하는데, 이것은array of TVarRec와 같다. 즉, Params는 구성원이 TVarRec인 수조이다.다시 말하면 매개 변수가 Params에 전송될 때 각종 유형이 Delphi에 의해 자동으로 TVarRec로 전환된다(도움말의Variant open array parameters 1절 참조).TVarRec의 정의를 보면 알 수 있듯이 실제 저장된 데이터 영역은 4Bytes이고 4Bytes를 초과한 저장 지침은 8Bytes이다. 주의해야 할 것은 TVarRec의 크기는 8Bytes이다(연구를 통해 이전 4Bytes에 저장된 데이터이고 5Byte는 유형이다).  2. 함수를 호출할 때 매개 변수가 전달되는 일반적인 상황 (stdcall을 사용하지 않은 경우).일반적인 함수나 과정에 대해 앞의 세 가지 파라미터는 각각 EAX, EDX, ECX에 놓고 뒤에 더 많은 파라미터가 있으면 창고 안에 있다.클래스의 방법에 대해 EAX는 클래스 인스턴스를 저장하는 데 사용되는 주소를 고정시키고 EDX, ECX는 각각 앞의 두 개의 매개 변수를 저장하고 나머지 매개 변수는 창고에 넣는다.스택에 저장된 모든 요소는 4Bytes를 차지하는데 앞에서 말했듯이 TVarRec에 저장된 데이터도 4Bytes이다. 마침 매개 변수가 스택에 한 자리를 차지하고 처리가 편리하다.또한 EAX로 결과가 반환됩니다.  3. 클래스를 호출하는 방법에는 기본적으로 숨겨진 파라미터인 Self가 첫 번째 파라미터로 전송되어 EAX 레지스터에 넣습니다.따라서 우리가 본 첫 번째 파라미터는 사실 두 번째이기 때문에 우리가 처리할 때 주의해야 한다.  4. ObjectPascal 문법 호출 방법으로 Delphi는 자동으로 매개 변수의 전달 문제를 처리하는데, 어셈블리에서 어떤 함수를 호출하기 전에 매개 변수를 수동으로 설정해야 한다.따라서 나는 내장 어셈블리의 방법으로 파라미터 전달 문제를 해결하기로 결정했다. 만약에 하나의 파라미터라면 EDX를 넣는다.만약 두 개의 매개 변수라면 각각 EDX, ECX를 넣는다.두 개 이상의 매개 변수에 대해 매개 변수 개수 - 두 개의 순환으로 후속 매개 변수를 순서대로 창고에 넣는다.그런 다음 보류 중인 메소드가 있는 인스턴스 주소를 EAX로 보내면 CALL이 됩니다.
function ExecuteRoutine(AObj: TObject; AName: string;
    Params: array of const): DWord;
  const
    RecSize = SizeOf(TVarRec); //                
  var
    PFunc: Pointer;
    ParCount: DWord;
  begin
    if not Assigned(AObj) then
      raise Exception.Create ('            ?');
    PFunc := AObj.MethodAddress(AName); //       
    if not Assigned(PFunc) then
      raise Exception.CreateFmt('    %s   Method: %s', [AObj.ClassName,
        AName]);
      
    ParCount := High(Params) + 1; //       

    asm
      PUSH        ESI                 //    ESI,         

      MOV         ESI, Params         // ESI        
      CMP         ParCount, 1         //       
      JB          @NoParam
      JE          @OneParam
      CMP         ParCount, 2
      JE          @TwoParams

    @ManyParams: //       
      CLD                             //       
      MOV         ECX, ParCount
      SUB         ECX, 2              //    ParCount - 2  
      MOV         EDX, RecSize        // EDX            ,     8 Bytes
      ADD         EDX, RecSize        //        
    @ParamLoop:
      MOV         EAX, [ESI][EDX]     //                
      PUSH        EAX                 //     
      ADD         EDX, RecSize        // EDX          
      LOOP        @ParamLoop

    @TwoParams: //     
      MOV         ECX, [ESI] + RecSize

    @OneParam: //     
      MOV         EDX, [ESI]

    @NoParam:
      MOV         EAX, AObj           //       ( ,     Self)
      CALL        PFunc               //     
      MOV         Result, EAX         //       Result

      POP         ESI                 //     
    end;
  end;

앞에서 말했듯이 모든 종류는 4Bytes에 넣을 수 있기 때문에 되돌아오는 값을 DWord로 정의하고 필요에 따라 종류를 변환할 수 있습니다.이 방법은 조정 대기 방법을 최대한 보호했지만 전혀 수정하지 않아도 된다. 단지 한 군데만 적당한 조정이 필요하다. DLL의 함수 반환 값과 같다. (ShareMem을 인용한다고 말하지 마라. 그것은 본 논문에서 논의한 범주에 속하지 않는다) 만약에 긴string을 되돌리려면 PChar로 바꾸고 필요한 공간을 신청하는 데 주의해라.다음은 사용된 예입니다. (다시 한 번 말씀드리지만, 조정 대기 방법은 반드시 특정한 종류의published 방법이어야 합니다.)
TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  published //       
    function TowInt(I, J: Integer): Integer;
    function ThreeInt(I, J, K: Integer): Integer;
    function FiveInt(X1, X2, X3, X4, X5: Integer): Integer;
    function ThreeChar(I, J, K: Char): PChar;
    function TwoStr(X, Y: string): PChar;
    function IntNBool(I: Integer; B: Boolean): Boolean;
  end;

  ...

  function ExecuteRoutine(AObj: TObject; AName: string;
    Params: array of const): DWord;
  ...

  function TForm1.TowInt(I, J: Integer): Integer;
  begin
    ShowMessage(Format('%d + %d', [I, J]));
    Result := I + J;
  end;

  function TForm1.ThreeInt(I, J, K: Integer): Integer;
  begin
    ShowMessage(Format('%d + %d + %d', [I, J, K]));
    Result := I + J + K;
  end;

  function TForm1.FiveInt(X1, X2, X3, X4, X5: Integer): Integer;
  begin
    ShowMessage(Format('%d + %d + %d + %d + %d', [X1, X2, X3, X4, X5]));
    Result := X1 + X2 + X3 + X4 + X5;
  end;

  function TForm1.ThreeChar(I, J, K: Char): PChar;
  var
    Res: string;
  begin
    ShowMessage(Format('%s + %s + %s', [I, J, K]));
    Res := I + J + K;
    Result := AllocMem(Length(Res) + 1);
    StrPCopy(Result, Res);
  end;

  function TForm1.TwoStr(X, Y: string): PChar;
  var
    Res: string;
  begin
    ShowMessage(Format('%s + %s', [X, Y]));
    Res := X + Y;
    Result := AllocMem(Length(Res) + 1);
    StrPCopy(Result, Res);
  end;

  function TForm1.IntNBool(I: Integer; B: Boolean): Boolean;
  begin
    if B then
      ShowMessage(IntToStr(I) + ' and True')
    else
      ShowMessage(IntToStr(I) + ' and False');

    Result := B;
  end;

  procedure TForm1.Button1Click(Sender: TObject);
  var
    i: Integer;
    b: Boolean;
    s: string;
  begin
    i := ExecuteRoutine(Self, 'ThreeInt', [10, 23, 17]);
    ShowMessage('Result: ' + IntToStr(i));

    i := ExecuteRoutine(Self, 'FiveInt', [1, 2, 3, 4, 5]);
    ShowMessage('Result: ' + IntToStr(i));

    b := Boolean(ExecuteRoutine(Self, 'IntNBool', [10, False]));
    if b then
      ShowMessage('Result: True')
    else
      ShowMessage('Result: False');

    s := PChar(ExecuteRoutine(Self, 'ThreeChar', ['a', 'b', 'c']));
    ShowMessage('Result: ' + s);

    s := PChar(ExecuteRoutine(Self, 'TwoStr', ['hello', ' world']));
    ShowMessage('Result: ' + s);
  end;

  ...

내가 이 방법을 궁극적인 것이 아니라 고급 해결 방안이라고 부르는 이유는 여전히 해결되지 않은 문제가 하나 있기 때문이다. 그것이 바로 변참 문제이기 때문이다.그러나 이것은 큰 문제가 아니다. 왜냐하면 변삼을 함수 반환값으로 대체할 수 있기 때문이다.예?여러 개의 값을 되돌려 주시겠습니까?그것은 구조체를 가리키는 바늘이나 가장 간단한 대상을 되돌려 주는 것을 권장한다.   
TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  published //       
    function TowInt(I, J: Integer): Integer;
    function ThreeInt(I, J, K: Integer): Integer;
    function FiveInt(X1, X2, X3, X4, X5: Integer): Integer;
    function ThreeChar(I, J, K: Char): PChar;
    function TwoStr(X, Y: string): PChar;
    function IntNBool(I: Integer; B: Boolean): Boolean;
  end;

  ...

  function ExecuteRoutine(AObj: TObject; AName: string;
    Params: array of const): DWord;
  ...

  function TForm1.TowInt(I, J: Integer): Integer;
  begin
    ShowMessage(Format('%d + %d', [I, J]));
    Result := I + J;
  end;

  function TForm1.ThreeInt(I, J, K: Integer): Integer;
  begin
    ShowMessage(Format('%d + %d + %d', [I, J, K]));
    Result := I + J + K;
  end;

  function TForm1.FiveInt(X1, X2, X3, X4, X5: Integer): Integer;
  begin
    ShowMessage(Format('%d + %d + %d + %d + %d', [X1, X2, X3, X4, X5]));
    Result := X1 + X2 + X3 + X4 + X5;
  end;

  function TForm1.ThreeChar(I, J, K: Char): PChar;
  var
    Res: string;
  begin
    ShowMessage(Format('%s + %s + %s', [I, J, K]));
    Res := I + J + K;
    Result := AllocMem(Length(Res) + 1);
    StrPCopy(Result, Res);
  end;

  function TForm1.TwoStr(X, Y: string): PChar;
  var
    Res: string;
  begin
    ShowMessage(Format('%s + %s', [X, Y]));
    Res := X + Y;
    Result := AllocMem(Length(Res) + 1);
    StrPCopy(Result, Res);
  end;

  function TForm1.IntNBool(I: Integer; B: Boolean): Boolean;
  begin
    if B then
      ShowMessage(IntToStr(I) + ' and True')
    else
      ShowMessage(IntToStr(I) + ' and False');

    Result := B;
  end;

  procedure TForm1.Button1Click(Sender: TObject);
  var
    i: Integer;
    b: Boolean;
    s: string;
  begin
    i := ExecuteRoutine(Self, 'ThreeInt', [10, 23, 17]);
    ShowMessage('Result: ' + IntToStr(i));

    i := ExecuteRoutine(Self, 'FiveInt', [1, 2, 3, 4, 5]);
    ShowMessage('Result: ' + IntToStr(i));

    b := Boolean(ExecuteRoutine(Self, 'IntNBool', [10, False]));
    if b then
      ShowMessage('Result: True')
    else
      ShowMessage('Result: False');

    s := PChar(ExecuteRoutine(Self, 'ThreeChar', ['a', 'b', 'c']));
    ShowMessage('Result: ' + s);

    s := PChar(ExecuteRoutine(Self, 'TwoStr', ['hello', ' world']));
    ShowMessage('Result: ' + s);
  end;

  ...

좋은 웹페이지 즐겨찾기