LazarusCompleteGuide:1.5

From 흡혈양파의 번역工房
Jump to: navigation, search

패키지

패키지는 여러 개의 유닛을 하나의 큰 논리 개체로 묶는다. 패키지는 고유의 디렉터리, 고유의 컴파일러 구성, 고유의 번역 파일을 가진다.


이는 다른 컴퓨터나 운영체제로 쉽게 복사가 가능한 독립적 개체로 만든다. 프로젝트와 마찬가지로 패키지 역시 다른 패키지를 이용할 수는 있지만, 프로젝트와 달리 속성(attribute) 또한 상속할 수 있다. 라자루스 0.9.28 버전은 한 번에 하나의 프로젝트만 열 수 있으나 packages는 얼마든지 열 수 있다.


따라서 패키지는 주로 프로젝트를 모듈화하는데 사용된다.


또한 라자루스에서는 플랫폼 독립성의 우선순위가 높기 때문에 패키지는 쉽게 재사용이 가능하며 경로나 설정을 변경하지 않고 다른 플랫폼에서도 쉽게 컴파일이 가능하다. 또한 새로운 컴포넌트, 메뉴 항목 등을 추가하기 위해 IDE에 설치할 수도 있다.


0.9.28 이하 버전에서 설계 시간, 실행 시간, 설계-실행 시간은 델파이에서와 마찬가지로 의무가 아니라 지시자에 불과하다. 프리 파스칼 컴파일러가 동적 패키지를 지원한다면 더 중요해진다.

그림 1.12: Package Graph는 모든 패키지, 패키지 의존성, 패키지 상태를 보여준다.


패키지의 모든 파일이 유닛일 필요는 없다; 파일, 폼 파일(.lfm), 2진 파일(이미지나 데이터베이스) 등의 기타 타입의 파일을 패키지에 추가할 수도 있다. 일반적으로, IDE는 이러한 파일 중 수정된 파일이 있을 시 자동으로 패키지를 재컴파일하기 때문에 패키지의 컴파일에 필요한 모든 파일은 패키지 내에 포함되어 있어야 한다. 좀 더 구체적으로 말하자면, 자동으로 생성된 package_name.compiled 파일의 일자보다 파일의 변경일자가 늦은 경우 이러한 일이 발생하는 것이다. IDE는 컴파일이 성공할 때마다 패키지의 출력 디렉터리에 .compiled 파일을 저장한다 (3.1.8절 참고).


파일들은 서로 다른 디렉터리에 위치할 수 있으며, 패키지 사용자에게 더 간편하긴 하지만 무조건 하위 디렉터리에 위치시켜야 할 필요는 없다.


모든 파일과 디렉터리명은 .lpk 파일로 저장된다. 그 결과, 모든 파일이 포함된 패키지는 다른 시스템으로 쉽게 복사가 가능하고 경로 이름을 변경할 필요 없이 컴파일할 수 있다. 다른 시스템에서 .lpk 파일을 연 후에 Compile 또는 Install을 클릭하기만 하면 된다. 윈도우 시스템에서는 디렉터 구분자(director separator)로 '\' 를 사용하고 리눅스와 다른 시스템에서는 '/' 를 사용하기 때문에 해당 정보는 .lpk 파일에 저장되고 경로는 파일이 열릴 때 변환된다. 'C:'와 'D:' 같은 드라이브 문자는 윈도우 시스템에서만 사용된다 (OS/2에서도 사용, 공동체에서 시작된 야심이 과했던 프로젝트가 얼마 전 실패하여 라자루스 지원이 부족하기 때문).


프리 파스칼에서 C 라이브러리

C 라이브러리는 파스칼 프로그램에서도 사용이 가능한데 여기에는 두 가지 방법이 있다:

  • 정적:
    정적 결합(static binding)의 경우, 라이브러리의 진입점(entry point)은 가능한 사용이 끝난 후 프로그램 또는 유닛의 일부인 것처럼 선언된다. 라이브러리는 항시 프로그램이 컴파일되고 실행되는 시스템 내에 존재해야 한다. 프로그램이 시작되면 라이브러리가 로딩되고, 라이브러리 내에서 진입점을 검색하기 때문이다.
    이 과정에서 오류가 발생 시 프로그램은 실행될 수 없다.
  • 동적:
    동적 결합의 경우, C 라이브러리 내 모든 함수에 대한 프로시저 변수(포인터)가 정의된다. 라이브러리는 메모리에서 로딩되고, 라이브러리의 쿼리(query)로 얻은 진입점 주소는 해당하는 변수에 저장된다. 다시 말해, 2진 파일이 항상 로딩됨을 의미하며, 라이브러리가 로딩되지 않은 상황에서 발생하는 문제, 즉 시스템에서 설치되지 않아 발생하는 문제들은 적절한 코드로 해결이 가능하다.
    정적 결합은 필요한 진입점의 선언을 요한다.


다음 예제에서는 명심해야 할 점이 3가지 있다:

procedure Free(P: Pointer); StdCall; external 'msvcrt' name 'free';


첫째, 호출 규칙을 나타내는 StdCall 식별자가 있다.


윈도우에서는 StdCall이지만 다른 대부분 시스템에서는 Cdecl이다.


둘째, 라이브러리명은 external 키워드 뒤에 따라온다.


여기서 파일 확장은 불필요한데 시스템이 알아서 해결하기 때문이다.


셋째, 진입점 이름은 name 키워드로 표시된다. 진입점 이름이 함수에 선언된 이름과 동일한 경우 이 부분을 건너뛰어도 좋다. 하지만 오브젝트 파스칼에서 진입점의 이름은 식별자의 이름과 다르게 주로 대·소문자를 구별하기 때문에 특별한 주의를 기울여야 한다.


따라서 위의 선언은 리눅스에서 다음 형태를 취한다:

procedure Free(P: Pointer); CDecl; external 'c' name 'free';


