2016-12-16 14 views
16

다음 함수를 사용하여 기존 객체의 인스턴스 클래스를 패치합니다. 그 이유는 제 3 자 클래스의 보호 된 기능을 패치해야하기 때문입니다.인스턴스 클래스를 패치 할 때 기본 클래스가 동일한 단위로 있어야합니까?

procedure PatchInstanceClass(Instance: TObject; NewClass: TClass); 
type 
    PClass = ^TClass; 
begin 
    if Assigned(Instance) and Assigned(NewClass) 
    and NewClass.InheritsFrom(Instance.ClassType) 
    and (NewClass.InstanceSize = Instance.InstanceSize) then 
    begin 
    PClass(Instance)^ := NewClass; 
    end; 
end; 

하지만 기본 클래스가 자체 단위로 정의 된 경우에만 코드가 작동합니다. 왜 그럴까요? 그것 없이는 작동하도록하는 해결 방법이 있습니까?

이 그 예에서 작업하는 동안

unit Unit1; 

interface 

uses 
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
    Dialogs, StdCtrls, wwdblook, Wwdbdlg; 

type 
    TwwDBLookupComboDlg = class(Wwdbdlg.TwwDBLookupComboDlg); // This is necessary 
    TForm1 = class(TForm) 
    Button1: TButton; 
    wwDBLookupComboDlg1: TwwDBLookupComboDlg; 
    procedure FormCreate(Sender: TObject); 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

type 
    TButtonEx = class(TButton) 
    end; 

    TwwDBLookupComboDlgEx = class(TwwDBLookupComboDlg) 
    end; 

procedure PatchInstanceClass(Instance: TObject; NewClass: TClass); 
type 
    PClass = ^TClass; 
begin 
    if Assigned(Instance) and Assigned(NewClass) 
    and NewClass.InheritsFrom(Instance.ClassType) 
    and (NewClass.InstanceSize = Instance.InstanceSize) then 
    begin 
    PClass(Instance)^ := NewClass; 
    end; 
end; 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    PatchInstanceClass(Button1, TButtonEx); 
    showmessage(Button1.ClassName); // Good: TButtonEx 

    PatchInstanceClass(wwDBLookupComboDlg1, TwwDBLookupComboDlgEx); 
    showmessage(wwDBLookupComboDlg1.ClassName); // Bad: TwwDBLookupComboDlg (should be TwwDBLookupComboDlgEx) 
end; 

end. 
이 작동

type 
    TwwDBLookupComboDlg = class(wwdbdlg.TwwDBLookupComboDlg); // <------ added! 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    PatchInstanceClass(wwDBLookupComboDlg1, TwwDBLookupComboDlgEx); 
    showmessage(wwDBLookupComboDlg1.ClassName); // shows TwwDBLookupComboDlgEx :-) 
end; 

end. 

(유일한 차이점은 TwwDBLookupComboDlg의 재 정의이다) 작동하지 않습니다, 나는 발견 이 현상은 TwwDBLookupComboDlg에서만 발생하지만 TButton에서는 발생하지 않습니다. 나는 이유를 모른다. 아쉽게도 wwdbdlg.pas는 무료가 아닙니다.


업데이트 : 발견

: 나는 TButtonTButtonEx을 비교하면 내가 wwdlg.TwwDBLookupComboDlgTwwDBLookupComboDlgEx을 비교하면, 두 값이 모두 608

있으며, 그 크기는 940와 944이다.

Unit1.TwwDBLookupComboDlgTwwDBLookupComboDlgEx을 비교하면 크기는 944 및 944입니다.

실제 문제는 다음과 같습니다. TwwDBLookupComboDlg = class(Wwdbdlg.TwwDBLookupComboDlg);을 정의하면 인스턴스 크기가 4 바이트 씩 커집니다!

간단한 데모. 이 프로그램이 문제가 TOpenDialog에서 발생

 
220 
220 
220 

하지만,이 발생하지 않습니다 출력은 XE7으로, 그러나 델파이 2007로 컴파일 할 때

{$APPTYPE CONSOLE} 

uses 
    Dialogs; 

type 
    TOpenDialog = class(Vcl.Dialogs.TOpenDialog); 
    TOpenDialogEx = class(TOpenDialog); 

