2009-04-26 5 views
2

예를 들어,이 클래스가 (런타임에) 가상 생성자를 가지고 있는지 확인하는 방법이 있습니까? 작동 RTTI를 사용하여 예를 들어,Delphi 클래스에 가상 생성자가 있는지 어떻게 알 수 있습니까?

procedure Test; 
var 
    Clazz: TClass; 
    Instance: TObject; 
begin 
    Clazz := TMyClass; 
    Instance := Clazz.Create; 
end; 

간단한 해결책이 있습니까 : clazz에 의해 참조되는 클래스는 가상 생성자가있는 경우

TMyClass = class(TObject) 
    MyStrings: TStrings; 
    constructor Create; virtual; 
    end; 

예를 들어,이 코드 내가 테스트하고 싶습니다 델파이 6에서 2009까지?

+1

는 ..., 나는 뭔가 누군가를 elses 코드에서 잘못되면 당신이 뭔가 잘못 ... – Alex

+1

예, 정확하게, 나는 검사 할을하고 있다고 생각합니다. 생성자가 가상으로 선언되지 않으면 호출되지 않으므로 "진짜 나쁜 일이 발생할 수 있습니다". – mjn

답변

4

TypInfo 유닛을 살펴보면 메소드가 RTTI를 사용하여 가상인지 여부를 알 수있는 방법이없는 것처럼 보입니다. 클래스 참조가있는 경우 VMT를 검사하여 고유 한 메서드를 롤 할 수 있습니다.

Allen Bauer에 따르면 this question에 대한 대답으로 vmtClassName이 가리키는 값 바로 앞에서 VMT의 끝을 찾을 수 있습니다. 첫 번째 사용자 정의 가상 메소드 (있는 경우)는 클래스 참조의 주소에서 발견됩니다. 즉, pointer(Clazz)^입니다. 이제 VMT의 사용자 정의 섹션의 시작 지점과 끝 지점을 알았으므로 테이블의 각 포인터를 Clazz.create에 대한 메서드 포인터의 코드 섹션과 비교하는 while 루프를 만드는 것은 너무 어렵지 않습니다. TMethod에 캐스팅되었습니다. 일치하는 항목이 있으면 가상 메소드입니다. 그렇지 않다면, 그렇지 않습니다.

예, 약간의 해킹이지만 제대로 작동합니다. 누구든지 더 나은 해결책을 찾을 수 있다면 더 많은 힘을 얻을 수 있습니다.

+0

생성자가 가상으로 선언 된 경우 VMT에서 참조되는 것을 올바르게 이해합니까? – mjn

+0

예, 다른 방법과 동일합니다. –

3

당신도 알다시피, 그것에 대해 더 많이 생각할수록 내가 준 대답이 적어지기 시작했습니다. 문제는 작성된 코드가 컴파일 타임에 알려진 정보만을 처리 할 수 ​​있다는 것입니다. Clazz가 TClass로 정의 된 경우 Clazz.Create를 TMethod에 두는 것은 항상 TObject.Create에 대한 메소드 포인터를 제공합니다.

Clazz를 "TMyClass 클래스"로 정의 할 수 있습니다. 이미 가상 생성자가 있습니다. 따라서 생성자를 오버라이드 할 수있는 최상위 생성자를 제공 할 것입니다. 그러나 귀하의 의견에 비추어 볼 때 가상 구성을 깨뜨리는 비 가상 생성자 (다시 삽입,)가있는 것 같습니다. 공장 패턴을 사용하고있을 가능성이 큽니다. 문제가 될 수 있습니다.

유일한 해결책은 RTTI를 사용하여 실제로 클래스에 연결된 생성자를 찾는 것입니다. "Create라는 메소드"에 대한 메소드 포인터를 얻을 수 있으며, 다른 답변에서 설명한 트릭에서 사용할 수 있습니다. 이렇게하려면 기본 가상 생성자 게시 된 선언해야합니다. 이렇게하면이를 대체하는 모든 메소드도 강제로 게시됩니다. 문제는 누군가가 여전히 을 다시 사용할 수 있다는 것입니다.을 사용하여 게시되지 않은 생성자를 더 높게 선언하고 스키마가 바닥에 충돌합니다. 자손 클래스가 수행 할 작업에 대해 어떤 보증도하지 않습니다.

이 질문에 기술적 인 해결책이 없습니다. 실제로 효과가있는 유일한 것은 교육입니다. 사용자는이 클래스가 팩토리 (또는 가상 생성자를 필요로하는 이유가 무엇이든)에서 인스턴스화되고 파생 클래스에서 생성자를 다시 도입하면 파손될 수 있음을 알아야합니다. 이 효과에 대한 문서와 소스 코드에 주석을 달아주십시오. 그것은 당신이 할 수있는 모든 것입니다.

2

마이클,

나는 당신의 질문을 얻을 수 있지만 소스 코드는 컴파일되지 않기 때문에, 나는 당신이 내 대답은 무엇 메이슨에 정교의 비트가

