소프트웨어를 개발하기 위해 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 – 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 연산자의 구현 코드입니다.
DELPHI의 원자 세계(2)
키워드: 델파이 컨트롤 기타
섹션 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 연산자의 구현 코드입니다.
클래스 유형을 사용하면 클래스를 변수로 사용할 수 있습니다. 클래스 변수는 특별한 개체로 이해될 수 있으며, 개체와 마찬가지로 클래스 변수의 메서드에 액세스할 수 있습니다. 예: 다음 프로그램 부분을 살펴보겠습니다.
유형
TSampleClass = TSampleObject의 클래스;
TSampleObject = 클래스( TObject )
공공의
생성자 생성;
소멸자 재정의;
클래스 함수 GetSampleObjectCount:Integer;
프로시저 GetObjectIndex:Integer;
끝;
var
aSampleClass : TSampleClass;
a클래스 : T클래스;
이 코드에서는 TSampleObject 클래스와 관련 클래스 유형 TSampleClass, 두 개의 클래스 변수 aSampleClass 및 aClass를 정의합니다. 또한 TSampleObject 클래스에 대한 생성자, 소멸자, 클래스 메서드 GetSampleObjectCount 및 개체 메서드 GetObjectIndex도 정의했습니다.
먼저 클래스 변수 aSampleClass와 aClass의 의미를 이해해보자.
분명히 정수 변수 i에 123개의 상수 값을 할당하는 것처럼 TSampleObject와 TObject를 상수 값으로 처리하여 aClass 변수에 할당할 수 있습니다. 따라서 클래스 유형, 클래스 및 클래스 변수 간의 관계는 유형, 상수 및 변수 간의 관계이지만 객체 수준이 아닌 클래스 수준입니다. 물론 TObject를 aSampleClass에 직접 할당하는 것은 합법적이지 않습니다. 왜냐하면 aSampleClass는 TObject 파생 클래스 TSampleObject의 클래스 변수이고 TObject에는 TSampleClass 유형과 호환되는 모든 정의가 포함되어 있지 않기 때문입니다. 반대로 TSampleObject는 TObject의 파생 클래스이고 TClass 유형과 호환되므로 TSampleObject를 Class 변수에 할당하는 것이 합법적입니다. 이는 객체 변수의 할당 및 유형 일치 관계와 정확히 유사합니다.
그럼, 클래스 메소드가 무엇인지 살펴보겠습니다.
소위 클래스 메소드란 위에서 정의한 GetSampleObjectCount 메소드와 같이 클래스 수준에서 호출되는 메소드를 말하는데, 이는 class 예약어로 선언된 메소드이다. 클래스 메소드는 객체 수준에서 호출되는 객체 메소드와 다릅니다. 객체 메소드는 이미 우리에게 익숙하며, 클래스 메소드는 항상 모든 클래스 객체의 공통 특성에 접근하고 제어하며 객체를 중앙에서 관리하는 수준에서 사용됩니다. TObject의 정의에서 ClassName, ClassInfo, NewInstance 등과 같은 수많은 클래스 메소드를 찾을 수 있습니다. 그 중 NewInstance도 virtual, 즉 가상 클래스 메소드로 정의되어 있다. 이는 파생 하위 클래스에서 NewInstance의 구현 메서드를 다시 작성하여 특별한 방법으로 해당 클래스의 개체 인스턴스를 생성할 수 있음을 의미합니다.
클래스 메소드에서 self 식별자를 사용할 수도 있지만 그 의미는 객체 메소드의 self와 다릅니다. 클래스 메소드의 self는 자신의 클래스, 즉 VMT에 대한 포인터를 나타내고, 객체 메소드의 self는 객체 자체, 즉 객체 데이터 공간에 대한 포인터를 나타냅니다. 클래스 메서드는 클래스 수준에서만 사용할 수 있지만 여전히 개체를 통해 클래스 메서드를 호출할 수 있습니다. 예를 들어, 객체 TObject의 클래스 메소드 ClassName은 aObject.ClassName 문을 통해 호출될 수 있습니다. 왜냐하면 객체 포인터가 가리키는 객체 데이터 공간의 처음 4바이트는 클래스 VMT에 대한 포인터이기 때문입니다. 반대로 클래스 수준에서는 객체 메서드를 호출할 수 없으며 TObject.Free와 같은 문은 불법이어야 합니다.
생성자는 클래스 메소드이고 소멸자는 객체 메소드라는 점은 주목할 가치가 있습니다!
무엇? 생성자는 클래스 메소드이고 소멸자는 객체 메소드입니다! 실수가 있었나요?
객체를 생성할 때 다음과 유사한 명령문을 명확하게 사용합니다.
aObject := TObject.Create;
분명히 TObject 클래스의 Create 메소드를 호출하고 있습니다. 객체를 삭제할 때 다음 문을 사용하십시오.
aObject.Destroy;
Free 메소드를 사용하여 객체를 해제하더라도 해당 객체의 Destroy 메소드가 간접적으로 호출됩니다.
그 이유는 매우 간단합니다. 객체가 생성되기 전에는 객체가 아직 존재하지 않고 클래스 메소드만 사용하여 객체를 생성할 수 있습니다. 반대로 객체를 삭제하면 클래스가 아닌 기존 객체가 삭제됩니다.
마지막으로 가상 생성자 문제에 대해 논의해 보겠습니다.
기존 C++ 언어에서는 가상 소멸자를 구현할 수 있지만 가상 생성자를 구현하는 것은 어려운 문제입니다. 왜냐하면 전통적인 C++ 언어에는 클래스 유형이 없기 때문입니다. 전역 개체의 인스턴스는 컴파일 시 전역 데이터 공간에 존재하며, 함수의 로컬 개체도 컴파일 시 스택 공간에 매핑되는 인스턴스입니다. 동적으로 생성된 개체라도 new 연산자를 사용하여 고정된 클래스 구조에 배치됩니다. 힙 공간에 있으며 생성자는 생성된 객체 인스턴스를 초기화하는 객체 메서드일 뿐입니다. 기존 C++ 언어에는 실제 클래스 메소드가 없습니다. 소위 정적 클래스 기반 메소드가 정의될 수 있지만 가상 클래스 메소드는 말할 것도 없이 특정 객체만 대상으로 할 수 있습니다. 효율적입니다. 따라서 전통적인 C++ 언어에서는 특정 개체 인스턴스가 생성되기 전에는 생성할 개체를 기반으로 개체 자체를 구성하는 것이 불가능하다고 믿습니다. 그것은 실제로 불가능합니다. 왜냐하면 이것은 논리에서 자기모순적인 역설을 만들 것이기 때문입니다!
그러나 동적 클래스 유형 정보, 진정한 가상 클래스 메소드, DELPHI의 클래스를 기반으로 구현된 생성자의 핵심 개념 덕분에 가상 생성자가 구현될 수 있습니다. 사물은 수업을 통해 생산되며, 사물은 성장하는 아기와 같고, 수업은 엄마이다. 아기 자신은 미래에 어떤 사람이 될지 모르지만, 엄마들은 자신만의 교육 방법을 활용하여 다양한 자녀를 양육한다. . 여러분, 원칙은 같습니다.
다양한 유형의 컨트롤이 자체 생성 메서드를 구현할 수 있도록 생성자 Create가 가상으로 정의되는 것은 TComponent 클래스의 정의에 있습니다. 이것이 TClass가 만든 클래스 같은 개념의 위대함이자 DELPHI의 위대함입니다.
................................................. ..
3장 WIN32의 시간과 공간관
나의 늙은 아버지는 어린 손자가 땅바닥에서 장난감을 가지고 노는 것을 보더니 나에게 이렇게 말씀하셨다. " 어렸을 때를 생각해보면 장난감 자동차, 작은 알람시계, 오르골 등을 자주 분해해서 어머니에게 혼나는 일이 많았습니다.
내가 처음으로 컴퓨터의 기본 원리를 이해한 것은 내가 분해한 오르골과 관련이 있었습니다. 고등학교 시절 만화책에서 흰 수염을 기른 할아버지가 스마트 기계의 이론을 설명하고 있었고, 콧수염을 기른 삼촌이 컴퓨터와 오르골에 대해 이야기하고 있었습니다. 그들은 컴퓨터의 중앙 처리 장치가 오르골의 발음에 사용되는 악보의 줄이고, 컴퓨터 프로그램은 오르골의 작은 원통에 빽빽하게 들어찬 돌기라고 말했습니다. 중앙 처리 장치의 회전에 따라 명령 포인터가 자연스럽게 움직이며 작은 원통의 음악을 나타내는 범프가 음악 리드의 진동을 제어하여 중앙 프로세서의 프로그램 실행과 동일한 명령을 생성합니다. 오르골은 장인이 작은 원통에 새긴 악보에 따라 아름다운 선율을 연주합니다. 컴퓨터는 프로그래머가 미리 프로그래밍한 프로그램을 기반으로 복잡한 처리를 완료합니다. 대학에 진학한 후, 흰 수염을 기른 노인은 과학의 거인 튜링이었고, 그의 유한 오토마타 이론은 전체 정보 혁명의 발전을 이뤘고, 콧수염을 기른 삼촌은 컴퓨터의 아버지인 폰 노이만이라는 것을 알게 되었습니다. 컴퓨터 아키텍처는 여전히 컴퓨터의 주요 아키텍처 구조입니다. 오르골이 헛되이 분해되지 않았기 때문에 어머니는 안심하실 수 있습니다.
단순하고 심오한 이해가 있어야만 심오하고 간결한 창작물을 만들 수 있습니다.
이 장에서는 Windows 32비트 운영 체제에서의 프로그래밍과 관련된 기본 개념을 논의하고 WIN32에서 시간과 공간에 대한 올바른 보기를 설정합니다. 이 장을 읽은 후 프로그램, 프로세스 및 스레드에 대해 더 깊이 이해하고 실행 파일, 동적 링크 라이브러리 및 런타임 패키지의 원리를 이해하고 글로벌 데이터, 로컬 데이터 및 메모리의 매개 변수에 대한 진실을 명확하게 볼 수 있기를 바랍니다. .
섹션 1 프로세스 이해
역사적인 이유로 인해 Windows는 DOS에서 시작되었습니다. DOS 시대에는 항상 프로그램이라는 개념만 있었고 프로세스라는 개념은 없었습니다. 당시에는 UNIX, VMS 등 일반 운영체제에만 프로세스라는 개념이 있었고, 다중 프로세스란 미니컴퓨터, 터미널, 다중 사용자 등을 의미하며, 이는 곧 돈을 의미하기도 했습니다. 대부분의 경우 상대적으로 저렴한 마이크로컴퓨터와 DOS 시스템만 사용할 수 있었는데, 운영체제를 공부하면서부터 프로세스와 미니컴퓨터를 접하기 시작했습니다.
Windows 3 이후였습니다. 예전에는 DOS에서는 동시에 하나의 프로그램만 실행할 수 있었지만 Windows에서는 여러 프로그램을 동시에 실행할 수 있었습니다. DOS에서는 프로그램을 실행하는 동안 동일한 프로그램을 동시에 실행할 수 없지만 Windows에서는 동일한 프로그램의 복사본을 두 개 이상 동시에 실행할 수 있으며 실행 중인 프로그램의 각 복사본은 프로세스입니다. 더 정확하게 말하자면, 프로그램을 실행할 때마다 작업이 생성되고, 각 작업은 프로세스입니다.
프로그램과 프로세스를 함께 이해하면 프로그램이라는 단어는 정적인 것을 가리키는 것으로 간주될 수 있습니다. 일반적인 프로그램은 EXE 파일 또는 EXE 파일과 여러 DLL 파일로 구성된 정적 코드 및 데이터입니다. 프로세스는 메모리에서 동적으로 실행되는 코드와 동적으로 변경되는 데이터인 프로그램의 실행입니다. 정적 프로그램을 실행해야 하는 경우 운영 체제는 이 작업을 위해 특정 메모리 공간을 제공하고 정적 프로그램 코드와 데이터를 이러한 메모리 공간으로 전송하며 이 공간에 프로그램 코드와 데이터를 재배치하고 매핑합니다. 내부에서 실행되어 동적 프로세스가 생성됩니다.
동시에 실행되는 동일한 프로그램의 두 복사본은 시스템 메모리에 두 개의 프로세스 공간이 있지만 해당 프로그램 기능은 동일하지만 동적으로 변경되는 상태가 다르다는 것을 의미합니다.
프로세스의 실행 시간 측면에서 각 프로세스는 동시에 실행됩니다. 전문 용어로는 병렬 실행 또는 동시 실행이라고 합니다. 그러나 이것은 주로 운영 체제가 우리에게 주는 피상적인 느낌입니다. 실제로 각 프로세스는 시간 공유 방식으로 실행됩니다. 즉, 각 프로세스는 프로세스의 프로그램 명령을 실행하기 위해 차례로 CPU 시간을 차지합니다. CPU의 경우 한 프로세스의 명령만 동시에 실행됩니다. 운영체제는 CPU에서 실행되는 각 프로세스의 현재 상태를 지속적으로 저장하고 전환하여 각 예약된 프로세스가 완전하고 지속적으로 실행되고 있다고 생각하도록 합니다. 프로세스의 시분할 스케줄링은 매우 빠르기 때문에 프로세스가 모두 동시에 실행되고 있다는 인상을 줍니다. 실제로 진정한 동시 작업은 다중 CPU 하드웨어 환경에서만 가능합니다. 나중에 스레드에 대해 이야기할 때 스레드가 프로세스를 실제로 구동하는 것이며 더 중요한 것은 프로세스 공간을 제공한다는 점을 알게 될 것입니다.
프로세스가 차지하는 공간 측면에서 각 프로세스 공간은 상대적으로 독립적이며 각 프로세스는 자체 독립 공간에서 실행됩니다. 프로그램에는 코드 공간과 데이터 공간이 모두 포함됩니다. 코드와 데이터는 모두 프로세스 공간을 차지합니다. Windows는 각 프로세스에 필요한 데이터 공간에 실제 메모리를 할당하고 일반적으로 코드 공간에 대한 공유 방법을 사용하여 프로그램의 하나의 코드를 프로그램의 여러 프로세스에 매핑합니다. 즉, 프로그램에 100K의 코드가 있고 100K의 데이터 공간이 필요한 경우, 즉 총 200K의 프로세스 공간이 필요하면 운영 체제는 프로그램이 처음 실행될 때 200K의 프로세스 공간을 할당하고 200K의 프로세스 공간을 할당합니다. 프로그램이 두 번째 실행될 때 운영 체제는 100K의 데이터 공간만 할당하는 반면 코드 공간은 이전 프로세스의 공간을 공유합니다.
위 내용은 Windows 운영체제에서의 프로세스에 대한 기본적인 시공간 관점입니다. 실제로 Windows의 16비트 운영체제와 32비트 운영체제에서는 프로세스에 대한 시공간 관점에 큰 차이가 있습니다.
시간적으로 보면 Windows 3.x와 같은 16비트 Windows 운영 체제의 프로세스 관리는 실제로는 단순한 다중 작업 관리 운영 체제에 불과합니다. 게다가 운영 체제의 작업 스케줄링은 수동적입니다. 작업이 메시지 처리를 포기하지 않으면 운영 체제는 기다려야 합니다. 16비트 Windows 시스템의 프로세스 관리 결함으로 인해 프로세스가 실행될 때 CPU 리소스를 완전히 차지합니다. 당시 마이크로소프트는 16비트 윈도우가 다른 작업을 계획할 수 있는 기회를 주기 위해 윈도우 애플리케이션 개발자들이 마음이 넓은 프로그래머라고 칭찬했다. 운영 체제. 반대로 Windows 95 및 NT와 같은 WIN32 운영 체제에는 실제 다중 프로세스 및 멀티 태스킹 운영 체제 기능이 있습니다. WIN32의 프로세스는 운영 체제에 의해 완전히 예약됩니다. 실행 중인 프로세스의 시간 조각이 끝나면 운영 체제는 프로세스가 여전히 데이터를 처리하고 있는지 여부에 관계없이 다음 프로세스로 적극적으로 전환합니다. 엄밀히 말하면, 16 비트 Windows 운영 체제는 완전한 운영 체제로 간주 될 수 없지만 32 비트 Win32 운영 체제는 진정한 운영 체제입니다. 물론 Microsoft는 Win32가 16 비트 Wind
공간 관점에서, 16 비트 Windows 운영 체제의 프로세스 공간은 비교적 독립적이지만 프로세스는 서로의 데이터 공간에 쉽게 액세스 할 수 있습니다. 이러한 프로세스는 실제로 동일한 물리적 공간에서 다른 데이터 세그먼트이기 때문에 부적절한 주소 작업으로 인해 공간 판독 및 쓰기가 잘못되어 운영 체제를 충돌시킬 수 있습니다. 그러나 Win32 운영 체제에서 각 프로세스 공간은 완전히 독립적입니다. Win32는 각 프로세스에 최대 4G의 가상 및 연속 주소 공간을 제공합니다. 소위 연속 주소 공간은 각 프로세스가 16 비트 윈도우의 세그먼트 공간이 아닌 $ 00000000에서 $ ffffffff의 주소 공간을 가지고 있음을 의미합니다. Win32에서는 다른 프로세스 공간의 데이터에 의도하지 않은 작업에 읽기 및 쓰기 작업에 대해 걱정할 필요가 없으며 작업을 괴롭히는 다른 프로세스에 대해 걱정할 필요가 없습니다. 동시에 Win32가 제공하는 연속 4G 가상 공간은 하드웨어를 지원하는 운영 체제가 맵핑 한 물리적 메모리입니다. 물리적 기억.
섹션 2 프로세스 공간
Delphi를 사용하여 Win32 응용 프로그램을 작성하면 프로세스가 실행될 때 과정의 내부 세계를 거의 신경 쓰지 않습니다. Win32는 우리의 프로세스에 4G의 지속적인 가상 프로세스 공간을 제공하기 때문에 세계에서 가장 큰 응용 프로그램은 현재 그 일부만 사용합니다. 프로세스 공간은 무제한 인 것처럼 보이지만 4G 프로세스 공간은 가상이며 기계의 실제 메모리는 이것과는 거리가 멀다. 프로세스에는 광대 한 공간이 있지만 스택 오버플로로 인해 일부 복잡한 알고리즘 프로그램, 특히 수많은 재귀 알고리즘이 포함 된 프로그램으로 인해 여전히 실행할 수 없습니다.
따라서 4G 프로세스 공간의 구조, 물리적 메모리와의 관계 등에 대한 심층적 인 이해는 Win32의 시공간 세계를 더 명확하게 이해하는 데 도움이되므로 실제 개발 작업에서 올바른 방법을 사용할 수 있습니다. 다양한 어려운 문제를 해결하기위한 세계관과 방법론.
다음으로, 우리는 간단한 실험을 사용하여 Win32의 프로세스 공간의 내부 세계를 이해합니다. 컵 레지스터와 조립 언어에 대한 지식이 필요할 수도 있지만 간단한 언어로 설명하려고했습니다.
Delphi가 시작되면 Project1 프로젝트가 자동으로 생성되며 시작하여 시작합니다. 예를 들어 Project1.dpr의 원래 프로그램에서 어디서나 중단 점을 설정하십시오. 예를 들어, 시작 문장에서 중단 점을 설정하십시오. 그런 다음 프로그램을 실행하면 중단 점에 도달하면 자동으로 중지됩니다. 현재 디버깅 도구에서 CPU 창을 열어 프로세스 공간의 내부 구조를 관찰 할 수 있습니다.
현재 명령어 포인터 레지스터 EIP는 프로그램 명령이 위치한 주소의 최고 16 진수 숫자에서 $ 0043E4B8로 중지됩니다. 현재 프로그램은 4G의 하단에 주소 위치에 있음을 알 수 있습니다. $ 00000000에서 $ ffffffff를위한 꽤 작은 주소 공간을 차지하는 공정 공간.
CPU 창의 명령 상자에서 프로세스 공간의 내용을 찾을 수 있습니다. $ 00400000 미만의 공간을 볼 때는 일련의 물음표가 00400000보다 낮은 컨텐츠에 나타납니다. 현재 Global Variable Hinstance의 16 진수 값을 보면 $ 00400000이라는 것을 알 수 있습니다. Hinstance는 프로세스 인스턴스의 핸들을 반영하지만 실제로는 16 비트 Windows에서 프로그램이 메모리에로드 될 때 시작 주소 값입니다. 따라서 프로세스 프로그램이 $ 00400000에서 시작하여, 즉 4G 가상 공간에서 4m에서 시작하는 공간은 프로그램이로드되는 공간이라고 생각할 수 있습니다.
$ 00400000 이후 및 $ 0044d000 이전에 주로 프로그램 코드 및 글로벌 데이터의 주소 공간입니다. CPU 창의 스택 상자에서 현재 스택의 주소를 볼 수 있습니다. 마찬가지로 현재 스택 주소 공간은 $ 0067B000에서 $ 00680000이며 길이는 $ 5000입니다. 실제로, 프로세스의 최소 스택 공간 크기는 $ 5000이며, Delphi 프로그램을 컴파일 할 때 프로젝트 링커 페이지에 설정된 최소 스택 크기 값과 $ 1000의 최소 스택 크기 값을 기반으로 얻습니다. 스택은 고급 주소에서 프로그램이 실행중인 스택이 충분하지 않으면 스택 공간의 크기를 하단 주소를 향한 상태로 증가합니다 프로세스 공간. 델파이 프로그램을 컴파일 할 때는 프로젝트의 링커 페이지에서 최대 스택 크기의 값을 설정하여 증가 할 수있는 최대 스택 공간을 제어 할 수 있습니다. 특히 깊은 서브 루틴 호출 관계 또는 재귀 알고리즘을 사용하는 프로그램에서는 최대 스택 크기의 값을 합리적으로 설정해야합니다. 서브 루틴을 호출하려면 스택 공간이 필요하고 스택이 소진 된 후 시스템에 "스택 오버플로"오류가 발생합니다.
스택 공간 이후의 공정 공간은 여유 공간이어야합니다. 실제로, Win32의 관련 정보는 80,000,000 달러 이후의 2G 공간이 시스템에서 사용하는 공간이라고 말합니다. 프로세스는 실제로 2G 공간만을 소유 할 수있는 것 같습니다. 실제로, 프로세스가 실제로 소유 할 수있는 공간은 2G가 아닙니다. $ 00000000에서 $ 00400000의 4m 공간은 제한된 지역이기 때문입니다.
그러나 무엇이든, 우리의 프로세스가 사용할 수있는 주소는 여전히 매우 광범위합니다. 특히 스택 공간 이후와 $ 80,000,000 이후에는 프로세스 공간의 주요 전장입니다. 시스템에서 프로세스에 의해 할당 된 메모리 공간은이 공간에 매핑되며 프로세스에 의해로드 된 동적 링크 라이브러리는이 공간에 매핑되며 새 스레드의 스레드 스택 공간 도이 공간에 매핑됩니다. 메모리 할당과 관련된 작업은 모두이 공간에 매핑됩니다. 여기에 언급 된 매핑은 실제 메모리 와이 가상 공간 사이의 대응을 의미합니다. ???? ".