귀하는 손님 이십니다
로그인
회원가입
  
  델마당 공식 은행계좌
  하나은행 227-910235-83607
  예금주 이상국(운영진)
프로젝트 게시판
투표게시판
델마당소개
기초부터 활용까지! 델파이 교육 - 데브기어
 광고문의 :
강좌, 팁, 정보 강좌, 팁, 정보 입니다.
글내용 - 강좌, 팁, 정보
 서로 다른 패키지에서 같은 이름의 유닛이 사용될 수 없는 이유.
coding
(권순호)
2018-10-09 오전 12:32:18
카테고리: 팁
1904회 조회



등록된 파일이 없습니다.
파스칼 언어에서 각기의 유닛은 다음과 같이 initialization과 finalization 섹션을 가질 수 있다.

unit Unit1;

interface

implementation

var o: TObject;

initialization
  o := TObject.Create;

finalization
  o.Destroy;
end.


각각의 유닛에 정의되어 있는 initialization과 finalization 섹션의 코드는 한번씩만 실행된다.
프로그램 시작할 때 initialization 섹션 코드 실행. 종료 전에 finalization 섹션 코드 실행.

실행파일이 만들어질 때
프로젝트에 포함된 유닛들의 initialization과 finalization 섹션의 코드가 One time으로
한번만 실행될 수 있도록 하기위해 컴파일러가 실행 파일 바이너리에 Unit Table 자료구조를 생성하고
사용되는 유닛들의 initialization과 finalization 엔트리 주소들이 여기에 들어 간다.
그리고 컴파일러에 의해서 생성된 Unit Table의 initialization과 finalization 엔트리 주소는
유닛 테이블에 기록되어 있는 순서대로 RTL의 스타트 업 코드에 의해서 One time으로 실행된다.
유닛에서 initialization 또는 finalization 섹션이 정의되어 있지 않으면 해당 엔트리엔
0 값이 컴파일러에 의해서 기록된다.

바이너리에서 Unit Table을 분석하면 사용되는 유닛의 갯수, 각기 유닛들의 initialization과 finalization
엔트리 주소도 알아낼 수 있다.

;; 컴파일된 프로그램 바이너리의 Unit Table의 한 부분...

CODE:0045A218 30 B3 44 00         dd offset @Menus@initialization$qqrv ; Menus::initialization(void)
CODE:0045A21C C0 B2 44 00         dd offset @Menus@Finalization$qqrv ; Menus::Finalization(void)




그러나 패키지일 경우에는 다음과 같은 문제가 발생한다.

패키지 A와 B 가 있다고 해 보자.

패키지 A와 B가 Contains에 같은 이름의 Unit1을 포함하고 있으면 컴파일은 성공적으로 되어도
IDE 에서 패키지 A를 Install 한 후에, 이어서 패키지 B를 Install 하려고 하면 같은 이름의 유닛이 
존재한다면서 에러를 발생시킨다.


왜 이런 현상이 일어날까?

만약 IDE가 에러를 발생시키지 않고 허용을 하게되면 심각한 문제가 일어나기 때문이다.

어떤 문제냐면
패키지 A가 로드될 때 Unit1의 Initialization 코드가 호출되고
패키지 B가 로드될 때도 같은 Unit1의 Initialization 코드가 중복 호출.

Unit1의 Initialization 코드가 One time으로 한번만 호출되어야 하는데 중복해서 실행되는 문제가 발생.


만약 패키지 B에서 Unit1을 Contains 하지 않고, Unit1을 사용하는(Contains) 패키지 A를 Requires로
포함했다면 그때는 문제가 발생하지 않는다.

Unit1의 Initialization 섹션은 패키지 A에서 한번만 실행되기 때문이다.
패키지는 기존의 DLL 구조에 Package Info Table 이 추가되어 있는 구조라서 패키지가 중복 사용되더라도
DLL Reference 카운트만 증가되고 메모리에 중복적재 되지않고 Initialization 도 한번만 실행 됨.