다음 소스 코드는 동적 결합을 실행한다:

uses dynlibs;
  type  TfreeProc = Procedure(P: Pointer); CDecl;
  var   Free: TfreeProc;
        H   : TlibHandle;
begin
  H := LoadLibrary('msvrt');
  if H <> NilHandle then
    Pointer(Free) := GetProcedureAddress(H, 'free');
end.


Dynlibs 유닛은 TLibHandleNilHandle 뿐만 아니라 LoadLibraryGetProcedureAddress 프로시저의 정의를 포함한다. 이러한 정의는 플랫폼 독립적이며 모든 시스템에서 작동한다. 하지만 라이브러리 이름은 호출 규칙과 마찬가지로 매우 다양할 수 있다.

그림 1.13: GUI 마법사 h2paswizard


라이브러리를 로딩하기 위해 어떠한 메소드를 사용하는지와 상관없이 라이브러리의 타입과 진입점을 정의하는 유닛은 항상 필요하다. 수동으로 생성할 수도 있지만 C 헤더 파일을 (.h) 파스칼로 변환하는 도구도 있다. 명령 행 도구인 h2pas는 완벽하진 않지만 C 헤더파일(constructs)을 파스칼로 변환해야 하는 경우 유용한 건 분명하다. 이 도구는 프리 파스칼에 포함되어 있다. 라자루스는 h2paswizard라 불리는 GUI 마법사를 포함하고 있는데, h2pas의 옵션 구성을 도와준다. 마법사는 라자루스 패키지로서 Package ⇒ Configure installed packages... 를 통해 설치된다. 이 마법사는 입력의 경우 라이브러리를 설명하는 하나 이상의 C 헤더 파일을 취하고, 출력의 경우 라이브러리를 프리 파스칼 프로그램으로 정적으로 결합하는 데 사용되는 파스칼 유닛을 생성시킨다.


가장 흔하게 사용되는 옵션은 기본적으로 활성화되고, 기타 옵션은 주로 생성된 코드와 연관되며 자기 해석적(self-explanatory)으로, 원하는 대로 구성할 수 있다. h2paswizard 에 관한 더 많은 정보는 다음 주소에서 이용 가능하다.


http://wiki.lazarus.freepascal.org/Creating_bindings_for_C_libraries.


파스칼 바인딩을 여러 개의 파일에 분배할 경우 라자루스 패키지 내에 그룹화되어야 한다.


C에서의 프리 파스칼 라이브러리

라자루스는 어떠한 C 프로그램에서든, 좀 더 정확히 말해 라이브러리를 결합할 수 있는 언어로 적힌 어떠한 프로그램에서든 호출할 수 있는 동적으로 로딩 가능한 라이브러리의 생성에 사용되는데, 프로그램 대신 라이브러리를 생성하기만 하면 된다. 프로그램과 유사한 라이브러리인데, 차이점이라고 한다면 program 키워드 대신 library 키워드로 시작한다는 점을 들 수 있다. 또한 exports 키워드는 라이브러리에서 어떠한 함수와 프로시저를 내보내기(export)를 할 수 있는지 표시한다.


다음 코드는 free 라는 이름의 함수를 내보낸다:

library newfree:

procedure Free(P: Pointer); Stdcall;
begin
  FreeMem(P);
end;

exports
  free;      // 대소문자를 구분!
end.


소스 코드가 컴파일되면 newfree.dll 이라 불리는 라이브러리가 (유닉스 시스템에서는 libnewfree.so) 생성되고, 이 라이브러리는 free (전부 소문자) 함수를 내보낸다. 호출 규칙 Stdcall 를 주목하라-현재 C 컴파일러 중에는 프리 파스칼의 기본 호출 규칙인 register를 이해하는 컴파일러가 없다. 일부 파스칼 구성(construct)은 라이브러리에서 허용되지 않는다는 점도 주의하라. 특히 stringsclasses 문자는 라이브러리로부터 내보내거나 가져오지 못한다. 그 이유는 파스칼 string 타입이 C 언어에서 알려져 있지 않기 때문이기도 하고 파스칼 클래스가 C++ 클래스와 바이너리수준의 호환이 불가능하기 때문이기도 하다.


이 과정에서 프리 파스칼 프로시저의 이름이 수정된다; 선언에 표시되는 이름과 어셈블러에 나타나는 이름이 동일하지 않다. 이러한 이유로 인해 exports 절에 이름을 쓸 때는 내보낼 때와 동일한 방식으로 쓰는 편이 낫다. 위의 예제에서는 소문자로 된 이름의 함수가 내보내기 된다.


IDE에서 새로운 컴포넌트 설치하기

이번 절에서 설명하게 될 예제는 사용자 고유의 패키지와 새로운 컴포넌트를 어떻게 생성하는지와 IDE에서 어떻게 설치하는지를 보여준다. 첫 작업은 Package ⇒ New package... 를 선택해 새 패키지를 생성하는 것이다.


패키지 창이 열리면서 NewPackage라는 이름의 새 패키지가 나타난다.


열린 창의 상단에 위치한 툴바에는 가장 자주 실행되는 저장하기, 컴파일하기, 파일과 같은 아이콘들이 있고, 하단에는 상세 내용이 표시된다. 그 외에 renaming이나 설치 제거(deinstalling)와 같은 모든 실행은 팝업 메뉴를 통해 접근할 수 있다.

그림 1.14: 패키지의 모든 구성 설정은 Package 창에서 편집할 수 있다.


Save 창을 클릭하면 저장하기 창이 열린다. 기존 패키지나 프로젝트와의 중복 또는 충돌을 피하기 위해 새 디렉터리 만들기를 권한다. 일부 도구에서는 기피하는 경향이 있으므로 디렉터리 이름에 특수문자나 공백의 사용을 피하도록 한다. 파일명은 유효한 파스칼 식별자여야 하고 .lpk 확장자를 가져야 한다. 확장자가 누락되면 자동으로 파일명에 추가된다. 많은 플랫폼에서 대문자와 소문자를 구별하므로 파일명은 모두 소문자로 하도록 한다. IDE는 프로젝트명과 파일명을 따로 취급하기 때문에 프로젝트명에 대문자를 사용해도 별다른 문제가 되지 않는다.


