소프트웨어를 개발하기 위해 DELPHI를 사용하는 과정에서 우리는 마치 초원 위의 행복한 소와 양 떼와 같으며, 오브젝트 파스칼 언어가 가져다주는 햇빛과 다양한 VCL 컨트롤이 제공하는 풍부한 수생 식물을 걱정 없이 즐기고 있습니다. 끝없이 펼쳐진 푸른 하늘을 올려다보고, 땅 위의 무성한 푸른 풀을 내려다보며, 우주가 얼마나 크고, 분자와 원자보다 작은 것이 무엇인지 누가 생각하겠습니까? 그것은 철학자들의 문제이다. 이때 철학자는 높은 산 꼭대기에 앉아 우주 성운의 변화를 바라보고 있었고, 땅 위를 기어다니는 곤충들을 바라보다가 갑자기 뒤돌아서 우리 방목자들을 향해 고개를 끄덕이며 미소를 짓고 있었다. 소와 양. 그는 풀 한 조각을 집어 입에 살짝 물고 눈을 감고 주의 깊게 맛을 보았습니다. 철학자의 입에서는 이 풀의 맛이 어땠을지 궁금합니다. 그러나 그의 얼굴에는 늘 만족스러운 미소가 떠돌고 있었다.
DELPHI의 미시적 원자 세계를 알고 이해하면 DELPHI의 거시적 응용 구조를 철저하게 이해할 수 있으며 이를 통해 더 넓은 이념적 공간에서 소프트웨어를 개발할 수 있습니다. 마치 뉴턴이 거시적인 물체의 운동을 발견했지만 물체가 왜 이렇게 움직이는지 알 수 없어 고민했던 것과 같습니다. 반면에 아인슈타인은 기본 입자의 법칙과 거시적인 물체의 운동 사이에서 상대성이론의 행복한 삶을 경험했습니다. !
섹션 1 TObject Atom
TObject란 무엇입니까?
이는 오브젝트 파스칼 언어 아키텍처의 기본 핵심이자 다양한 VCL 컨트롤의 근원입니다. TObject는 DELPHI 애플리케이션을 구성하는 원자 중 하나로 생각할 수 있습니다. 물론 TObject는 기본 Pascal 구문 요소와 같은 더 미묘한 입자로 구성됩니다.
TObject는 DELPHI 컴파일러에 의해 내부적으로 지원되기 때문에 TObject는 DELPHI 프로그램의 원자라고 합니다. TObject를 조상 클래스로 지정하지 않더라도 모든 객체 클래스는 TObject에서 파생됩니다. TObject는 시스템의 일부인 시스템 유닛에서 정의됩니다. System.pas 유닛의 시작 부분에 다음 주석 텍스트가 있습니다:
{미리 정의된 상수, 유형, 프로시저, }
{ 및 함수(예: True, Integer 또는 }
{Writeln)에는 실제 선언이 없습니다.}
{ 대신 컴파일러에 내장되어 있습니다. }
{ 선언된 것처럼 처리됩니다 }
{ 시스템 장치의 시작 부분에.}
이는 이 단위에 미리 정의된 상수, 유형, 프로시저 및 함수(예: True, Integer 또는 Writeln)가 포함되어 있음을 의미합니다. 이들은 실제로 선언되지 않지만 컴파일러에 내장되어 있으며 컴파일 시작 시 사용됩니다. 명시된 정의가 되어야 합니다. Classes.pas 또는 Windows.pas와 같은 다른 소스 프로그램 파일을 프로젝트 파일에 추가하여 소스 코드를 컴파일하고 디버그할 수 있지만 컴파일을 위해 System.pas 소스 프로그램 파일을 프로젝트 파일에 추가할 수는 없습니다. DELPHI는 System!의 중복 정의에 대한 컴파일 오류를 보고합니다.
따라서 TObject는 컴파일러가 내부적으로 제공하는 정의입니다. 프로그램을 개발하기 위해 DELPHI를 사용하는 사람들에게 TObject는 원자적인 것입니다.
System 유닛의 TObject 정의는 다음과 같습니다.
TObject = 클래스
생성자 생성;
절차 무료;
클래스 함수 InitInstance(인스턴스: 포인터): TObject;
절차 CleanupInstance;
함수 클래스 유형: TClass;
클래스 함수 ClassName: ShortString;
클래스 함수 ClassNameIs(const Name: string): Boolean;
클래스 함수 ClassParent: TClass;
클래스 함수 ClassInfo: 포인터;
클래스 함수 InstanceSize: Longint;
클래스 함수 InheritsFrom(AClass: TClass): 부울;
클래스 함수 MethodAddress(const Name: ShortString): 포인터;
클래스 함수 MethodName(주소: 포인터): ShortString;
function FieldAddress(const Name: ShortString): 포인터;
function GetInterface(const IID: TGUID; out Obj): 부울;
클래스 함수 GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
클래스 함수 GetInterfaceTable: PInterfaceTable;
function SafeCallException(ExceptObject: TObject;
ExceptAddr: 포인터): HResult;
프로시저 AfterConstruction;
프로시저 BeforeDestruction;
절차 디스패치(var 메시지);
절차 DefaultHandler(var 메시지);
클래스 함수 NewInstance: TObject;
프로시저 FreeInstance;
소멸자 가상을 파괴하다;
끝;
다음으로 TObject 원자의 문을 점차적으로 두드려 내부에 어떤 구조가 있는지 살펴보겠습니다.
우리는 TObject가 모든 객체의 기본 클래스라는 것을 알고 있습니다. 그렇다면 객체란 정확히 무엇입니까?
DELPHI의 모든 개체는 메모리에서 개체가 차지하는 공간을 나타내는 포인터입니다! 객체는 포인터이지만 객체의 멤버를 참조할 때 MyObject^.GetName 코드를 작성할 필요는 없고 MyObject.GetName만 작성할 수 있습니다. 이는 오브젝트 파스칼 언어의 확장된 구문입니다. 컴파일러에서 지원됩니다. C++ Builder를 사용하는 친구들은 객체와 포인터 사이의 관계에 대해 매우 명확합니다. 왜냐하면 C++ Builder의 객체는 포인터로 정의되어야 하기 때문입니다. 객체 포인터가 가리키는 곳은 객체가 데이터를 저장하는 객체 공간이다. 객체 포인터가 가리키는 메모리 공간의 데이터 구조를 분석해 보자.
객체 공간의 처음 4바이트는 객체 클래스의 가상 메소드 주소 테이블(VMT?C Virtual Method Table)을 가리킵니다. 다음 공간은 객체 자체의 멤버 데이터를 저장하는 공간으로, 객체의 가장 원시적인 조상 클래스의 데이터 멤버부터 객체 클래스의 데이터 멤버까지 전체 순서대로 저장된다. 데이터 멤버는 클래스의 각 수준에서 정의됩니다.
클래스의 가상 메서드 테이블(VMT)은 클래스의 원래 조상 클래스에서 파생된 모든 클래스의 가상 메서드에 대한 프로시저 주소를 보유합니다. 클래스의 가상 메소드는 가상 예약어로 선언된 메소드입니다. 가상 메소드는 객체 다형성을 달성하는 기본 메커니즘입니다. 예약어 Dynamic으로 선언된 동적 메소드도 객체 다형성을 달성할 수 있지만 이러한 메소드는 가상 메소드 주소 테이블(VMT)에 저장되지 않으며 클래스 저장 공간을 절약할 수 있는 오브젝트 파스칼의 또 다른 메소드일 뿐입니다. 하지만 통화 속도가 희생됩니다.
클래스의 가상 메소드를 직접 정의하지 않더라도 클래스의 객체에는 여전히 가상 메소드 주소 테이블에 대한 포인터가 있지만 주소 항목의 길이는 0입니다. 그런데 Destroy, FreeInstance 등과 같이 TObject에 정의된 가상 메소드는 어디에 저장됩니까? 해당 메서드 주소는 VMT 포인터를 기준으로 음수 방향의 공간 오프셋에 저장되는 것으로 나타났습니다. 실제로 VMT 테이블의 음수 방향으로 76바이트 오프셋된 데이터 공간은 객체 클래스의 시스템 데이터 구조이며 이러한 데이터 구조는 컴파일러와 관련되어 있으며 향후 DELPHI 버전에서 변경될 수 있습니다.
따라서 VMT는 음수 오프셋 주소 공간에서 시작하는 데이터 구조라고 생각하면 음수 오프셋 데이터 영역은 VMT의 시스템 데이터 영역이고, VMT의 양수 오프셋 데이터는 사용자 데이터 영역(맞춤형 가상 방식)이다. 주소 테이블). TObject에 정의된 클래스 정보나 객체 런타임 정보와 관련된 함수 및 프로시저들은 일반적으로 VMT의 시스템 데이터와 관련되어 있다.
VMT 데이터는 클래스를 나타냅니다. 실제로 VMT는 클래스입니다. 오브젝트 파스칼에서는 TObject, TComponent 등과 같은 식별자를 사용하여 DELPHI 내부에서 해당 VMT 데이터로 구현되는 클래스를 나타냅니다. 예약어 클래스로 정의된 클래스의 타입은 실제로 해당 VMT 데이터에 대한 포인터이다.
우리 애플리케이션의 경우 VMT 데이터는 정적 데이터입니다. 컴파일러가 애플리케이션을 컴파일한 후 이 데이터 정보가 결정되고 초기화되었습니다. 우리가 작성하는 프로그램 명령문은 VMT 관련 정보에 접근하고, 객체의 크기, 클래스 이름, 런타임 속성 데이터 등의 정보를 얻거나, 가상 메소드를 호출하거나 메소드의 이름과 주소를 읽는 등의 작업을 할 수 있습니다.
객체가 생성되면 시스템은 객체에 대한 메모리 공간을 할당하고 객체를 관련 클래스와 연결합니다. 따라서 객체에 할당된 데이터 공간의 처음 4바이트는 클래스 VMT 데이터에 대한 포인터가 됩니다.
물체가 어떻게 태어나고 죽는지 살펴보겠습니다. 세 살배기 아들이 풀밭에서 뛰어다니는 모습을 보면서 생명의 탄생 과정을 직접 목격했기 때문에 인생의 의미와 위대함을 진정으로 깨달을 수 있었습니다. 죽음을 경험한 사람만이 삶을 더 이해하고 소중히 여길 것입니다. 그럼 객체의 생성과 소멸 과정을 이해해 볼까요!
우리 모두는 다음 명령문을 사용하여 가장 간단한 객체를 구성할 수 있다는 것을 알고 있습니다.
AnObject := TObject.Create;
컴파일러는 다음과 같이 컴파일을 구현합니다.
TObject에 해당하는 VMT를 기반으로 TObject의 Create 생성자를 호출합니다. Create 생성자는 시스템의 ClassCreate 프로세스를 호출하고, 시스템의 ClassCreate 프로세스는 내부에 저장된 VMT 클래스를 통해 NewInstance 가상 메서드를 호출합니다. NewInstance 메소드를 호출하는 목적은 객체의 인스턴스 공간을 설정하는 것입니다. 이 메소드는 오버로드되지 않았으므로 TObject 클래스의 NewInstance입니다. TObjec 클래스의 NewInstance 메서드는 GetMem 프로시저를 호출하여 VMT 테이블에서 컴파일러에 의해 초기화된 개체 인스턴스 크기(InstanceSize)를 기반으로 개체에 대한 메모리를 할당한 다음 InitInstance 메서드를 호출하여 할당된 공간을 초기화합니다. InitInstance 메소드는 먼저 객체 클래스에 해당하는 VMT에 대한 포인터로 객체 공간의 처음 4바이트를 초기화한 다음 나머지 공간을 지웁니다. 객체 인스턴스를 설정한 후 가상 메서드 AfterConstruction도 호출됩니다. 마지막으로 객체 인스턴스 데이터의 주소 포인터를 AnObject 변수에 저장하면 AnObject 객체가 탄생합니다.
마찬가지로 다음 명령문을 사용하여 객체를 삭제할 수 있습니다.
AnObject.Destroy;
TObject의 소멸자 Destroy는 시스템 고유의 가상 메소드 중 하나인 가상 메소드로 선언됩니다. Destory 메서드는 먼저 BeforeDestruction 가상 메서드를 호출한 다음 시스템의 ClassDestroy 프로세스를 호출합니다. ClassDestory 프로세스는 VMT 클래스를 통해 FreeInstance 가상 메서드를 호출하고, FreeInstance 메서드는 FreeMem 프로세스를 호출하여 개체의 메모리 공간을 해제합니다. 이와 같이 시스템에서 개체가 사라집니다.
사물의 파괴 과정은 사물의 구성 과정보다 단순하다. 마치 생명의 탄생은 긴 잉태 과정이지만 죽음은 상대적으로 짧다는 것은 피할 수 없는 법칙인 것 같다.
객체의 생성 및 소멸 과정에서 NewInstance와 FreeInstance라는 두 개의 가상 함수가 호출되어 객체 인스턴스의 메모리 공간을 생성하고 해제합니다. 이 두 함수를 가상 함수로 선언하는 이유는 사용자가 자신의 메모리를 관리해야 하는 특수 개체 클래스(예: 일부 특수 산업 제어 프로그램)를 작성할 때 확장할 수 있는 공간을 허용하기 위한 것입니다.
AfterConstruction과 BeforeDestruction을 가상 함수로 선언하는 것은 미래에 파생 클래스에 새로 태어난 객체가 객체를 생성한 후 처음으로 신선한 공기를 마시게 하고 객체가 죽기 전에 여파를 완료할 수 있는 기회를 제공하는 것이기도 합니다. . 이것은 모두 의미가 있는 것입니다. 실제로 TForm 객체와 TDataModule 객체의 OnCreate 이벤트와 OnDestroy 이벤트는 TForm과 TDataModule 오버로드의 두 가상 함수 프로세스에서 각각 트리거됩니다.
또한, TObjec에서는 객체가 비어 있는지(nil)가 불분명할 때 객체를 안전하게 해제하기 위해 특별히 제공하는 가상 메소드가 아닌 Free 메소드도 제공합니다. 실제로 객체가 비어 있는지 여부를 파악할 수 없다면 프로그램 로직이 불분명한 문제가 있습니다. 그러나 누구도 완벽하지 않으며 실수를 할 수 없습니다. 우발적인 실수를 피하기 위해 Free를 사용하는 것도 좋은 방법입니다. 그러나 올바른 프로그램을 작성하는 것은 그러한 솔루션에만 의존할 수 없습니다. 프로그래밍의 첫 번째 목표는 프로그램의 논리적 정확성을 보장하는 것입니다.
관심 있는 친구들은 대량의 코드가 어셈블리 언어로 작성된 시스템 유닛의 원본 코드를 읽을 수 있습니다. 주의 깊은 친구들은 TObject의 생성자 Create와 소멸자 Destory가 어떤 코드도 작성하지 않았다는 것을 발견할 수 있습니다. 실제로 디버깅 상태의 Debug CPU 창을 통해 Create 및 Destory의 어셈블리 코드가 명확하게 반영될 수 있습니다. DELPHI를 만든 마스터는 사용자에게 너무 많은 복잡한 것을 제공하고 싶지 않았기 때문에 사용자가 간단한 개념을 기반으로 애플리케이션을 작성하고 복잡한 작업을 시스템 내부에 숨겨서 수행할 수 있기를 원했습니다. 따라서 System.pas 유닛을 퍼블리싱할 때 이 두 함수의 코드를 특별히 제거하여 사용자가 TObject가 모든 것의 소스라고 생각하게 하고 사용자 파생 클래스가 완전히 무에서 시작된다는 것 자체는 잘못된 것이 아닙니다. DELPHI의 가장 필수적인 코드를 읽으려면 약간의 어셈블리 언어 지식이 필요하지만 이러한 코드를 읽으면 DELPHI 세계의 기원과 발전에 대한 더 깊은 이해를 얻을 수 있습니다. 비록 많이 이해하지 못하더라도 최소한 몇 가지 기본적인 사항은 이해할 수 있다면 DELPHI 프로그램을 작성하는 데 큰 도움이 될 것입니다.
섹션 2 T클래스 원자
System.pas 유닛에서 TClass는 다음과 같이 정의됩니다.
TClass = TObject의 클래스;
이는 TClass가 TObject의 클래스임을 의미합니다. TObject 자체가 클래스이기 때문에 TClass는 소위 클래스의 클래스입니다.
개념적으로 TClass는 클래스의 한 종류, 즉 클래스입니다. 그러나 우리는 DELPHI 클래스가 VMT 데이터 조각을 나타낸다는 것을 알고 있습니다. 따라서 클래스는 VMT 데이터 항목에 대해 정의된 유형이라고 볼 수 있습니다. 실제로는 VMT 데이터를 가리키는 포인터 유형입니다!
이전의 전통적인 C++ 언어에서는 클래스의 유형을 정의할 수 없었습니다. 객체가 컴파일되면 고정되고 클래스의 구조 정보가 절대 기계어 코드로 변환되므로 전체 클래스 정보가 메모리에 존재하지 않습니다. 일부 상위 객체 지향 언어는 동적 액세스와 클래스 정보 호출을 지원할 수 있지만 복잡한 내부 해석 메커니즘과 더 많은 시스템 리소스가 필요한 경우가 많습니다. DELPHI의 오브젝트 파스칼 언어는 고급 객체 지향 언어의 뛰어난 기능 중 일부를 흡수하는 동시에 프로그램을 기계어 코드로 직접 컴파일하는 전통적인 장점을 유지하여 고급 기능 및 프로그램 효율성 문제를 완벽하게 해결합니다.
DELPHI가 애플리케이션에서 완전한 클래스 정보를 유지하기 때문에 클래스의 VMT 데이터가 핵심 핵심 역할을 하는 런타임 시 클래스를 변환하고 식별하는 등의 고급 객체 지향 기능을 제공할 수 있습니다. 관심 있는 친구들은 System 유닛에서 AsClass와 IsClass의 두 가지 어셈블리 프로세스를 읽을 수 있습니다. 이는 클래스와 VMT 데이터에 대한 이해를 심화하기 위한 as 및 is 연산자의 구현 코드입니다.
...
다음 내용에는 가상 생성자에 대한 이해, 인터페이스 구현 메커니즘 및 예외 처리 구현 메커니즘 등도 포함됩니다. DLPHI의 기본 원칙. 메이데이 이후에 완성해서 모두에게 공헌할 수 있었으면 좋겠습니다.