델파이는 같은 유닛의 Initialization 섹션이 중복 실행되지 못하도록 막기위해
기존의 DLL 구조 위에 사용되는 유닛들의 명세가 포함되도록 패키지란 형식을 만들어서 사용하고 있다.
IDE에 컴포넌트가 많이 설치되어 있을 수록 IDE가 처리해야 할 유닛들의 명세정보도 더 커지게 된다.
패키지에 포함된 유닛들의 명세를 이용해서 같은 이름의 유닛이 사용되지 못하도록 막음.

델파이 파스칼 언어가 Initialization과 Finalization의 종속적인 구조제한을 가질 수 밖에 없는 것은
파스칼은 C++과 달리 클래스 스스로 constructor를 호출해서 객체를 생성하는 기능이 없기 때문이다.

CClassA a;  // C++에선 클래스 스스로 생성자를 호출하면서 인스턴스가 활성화 될 수 있지만

파스칼에선 명시적으로 생성자(Create)를 호출해서 무조건 힙에 메모리를 할당해서 인스턴스가 
할당되어야만 함.


컴파일된 바이너리에서 Package Info 테이블을 분석하면 Contains된 유닛들과 Requires로 참조되는 dcp 
파일들의 목록도 알아낼 수 있다. 델파이에서 dcp 파일은 C++로 비교하면 import 라이브러리 역할을
하기도 하고, 참조되는 패키지의 인터널 TypeInfo를 IDE에 제공하는 역할을 하기도 한다. 

소스코드 없이 패키지 파일과 dcp 파일만 배포되더라도 dcp 파일을 이용해서 역으로 패키지 파일을 분석해 
낼 수도 있다. dcp 는 컴파일된 dcu 파일들을 dcp 헤더를 붙여서 포함하고 있는 구조이므로.

Object Inspector에서의 Reflection과 Data Runtime Binding을 지원하기 위해 델파이 컴파일러는 
필연적으로 많은 양의 디테일한 Extended RTTI 정보를 생성할 수 밖에 없다. 컴파일러에 의해 무지막지하게 
생성되는 Extended RTTI 내부구조를 이용해서 소스에 근접하는 수준으로의 리버스 엔지니어링도 가능하다. 

클래스들의 네임, 패런트 클래스, 프로퍼티, 이벤트 핸들러, 클래스 데이타 멤버 및 함수, set 등의 element 
필드 등의 구체적이고 디테일한 Extended RTTI 정보가 생성되기 때문이다. 

gcc 같은 C++ 컴파일러도 RTTI 정보를 생성하기도 하지만 소스코드에서 명시적으로 typeid를 요구할 때만 
그 부분에 한정되어 생성되고, 생성되는 정보도 type name 밖에 없어서 디버깅 정보가 없으면 사실상 
소스 수준으로의 리버스 엔지니어링이 C++로 컴파일된 바이너리에서는 불가능 하다.

델파이 버전이 올라갈 수록 담야할 정보가 많아져서 Extended RTTI 데이타는 더 커지고, 실행파일의 크기가
커지도록 하는 요인이 되기도 한다.

이런 구조는 해킹에 취약한 델파이의 맹점이기도 하다.

;; 해킹에 취약한 델파이 Extended RTTI 내부구조의 한 부분...

