델파이의 인터페이스 트랩
이제 두 가지 주요 함정을 알고 있습니다.
트랩 1. 인터페이스 유형 변환 트랩
a) 객체가 실제로이 인터페이스를 구현 하더라도이 참조의 유형을 선언하지 않는 인터페이스에 대한 객체 참조를 시전 할 수 없습니다 (hehe, 장점은 발음하기가 어렵습니다). b) 객체 변수가 인터페이스 변수에 할당되고 인터페이스 변수가 객체 변수에 할당되면 객체 변수의 주소가 변경되었습니다. 즉, 더 이상 원래 객체가 아니지만 오류 주소를 가리 킵니다. . 예 : i1 = 인터페이스
기능 : 부울;
끝;
tc1 = 클래스
att1 : 정수;
끝;
TC2 = 클래스 (TC1, i1)
att2 : 정수;
기능 : 부울;
끝; intf1 : i1; obj1 : tc!; obj2 : tc2; obj2 : = tc2.create;
OBJ1 : = OBJ2.
i1 (obj2).
I1 (OBJ1). OBJ1의 유형 TC1은 i1이 구현되었다고 선언하지 않으므로 OBJ1이 I1을 구현하더라도 i1로 변환 할 수 없습니다. 또한 객체를 인터페이스로 변환 한 다음 뒤로 변환하면 문제가 발생합니다. obj2 : = tc2.create; obj2.att1 : = 0;
intf1 : = obj2; // 정확합니다. OBJ2 : = intf1; intf1. OBJ2.ATT1 : = 0; 즉, 객체 참조에서 포인터 참조로 변환 한 후 주소가 변경되지만 포인터 참조가 객체 참조에서 돌아온 다음 객체 참조 (Delphi의 버그?)로 돌아갑니다.
트랩 2. 인터페이스 수명 관리
상식 (여기서는 델파이 사용법이 아닌 상식을 프로그래밍하는 것)에 근거하여 인터페이스에는 실제 객체를 전혀 생성 할 수 없기 때문에 인터페이스에는 수명 관리가 필요하지 않다고 생각합니다. 그러나 Delphi는 다시 한 번 내 상식에 부딪쳤다 (허, 왜 "갱신"이라고 말합니까?) 인터페이스는 수명이 있으며 다음 세 가지 방법을 구현해야합니다. 함수 QueryInterface (const IID : tguid; Out OBJ) : hresult; STDCALL; 기능 _addref; stdcall; 함수 _Release; 그래서이 세 가지 방법을 구현하는 방법을 모르겠습니다. J이 세 가지 메소드를 직접 구현하지 않으려면 tcomponent를 사용할 수 있습니다. Tcomponent 가이 세 가지 방법을 구현 했으므로이를 상속받을 수 있으므로이 세 가지 방법을 구현할 필요가 없습니다. 자신감으로 사용할 수 있습니까? 대답은 아니오입니다. 델파이는 비밀리에 인터페이스 변수를 nil로 설정했을 때 (내 기대를 넘어서서). 함수 _intfclear (var dest) : pointer; ) ._Release; 끝; 끝; 그리고 _Release 일 때 무엇을 했습니까? 함수 tcomponent._Release : fvclcomobject = nil이면 시작된 경우; , 아무것도하지 않을 것입니다. 우리가 COM 객체로 작동하지 않으므로 문제가 없습니까? 답은 여전히 아니오입니다. OBJ2 : = tc2.create; obj2; intf1.do; 불법 주소 액세스 오류가 발생합니다. 왜? 위에서 언급 한 바와 같이, 인터페이스 참조가 nil로 설정되면 _intfclear가 호출되며 _intfclear는 객체의 _release를 호출하고 자연스럽게 불법 주소 액세스 오류가 발생합니다. 어떤 사람들은 그것이 불필요하다고 말합니다. 인터페이스 참조는 주소 일 뿐이며 수동으로 설정할 필요가 없습니다. obj2 : = tc2.create; tryintf1 : = obj2; intf1.do; 결과는 여전히 예기치 않은 주소 액세스 오류 일 수 있습니다. 왜? 델파이 컴파일러는 영리하기 때문에이 주소를 NIL에 대해 설정하는 것을 잊어 버리므로 자동으로 델파이 컴파일러가 너무 영리한 것 같습니다. 그것을 해결하는 방법? 방법 1 : 인터페이스 참조를 NIL에 먼저 설정 한 다음 객체를 해제하십시오. intf1 : = nil; 포인터 (intf1) : = nil; 두 번째 방법을 사용하는 경향이 있으므로 누가 먼저 출시할지 생각할 필요가 없습니다. 또한 일부 디자인 패턴에서는 인터페이스 참조 만 보유 할 수 있으며,이 시점에서 참조 된 객체가 언제 릴리스 될지 알 수 없습니다. 예를 들어, 복합 패턴을 고려하십시오. tcomposite = class (tcomponent, i1) 개인 인터리스트 : txcontainer; // "잎"에 대한 인터페이스 참조를 저장하는 컨테이너 클래스. 공개 절차 추가 (Aintf : i1); 분명히 "잎"이이 "합성 객체"객체보다 나중에 방출 될 것인가? 나는 그렇게 생각하지 않습니다. 이 규정이 의무화되면 많은 유연성이 손실됩니다. 따라서 이러한 인터페이스 참조가 NIL에 배치 될 때 객체가 해제 된 후 불법 주소 액세스 오류를 피하기 위해 원래 객체와 관계가 없을 것이라고 생각합니다. 어떤 컨테이너를 사용해야합니까? 정렬? tlist? 장관? 우선, 우리가 수용하고자하는 것은 인터페이스이기 때문에 장관이어야한다고 생각합니다. 그러나 그에게 자유로울 때, 그것은 모든 인터페이스를 nil에 수용하는 모든 인터페이스를 넣습니다. 이것이 바로 우리가 원하지 않는 것입니다. 또는 인터페이스 참조 IT 저장을 무료 전에 포인터로 변환 한 다음 NIL로 설정할 수 있습니다. I : = 0에서 인터리스트를 제공합니다 .count -1 dopointer (interlist.Items [i]) : = nil; 그런 다음 배열을 시도합니다. 인터리스트 : I1 배열; 동적 배열을 해제해서는 안됩니다. 매우 유용한 것처럼 보이지만 컴파일러가 출시 될 때 각 요소를 여전히 NIL로 설정하고 인터페이스로서 여전히 불법 주소 액세스 오류가 가능합니다. I : = 하이 (ARR) Dopointer (ARR [i])를 위해 이런 식으로 수행 할 수 있습니다. 당신이 그것을 사용하고 당신은 그것을 기억하지 못할 수도 있습니다. 마지막으로 tlist를 사용하십시오. 그러나 TLIST에는 포인터가 있으므로 ADD는 다음과 같이 진행해야합니다. XXX.ADD (AINTF : I1) Interlist.Add (AINTF)를 시작하십시오 출시시 특별 처리가 필요합니다. 완벽 해 보이지만 여전히 함정이 있습니다. interlist.add (tc2.create) 또는 obj2; 순수한 포인터이기 때문에 인터페이스로 변환 될 때 인터페이스 참조를 참조하는 객체의 주소 변환은 수행되지 않으므로 (수행 방법은 모릅니다) 인터페이스에서 선언 한 메소드를 호출 할 때. 또 다른 불법 주소 액세스 오류입니다. 이것은 interlist.add (포인터 (i1 (tc2.create)); 약간 번거로운 것이지만 이것이 가장 좋은 솔루션입니다 (내가 아는 것처럼). 전송이 첫 번째 통화에서 오류를 일으킨다는 것을 잊어 버리면 오류를 찾기가 더 쉽습니다 (배열 사용과 비교할 때). 1. TLIST를 사용하여 인터페이스 참조를 관리하십시오. 2. 추가 할 때 객체를 인터페이스로 변환 한 다음 원래 인터페이스 참조 인 경우 포인터로 직접 변환 할 수 있습니다. 3. TLIST를 사용하여 관리되지 않는 인터페이스 참조. 인터페이스를 참조하여 다음 방법으로 수동으로 NIL로 설정해야합니다. POINter (intFREF) : = NIL; 따라서,이 세 가지 방법을 구현하는 데 어려움을 겪을 수도 있습니다. 그러나 _Release는 다음과 같이 구현됩니다. 대상. 나는 그 당시에는 그것을 사용한 적이 없어서 충격을 받았다. 이것이 구현 방식이며, TComponent의 상속보다 더 많은 문제가 발생합니다. 달리 사용하지 않는 한 권장되지 않습니다. 위의 인터페이스에 대한 모든 토론은 일반적으로 사용되는 언어 수준 인터페이스로 제한되며 COM+ 인터페이스에 대한 논의는 없습니다. 이러한 이상한 델파이 인터페이스의 구현은 COM+ 인터페이스에서 개발하는 것과 많은 관련이 있으며, 이는 역사적 부담으로 인한 타협의 결과입니다.