LazarusCompleteGuide:7.2
컴포넌트 모델
델파이 애플리케이션을 라자루스로 포팅하는 이유에는 여러 가지가 있다.
하지만 CodeGrer-Embarcadero가 버전 7이후 델파이를 개발한 방식을 살펴보면 그 이유는 충분하다-네이티브 에플리케이션을 쓰기 위해 .NET를 설치하고 활성화시키는 것이 이상해 보이긴 하지만 말이다. 애플리케이션의 대상 플랫폼은 64-bit Windows일 수도 있고 (작성 시에는 델파이로는 불가능) 아니면 Linux와 MacOS X이 될 수도 있다.
라자루스는 번거로움을 피하고 애플리케이션을 포팅할 수 있도록 다양한 방법을 지원하지만, 새 플랫폼으로 포팅 시 최선의 첫 번째 단계는 애플리케이션의 설계를 주의 깊게 살펴보는 것이다.
제3자 컴포넌트를 이용해왔는지 확인하는 것이 꼭 필요하다. 이를 위해선 델파이 컴포넌트를 구성하는 것이 무엇인지, 그리고 델파이 컴포넌트가 라자루스 컴포넌트와 어떻게 다른지를 확인해야 한다. 컴포넌트의 소스 코드를 이용 가능한지 아는 것도 중요한데, 바이너리 형태로만 이용 가능한 델파이 컴포넌트들은 (.DCU 파일 또는 .DCP 패키지) 라자루스에서 이용할 수 없기 때문이다.
델파이의 컴포넌트
델파이 컴포넌트를 분류하는 방법에는 여러 가지가 있다. 하지만 가장 뚜렷한 구별은 아마도 시각적 그리고 비시각적 델파이 컴포넌트로 나누는 경우일 것이다. 두 가지 모두 기반 클래스의 자손이지만 완전히 새로운 기능을 구현한다; 아니면 기존 VCL 클래스 라이브러리에서 컴포넌트를 확장하는 매우 향상된 컴포넌트일지도 모른다. 후자의 경우, 기존 LCL 컴포넌트가 유사한 기능을 이미 제공하는지 확인해야 한다.
또 다른 타입의 델파이 컴포넌트로, Windows API 기능을 래핑(wrap)하는 컴포넌트를 들 수 있다. 그러한 래퍼(wrapper)의 훌륭한 예로 RichEdit 컴포넌트뿐만 아니라 German journal Toolbox 의 자손 TSerial 과 TPrinter 를 들 수 있다: 이 컴포넌트들은 라자루스로 쉽게 포팅이 가능하지만 Windows에서만 기능할 것이다.
Windows API를 둘러싼 저수준의 래퍼(wrapper)에 해당하는 세 번째 컴포넌트 범주는 Windows OLE 객체에 인터페이스를 제공하는 컴포넌트들이다: ActiveX 래퍼. 일반적인 사용 예로, Internet-Explorer 모듈 또는 Adobe Acrobat reader 컴포넌트를 들 수 있으며, Microsoft Office 모듈에 대한 래퍼, Word, Excel을 들 수 있다. 이러한 컨트롤들은 Windows 플랫폼을 대상으로 하므로 플랫폼 독립성을 목표로 한 라자루스는 해당 컨트롤들의 포팅 방법을 제공하지 않는다.
이러한 고려사항들은 다계층으로 구성된 델파이 컴포넌트 이식성(portability) 범주로 이어진다.
- 이식이 가장 단순한 컴포넌트는 VCL 확장자를 제공하는 컴포넌트들이다. VCL과 LCL은 매우 유사하지만 VCL은 Windows에 중점을 두어 때때로 이식이 까다롭다. 그럼에도 불구하고 보통은 이러한 컴포넌트들은 다른 플랫폼으로 이식 가능하다.
- 다른 시각적 델파이 컴포넌트의 경우, 윈도우에 깊이 뿌리를 두고 있어 이식 과정이 거의 불가능에 가깝고, 아래에서 위로 크로스 플랫폼 방식으로 다시 작성하는 편이 오히려 수월한 것이 일반적이다.
- Windows API-wrapper 컴포넌트들은 주로 이식 가능하지만 보통 Windows에서만 실행된다.
- ActiveX 컨트롤은 이식 불가하며, 시도조차 무의미하다.
아키텍처의 차이
델파이에서 라자루스로 포팅을 시작할 때는 컴포넌트의 두 가지 측면을 인식해야 한다:
- 델파이에서 컴포넌트에 속한 것.
- 이 중 실제로 필요한 것.
델파이의 컴포넌트는 항상 .pas 소스 파일에 구현된다. 델파이 2009 이전에 이 파일은 항상 Windows ANSI 포맷으로 되어 있었고, 라자루스에서는 UTF-8 인코딩으로 변환되어야 한다. .pas 소스 파일과 관련해 하나 또는 이상의 리소스 파일이 존재할 수 있는데, 주로 Borland Resource Workshop으로 생성된다. 이러한 파일들은 .res 또는 .dcr (델파이 컴포넌트 리소스를 의미) 확장자로 되어 있다. .dcr 파일은 보통 컴포넌트 팔레트에서 컴포넌트를 표시하는 아이콘을 포함하지만 다른 정보를 포함할 수도 있다. .dcr 파일 내 아이콘은 Windows 아이콘이 아니라 2, 16, 또는 256개 색상을 이용하는 24x24 크기로 된 Windows 비트맵이다.
비트맵의 이름은 컴포넌트 이름의 대문자 버전으로 되어야 한다. 소스 파일에 {SR *.dcr} 문을 종종 볼 수 있을 것이다. 보통 컴포넌트의 이미지를 생성하는 툴은 델파이의 이미지 에디터였을 것이다.
델파이는 패키지의 생성을 지원한다. 패키지는 특별한 유형의 DLL로, 여기에 컴포넌트 정보가 저장된다. 델파이 패키지의 장점은 델파이 IDE에서 사용하기 위해 컴포넌트 라이브러리를 재컴파일할 필요가 없다는 점이다 (단, 델파이 버전 1과 2에선 여전히 재컴파일이 필요). 컴포넌트는 델파이의 IDE와 정적으로 연결되진 않지만ㅡ라자루스와 반대로-델파이 패키지를 이용해 동적으로 로딩된다. 따라서 델파이 패키지는 라자루스 패키지와 동등하지 않다. 위의 내용은 이번 절의 처음에 제시한 질문 2를 강조한다: 라자루스로 컴포넌트의 이식이 가능하려면 컴포넌트의 소스 파일이 확실히 필요하다-설사 Windows에서만 사용될지라도. 게다가 컴포넌트 팔레트에 대한 이미지를 이미지 파일로 필요하다. 마지막으로, .res 파일에 추출해야 할 문자열이 있는지 여부를 꼭 확인해야 한다.
델파이 윈도우 컴포넌트를 위한 파스칼 소스 파일은 본질적으로 그 라자루스 윈도우 컴포넌트와 다르지 않다: 파스칼 언어는 가상적으로 동일하다. 반면 리소스 바인딩(resource binding)은 두 컴포넌트 간에 상당히 다르다.
리소스 변환하기
앞서 언급하였듯, 델파이는 컴포넌트 팔레트에 컴포넌트를 표시하기 위해 .dcr 파일에 비트맵을 이용한다. 컴포넌트가 아주 오래된(prehistoric) 16-bit 컴포넌트가 아니라고 가정 시 32-bit 리소스 파일이 위치하고 변환된다. 델파이 이미지 에디터(Delphi Image Editor)에 이를 로딩하는 것도 한 가지 방법이다. 이 프로그램은 비트맵을 내보내진 못하지만 (리소스 워크숍은 가능) 비트맵을 편집할 수는 있다. 편집된 비트맵은 클립보드로 복사하여 Microsoft Paint와 같은 다른 애플리케이션으로 붙여넣기 할 수 있다. 이후 .BMP 비트맵 파일로 저장 가능하다.
또 다른 방법은 무료로 된 XN 리소스 에디터를 사용하는 것으로,
http://www.wilsonc.demo.co.ur/delphi2006.htm 에서 다운로드할 수 있다.
이 프로그램은 .DCR 파일을 읽고 이미지나 문자열과 같은 부분들을 (한 번에 하나씩) 추출한다.
컴포넌트 비트맵을 갖게 된 이상 .dcr 파일에 해당하는 라자루스의 .lrs 파일로 내장(embed)되어야 한다. 여기에 라자루스 리소스 컴파일러 lazres를 이용할 수 있다. 이는 라자루스에서 소스 코드 폼에 분배되고, 라자루스 설치 디렉터리 하의 툴 디렉터리에서 이용 가능한 소스로부터 컴파일되어야 한다. PATH 환경 변수는 프리 파스칼 컴파일러 바이너리 디렉터리를 포함하지 않는 경우-윈도우에서 권장하는데, 그 내에 cygwinl.dll이 다른 cygwin 애플리케이션과 충돌할 수 있기 때문이다-lazres 툴은 바이너리에 대한 완전한 경로명을 이용해 컴파일되어야 한다.
라자루스가 c:\lazarus에 설치되고, 컴파일러가 프리 파스칼 2.4.2 버전이라고 가정할 때 lazres는 아래 명령을 이용해 명령 프롬프트 창으로부터 컴파일할 수 있다:
> cd \lazarus\tools
> \lazarus\fpc\2.4.2\bin\i386-win32\make
> \lazarus\fpc\2.4.2\bin\i386-win32\strip *.exe
이를 완료하였다면 lazres.exe 를 이용해 컴포넌트 소스 파일이 위치한 디렉터리에 리소스 파일을 생성할 수 있다. 컴포넌트 소스가 c:\lazarus\components\fabprocess에 위치한다고 가정하면 아래 명령은:
> cd \lazarus\components\fabprocess
> \lazarus\tools\lazres fabproc.lrs tfabprocess.xpm
리소스 파일 fabproc.lrs를 생성하여 'tfabprocess' 라는 이름의 컴포넌트의 아이콘을 포함할 것이다.
리소스 파일 fabproc.lrs를 생성하여 'tfabprocess' 라는 이름의 컴포넌트의 아이콘을 포함할 것이다.
프로그래머는 모든 문자열의 추출을 비롯해 .dcr 파일 내 모든 이미지에 대해 위의 단계들을 반복해야 할 것이다. RES 파일은 .RC 파일로부터 생성되므로 반대 방향으로 이동도-.res 를 .rc로 역컴파일-가능하지만, 이를 실제로 실행하는 애플리케이션은 Borland Resource Workshop이 유일하다.
Resource Workshop을 이용할 수 없는 경우 델파이에서 컴파일해온 것처럼 .res 파일을 라자루스 파일로 삽입하는 방법이 있다. 하지만 보통은 $R 지시어로 (이미 가지고 있거나 재구성이 가능할 경우) .rc 소스 파일에 삽입하는 편이 나은데, 그리고 나면 FPC 리소스 툴을 이용해 컴파일되기 때문이다.
소스 파일 준비하기
프리 파스칼은 스스로를 델파이에 적응시키지만 그 반대는 적용되지 않는다. 그럼에도 불구하고 컴파일러는 서로 다르며, 일부 세부 내용들은 더욱 더 상이하다. 프리 파스칼 컴파일러는 컴파일러 모드를 가진다: 그리고 이 모드가 델파이와 호환 수준을 결정한다. 컴포넌트를 변환할 때는 실행하고자 하는 적절한 컴파일러 모드를 명시해야 한다.
- 일반 FPC; 기본 모드. 클래스, 예외, 스레드, 또는 다른 델파이 기능들이 허용되지 않는다. 분명한 건 델파이 컴포넌트의 포팅에 그다지 적절하지 않다는 점이다.
- Turbo-Pascal 호환 모드는 FPC 모드와 동일한 한계를 가지므로 이 또한 적절치 않다.
- Macpas 모드는 컴파일러가 Mac에서 사용되는 파스칼 변형어(dialect)를 컴파일할 때 사용하는 모드이다.
- Objfpc 모드는 컴파일러의 오브젝트 파스칼 모드에 있고, 클래스와 스레드의 사용을 허용한다.
- 델파이 모드에서 컴포넌트는 가능한 한 델파이와 호환을 시도한다.
컴파일러 모드는 컴파일러 명령 행이나 지시어를 통해 컴파일러 모드를 선택한다. 지시어는 각 소스 파일에 삽입되어야 하고, 어떤 포함된(included) 파일이든 이를 비롯한 전체 파일에 대해 컴파일러가 사용하는 모드를 선택한다. 위의 리스트에서 분명한 건 델파이 컴포넌트의 포팅에 적절한 모드는 마지막 두 개의 모드밖에 없다는 사실이다. 일반적으로는 objfpc 모드에서 사용되는데, 메소드 파라미터 이름이 프로퍼티 이름으로 사용된 적이 없을 것이다. FPC가 아닌 델파이에서 컴파일하는 아래 예제를 통해 설명하고자 한다:
TmyComponent = Class(TComponent)
FValue: integer;
Procedure SetOtherValue(AValue : Integer);
Property AValue : Integer Read FValue Write FValue; // Syntax error in FPC
end;
SetOtherValue 의 구현에서 식별자 AValue가 모호하다; 파라미터 이름일까, 프로퍼티 이름일까? 따라서 objfpc 모드에서 그러한 코드를 직면 시 'Duplicate identifier Error(중복 식별자 오류)'가 발생한다. 델파이 모드에선 델파이 규칙을 따른다: AValue 파라미터가 AValue 프로퍼티보다 우선한다. 라자루스 팀은 항상 objfpc 모드를 사용하므로 LCL 내 어디서든 사용되며, LCL 저장소 내 패키지를 포함하기 위한 조건이다. 유닛에 대한 컴파일러 모드를 델파이 모드로 설정하기 위해서는 아래 내용을 unit 키워드가 포함된 행 앞에 삽입해야 한다:
{$ifdef FPC}
{$mode delphi}
{$endif}
IFDEF 는 델파이 컴파일러가 유닛을 여전히 컴파일할 수 있도록 보장한다. 이는 {$mode…} 지시어를 이해하지 못하며 이를 만나면 오류를 발생시킨다. 델파이에선 objfpc 모드에서 적힌 코드를 컴파일 할 수 없기 때문에 objfpc 모드를 명시할 때 $ifdef 지시어는 의미가 통하지 않으므로 objfpc 모드를 선택 시엔 다음으로 충분하다: {$mode objfpc}.
두 컴파일러에서 코드를 사용해야 하는 경우 델파이 모드가 유일한 선택권이다. FPC에서만 작업해야 하는 코드의 경우 objfpc 모드가 더 낫겠다.
이식할 소스 코드를 준비 시 다음 작업은 컴포너트 소스 파일의 uses 절에 명명된 유닛의 사례를 검사하는 일이다. 파스칼은 대·소문자에 민감하지 않은 언어이지만 모든 유사 유닉스 체제들은 대·소문자에 민감한 파일 시스템을 사용한다. 따라서 코드를 유닉스로 이식할 때는 유닛명의 대·소문자가 중요해진다: 컴파일러는 각 유닛명을 올바른 유닛 파일에 일치시킬 수 있어야 한다. 따라서 이를 확실히 보장하는 방법은 두 가지가 있다:
- 유닛명이 유닛 파일명의 대·소문자와 일치하도록 만들어진다. 컴파일러는 uses 절에 정확한 파일명을 이용해 유닛명을 먼저 검색한다.
- 모든 파일은 모두 소문자로 된 파일명을 이용해 저장된다. 컴파일러가 uses 절 이름과 정확히 동일한 철자로 된 유닛 파일을 찾지 못하는 경우, 모두 소문자로 된 파일명을 이용해 유닛 파일을 검색한다.
include directives 와 included files 에도 동일한 규칙이 적용된다.
VCL과 LCL
컴포넌트를 델파이에서 라자루스로 포팅한다는 것은 한 컴파일러로부터 (dcc32.exe) 다른 컴파일러로의 (FPC) 포팅을 의미할 뿐 아니라 하나의 GUI 플랫폼에서 (주로 Windows) 다른 플랫폼으로 포팅을 (LCL; 라자루스 컴포넌트 라이브러리) 의미하기도 한다.
LCL은 결코 델파이 VCL의 단순한 재구현이 아니다. LCL은 기반이 되는 운영체제나 그래픽 서브시스템과 독립적인 완전한 크로스 플랫폼 툴킷이라고 설명하는 편이 더 정확하다. 이는 모든 지원되는 플랫폼에서 동일하게 작동하는, 잘 정의된 API를 제공한다. LCL용으로 작성된 컴포넌트는 모든 지원 플랫폼에서 작동할 것이다.
따라서 LCL로의 포팅이란 Windows API에서 LCL가 제공하는 API로 변경된다는 뜻을 암시한다. LCL은 Windows 메시지 시스템과 비슷한 메시지 시스템을 제공하지만 유사점은 거기서 끝이다. 많은 Windows API 호출과 메시지가 LCL에선 존재하지 않는다.
라자루스 애플리케이션은 세 개의 계층으로 구성된다:
- 애플리케이션 코드, 이것은 실제 프로그램이다.
- LCL 클래스 – VCL과 유사한 클래스의 라이브러리.
- LCL 인터페이스. 기반이 되는 GUI에 대한 추상 계층.
애플리케이션 프로그래머는 가장 하위 계층, LCL 인터페이스에 대해 염려하지 않아도 된다. LCL 클래스를 아는 것으로 충분하다. 이들은 VCL 에 해당하는 클래스와 가능한 한 비슷하게 구성된다. 하지만 컴포넌트를 이식해야 하는 경우, LCL 인터페이스 추상 계층이 어떻게 기능하는지 이해하는 것이 꼭 필요한데, 어떤 Windows API 호출이든 LCL 인터페이스에 해당하는 호출로 해석되어야 하기 때문이다.
최근에는 GTK, GTK2, Qt4, Carbon을 비롯해 빼놓을 수 없는 32/64-bit Windows에 대한 추상 계층의 구현부가 있다.
추상 계층은 단일 클래스, TINterfaceBase 의 자손으로 구현된다 (유닛 interfacebase 내). GTK2에 대한 인터페이스를 구현하는 클래스를 Tgtk2WidgetSet 이라 부른다. LCL이 컴파일되면 TInterfaceBase 의 자손이 인스턴스화되어야 하는데, 이 인스턴스는 LCL 내 모든 클래스에 의해 사용된다. 해당 인스턴스는 LCL가 GUI 이벤트를 처리할 때 필요로 하는 호출들을 모두 제공하며, 새 창을 표시하는 등의 기능을 한다. 짧게 말하자면 그래픽 시스템과 상호작용하는 호출을 전부 포함한다고 보면 된다.
아래는 인터페이스부의 일부이다:
Procedure HandleEvents;
procedure WaitMessage;
Procedure AppInit;
procedure AppTerminate;
function IntSendMessage3(LM_Message; Integer; Sender : TObject; Data: Pointer) : Integer;
function CreateDIBitmap(DC : HDC; Var InfoHeader : TbitmapInfoHeader;
dwusage : Dword; InitBits: Pchar; var InitInfo :
TbitmapInfo; wusage: UINT) : Hbitmap;
이러한 메소드들은 추상적이며, Tgtk2WidgetSet 과 같은 자손에 의해 구현되어야 한다 (물론 여기에 표시된 내용보다 더 많은 호출이 존재한다). 인터페이스 객체는 주로 메시지를 이용해, 때로는 직접 메소드 호출을 이용해 클래스들과 통신한다. 시스템은 Windows 메시징 시스템과 많이 비슷한데, 단, 메시지 번호는 상이할 것이다. 하지만 메시지 이름은 비슷하게 유지되어 왔다. 예를 들어, Windows WM_SIZE 메시지는 좀 더 플랫폼 독립적이도록 LM_SIZE로 재명명되었다. FPC는 델파이에 도입된 메시지 처리message handling 메소드라는 개념을 지원하며, 이는 컨트롤이 LM_SIZE 메시지에 응답하기 위해선 아래와 같은 메소드를 구현할 수 있음을 의미한다.
procedure WMSize(var Message: TLMSize); message LM_SIZE;
위와 같이 델파이에서 메소드가 구현되는 것과 매우 유사하다. 타입 이름 TWMSize 는 TLMSize 로 변경되고, 메시지 식별자는 LM_SIZE로 변경되었다. LCL 계층은 메시지가 컨트롤로 전달되었음을 볼 것이다.
반면 LCL 클래스는 주로 인터페이스로 알리기 위해 TInterfaceBase 클래스의 메소드를 사용한다. LCL 클래스에서 인터페이스 계층으로 메시지가 전송되는 경우는 거의 드물기 때문에, 컴포넌트가 메시지의 전송에 많이 의존한다면 LCL로 이식이 힘들 가능성이 크다.
LCL 내 메시지들은 내부 통신 수단으로 간주되므로 이에 대한 의존은 좋은 생각이 아니다. 일반적으로, 각 메시지의 경우 LCL 기반 클래스 내에 해당하는 메소드가 존재할 것이며 (TControl 과 TWinControl) 이 메소드는 메시지 핸들러에 의해 호출될 것이다. 따라서 포팅 시 메시지 메소드를 가능한 한 적게 사용하고, 대신 이식된 메시지에 해당하는 메소드를 덮어쓰는 것이 좋겠다. LM_SIZE의 경우 컴포넌트의 DoSetBounds 메소드를 덮어쓰는 것을 의미한다.
안타깝게도 지원되는 메시지나 그에 해당하는 메소드에 관한 전반적인 리스트가 없다. 무엇이 가능하고 불가능한지 알아보기 위해선 LCL 소스를 연구해야 한다. LCL 내 controls 는 대다수의 메시지 핸들러에 대한 적합한 개요를 제공하므로 이를 먼저 살펴보는 것이 좋겠다.
인터페이스 유닛
델파이 개발자들은 Windows의 대다수 기능을 델파이와 함께 전달된 Windows 유닛에 캡슐화하였으므로 이 유닛은 대부분 델파이 프로그램에서 찾을 수 있을 것이다. Messages 와 같은 저수준(low-level) 유닛 몇 가지도 마찬가지다. 이러한 유닛은 그다지 이식성이 좋지 않다는 것은 말할 필요도 없겠다. 결과적으로 LCL 클래스에 포함되지 않지만 대신 다른 유닛으로 대체된다.
이러한 비이식성 윈도우 중심의 기반 유닛을 제외한 델파이의 유닛 및 클래스 명명규칙(naming scheme)을 가능한 한 그대로 재현하고자 시도하였다. 이는 중간 정도의 이식성을 가진 애플리케이션의 경우, 위에 언급한 크로스 플랫폼 대·소문자 민감 문제를 제외하고 uses 절을 거의 건들지 않은 채 유지할 수 있음을 의미한다. 아래는 관련 요점을 몇 가지 더 소개한다.
- 델파이가 제공하는 기능 Windows unit은 여러 라자루스 유닛으로 나뉜다: 대부분 GUI 관련 호출에 대한 interfacebase. 대부분 타입과 상수 정의는 LCLType unit으로 이동하며, 일반적으로 호환성을 위해 제공된다.
- 메시지는 (이름과 레코드 정의) LMessages 유닛에서 찾을 수 있을 것이다.
- IntfGraphics unit는 이미지를 (비트맵) 처리하는 데 필요로 하는 대부분을 포함한다.
하지만 이 리스트가 델파이 컴포넌트를 라자루스로 포팅 시 발생할 수 있는 모든 'Identifier not found(식별자를 찾을 수 없음)' 컴파일러 오류를 해결할 수 있는 것은 아니다. 특정 메시지나 구조, 혹은 호출이 라자루스에서 존재하지 않을 수도 있으며, 이런 경우 다른 것을 사용하거나 시도해야 할 것이다.