창에 MyPackage1과 같은 파일명을 입력하면 IDE는 파일명을 소문자로 쓸 것인지 물을 것이다. 패키지명은 MyPackage1이 되고 파일명은 mypackage1.lpk가 될 것이다. 동일한 이름을 가진 유닛이 자동으로 생성된다는 점을 명심하라. 이 유닛은 IDE에 의해 생성되며 내용이 변경될 때마다 덮어쓰기가 되기 때문에 내용을 따로 편집할 수 없다. 이후에 이름은 Package 대화창 팝업 메뉴를 통해 변경할 수 있다.


모든 라자루스 구성 파일과 마찬가지로 새로운 .lpk 파일은 사람이 읽을 수 있는 XML 파일이다. 다시 말해, 필요하다면 수동으로 검색 및 편집이 가능하다. IDE가 파일명을 인식하면 그 시점부터는 해당 이름으로만 패키지를 찾을 수 있다. Package 메뉴에서 Open recent package...로 열린 대화창에서도 나타난다.


새 컴포넌트는 Package 대화 창에서 Add를 클릭해 쉽게 추가가 가능하다. 버튼을 클릭하면 파일과 의존성을 생성 및 추가하는 대화 창이 열린다. 새 컴포넌트의 키 파라미터(key parameter)는 New Component 탭에서 입력한다. 예를 들어, 클릭하면 짧은 메시지가 표시되는 TButton을 새로 만든다고 가정하자.


그렇다면 가장 먼저 TButton을 기반 클래스로 (parent type) 선택하라. 그리고 나면 IDE는 다른 모든 필드에 기본 값을 입력한다. 여기서 클래스 이름은 TMessageButton일 것으로 추정되는데, 이는 컴포넌트 팔레트의 Misc 탭에 표시될 것이다. 원한다면 팔레트 탭 이름을 My Components와 같이 새 이름으로 입력할 수도 있다. 일반적인 파스칼 규칙은 새로운 파일과 유닛 이름에 적용되며, 유닛은 패키지와 동일한 이름을 가질 수 없다. OK를 클릭하면 새 유닛이 소스 에디터에 나타난다.

unit MessgeButton;
{$mode objfpc}{$H+}
interface

uses  Classes, SysUtils, LResources, Forms,
      Controls, Grphics, Dialogs, StdCtrls;
type
 TMessageButton = class(TButton)
 private
 { Private declarations }
 protected
 { Protected declarations }
 public
 { Public declarations }
 published
 { published declarations }
 end;

procedure Register;
implementation
  procedure Register;
  begin
    RegisterComponents('Misc', [TMessageButton]);
  end;
end. { Unit }


유닛은 패키지에 자동으로 추가되고 패키지의 주 디렉터리에 저장된다. 유닛은 TMessageButton이라는 새 클래스와 Register 프로시저를 포함한다. 초기화 중에 IDE는 이러한 개체를 호출한다. 그리고 RegisterComponents 프로시저는 Misc 팔레트 페이지에 새 클래스를 등록한다. 기타 IDE 확장도 여기서 등록할 수 있다. IDE는 TButton이라는 기반 클래스를 포함하는 Buttons 유닛을 uses 절로 추가하기도 한다. Buttons 유닛은 LCL 패키지에 속하기 때문에 그에 대한 의존성이 추가되며, Package 대화 창의 Files 탭에 표시된다. 이러한 의존성의 결과, 패키지의 검색 경로는 자동으로 연장되어 LCL의 모든 유닛을 사용할 수 있다. 그리고 나서 Package 대화 창에서 Install을 클릭하면 새 패키지를 저장 및 컴파일하고, 의존성을 검사하며, 새로운 IDE를 컴파일한다. 이 과정에서 IDE는 어떠한 일이 발생할 것인지 설명하는 대화 창을 표시하여 IDE 재컴파일의 승인을 요청한다. 컴파일은 처음 실행 시 몇 분간의 시간이 소요될 것이다. 컴파일이 끝나면 운영체제는 작업 메모리에서 이용할 수 있는 데이터를 갖게 되며 IDE의 컴파일은 몇 초 안에 완료될 것이다.


기본 구성이라면 라자루스는 지금쯤 자동으로 재시작될 것이다. 자동으로 재시작이 되지 않을 경우 수동으로 시작해야 한다.


재시작이 완료되면 Misc 탭의 컴포넌트 팔레트에 새 컴포넌트가 나타날 것이다. 새 클래스에 대해 명시된 아이콘이 없으므로 표준 아이콘이 사용된다. 해당 컴포넌트는 이제 다른 컴포넌트와 마찬가지로 폼에 위치시킬 수 있다.


다중 프로젝트 안에서의 유닛

시간이 지나면서 대부분 프로그래머들은 그들의 프로젝트에서 대부분 사용할 수 있는 유용한 함수와 유닛을 몇 가지씩 개발한다. 델파이에서 이러한 유닛을 묶어두는 디렉터리는 유닛(Unit) 검색 경로에서 쉽게 추가할 수 있다. 이 방법은 라자루스에서도 가능한 원리긴 하지만 몇 가지 결점도 있다. 이러한 유닛은 다른 컴퓨터로 전송되면 모든 검색 경로가 업데이트되어야 한다. 또한 컴파일러가 유닛으로부터 .ppu 파일을 생성하는데 이러한 파일들은 최근 컴파일러 구성을 이용해 생성된다. 다른 프로젝트에서 다른 구성이 있는 경우 .ppu 파일이 자동으로 재생성되지 않아 이상한 오류를 발생할 수 있다. 라자루스 패키지는 유닛을 마우스 클릭 몇 번으로 생성할 수 있는 잘 정의된 개체로 번들(bundle)한다. 유닛이 C:\Sundry 디렉터리에 있다고 가정하자. Packages ⇒ New Package 를 클릭하면 Package 대화 창이 열리면서 새 패키지가 생성된다. 대화 창 좌측 상단에 Save at을 클릭하면 파일 선택창이 열리고 C:\Sundry\Sundry.lpk 라는 이름의 파일이 선택되어야 한다. 구성에 따라 IDE는 파일의 이름을 소문자로 변경해야 하는지를 묻는데, 이에 답은 "Yes"로 한다. 여기까지 완료되면 새 패키지는 Sundry라 불리고 C:\Sundry\Sundry.lpk 라는 파일명을 가진다.


