델파이의 구조와 파괴 분석
델파이의 1 개 객체 모델 : 2
1.1 객체 이름은 무엇을 의미합니까? 2
1.2 물체는 어디에 저장됩니까? 2
1.3 객체에 저장되는 것은 무엇입니까? 그들은 어떻게 저장됩니까?
2 생성자 및 생성 객체 5
2.1 생성자 란 무엇입니까? ( "특별"클래스 방법) 5
2.2 객체 생성의 전체 과정 5
2.3 생성자의 대체 사용 (생성자의 다형성을 구현하기 위해 클래스 참조 사용) 6
3 파괴자와 파괴 대상 7
3.1 소멸자 란 무엇입니까 ( "자연스럽게"가상 방법) 7
3.2 객체 파괴의 전체 과정 7
3.3 파괴, 무료, FreeAndnil, 릴리스 사용 및 차이 7
4 VCL 건축 및 파괴 구조 8
5 생성자와 소멸자 9
델파이의 구조와 파괴 분석
초록 : VCL/RTL의 연구를 통해이 논문은 생성자 및 소멸자의 구현 메커니즘과 VCL의 객체의 아키텍처를 분석하고 객체를 올바르게 작성하고 출시하는 방법을 설명합니다.
키워드 : 구성, 파괴, 물체 생성, 물체, 힙, 스택, 다형성을 파괴합니다.
저자 : Majorsoft
질문
Delphi의 생성자 및 소멸자의 구현 메커니즘은 무엇입니까? 객체를 올바르게 작성하고 릴리스하는 방법은 무엇입니까?
해결책
구조 및 파괴는 델파이를 사용하는 과정에서 발생하는 일반적인 문제입니다. 다음은 VCL/RTL 소스 코드의 연구를 통해 생성자 및 파괴자의 구현 메커니즘을 이해하는 것입니다.
델파이의 1 개 객체 모델 :
1.1 객체 이름은 무엇을 의미합니까?
C ++와 달리 Delphi의 객체 이름 (변수라고도 함)은 객체의 참조를 나타내며 객체 자체를 나타내지 않으며, 이는 "객체 참조 모델"이라고하는 객체와의 포인터와 동일합니다. 그림과 같이 :
OBJ (객체 이름) 실제 객체입니다
VMT 입력 주소
데이터 구성원
그림 1 객체 이름은 메모리의 객체를 나타냅니다.
1.2 물체는 어디에 저장됩니까?
각 응용 프로그램은 4 가지 영역으로 실행되도록 할당 된 메모리를 나누게합니다.
코드 영역
글로벌 데이터 영역
힙 영역
스택 영역
그림 2 프로그램 메모리 공간
코드 영역 : 모든 기능 코드를 포함하여 프로그램의 프로그램 코드 저장
글로벌 데이터 영역 : 글로벌 데이터를 저장합니다.
힙 영역 : 동적 데이터 (델파이의 물체 및 문자열 포함)를 저장하는 "무료 저장 영역"이라고도합니다. 범위는 소멸자가 호출 될 때까지 응용 프로그램의 전체 수명주기입니다.
스택 영역 : "자동 스토리지 영역"이라고도합니다. 로컬 데이터를 C ++에 저장하면 실제로 Auto 유형 변수입니다. 범위는 함수 내부에 있으며 시스템은 기능이 호출 된 후 스택 공간을 즉시 재활용합니다.
C ++에서는 힙 또는 스택 또는 글로벌 데이터에서 객체가 생성 될 수 있습니다. 델파이에서는 모든 객체가 힙 스토리지 영역에 구축되므로 델파이 생성자를 자동으로 호출 할 수는 없지만 프로그래머 자신이 호출해야합니다 (디자이너의 구성 요소를 드래그하면 객체는 델파이에 의해 생성됩니다). 다음 프로그램은 Delphi와 C ++에서 객체 생성의 차이점을 설명합니다.
델파이에서 :
절차 createobject (var fooobjref : tfooobject);
시작하다
fooobjref : = tfooobject.create;
// 프로그래머가 호출하면 절차가 호출 된 후에는 객체가 여전히 존재합니다.
fooobject.caption = '나는 createobject ()의 스택에 만들어졌다';
끝;
그리고 C ++에서 :
tfooobject createobject (void);
{
tfooobject fooobject; // 로컬 객체를 만듭니다
// 정적 tfooobject fooobject; // 정적 로컬 객체를 만듭니다
// 객체는 기본 생성자를 호출하여 자동으로 생성되며 현재 기능 스택에서 개체가 생성됩니다.
fooobject.caption = '나는 createobject ()의 스택에 만들어졌다';
반환 fooobject;
// 리턴 할 때 객체가 복사되고 기능 호출이 완료되면 원래 생성 된 객체가 자동으로 파괴됩니다}
tfooobject fooobject2; // 글로벌 개체를 만듭니다.
void main ();
{tfooobject* pfooobject = new tfooobject;
// 힙 객체를 만듭니다. 함수가 호출 된 후에도 객체는 여전히 존재하며 복사 할 필요가 없습니다. }
1.3 객체에 저장되는 것은 무엇입니까? 그들은 어떻게 저장됩니까?
C ++와 달리 Delphi의 객체는 데이터 구성원 및 가상 메소드 테이블 (VMT)의 항목 주소 만 저장하지만 그림과 같이 메소드를 저장하지 마십시오.
객체 가상 메소드 테이블 코드 세그먼트
VMT 주소
이름 : 문자열
너비 : 정수;
CH1 : char;
…
Proc1
func1
…
Procn
funcn
…
그림 3 물체의 구조 ...
위의 진술에 대해 몇 가지 질문이있을 수 있습니다. 다음 절차를 참조하십시오.
tsizealigntest = class
사적인
I : 정수;
CH1, CH2 : char;
J : 정수;
공공의
절차 showmsg;
절차 virtmtd;
끝;
memo1.lines.add (inttostr (sizetest.instancesize)+': instancesize');
memo1.lines.add (inttoStr (Integer (sizetest))+'<-start addr');
memo1.lines.add (inttostr (integer (@(@(@(sizetest.i)))+'<-sizetest.i');
memo1.lines.add (inttostr (integer (@(@(@(sizetest.ch1)))+'<-sizetest.ch1');
memo1.lines.add (inttostr (integer (@(@(@(sizetest.ch2)))+'<-sizetest.ch2');
memo1.lines.add (inttostr (integer (@(@(@(sizetest.j)))+'<-sizetest.j');
결과는 다음과 같습니다.
16 : Instanctize
14630724 <-start addr
14630728 <-sizetest.i
14630732 <-sizetest.ch1
14630733 <-sizetest.ch2
14630736 <-sizetest.j
데이터 멤버와 VMT 항목 주소는 16 바이트를 차지합니다!
그렇다면 회원 기능은 어디에 저장됩니까? Delphi는 RTL (런타임 유형 라이브러리)을 기반으로하기 때문에 모든 멤버 기능은 클래스에 저장됩니다. 회원 기능은 실제로 메소드 포인터이며, 이는 멤버 기능의 입력 주소를 가리 킵니다. 그렇다면 멤버 함수의 입력 주소를 찾는 방법은 무엇입니까? 정적 함수의 경우이 작업은 컴파일러에 의해 수행됩니다. 멤버 기능의 입력 주소는 클래스 객체 참조/포인터의 유형에 따라 직접 찾을 수 있습니다 (객체는 존재할 필요가 없습니다. 이번에). 소위 정적 바인딩 (동적 메소드 포함), 런타임 테이블 VMT 항목 주소 (즉, 개체의 첫 4 바이트, 개체. 이 시점에 존재해야합니다. 그렇지 않으면 포인터 액세스 오류가 발생합니다.) 소위 동적 바인딩 인 멤버 함수의 입력 주소를 찾을 수 있습니다.
알아채다
위에서 언급했듯이 모든 멤버 기능은 클래스에 저장되며 실제로 가상 메소드 테이블 VMT를 포함합니다. Delphi의 코드 자동 완성 함수 (컴파일 정보에 따라 다름)에서 객체 이름을 입력 한 다음 "", "", Delphi는 모든 데이터 멤버 및 모든 정적 메소드, 모든 가상 메소드, 모든 클래스를 나열하는 것을 볼 수 있습니다. 방법, 모든 생성자 및 소멸자, 이것이 사실이라면 시도해 볼 수 있습니다.
클래스 가상 메소드 테이블의 VMT 입력 주소
데이터 멤버 템플릿 정보
정적 메소드 테이블 등
가상 방법 표 vmt
물체
VMT 입력 주소
데이터 구성원
위의 프로그램은 또한 아래 그림과 같이 4 바이트 (Windows Default Alignment)로 정렬되는 객체 데이터 구성원 (물리적 데이터 구조)의 정렬을 보여줍니다.
VMT 입구 Addr
나
CH1CH2
J.
2 생성자 및 객체 생성
2.1 생성자 란 무엇입니까? ( "특별"방법)
OO (Object-Oriented) 아이디어의 의미에서 생성자는 객체의 생성을 담당하지만 Delphi 또는 C ++이든 OOP 언어의 구현 측면에서 생성자는 객체의 초기화 만 수행합니다 (포함 내부 하위 주제를 호출)는 객체를 만드는 전체 프로세스에 책임이 없습니다 (2.2 참조).
또한 C ++와 달리 Delphi는 생성자의 다른 방법 유형을 정의합니다 (MKConstructor, Delphi 설치 디렉토리의 125 라인을 참조). . 클래스 (클래스 이름/클래스 참조/클래스 포인터)를 통해서만 호출 될 수 있지만 일반적인 클래스 방법은 클래스와 객체를 통해 호출 할 수 있습니다. 수업 방법에서 자체는 클래스를 가리키며, 여기서 우리는 일반적으로 데이터 구성원을 초기화하여 실제 객체로 만들기 위해 자체 매개 변수 덕분입니다.
기본적으로 생성자는 정적 인 기능으로이를 생성자의 다형성을 실현할 수 있습니다 (2.4 참조). 여러 생성자를 생성하고 파생 클래스에서 부모 클래스의 생성자를 직접 오버레이 할 수 있습니다. 이러한 방식으로 부모 클래스의 생성자는 파생 클래스에서 차단되며 이러한 기술은 "아키텍처"를 형성합니다. 구조 및 파괴의 (4 참조)
2.2 객체 생성의 전체 과정
객체를 생성하는 완전한 프로세스에는 공간 할당, 물리적 데이터 구조 구성, 초기화 및 내부 하위 객체 작성이 포함되어야합니다. 위에서 언급 한 바와 같이, 생성자는 초기화 및 내부 하위 객체의 생성자를 호출 할 수 있습니다. 그러면 공간의 할당을 어떻게 완료하고 물리적 구조를 구성합니까? 컴파일러가 추가 일을하고 있기 때문에 우리는 모릅니다. 생성자로 컴파일 할 때는 함수를 구성하기 전에 "@classcreate"어셈블리 코드 라인이 삽입됩니다. :
함수 _classcreate (aclass : tclass; alloc : boolean) : tobject;
ASM
{ -> eax = VMT에 대한 포인터}
{<-eax = 인스턴스에 대한 포인터}
…
dword ptr [eax] .vmtnewinstance // newinstance를 호출하십시오
…
끝; {/source/rtl/sys/system.pas, 줄 8939}
vmtnewinstance = -12; 클래스에서 Newinstance 함수의 오프셋입니다.
클래스 기능 Newinstance : Tobject;
클래스 함수 tobject.newinstance : tobject;
시작하다
결과 : = initInstance (_getMem (instanceSize));
끝;
"initinstance (_getMem (instancesize))"은 세 가지 기능을 차례로 호출합니다.
1) 먼저 instanceize ()을 호출하여 실제 클래스의 객체 크기를 반환합니다.
클래스 함수 tobject.instanceize : longint;
시작하다
결과 : = pinteger (정수 (self) + vmtinstancesize)^; // 실제 클래스의 객체 크기를 반환합니다.
끝;
2) _getMem ()을 호출하여 힙에 인스턴스 크기의 메모리를 할당하고 객체의 참조를 반환합니다.
3) initInstance ()를 호출하여 물리적 데이터 구조를 구성하고 정수 데이터 멤버의 값을 0으로 설정하고, 포인터를 nil로 설정하는 등의 기본값을 설정합니다. 가상 메소드가있는 경우 가상 메소드 테이블 VMT의 입력 주소를 객체의 첫 4 바이트에 할당하십시오.
NewInstance를 호출 한 후 현재 객체에는 "빈 쉘"이 있고 실제 "컨텐츠"가 없으므로 객체를 의미있게 초기화하려면 사용자 정의 된 생성자를 호출하고 내부 하위 객체의 생성자를 호출해야합니다. 프로그램의 객체는 현실 세계의 물체를 진정으로 반영합니다. 이것은 객체 생성의 전체 과정입니다.
2.3 생성자의 대체 사용 (생성자의 다형성을 구현하기 위해 클래스 참조 사용)
델파이에서는 클래스도 객체로 저장되므로 클래스 수준의 다형성 구현을 제공하는 클래스 참조 및 가상 클래스 방법으로 구현됩니다. 클래스 메소드를 가상 메소드로 설정하고 파생 클래스에서 재정의 한 다음 기본 클래스의 참조/포인터를 통해 호출하여 객체가 실제 클래스를 가리키는 클래스 참조/포인터를 기반으로 구성되도록합니다. 다음 프로그램을 참조하십시오.
tmyclass = 클래스
생성자 생성; 가상;
끝;
ttmyclass = tmyclass의 클래스; // 기본 클래스의 클래스 참조
tmyclasssub = class (tmyclass)
생성자 생성; 오버라이드;
끝;
절차 CreateObj (aclass : ttmyclass; var ref);
시작하다
tobject (ref) : = aclass.create;
// ref는 유형이 없으며 모든 유형과 호환되지 않으므로 사용하면 명시 적으로 캐스트해야합니다 (cast).
// aclass는 클래스 참조, 통합 된 기능 인터페이스 및 다른 구현입니다.
// aclass에 의해 참조/지적 된 실제 클래스를 기반으로 객체를 구성합니다.
끝;
…
createobj (tmyclass, obj);
CreateObj (tmyclasssub, subobj);
3 파괴자와 물체를 파괴합니다
3.1 소멸자 란 무엇입니까 ( "자연스럽게"가상 방법)
의미 적으로 말하면, 소멸자는 물체를 파괴하고 자원을 공개 할 책임이 있습니다. 델파이에서 동의어.
Delphi는 또한 Destructor의 메소드 유형을 정의합니다 (mkconstructor, delphi 설치 디렉토리의 125 라인 /source/rtl/common/typinfo.pas를 참조하십시오). 가상; "VCL 클래스의 모든 조상에서. VCL은 왜 이렇게합니까? 다형성 상황에서 물체가 올바르게 파괴 될 수 있기 때문입니다. 가상 메소드가 사용되지 않으면 기본 클래스 하위 목체 만 파괴하여 소위 "메모리 누출"을 초래할 수 있습니다. 따라서 올바른 파괴자를 보장하기 위해 소멸자는 재정의 선언을 추가해야합니다.
3.2 객체 파괴의 전체 과정
먼저 파생 클래스 하위 목체를 파괴 한 다음 기본 클래스 하위 객체를 파괴하십시오.
힌트
파생 클래스에서, 기본 클래스 하위 객체는 기본 클래스에서 상속 된 부분을 지칭하고, 파생 클래스의 하위 개체는 새로 추가 된 부분을 나타냅니다.
3.3 파괴, 무료, FreeAndnil, 릴리스 사용 및 차이점
파괴 : 가상 방법
메모리를 해제하고, Tobject에서 가상으로 선언하고, 일반적으로 하위 클래스로 재정의하고, 상속 된 키워드를 추가하여 파생 된 클래스 객체가 올바르게 파괴되도록하십시오.
그러나 파괴는 직접 사용할 수 없습니다. 왜?
물체가 nil 인 경우 여전히 파괴를 호출하면 오류가 발생합니다. Destroy는 가상 방법이므로 객체의 첫 4 바이트를 기반으로 가상 메소드 테이블 VMT의 입구 주소를 찾아 파괴의 입구 주소를 찾을 수 있으므로 객체가 현재 존재해야합니다. 그러나 무료는 객체 자체가 존재하지 않더라도 객체가 존재하는지 여부를 결정하는 경우에만 결정됩니다. 무료 사용은 파괴를 사용하는 것보다 안전합니다.
2) 자유 : 정적 방법
물체가 niL인지 테스트하고 NIL이 아닌 경우 파괴가 호출됩니다. 다음은 무료로 델파이 코드입니다.
절차 tobject.free;
시작하다
자기 <> nil이라면
파괴하다;
끝;
자신의 강점과 약점으로부터 배우는 것이 좋지 않습니까?
그러나 호출 파괴는 객체를 파괴하지만 NIL에 대한 객체의 참조를 설정하지는 않습니다.
3) FreeAndnil; 일반 방법, 비 객체 방법, 비 클래스 방법.
sysutils 단위의 FreeAndnil 정의
절차 FreeAndnil (var obj);
var
온도 : Tobject;
시작하다
온도 : = tobject (obj);
포인터 (obj) : = nil;
온도;
끝;
객체가 올바르게 해제되도록 자유/파괴 대신 사용하는 것이 좋습니다.
4) 릴리스; TCUSTOMFORM에 정의 된 정적 방법.
창의 모든 이벤트가 처리 된 후 자유 함수가 호출됩니다. 종종 창을 파괴하는 데 사용되며 이벤트 처리 가이 창에서 일정 시간이 걸리면이 방법은 창 이벤트가 처리 된 후에 만 창이 파괴 될 수 있습니다. 다음은 tcustomform.release의 Delphi 소스 코드입니다.
절차 tcustomform.Release;
시작하다
postmessage (핸들, cm_release, 0, 0);
// 모든 Window 이벤트 메시지가 처리 된 후 CM_REREASE 메시지를 Window로 보내십시오.
// CM_RELEASE 메시지 처리 프로세스 CMRELEASE를 다시 호출하십시오
끝;
CM_REREASE 메시지 처리에 대한 CMRELEASE의 다음 정의를 살펴 보겠습니다.
절차 cmrelease (var 메시지 : tmessage);
절차 tcustomform.cmrelease;
시작하다
결국에는 무료입니다.
끝;
4 VCL 건축 및 파괴 구조
Tobject
생성자 생성; // 정적 메소드
소멸자 파괴; 가상;
tpersistent
소멸자 파괴;
tcomponent
생성자 생성 (aowner : tcomponent);
소멸자 파괴;
tcontrol
생성자 생성 (aowner : tcomponent);
소멸자 파괴;
…
다음은 VCL의 건축 및 파괴의 소스 코드를 분석하여 tcontrol을 예로 들어 보입니다.
생성자 TCONTROL.CREATE (AOWNER : TCOMPONTE);
시작하다
상속 된 Create (aowner); // 기본 클래스 하위 목체를 생성하고 Aowner에게 파괴 권리를 넘겨줍니다. 앞에 두십시오
// 이것은 "기본 클래스 하위 목체를 먼저 만들고 파생 된 클래스 서브 버젝트 생성"의 순서를 보장합니다.
… // 초기화를하고 내부 하위 목체의 생성자를 호출하십시오
끝;
소멸자 tcontrol.destroy;
시작하다
… // 파생 된 클래스에서 내부 하위 주체를 파괴합니다
상속 된 파괴; // 기본 클래스 객체를 파괴하고 끝에 넣습니다.
// 이것은 "파생 된 클래스 하위 객체의 첫 번째 파괴, 기본 클래스 하위 객체의 파괴"의 순서를 보장합니다.
끝;
5 생성자와 소멸자를 올바르게 사용하십시오
상기 분석 후, 다음은 생성자와 소멸자 사용 원리를 요약합니다.
객체를 사용하기 전에 먼저 물체를 생성하고 제 시간에 물체를 파괴하여 리소스를 확보해야합니다.
두 객체가 할당을 참조하면 표시되지 않은 객체 (참조되지 않은 개체를 참조)를 해제 할 수 있는지 확인하십시오.
구성 요소를 만들 때 호스트 구성 요소 (즉, Aowner 매개 변수, 일반적으로 양식을 사용)를 설정하는 것이 좋습니다 Form/Data Module 설계의 Delphi는 구성 요소를 작성하는 방법입니다. 따라서 우리는 구성 요소를 호출하는 소멸자를 쓸 필요가 없습니다.
함수의 리턴 유형이 객체 인 경우 결과는 객체에 대한 참조이기도하므로 결과에 의해 참조 된 객체가 존재해야합니다.
obj <> nil 또는 할당 된 (nil)를 사용하려면 물체가 존재하는지 테스트하려면 obj : = nil 후에도 obj : = nil도 호출해야합니다.
데모 프로그램의 소스 코드를 참조하십시오.
지침 (권장)
모든 Delphi 프로그램은 C ++ 프로그램의 경우 Win2k+Delphi6 SP2에 전달되었습니다. 이 기사에 대한 이해를 심화시키기 위해 데모 프로그램을 참조하는 것이 좋습니다.
이 기사에는 VCL/RTL 학습에 대한 경험과 경험이 포함되어 있습니다.
이 기사를 읽기 전에 독자는 지향적 인 파스칼 언어에 대한 특정 이해를 가져야하며 이러한 개념 중 일부에 대해 명확하지 않으면 관련 기사를 참조하십시오.
이 기사를 통해 델파이의 객체 모델, 건축 및 파괴 구현 메커니즘, VCL의 건축 및 파괴 아키텍처를 이해하고 건축 및 파괴 방법의 사용을 마스터 할 수 있어야합니다. 델파이의 구조와 파괴는 C ++의 구조와 파괴보다 훨씬 간단하며, 우리는 그것을 마스터 할 수 있어야합니다.