begin 
    Writeln(Vcl.Dialogs.TOpenDialog.InstanceSize); 
    Writeln(TOpenDialog.InstanceSize); 
    Writeln(TOpenDialogEx.InstanceSize); 
    Readln; 
end. 

 
188 
192 
192 

을 방출 TCommonDialog.

업데이트 2 : 최소한의 예를

program Project1; 

{$APPTYPE CONSOLE} 

uses 
    Classes, Dialogs; 

type 
    TOpenDialog = class(TCommonDialog) 
    private 
    FOptionsEx: TOpenOptionsEx; 
    end; 

    TOpenDialogEx = class(Project1.TOpenDialog); 

begin 
    Writeln(Project1.TOpenDialog.InstanceSize); // 100 
    Writeln(TOpenDialogEx.InstanceSize); // 104 
    Readln; 
end. 
+0

직접 붙여 넣을 수있는 [mcve]가 없습니까? 그렇지 않으면 우리는 스스로 그것을 만들어야합니다. 우리 각자. 그렇게 비효율적이지 않습니까? 네가 해낸다면 우리는 모두 이익을 얻는다. 우리에게 당신을 도울 수있는 최대한 간단하게 만들어야하는 책임이 있습니까? –

+0

자, 여기 있습니다 : http : //pastebin.com/SL2gKBTR. 이 예제를 작업하면서이 현상은 TwwDBLookupComboDlg에서만 발생하지만 TButton에서는 발생하지 않는다는 것을 알게되었습니다. 나는 이유를 모른다. 아쉽게도 wwdbdlg.pas는 무료가 아닙니다. –

+0

오프 사이트 링크에 ​​있어서는 안됩니다. 질문에 있어야합니다. 그러나 내가 가지고 있지 않은 컨트롤을 통해서만 발생한다면 나는 확실히 도울 수 없다. 또한 아직 완성되지 않았습니다. 콘솔 응용 프로그램에 넣으면 완료됩니다. –

답변

10

이 컴파일러의 이전 버전에 대한 컴파일러의 행동에 이상한 (아마도 버그)로 나타납니다.다음 코드까지이 깍았다했습니다 델파이 6의 출력에

{$APPTYPE CONSOLE} 

type 
    TClass1 = class 
    FValue1: Double; 
    FValue2: Integer; 
    end; 

    TClass2 = class(TClass1); 

begin 
    Writeln(TClass1.InstanceSize); 
    Writeln(TClass2.InstanceSize); 

    Writeln; 
    Writeln(Integer(@TClass1(nil).FValue1)); 
    Writeln(Integer(@TClass1(nil).FValue2)); 

    Writeln; 
    Writeln(Integer(@TClass2(nil).FValue1)); 
    Writeln(Integer(@TClass2(nil).FValue2)); 

    Readln; 
end. 

은 다음과 같습니다

 
20 
24 

8 
16 

8 
16 

컴파일러는 두 개의 클래스 선언 다르게 정렬을 처리하기 위해 나타납니다. 클래스에는 8 바이트 정렬이 있고 그 뒤에 4 바이트 정수가 오는 double이 들어 있습니다. 따라서 클래스는 실제로 크기를 8의 배수로 만들기 위해 마지막에 4 바이트의 패딩을 가져야합니다. 첫 번째 클래스에는이 패딩이없고 두 번째 클래스에는 패딩이 없습니다.

여기서 코드는 필드에 대한 오프셋이 변경되지 않았 음을 증명하며, 그 차이는 정렬을 위해 존재하는 유형의 끝에있는 패딩에 있습니다.

분명히 Delphi 2007 컴파일러에 대한 패치를 얻지 못할 것입니다. 제 의심은 NewClass.InstanceSize = Instance.InstanceSize이라는 검사와 패치 코드가 여전히 올바르게 작동하는지 검사를 제거 할 수 있다는 것입니다. 그런 다음 데이터 멤버를 패치 클래스에 추가하지 않도록해야합니다.

또 다른 방법은 코드를 패치하기 위해 다른 메커니즘을 사용하는 것입니다. 원래의 문제에 대한 더 많은 지식이 없으면 그게 무엇인지 말할 수 없다.

+0

'{$ ALIGN 4}'를 사용하는 것이 안전할까요? – kobik

+0

@kobik 원하는 효과가 있습니까? –

+0

예, 크기는 두 경우 모두 같습니다. (20,24 대신에) 두 클래스 모두 16 개입니다. – kobik