다음 단계는 패키지 대화 창에서 Add를 클릭하여 유닛을 추가하는 단계로, 여러 개의 탭이 있는 대화창이 열린다. Add Files 탭에서 Browse를 선택하면 유닛을 선택할 수 있는 파일 선택창이 열린다. 사용자는 .lfm 파일 뿐만 아니라 다른 타입의 파일들을 포함시킬 수도 있다. 파일 선택창이 닫히면 선택된 파일이 대화창에 열거되어 나타난다. 원한다면 목록을 편집할 수도 있다. 그리고 나면 Add files to package 를 클릭한다. 대화창이 닫히고 나면 파일이 패키지에 추가되고 Package 대화창에 열거된다. 마지막으로 Save 를 클릭하면 디스크에 패키지를 기록한다.


Sundry 유닛이 FCL의 표준 유닛으로만 접근할 경우 패키지는 이 시점에서 완료된다. 패키지가 더 많은 폼 파일을 포함해야 하거나 LCL에 속한 유닛을 더 필요로 하는 경우 LCL 패키지를 원하는 패키지 목록에 추가해야 한다. 해당 작업은 Package 대화창에서 Add 를 클릭하여 New Requirement 탭을 선택한 후 Package Name 필드에서 LCL 패키지를 선택하면 된다. OK를 클릭해 대화창이 닫히면 LCL이 Required Packages 에 나타날 것이다. 실행 효과는 Package 대화창 Compiler Options 탭의 Inherited 탭에 표시된다.


모든 의존성이 해결되었는지 검사하기 위해서는 Package 대화창에서 Compile을 클릭하라.


컴파일러가 유닛을 찾을 수 없는 경우 Sundry'는 다른 패키지를 필요로 함을 의미한다. 패키지가 컴파일되고 나면 다른 컴퓨터를 포함해 원하는 곳에 쉽게 복사가 가능한 자유로운(autonomous) 패키지가 된다. IDE는 생성된 .ppu 파일들을 묶는 고유의 출력 디렉터리를 패키지에 부여하기도 한다.


프로젝트에서 패키지를 사용하기 위해서는 Package 대화창에서 More... ⇒ Add to project 를 클릭한다. Sundry 에 대한 Package 대화창을 다시 열려면 Package ⇒ Open recent packages... 를 선택한다. 프로젝트의 의존성은 Project ⇒ Project Inspector 에 열거되어 있으며, 의존성을 삭제하거나 새로운 의존성을 추가할 수 있다.


Sundry 패키지는 다른 패키지에서도 사용 가능하다.


위의 단계를 따르다보면 각 경로(path)가 한 번만 명시되어 있다는 점을 눈치 챌 것이다. 모든 검색 경로는 패키지나 프로젝트에서 파생된 것이다. 이러한 점 때문에 라자루스 패키지는 플랫폼 독립적이며(platform-independent) 쉽게 이동이 가능하다. 또한 패키지를 사용하고자 하는 다른 개발자들은 패키지가 어떻게 구조화되었는지 염려할 필요가 없으며, 검색 경로를 조정할 필요도 전혀 없다.


가상 유닛

컴포넌트 팔레트(Component Palette)의 모든 컴포넌트 클래스는 유닛과 패키지를 하나씩 필요로 한다. 유닛이 컴포넌트 클래스를 정의하나 등록(register)하지 않은 경우, 패키지에 가상으로 포함시킬 수 있다. 이에 해당하는 예로 프리 파스칼의 표준 유닛인 ibconnection.pp 를 들 수 있겠다. SQLDBLaz 패키지는 ibconnection.pp 유닛에 대한 컴포넌트 클래스 TIBConnection 를 등록하는 가상 유닛 ibconnection을 정의한다. 그 결과 패키지와 패키지 파일이 클래스에 등록된다.


다른 플랫폼들을 위한 유닛

많은 패키지들이 매우 다양한 플랫폼에서 실행된다. 주로 통일된 외부 API와 함께 각 플랫폼마다 내부적으로 다른 코드가 제공된다. 약간의 차이는 조건부 컴파일로 충분히 해결 가능하다.

{$IFDEF MSWindows}
    // Code for Windows 98, ME, NT, 2K, XP, WinCE, etc.
{$ELSE}
    // Code for Linux, BSD, OS X, Solaris, etc.
{$EndIF}


단 차이가 크거나 서로 다른 여러 개의 플랫폼에서 실행할 경우 상황이 많이 복잡해지므로 각 플랫폼 혹은 유닛에 대해 구분된 include 파일을 생성하는 편이 낫다.


  uses
 {$IFDEF MSWindows}
  WindowsUnit
 {$ENDIF}
 {$IFDEF Unix}
  UnixUnit
 {$ENDIF}
;


여기서는 두 개의 유닛, 즉 WindowsUnitUnixUnit 를 패키지로 추가해야 하지만 항상 패키지로 컴파일할 필요는 없다. 유닛의 자동 컴파일을 막기 위해서는 Package 대화창에서 대화창 최하단에 파일 속성을 표시하도록 선택하여 unit 옵션을 사용하는것을 비활성화시킨다. 그리고 나면 IDE는 유닛의 변경일자를 확인하지만 (예: WindowsUnit) 패키지로 유닛을 자동으로 컴파일하지는 않을 것이다. 컴파일러는 앞서 언급한 IFDEF 지시어로 인하여 이를 재귀적으로 실행한다.