CODE:0044C101 05 54 46 6F 72 6D                             db 5,'TForm'
CODE:0044C107 00 C0 44 00                                   dd offset _cls_Forms_TForm
CODE:0044C10B 90 BF 44 00                                   dd offset off_44BF90
CODE:0044C10F 5E 00                                         dw 94
CODE:0044C111 05 46 6F 72 6D 73                             db 5,'Forms'
CODE:0044C117 55 00                                         dw 85                   ; PropCount
CODE:0044C119 D4 35 41 00 3C 00 00 FE 54 0D+                dd offset off_4135D4    ; PropType
CODE:0044C119 43 00 01 00 00 00 00 00 00 80+                dd 0FE00003Ch           ; GetProc
CODE:0044C119 00 00 00 80 0D 00 06 41 63 74+                dd offset sub_430D54    ; SetProc
CODE:0044C119 69 6F 6E                                      dd 1                    ; StoredProc
CODE:0044C119                                               dd 80000000h            ; Index
CODE:0044C119                                               dd 80000000h            ; Default
CODE:0044C119                                               dw 0Dh                  ; NameIndex
CODE:0044C119                                               db 6,'Action'           ; Name
CODE:0044C13A 3C D9 42 00 20 02 00 FF 78 22+                dd offset off_42D93C    ; PropType
CODE:0044C13A 45 00 1C 11 45 00 00 00 00 80+                dd 0FF000220h           ; GetProc
CODE:0044C13A 00 00 00 80 0E 00 0D 41 63 74+                dd offset sub_452278    ; SetProc
CODE:0044C13A 69 76 65 43 6F 6E 74 72 6F 6C                 dd offset sub_45111C    ; StoredProc
CODE:0044C13A                                               dd 80000000h            ; Index
CODE:0044C13A                                               dd 80000000h            ; Default
CODE:0044C13A                                               dw 0Eh                  ; NameIndex
CODE:0044C13A                                               db 13,'ActiveControl'   ; Name
CODE:0044C162 88 C0 42 00 5B 00 00 FF 98 0F+                dd offset off_42C088    ; PropType
CODE:0044C162 43 00 01 00 00 00 00 00 00 80+                dd 0FF00005Bh           ; GetProc
CODE:0044C162 00 00 00 00 0F 00 05 41 6C 69+                dd offset sub_430F98    ; SetProc
CODE:0044C162 67 6E                                         dd 1                    ; StoredProc
CODE:0044C162                                               dd 80000000h            ; Index
CODE:0044C162                                               dd 0                    ; Default
CODE:0044C162                                               dw 0Fh                  ; NameIndex
CODE:0044C162                                               db 5,'Align'            ; Name
CODE:0044C182 00 10 40 00 E0 02 00 FF 7C 4E+                dd offset off_401000    ; PropType
CODE:0044C182 45 00 01 00 00 00 00 00 00 80+                dd 0FF0002E0h           ; GetProc
CODE:0044C182 00 00 00 00 10 00 0A 41 6C 70+                dd offset sub_454E7C    ; SetProc
CODE:0044C182 68 61 42 6C 65 6E 64                          dd 1                    ; StoredProc

Initialization과 Finalization 섹션을 정의할지 안할지는 사용자 마음대로 이므로 안전장치로
같은 이름의 유닛이 사용되지(Contains) 못하도록 하는 것임. 파스칼 언어가 갖고있는 구조적인 문제를 
피해가기 위한 일종의 편법.


질문란에 올라온 다음 글의 해결 방법.

https://www.delmadang.com/community/bbs_view.asp?bbsNo=17&bbsCat=0&indx=455962&page=1


IDE에서 기본 설정으로 생성되는 패키지 프로젝트는 디폴트로 패키지 속성이
'Designtime and runtime' 속성으로 생성된다.

Designtime 속성을 갖고 있기 때문에 IDE에서 Install이 가능한 것이다.

패키지 프로젝트 옵션에서 Description --> Usage options 에서 패키지 속성을
Runtime only 로 바꾸어서 사용하면 된다.

Runtime 패키지는 Designtime 패키지와 달리 IDE 어드레스 공간에 매핑(Install)되지 않고
컴파일만 해서 사용하면 된다.

질문에 해당하는 패키지 A와 B는 하나의 실행 프로그램에서 동시에 사용되지 않고
서로 다른 플펫폼에서 개별적으로 사용되는 경우라서 패키지 속성을 Runtime only로 컴파일 해서 사용하면
같은 이름의 유닛을 Contains에 가져도 상관 없다. 별개의 플렛폼에서 독립적인 Runtime 패키지로 사용되는
경우이므로.