질문 ;-)의 포인트를 놓칠 생각 그의 두 번째 대답에서 설명하려고했습니다.

문제는 가상 생성자가있는 기본 클래스를 참조하는 '클래스 참조'(예 : TClass 또는 TComponentClass)가 있다는 것입니다. 그러나 TClass는 (TClass는 가상 생성자가 아닌 클래스를 참조하지만) TComponentClass는 않습니다.

클래스 참조를 사용하여 생성자에 대한 호출을 분해 할 때 차이가 있음을 알 수 있습니다. 간접

  • 가상이 아닌 생성자가하는 호출을 가상 생성자가 호출

    • : 당신이 클래스 참조를 통해 가상 생성자를 호출 할 때 는, 코드가 아닌 가상 생성자를 호출 할 때 약간 다릅니다 그래서

      TestingForVirtualConstructor.dpr.37: ComponentClassReference := TMyComponentClass; 
      00416EEC A1706D4100  mov eax,[$00416d70] 
      TestingForVirtualConstructor.dpr.38: Instance := ComponentClassReference.Create(nil); // virtual constructor 
      00416EF1 33C9    xor ecx,ecx 
      00416EF3 B201    mov dl,$01 
      00416EF5 FF502C   call dword ptr [eax+$2c] 
      TestingForVirtualConstructor.dpr.39: Instance.Free; 
      00416EF8 E8CFCDFEFF  call TObject.Free 
      TestingForVirtualConstructor.dpr.41: ClassReference := TMyClass; 
      00416EFD A1946E4100  mov eax,[$00416e94] 
      TestingForVirtualConstructor.dpr.42: Instance := ClassReference.Create(); // non-virtual constructor 
      00416F02 B201    mov dl,$01 
      00416F04 E893CDFEFF  call TObject.Create 
      TestingForVirtualConstructor.dpr.43: Instance.Free; 
      00416F09 E8BECDFEFF  call TObject.Free 
      

      당신이 변수 O를 가지고 때 직접 호출

    이 분해 무슨 뜻인지 보여줍니다 f 형식 클래스 참조를 생성자가 가상이라고 가정하고 해당 변수를 통해 해당 생성자를 호출하면 해당 변수의 실제 클래스에 가상 생성자가 포함될 것입니다.

    생성자가 구현 된 실제 클래스를 결정할 수 없습니다 (예 : .DCU, .MAP, .JDBG 또는 기타 소스와 같은 추가 디버깅 정보 없이는).

    program TestingForVirtualConstructor; 
    
    {$APPTYPE CONSOLE} 
    
    uses 
        Classes, SysUtils; 
    
    type 
        TMyComponentClass = class(TComponent) 
        MyStrings: TStrings; 
        constructor Create(Owner: TComponent); override; 
        end; 
    
    constructor TMyComponentClass.Create(Owner: TComponent); 
    begin 
        inherited; 
    end; 
    
    type 
        TMyClass = class(TObject) 
        MyStrings: TStrings; 
        constructor Create(); 
        end; 
    
    constructor TMyClass.Create(); 
    begin 
        inherited; 
    end; 
    
    procedure Test; 
    var 
        // TComponentClass has a virtual constructor 
        ComponentClassReference: TComponentClass; 
        ClassReference: TClass; 
        Instance: TObject; 
    begin 
        ComponentClassReference := TMyComponentClass; 
        Instance := ComponentClassReference.Create(nil); // virtual constructor 
        Instance.Free; 
    
        ClassReference := TMyClass; 
        Instance := ClassReference.Create(); // non-virtual constructor 
        Instance.Free; 
    end; 
    
    begin 
        try 
        Test; 
        except 
        on E: Exception do 
         Writeln(E.Classname, ': ', E.Message); 
        end; 
    end. 
    

    다시 원래의 질문을 얻으려면 : 클래스 참조 참조 가상 생성자를 갖는 기본 클래스, 당신은 당신이 항상 호출 할 것이 확실하면 다음

    컴파일 수행하는 예제 코드입니다 간접 참조를 사용하는 가상 생성자. 클래스 참조가 비가 상 생성자가있는 기본 클래스를 참조 할 때 직접 호출을 사용하여 항상 가상이 아닌 생성자를 호출 할 것입니다.

    희망 사항이 귀하의 질문에 더 많은 빛을 비추 길 바랍니다. 이 정보를 필요로하는 경우

    --jeroen

  • +1

    네, 저의 두 번째 게시물에서 겪으 려던 요지입니다. 좋은 설명. 이러한 것들은 ASM에서 일러스트레이션을 줄 때 항상 (실제로 진행되는 것을 이해하는 프로그래머에게) 더 이해가됩니다. +1. –

    +0

    감사합니다. 항상 작동하고 잘 설명하는 간결한 예제를 얻는 것은 항상 힘든 일입니다. –