그림 1.15: Printer4Lazarus 패키지는 운영체제에 따라 세 가지 프린트 라이브러리로 접근을 제공한다. 컴파일러 파라미터는 Compiler Options Show Options에서 표시된다. Inherited 표는 다른 파라미터들의 소스를 보여준다.


그러한 유닛들이 여러 개가 있는 경우 이 방법은 금세 불편해진다. 이러한 이유로 라자루스는 검색 경로에 매크로를 사용하도록 한다. 예를 들어, Handy 유닛을 두 가지 버전, 즉 Windows 32와 Linux에서 이용할 수 있다고 가정하자. 이러한 두 개의 유닛은 하위디렉터리에 저장되며, 프리 파스칼을 따라 운영체제 이름으로 지정된다:

MyPackage/
   mypackage.lpk
   mypackage.pas
   mainunit.pas
   win32/handy.pas
   linux/handy.pas


Include 파일은 종종 유닛 대신 사용된다.


Package 대화창에서 Compiler Options ⇒ Paths ⇒ Other Unit Files 에서 엔트리는 이제 추가 경로 없이 다음과 같아야 한다:

$(TargetOS)


앞에 디렉터리 구분자(separator)가 붙어도 괜찮으며, 윈도우에서는 다음과 같은 엔트리,

$(TargetOS)\


리눅스의 경우 다음과 같은 엔트리도 허용된다.

$(TargetOS)/


라자루스 9.26 버전 이상부터 TargetOS 매크로는 운영체제에 따라 다음 값 중 하나를 취한다: Darwin, freebsd, linux, netbsd, openbsd, solaris, win32, win64, wince. 그 외 유용한 매크로는 아래와 같다:


매크로 대상
TargetCPU i386, powerpc, m68k, x86_64, sparc, arm
TargetOS windows, unix


조건부 컴파일에 자주 사용되는 조합은 다음과 같다:

$(TargetOS)-$(TargetCPU)


위의 매크로들은 운영체제에 따라 검색 경로를 변경하여 컴파일러와 코드 툴이 컴파일 중에 올바른 handy.pas를 검색하도록 해준다. 이러한 방법을 이용 시 코드는 IFDEF 로부터 완전히 자유로워진다는 점을 주목해야 한다. 또 다른 방법으로는 환경변수들의 값을 리턴해주는 $Env( ) 를 들 수 있다. 예를 들어, 특수 라이브러리로의 경로를 포함하는 LIBPATH라는 이름의 환경 변수를 정의할 수 있겠다. $Env(LIBPATH)는 이 값을 모든 라자루스 경로에서 사용하도록 허용한다.


패키지 찾기

패키지는 패키지 이름과 버전으로 정의된다. 이름은 유효 파스칼 식별자여야 하므로 첫 문자(character)는 알파벳(A…Z, a…z) 또는 밑줄(_)로 시작되고 그 뒤부터는 문자 또는 숫자가 가능하다. 대-소문자는 파일명에서만 중요하다. 이론상으로 패키지 이름은 256자 길이까지 가능하지만 일부 링커에서는 전체 경로명을 255자 길이로 제한한다. 이는 이름이 긴 프로시저가 중첩된 곳에서 문제를 발생시킬 수 있다. 다행히도 이러한 유형의 링크 툴이 점점 줄어들고 있는 추세이다.


버전 번호는 4자리 숫자로 구성되는데, 라자루스에서는 이를 Major, Minor, Release, Build라고 부른다. trailing 위치가 부족한 버전 번호는 자동으로 0으로 채워진다.


패키지 파일명은 항시 패키지 이름으로 시작하여 대-소문자의 이탈(deviation)을 허용하고, 확장자 .lpk로 ('라자루스 패키지'를 의미) 끝이 난다. 파일명은 가급적 소문자로 기입해야 모든 운영체제에서 쉽게 파일을 찾을 수 있다. 모든 IDE 구성 파일이 그렇듯 .lpk 파일은 XML 파일이며, TXMLConfig 컴포넌트를 이용해 읽고 쓰인다. 텍스트 에디터를 이용해 수동으로 편집도 가능하다. 포맷은 이전 버전과 호환이 가능하기 때문에 새로운 버전의 IDE도 기존 버전을 읽을 수 있다.


오래된 IDE 버전도 새로운 파일 버전을 읽을 수는 있지만 새로 개발된 파라미터는 간과할 것이며 파일이 저장되면 파기할 것이다. 프로젝트가 열리면 IDE는 자동으로 필요한 패키지를 모두 바탕화면에 로딩시킬 것이다. 프로젝트에는 보통 사용된 패키지 목록만 포함되어 있는데, 패키지 이름과 버전의 최저 및 최고 버전으로 표시된다. 각 이름과 버전 범위과 관련해 IDE는 일치하는 .lpk 파일을 검색하고 열어서 패키지 목록 등을 로딩하는데, 이는 모든 의존성이 해결될 때까지 계속된다.


가장 먼저 Configdirectory/packagefiles.xml에 저장된 최근 열어 본 패키지 목록을 검색한다. 이 목록은 Package ⇒ Open recent package... 에서 표시되는 목록과 연관이 있지만 구체적인 파일 수를 제한하지 않고 버전 번호까지 저장하기 때문에 크기가 더 크다. 프로그래머가 새로운 패키지 파일을 열 때마다 이 목록에는 엔트리가 기록된다. 패키지가 새로운 장소로 이동하면 엔트리는 업데이트된다. 이러한 이유로 새 패키지가 생성되면 .lpk 파일은 IDE를 위해 한 번만 식별되어야 하며, 그 이후로는 모든 프로젝트와 패키지가 .lpk 파일을 자동으로 검색한다.


