LazarusCompleteGuide:7.3
프로세스 포팅하기
작은 델파이 컴포넌트를 라자루스로 포팅하는 예제를 통해 앞 절에서 설명한 내용을 모두 실습하고자 한다. 라자루스로의 포팅은 윈도우에서 실행할 수 있겠다. 하지만 리눅스에서 포팅을 시도해야 하는 이유도 몇 가지 있다: Windows unit 이 Win32 배포판에 FPC와 함께 제공된다는 이유가 첫 번째이다. 따라서 컴파일러는 불평 없이 그 유닛으로 모든 호출을 컴파일할 것이다. 단 리눅스에선 불가능하다: Windows unit이 존재하지 않아 컴포넌트를 포팅하는 사람이 소스 내 Windows unit의 모든 발생에 집중하여 제거해야만 하기 때문이다 (제거하지 않으면 컴파일러가 오류를 보고할 것이다).
리눅스에서 포팅을 해야 하는 또 다른 이유는, 컴포넌트가 크로스 플랫폼으로 작동하는지 직접 검증이 가능하다는 점 때문이다. 마지막으로, 리눅스 파일 시스템이 대·소문자에 민감하다는 이유를 들 수 있겠다. 이로 인해 포팅에 관여되는 모든 유닛의 이름에 대·소문자를 올바르게 선택해야만 한다.
델파이 포팅 예제로 편집 컴포넌트을 들 수 있는데 (TEditBtn), 일반 편집 컨트롤에 속도버튼이 부착된 형태이다. 속도버튼의 목적은 사용자가 값을 선택할 수 있도록 대화창을 팝업시키는 수단을 제공하기 위함이다. 그리고 나면 컴포넌트는 그 값을 편집 컨트롤에 삽입한다. 기본 컴포넌트에 가능한 변형(variation)은 아래를 포함할 수 있다:
- 파일을 선택하기 위한 file dialog 표시하기.
- 디렉터리를 선택하기 위한 directory dialog 표시하기.
- 일자를 선택하기 위한 calendar 표시하기.
- 계산을 수행하기 위한 calculator 표시하기.
- 데이터세트를 비롯해 데이터세트에서 특정 값을 검색하도록 설계된 적절한 컨트롤을 포함하는 form with a grid 표시하기.
- 위의 패러다임(paradigm)에 일치하는 내용 중 실행하고자 하는 것. 사실 첫 네 개의 작업을 실행하기 위해 일반 용도로 된 컴포넌트의 특수화된 자손들이 생성되는데, 이 글을 읽을 때쯤이면 라자루스의 컴포넌트 팔레트에서 찾을 수 있을 것이다.
오리지널 컴포넌트 TEditBtn (Louis Louw)는 Torry 페이지에서 찾을 수 있다. 클래스 선언은 꽤 간단하다 (published 프로퍼티는 배제):
TEditBtn = class(TEdit)
FButton: TSpeedButton;
FEditorEnabled: Boolean;
FOnBtnClick : TNotifyEvent;
procedure SetGlyph(Pic: TBitmap);
function GetGlyph : TBitmap;
procedure SetNumGlyphs(ANumber: Integer);
function GetNumGlyphs:Integer;
function GetMinHeight: Integer;
procedure SetEditRect;
procedure WMSize(var Message: TWMSize); message WM_SIZE;
procedure CMEnter(var Message: TCMGotFocus); message CM_ENTER;
procedure CMExit(var Message: TCMExit); message CM_EXIT;
procedure WMPaste(var Message: TWMPaste); message WM_PASTE;
procedure WMCut(var Message: TWMCut); message WM_CUT;
protected
procedure GetChildren(Proc: TGetChildProc; Root: TComponent);
override;
function IsValidChar(Key: Char): Boolean; virtual;
procedure aClick (Sender: TObject); virtual;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure KeyPress(var Key: Char); override;
procedure CreateParams(var Params: TCreateParams); override;
procedure CreateWnd; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Button: TSpeedButton read FButton;
end;
리스트를 살펴보면 포팅을 고려 시 우리가 살펴보아야 할 메소드가 무엇인지 명확하다-시작해야 할 메시지 메소드. CreateParams 메소드와 CreateWnd 호출도 위험하다-Windows에서 이들은 컴포넌트에 대한 창 핸들을 생성하는 역할을 한다. LCL은 창 핸들을 생성하는 고유의 메커니즘을 가지고 있으므로 이러한 호출들 또한 조정되어야 할 것이다.
먼저 uses 절을 살펴볼건데, 델파이에서는 아래와 같다:
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Mask, Buttons, ExtCtrls;
앞서 언급한 내용을 명심하면서 아래와 같이 변경한다:
uses
LCLType, LMessages, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls, Buttons, ExtCtrls;
Mask unit 은 전혀 필요 없으므로 제거되었다.
이후 아래 액션을 취하여 메시지 메소드를 조정하였다.
필요한 액션은 컴포넌트에서 오버라이드되는 DoSetBounds 에서 실행할 것이므로 WMSize 호출은 제거된다.
CM_ENTER 메시지는 정의되긴 하지만 작동하진 않는다. 대신 LM_SETFOCUS 메시지를 사용할 것이다.
TCMGotFocus 레코드는 그에 해당하는 라자루스 레코드, TLMSetFocus 를 가진다. 이를 고려해 CMEnter 메소드의 선언을 아래와 같이 변경한다:
procedure WMSetFocus(var Message: TLMSetFocus); message LM_SETFOCUS;
TCMGotFocus 메시지 레코드가 LCL로부터 제거되므로 일반 TLMessage 레코드가 대신한다.
마찬가지로 TCMExit 는 LCL에 존재하지 않아 CMExit 메소드를 아래와 같이 조정하게 된다:
procedure WMKillFocus(var Message: TLMKillFocus); message LM_KILLFOCUS;
WMPaste 와 WMCut 메소드는 아직 라자루스에서 지원하지 않기 때문에 삭제된다: LM_PASTEFROMCLIP 과 LM_CUTTOCLIP 메시지 상수가 정의되기는 하는데 그렇다고 지원이 되는건 아니다.
CreateWnd 와 CreateParams 또한 제거되는데, LCL이 해당 메소드들이 필요로 하는 기능을 제공하지 않기 때문이다.
아래 메소드들 또한 무용지물로, 오리지널 컴포넌트에서 어떤 기능도 수행하지 않으므로 제거되었다.
function IsValidChar(Key: Char): Boolean; virtual;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure KeyPress(var Key: Char); override;
이러한 메소드들의 구현은 어떤 문제도 제시하지 않으며, 변경할 필요 없이 컴파일한다. 하지만 일부 메소드의 구현은 충분히 생각할 필요가 있다. 생성자(constructor)를 먼저 고려해보자:
constructor TEditBtn.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FButton := TSpeedButton.Create (Self);
FButton.Width := 15;
FButton.Height := 17;
If csDesigning in ComponentState
Then FButton.Visible := True
Else FButton.Visible := False;
FButton.Parent := Self;
FButton.OnClick := aClick;
FButton.Cursor:= crArrow;
ControlStyle := ControlStyle - [csSetCaption];
FEditorEnabled := True;
end;
언뜻 보면 코드에 어떤 문제도 발견되지 않는다. 변경할 필요가 있는 유일한 코드는 Parent 의 설정인데, 라자루스에서는 모든 위젯이 자식 위젯을 포함할 수 있는 것은 아니기 때문이다. 그에 따라 우리는 Parent 를 설정하는 행을 변경하여 폼이 버튼의 Parent 로 만들어지도록 해야 한다. 이는 편집 컨트롤을 기준으로 두는 대신 폼을 기준으로 버튼의 Top 과 Left 좌표를 계산할 것을 의미한다. 이후 메소드들은 이 점을 고려해야 한다. 나머지 코드 부분은 컴파일되어 문제없이 작동할 것이다. 그러면 변경된 생성자 본체는 아래와 같은 모습일 것이다:
inherited Create(AOwner);
FButton := TSpeedButton.Create(Self);
FButton.Width := Self.Height;
FButton.Height := Self.Height;
FButton.FreeNotification(Self);
CheckButtonVisible;
FButton.OnClick := @DoButtonClick;
FButton.Cursor := crArrow;
ControlStyle := ControlStyle - [csSetCaption];
FDirectInput := True;
명료성을 위해 몇몇 변수 이름을 변경하였다. CheckButtonVisible 메소드는 버튼을 시각적으로 만들 것인지 아닌지를 확인한다. ButtonOnlyWhenFocused 프로퍼티는 (위의 클래스 선언에서 생략) 편집 컨트롤에 포커스를 두지 않을 때 버튼을 표시할 것인지를 결정한다. 확인(check)은 여러 장소에서 이루어져야 하므로 그에 대한 코드는 구분된 프로시저로 이동되었다. 버튼 너비는 사용자가 ButtonWidth 프로퍼티를 통해 설정할 수 있으며, 처음에는 사각형으로 버튼이 만들어진다: 높이는 너비와 동일하다.
문제가 일어나는 다음 메소드는 SetEditRect 이다.
procedure TEditBtn.SetEditRect;
var
Loc: TRect;
begin
SendMessage(Handle, EM_GETRECT, 0, LongInt(@Loc));
Loc.Bottom := ClientHeight + 1; {+1 is workaround for Windows paint bug}
Loc.Right := ClientWidth - FButton.Width - 2;
Loc.Top := 0;
Loc.Left := 0;
SendMessage(Handle, EM_SETRECTNP, 0, LongInt(@Loc));
SendMessage(Handle, EM_GETRECT, 0, LongInt(@Loc)); {debug}
end;
윈도우에서 SetEditRect 호출은 편집 컨트롤의 편집 영역을 제한하여 버튼이 편집 컨트롤 내에 그려진다. 이러한 코드는 버튼 아래를 제외한 영역만 편집에 사용할 수 있음을 윈도우에 알림으로써 커서가 절대로 버튼 '아래'로 사라지지 않도록 보장한다. 이 호출은 두 가지 연관된 이유로 인해 사라졌다:
- LCL에서 버튼이 편집 컨트롤 외부에 그려진다.
- 편집 컨트롤이 버튼을 포함할 수 없는 라자루스에서는 호출이 지원되지 않으므로 호출을 추가한다는 것 자체가 말이 안 된다. 따라서 호출이 제거되었다.
다음으로 확인해야 할 메소드 구현은 WMSize 메시지 핸들러이다. 그 기능은 DoSetBounds 호출로 이동되었다:
procedure TCustomEditButton.DoSetBounds(ALeft, ATop, AWidth,
AHeight: Integer);
begin
inherited DoSetBounds(ALeft, ATop, AWidth, AHeight);
DoPositionButton;
end;
버튼의 실제 위치조정은 DoPositionButton 호출에서 처리된다 (시각적으로 만들어짐, 따라서 오버라이드 가능).
procedure TCustomEditButton.DoPositionButton;
begin
if (FButton<>nil) then
FButton.SetBounds(Left+Width, Top, FButton.Width, Height);
end;
이 메소드는 별다른 특별한 기능을 하지 않는다. 버튼 너비는 유지되며 사용자는 ButtonWidth 프로퍼티를 통해 설정할 수 있다.
LCL에서 컨트롤을 충분히 크게 만들기 때문에 GetMinHeight 메소드도 사실상 필요 없는 메소드이므로 제거된다.
최종 포팅된 컴포넌트의 코드는 본 저서에 딸려 있는 CD-Rom에서 찾을 수 있다. 일부 자손 컴포넌트들과 함께 코어 라자루스 컴포넌트로서 포함될 가능성이 크다.
라자루스에 컴포넌트 설치하기
델파이 컴포넌트의 코드를 라자루스에 성공적으로 포팅하고 나면 컴포넌트 팔레트에서 이용 가능하길 원할 것이다. 델파이에서는 패키지를 이용하면 될 일이다ㅡ패키지를 생성하여 유닛을 패키지에 삽입하고, 패키지를 재컴파일하여 IDE에서 설치하면 된다. 편집 컨트롤과 같은 단일 컴포넌트의 경우, 명시적으로 패키지의 일부가 아닌 델파이 컴포넌트들을 모두 포함하는 특수 dclusr 패키지를 이용할 수 있겠다. 불행히도 라자루스는 (아니면 좀 더 나은 FPC조차) 아직 패키지의 동적 로딩을 지원하지 않는다. 하지만 라자루스 설계자들은 새 컴포넌트를 쉽게 설치할 수 있도록 만들었다. 라자루스는 오픈 소스이기 때문에 모든 소스가 함께 딸려온다. 따라서 새 컴포넌트를 설치하려면 단순히 포함된 새 컴포넌트로 라자루스를 재컴파일하기만 하면 된다. IDE는 이를 위해 Tools 메뉴에서 두 가지 메뉴 옵션을 제공한다:
- Build Lazarus 이 옵션은 라자루스를 재컴파일하고, 포함하도록 표시된 패키지라면 무엇이든 컴포넌트 팔레트로 추가할 것이다.
- Configure "Build Lazarus" 이 옵션은 Build Lazarus 가 호출되었을 때 실행할 액션을 구성하도록 해준다. 이 대화창에서 Profile to Build 콤보상자 드롭다운을 이용해 Build IDE with Packages 옵션을 선택하면 컴포넌트와 함께 패키지가 포함된다.
그렇다면, 컴포넌트 팔레트에 편집 컨트롤을 포함시키려면 먼저 Configure "Build Lazarus" 대화창에서 Build IDE with Packages 옵션을 설정한다. 그리고 기존 패키지에 editbtn 유닛을 추가하거나, 새 패키지를 생성해야 한다. File ⇒ New 메뉴가 Package 엔트리를 포함한 대화창을 표시할 것이다-새 패키지를 생성하기 위해 해당 엔트리를 더블 클릭하면 패키지 에디터가 열린다. 패키지 에디터에선 Add [+] 버튼을 이용해 패키지에 유닛을 추가하는데, 그림 7.4에서 볼 수 있다.
유닛을 패키지에 추가할 때는 유닛이 컴포넌트를 등록시키기 위한 Register 프로시저를 갖고 있는지 라자루스에 알려야 한다. Has register procedure 플래그가 설정되었다면 라자루스는 유닛의 Register 프로시저를 호출할 것이다. 그 결과, 컴포넌트가 컴포넌트 팔레트에 포함될 것이다.
Virtual unit 옵션은 패키지가 패키지 파일 리스트에 속하지 않는 유닛에 컴포넌트를 등록하는 경우를 위해 설계되었다. 이러한 경우, 패키지에 포함되지 않은 유닛은 파일 리스트에 'virtual unit '으로 추가된다. 이는 라자루스가 패키지 의존성을 올바르게 결정하도록 해준다. 예를 들어, 이러한 사용의 예로 기본 라자루스 배포판에 포함된 sqldblaz 또는 bflaz 패키지를 살펴보면 되는데, 이 두 패키지는 가상 유닛을 포함한다.
따라서 편집 버튼을 등록시키려면 패키지에 editbtn unit을 추가하고 (editbutton 이라 부르도록 한다), Has register procedure 플래그를 설정한다.
패키지 에디터의 Install 버튼을 이용해 IDE에 패키지를 포함하도록 정할 수 있다. IDE가 재빌드될 때 패키지가 추가될 것이다. 이제 패키지를 설치하고, Tools 버튼으로 라자루스를 재빌드한 후 모든 것이 성공적으로 컴파일되고 나면 라자루스를 재시작하는 일만 남는다. 편집 버튼이 포함된 ‘MBS’라는 새 컴포넌트 팔레트 페이지가 있을 것이다.
컴포넌트 팔레트
델파이에서 아이콘을 컴포넌트 팔레트에 추가 시 자주 사용하는 방법은 컴포넌트 이름을 가진 비트맵으로 리소스 파일을 생성하는 것이다. 라자루스도 마찬가지지만, 리소스를 추가하는 절차엔 차이가 있다. 오래된 FPC 컴파일러는 윈도우를 제외한 플랫폼에서 네이티브 리소스의 추가를 지원하지 않았기 때문이다. 따라서 라자루스는 고유의 리소스 메커니즘을 생성하였다.
라자루스 컴포넌트 팔레트에 아이콘을 추가하려면 아래 단계를 따라야 한다:
- 오리지널 델파이 리소스로부터 비트맵을 추출한다. 델파이의 이미지 에디터를 이용하거나 처음부터 재생성하는 방법이 있다.
- lazres 툴이 라자루스 사본에 컴파일된 형태로 제공되지 않는 경우, 컴파일 및 설치를 한다:
cd lazarus/tools make all strip lazres svn2revisioninc updatepofiles lrstolfm sudo make install
위를 실행 시 경로에 lazres 툴을 설치할 것이다. - 명령 행에 BMP 이미지를 라자루스 리소스로 변환하라:
lazres editbtn.lrs editbtn. bmp
- 명령 행에 하나 이상의 이미지 이름을 추가할 수 있고, 각 이미지는 리소스로 추가될 것이다.
- lresources 유닛을 컴포넌트 유닛의 (주로 컴포넌트가 등록된 유닛) uses 절에 확실히 포함시킨다. IDE는 대개 자동으로 이를 실행한다.
- .lrs 파일을 유닛의 initialization 부에 추가한다. 이 방법이 아니면, 유닛의 initialization 부에서 호출할 수 있는 구분된 프로시저를 생성하는 방법도 있다.
begin {$I editbtn.lrs} end; initialization LoadResources; end.
일반적으로, 리소스는 프로그램이 그에 대한 접근을 필요로 하기 전에 로딩되어야 한다. 대부분 리소스의 경우 바이너리 파일로 추가 시 동일한 단계를 따른다. LazarusResources 인스턴스 클래스는 이러한 방식으로 추가된 모든 리소스를 관리한다. 예를 들어, 리소스로서 추가된 이미지를 로딩하기 위해 다음 코드를 사용한다:
With TPixmap.Create do
begin
LoadFromLazarusResource('TEditButton');
// other code
end;
end;
대·소문자를 구별하지 않고 리소스가 검색된다.
프로퍼티와 컴포넌트 에디터에 대한 IDE 인터페이스는 델파이의 프로퍼티 및 컴포넌트 에디터 인터페이스와 비슷하게 작용한다. 이를 제외한 인터페이스의 경우는 완전히 틀리다.
IDEIntf 패키지는 모든 라자루스 IDE 인터페이스 외에도 propedits.pp 와 graphpropedits.pas 유닛에 있는 30개 이상의 프로퍼티 에디터의 예제를 비롯해 componenteditors.pas 유닛에 있는 다수의 컴포넌트 에디터 예제를 포함한다.