packagefiles.xml 목록에서 원하는 이름과 버전을 가진 패키지를 검색할 수 없는 경우, IDE는 packager/globallinks/ 소스 코드 디렉터리에서 .lpl (라자루스 패키지 링크) 파일을 검색한다. .lpl 파일은 파일명에 이름과 버전이 표시되어 있다. 예를 들어, packager/globalinks/cgilaz-0.lpl 이란 파일은 버전 번호가 0.0.0.0 이상인 cgilaz 라는 이름의 패키지를 모두 검색한다.


cgilaz-1.0.lpl 이라는 이름의 .lpl 파일이 하나 더 있는 경우 1.0.0.0 버전 이상에 해당하는 모든 의존성이 우선한다. .lpl 파일은 .lpk 파일의 경로명으로 된 하나의 행만 포함한다. 해당 경로명은 매크로를 포함할 수도 있다. 한편 .lpl 파일은 외부 공급자나 관리자에 의해 사용될 수 있는데, 한 시스템의 모든 사용자들을 위한 패키지를 설치해야 하는 경우를 예로 들 수 있겠다. .lpl 파일은 패키지의 소스 파일이 시스템 내 중간 위치 어딘가 저장되도록 해준다. 다음으로 IDE는 의존 대상이 되는 프로젝트 또는 패키지와 동일한 디렉터리에서 .lpk 파일을 검색한다.


마지막으로, 모든 패키지와 모든 프로젝트는 .lpk 파일에 대해 고유의 위치를 정의할 수 있다. 이를 위해서는 Package 대화창이나 프로젝트 인스펙터에서 의존성을 오른쪽 마우스로 클릭한 후 Store dependency filename 을 선택한다. 이는 현재 사용 중인 패키지의 상대 경로를 저장한다. 여러 개의 상호 의존적인 .lpk 파일을 포함하는 큰 라이브러리 혹은 패키지가 공급되는 예제 프로젝트에서 사용하면 좋은데, 이는 서로를 참조하여 사용자가 어디서든 시작할 수 있도록 해주기 때문이다. 예를 들어, 우선 사용자가 예제 프로젝트를 열면 IDE는 가까운 디렉터리에서 시작해 .lpk 파일을 검색하여 자동으로 로딩시킬 것이다. IDE가 현재 인식할 수 있는 패키지는 View ⇒ IDE internals ⇒ Package links에서 살펴볼 수 있다.


라자루스 0.9.26 버전의 경우에는 패키지 보기만 가능하다.


동일한 이름을 가진 두 개의 패키지는 동시에 열 수 없다. 둘 중 하나가 열린 경우 나머지는 자동으로 대체된다. 이러한 현상은 1.0 버전의 패키지가 설치되어 있을 때 2.0 이상 버전의 패키지를 필요로 하는 프로젝트가 열릴 때도 발생한다. 이러한 상황에서 IDE는 2.0 이상 버전의 패키지를 자동으로 로딩시키고 메모리 내 기존 패키지를 교체한다. 하지만 관리 데이터(administrative data)만 교체될 뿐, 설치된 컴포넌트와 등록된 IDE 확장은 유지된다.


2.0 버전 패키지의 새로운 컴포넌트는 IDE가 재컴파일되고 재시작되었을 때에만 설치된다. 따라서 사용자는 패키지의 다른 버전들을 편리하게 작업할 수 있게 된다. 델파이에서 요하는 검색 경로 또는 파일의 수동 편집이 라자루스에서는 불필요하다.


디렉터리와 검색 경로

각 패키지는 고유의 출력 디렉터리와 고유의 검색 경로를 갖는다. 경로는 Package 대화창의 Compiler Options 에서 구성된다. 모든 검색 경로는 .lpk 파일로 저장되고 매크로를 포함할 수 있다. 매크로는 1.5.6절에서 설명하였듯 서로 다른 유닛을 결합시키고 다른 플랫폼들에서 사용하기 위한 파일을 포함시키는 데 사용할 수 있다. 컴파일러 구성은 대개 프로젝트의 구성과 동일하다. 링커 구성은 일반 패키지에선 중요하지 않으며 컴파일러는 링커 구성을 무시한다. 패키지 자체가 컴파일 된 경우 컴파일러 구성이 필요하며, 다른 패키지로 전달되지 않는다. 하지만 사용 중인 패키지의 영향을 받는다. 상속된 경로와 옵션은 Inherited 탭에 표시된다.


컴파일러 구성과 마찬가지로 패키지는 고유의 검색 경로와 설정을 다른 패키지로 전할 수 있다. 이는 Package 대화창의 Options ⇒ Usage 에서 구성된다. 해당 탭의 모든 필드는 주로 비어 있으나 entry $(PkgOutDir)/ 를 포함하는 검색 경로 Units 는 제외한다. PkgOutDir 라는 매크로는 컴파일러 옵션에서 구성되는 패키지의 출력 디렉터리를 나타낸다. 패키지에 대해 컴파일된 모든 유닛은 ( .ppu 파일) 출력 디렉터리에 위치된다. 프로젝트가 이 패키지를 필요로 하면 패키지의 출력 디렉터리가 프로젝트의 검색 경로로 첨부되며 내용은 Project ⇒ Compiler Options ⇒ Inherited 에서 확인할 수 있다. 이런 방식으로 컴파일러는 패키지의 .ppu 파일을 검색할 수 있지만 그 소스 파일은 검색하지 않는다. 소스 파일과 .ppu 파일의 구분은 매우 중요한데, 컴파일러가 패키지의 소스 파일을 컴파일하기 위해 의도치 않게 프로젝트의 컴파일러 구성을 사용하는 현상을 방지하기 때문이다.


델파이를 배경으로 사용하는 사람들에게 한 가지 충고를 거듭하자면, 'Unit not found' 오류에 대한 응답으로 제 멋대로 검색 경로를 추가하지 말길 바란다. 대신 패키지가 프로젝트 인스팩터 또는 Package 대화창에서 의존성으로 열거되는지 여부를 살펴본 후, 다양한 일반적 구성 오류를 검사하는 Compiler Options 대화창에서 검사를 실행하도록 한다. 검색 경로는 컴파일 중에 패키지에 속하는 유닛을 발견할 수 없을 때에만 확장해야 한다.


각 패키지에는 고유의 컴파일러 구성이 있기 때문에 유닛, include 파일, 소스 파일, 객체 파일(object file), 라이브러리, 그리고 고유의 컴파일러 파라미터도 가질 수 있음을 의미하므로, 프로젝트 설정은 패키지에 어떠한 영향도 미치지 않는다. 유일한 예외는 패키지가 $(TargetOS) 혹은 $(LCLWidgetType) 과 같은 전역 매크로를 사용할 때 발생한다. 이러한 매크로들은 프로젝트에 의해 정의된다. 설정은 패키지 유닛이 위치한 모든 디렉터리와 유닛의 검색 경로 내 모든 디렉터리에 적용된다. 디렉터리의 설정을 보고 싶다면 소스 에디터에서 유닛의 팝업 메뉴창을 열어 File Settings... ⇒ Unit Info 를 선택한다. 여기서 나타나는 대화창은 유닛에 관한 검색 경로, include 파일, 소스 파일을 보여줄 것이다. 여기서 어떠한 경로도 나타나지 않을 경우, IDE가 디렉터리 내 어떠한 파일도 패키지 혹은 프로젝트와 연관시킬 수 없음을 의미하는 것이 보통이다.


소스 파일이 속해 있는 패키지는 소스 에디터의 팝업 메뉴에서 Open File...을 선택하여 볼 수 있다. 파일이 패키지에 속한 경우 패키지가 나타날 것이다. 패키지와 프로젝트의 컴파일러 옵션이 혼합되지 않도록 소스 디렉터리를 구분해야 한다. 일반적으로는 패키지의 출력 디렉터리, 즉 .ppu 파일을 포함하는 디렉터리만 프로젝트에서 표시된다 (visible).


의존성과 상속

모든 패키지는 패키지 묶음을 사용할 수 있다. 다시 말해서 상속된 검색 경로와 컴파일러 파라미터가 패키지 고유의 경로와 파라미터로 첨부됨을 의미한다. 의존성은 Package 대화창에서 구성된다. 의존성은 Package 대화창에서 Add ⇒ New Requirement 에서 추가가 가능하다. 이 메뉴에서 최저 및 최대 버전 번호도 명시할 수 있다. 의존성을 제거하기 위해서는 (필요한 패키지) 의존성을 선택한 후 팝업 메뉴에서 Remove dependency를 선택하라.


검색 경로와 파라미터는 Package 대화창의 Options ⇒ Usage에서 상속된다. 컴파일러 구성은 상속되지 않지만 사용 중인 패키지에 대해 상속된 경로를 포함한다. 어떤 패키지가 제 2의 패키지에 의존하는데 제 2의 패키지가 다른 제 3의 패키지에 의존하는 경우, 제 1의 패키지는 나머지 두 패키지에서 이러한 설정들을 모두 상속한다.


IDE는 패키지를 내재적으로 그래프로 관리하는데, 순환 의존성(circular dependency)을 처리할 수 있음을 의미한다. 그러나 IDE는 그러한 의존성이 발생 시 경고를 발생시키며 순환 의존성이 제거될 때까지 패키지의 컴파일을 거절한다. 모든 의존성과 패키지는 Package ⇒ Package Graph...에서 살펴볼 수 있다. IDE는 항시 내부 그래프에서 패키지를 위상 정렬한다.


패키지 컴파일하기

IDE는 모든 패키지를 위상적으로 컴파일하므로, 먼저 의존성을 필요로 하지 않고 패키지를 컴파일함을 의미한다. 기본 값(by default)으로는 파일이 패키지에 속하거나 환경이 변경되었을 시에 패키지가 재컴파일된다. 환경에는 컴파일러와 컴파일 파라미터가 포함된다. 컴파일이 완료되면 이러한 데이터는 packagename.compiled 파일에 저장된다. 해당 파일의 예는 다음과 같다:

<?xml version="1.0"?>
<CONFIG>
  <Compiler Value="/usr/bin/ppc386" Date="1204315279"/>
  <Params Value=" -S2cgi -OG1 -g1 -vewnhi -1 -Fu../../ideintf/units/i386-linux/ -Fu../../lcl/units/i386-linux/ -Fu../../lel/units/i386-linux/gtk2/ -Fu../../packager/units/i386-linux/ -Fu. -FUlib/i386-linux/ -dLCL -dLCLgtk2 runtimetypeinfocontrol s.pas"/>
</CONFIG>


위에서 볼 수 있듯이 해당 파일은 컴파일러의 경로, 컴파일러의 변경일자, 파라미터를 보유한다. 여기서 IDE는 다른 컴파일러가 설치되었는지, 또는 위젯 셋이 다른 것으로 대체되었는지 살펴본다. 이러한 정보는 IDE의 구성 디렉터리 대신 패키지와 함께 저장이 되기 때문에 여러 개의 IDE에서 동일한 패키지를 동시에 사용하는 것이 가능하다.


게다가 패키지는 라자루스 없이 그 내용을 기반으로 컴파일이 가능하다. 라자루스 0.9.24 이상 버전부터는 lazbuild 라는 명령 행 프로그램을 이용해 패키지와 프로젝트를 직접 컴파일할 수 있다. 0.9.26 버전부터는 lazbuild 는 패키지가 있는 IDE를 컴파일 할 수도 있다. 무언가 변경될 때마다 패키지가 컴파일되는 기능이 필요 없는 경우 Package 대화창의 Options ⇒ IDE Integration ⇒ Update 에서 자동 실행을 취소할 수 있다. 비자동 컴파일에 대한 옵션을 '수동 컴파일'이라 하는데, 패키지가 Package 대화창의 Compile 버튼 또는 'Auto-rebuild when rebuilding all'을 통해서만 컴파일된다는 뜻으로, 프로그래머가 Package 대화창의 Run ⇒ Build all or More... ⇒ Recompile all required 를 선택할 때만 패키지가 컴파일됨을 의미한다.



패키지 번역하기

패키지 내에 다른 언어로 번역해야 하는 모든 메시지와 문자열은 리소스 문자열(resource string)로 정의될 수 있다:

Resourcestring
  rsTitle = 'Title';
  rsOk    = 'Ok';

procedure TMainForm.FormCreate(Sender: TObject);
begin
  Caption          := rsTitle;
  OkButton.Caption := rsOk;
end;


컴파일러는 각 유닛의 리소스 문자열을 .rst 파일로 추출한다. 이러한 파일을 위한 번역 편집기는 없기 때문에 라자루스가 자동으로 .rst 파일을 .po 파일로 변환할 수 있다. 널리 사용되는 포맷은 GNU 번역 프로젝트의 도구를 이용해 번역이 가능하다. 해당 기능은 Options ⇒ Enable i18n에서 i18n 옵션을 활성화시킨 후, languages 혹은 po 와 같은 하위디렉터리를 선택한다. 모든 파일명이 그렇듯 패키지의 하위디렉터리는 .lpk 파일을 참조한다. 라자루스에서는 디렉터리가 자동으로 생성되지 않기 때문에 디렉터리가 항시 존재하도록 해야 한다.


패키지가 컴파일될 때 컴파일러는 출력 디렉터리에 .rst 파일을 생성하고, 라자루스는 i18n 디렉터리에 .po 파일을 생성한다. 가장 단순한 포맷은 다음과 같다.

msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"

#: unit1.rsok
msgid "Ok"
msgstr ""

#: unit1.rstitle
msgid "Title"
msgstr ""


각 리소스 문자열에는 원본 텍스트와 번역본이 있다. 이는 베이스 파일(base file)로서 자동으로 생성되며 편집해서는 안 된다.


위의 파일은 파일명에 국가 코드를 포함시켜 각 언어별로 복사본을 생성해야 한다. 예를 들어, messagebutton.po 라는 이름으로 된 original .po 파일에서 messagebutton.de.po 라는 이름의 독일어 번역 파일을 생성할 수 있겠다. 해당 파일은 이후에 수동으로 번역하거나 POEdit (아래 참고) 또는 kbabel과 같은 프로그램을 이용해 간편하게 편집이 가능하다.


새로운 리소스 문자열이 추가된 경우 IDE는 각 컴파일이 끝나면 자동으로 .po 파일을 모두 추가한다. 0.9.26 이상 버전에서는 LCL가 어디서든 UTF-8을 사용하기 때문에 .po 파일 또한 UTF-8에서 써야 한다. IDE가 설치된 패키지에 대해 자동으로 .po 파일을 로딩하지만 자신만의 애플리케이션에서 이러한 기능은 프로그래머에게 달려 있다. 이것은 메인 프로그램에 (.lpr 파일) 다음 코드를 포함시킬 때 가능하다:

program ...
uses  ..., Translations, gettext;

procedure Translate;
  var  PODirectory, Lang, FallbackLang: String;
begin
  // all .po files are located in the pofiles/ subdirectory
  PODirectory  :=  'pofiles' + PathDelim;
  Lang         := '';
  FallbackLang := '';
  GetLanguageIDs(Lang, FallbackLang);  //in unit gettext
  Translations.TranslateUnitResourceStrings('LCLStrConsts', PODirectory + 'lclstrconsts.%s.po', Lang, FallbackLang);
  Translations.TranslateUnitResourceStrings('MessageButton', PODirectory + 'messagebutton.%s.po', Lang, FallbackLang);
end;

begin
  Translate;
  Application.Initialize;
  Application.CreateForm(TMainForm,MainForm);
  Application.Run;
end.


위의 루틴은 프로그램이 실행될 때 모든 .po 파일이 profiles/ 하위디렉터리에 위치한다고 가정한다. 모든 패키지의 .po 파일을 한 자리 묶어두는 것은 프로그래머의 책임이다. LCL의 .po 파일은 lazarus/lcl/languages/ 에 저장된다.


위의 작업은 향후 라자루스 버전에서는 자동으로 실행되어야 한다. 실제 번역은 Translation.TranslateUnitResourceStrings 를 호출함으로써 실행된다. 첫 번째 파라미터는 .po 의 유닛 이름이며, 두 번째 파라미터는 파일명과 언어 코드의 (ISO 3166-1 국가 코드) 플레이스 홀더(placehold)를 포함하는 문자열이다. 최대한 빨리 Translate 프로시저를 호출하는 것이 중요한데, 이 프로시저는 리소스 문자열만 번역하고 이미 복사된 문자열은 모두 무시하기 때문이다.


.po 파일을 진부하게 수동으로 편집할 필요는 없다. 이러한 작업을 위해 특별히 설계한 POEdit라는 에디터가 해당 작업을 훨씬 수월하게 해주는데 심지어 글자 문자열(character string)에서 오류를 검사하기도 한다. POEdit는 .po 파일로부터 이진 POT 파일을 생성하기도 하지만 이러한 파일들은 라자루스에서 필요가 없다.


POEdit는 모든 리눅스 배포판과 FreeBSD에서 이용할 수 있다. 윈도우와 OS X 버전들은 www.poedit.net에서 다운로드할 수 있다. 수동 컴파일은 권하지 않는데, Berkeley DB나 wxWidgets와 같이 상당히 많은 수의 의존성을 해결해야 하기 때문이다.

그림 1.16: POEdit에서 라자루스 IDE의 독일어 파일