LazarusCompleteGuide:5.1
플랫폼 특정적
플랫폼마다 디렉터리 경로에 서로 다른 구분자(separator)를 사용한다. 현재 플랫폼에 유효한 구분자는 라자루스 내 상수 PathDelim 에서 항시 이용할 수 있다. 이식 가능한 코드에는 "/" 또는 "\" 를 사용해선 안 되며 대신 이 상수를 사용하는데, 아래 예제에서 설명하겠다:
// Only valid for Windows:
MyPath := BaseDir + '\extradir';
// Platform-independent form:
MyPath := BaseDir + PathDelim + 'extradir';
// Or:
MyPath := SetDirSeparators(BaseDir + '\extradir');
물론 플랫폼 특정적 상수가 항상 틀린 것은 아니며, 오히려 정보를 다른 컴퓨터로 전송해야 하는 경우 유용하게 이용할 수 있다. 하지만 일반적으로는 위에 언급한 상수들의 사용을 권하는 바이다. 단, PathDelim 은 남용하질 않길 바란다. 데이터가 FTP로 전송되는 경우에는 실행되는 운영체제와 상관없이 항상 유닉스 구분자가 사용된다! 각 플랫폼에는 텍스트 파일 내에서 행의 끝(end-of-line) 문자에 대한 고유의 규칙이 있어야 한다. 프리 파스칼은 플랫폼 특정적 값에 대한 대안책으로 LineEnding 상수를 제공한다.
상수 | 설명 |
AllFilesMask | 폴더 내 모든 파일을 검색할 때 사용할 올바른 마스크(mask)를 삽입한다. Unix 및 유사 플랫폼에서는 * 이며, DOS와 Windows 에선 *.* 이다. |
LineEnding | 새로운 라인 마커(line marker). 공통된 값은 #10 (UNIX, BeOS, Amiga), #13#10 (Windows, DOS, SymbianOS, PalmOS, OS/2, 인터넷 프로토콜 HTTP, FTP, SMTP 등), #13 (Mac) 이다. |
LFNSupport | 긴 파일명 지원. 파일명을 8.3 포맷만 지원하는 DOS에서만 False. |
DirectorySeparator | PathDelim과 같이 경로 내 디렉터리 명을 구분한다. |
DriveSeparator | 드라이브를 나머지 경로로부터 구분한다. DOS, Windows, OS/2에만 드라이브 구분자라는 개념을 사용한다. |
PathDelim | DirectorySeparator를 참고한다. |
PathSeparator | 경로 리스트를 제공해야 할 때 다중 경로를 구분한다. 보통은 ";" 가 사용된다. DirectorySeparator와 PathDelim의 차이를 주목한다. |
maxExitCode | 프로세스 종료 코드(process exit code)의 최대 값. |
MaxPathLen | 경로에 가능한 최대 길이, 파일명을 포함. |
FileNameCase- | 운영체제가 대·소문자가 구별된 파일명을 지원하는지 나타낸다. |
Sensitive | 이 또한 접근되는 파일 시스템에 따라 좌우됨을 명심한다. 예를 들어, FAT 파티션은 유닉스에서 접근한다 하더라도 대.소문자를 구별할 것이다. |
UnusedHandle | 유효하지 않은 (invalid) 파스칼 파일 핸들을 의미한다. |
StdInputHandle | 표준 입력에 대한 파스칼 파일 핸들. |
StdOutputHandle | 표준 출력에 대한 파스칼 파일 핸들. |
StdErrorHandle | 표준 오류에 대한 파스칼 파일 핸들. |
CtrlZMarksEOF | 부울(boolean) 값으로, 문자 #26이 파일 끝을 표시하는지 여부를 나타낸다 (오래된 DOS 규칙). |
표 5.3: 프리 파스칼에서 사용되는 운영체제 독립적 상수 |
표 5.3은 다양한 운영체제에서 입력과 출력을 처리하는 데 나타나는 차이를 가리기 위해 프리 파스칼이 제공하는 상수들의 개요를 나타낸다.
플랫폼 독립적 소프트웨어를 생성할 때는 여러 단계(multiple layers)로 생각할 것을 명심해야 한다. 라자루스는 주요 플랫폼 Windows, MacOS X, Linux/Unix를 지원한다. 하나의 프로그램 소스를 이러한 플랫폼에 맞게 단순히 재컴파일할 수 있다면 가장 이상적이겠지만, 이 세 가지 플랫폼이 지원 플랫폼의 범위에 모두 해당하는 것은 아니다. 지원하는 운영체제는 다음과 같다:
- Windows 32와 Windows 64부터 Windows 7까지
- Windows CE
- MacOS X with Carbon<NR>(시각적 컴포넌트 개발은 아직 Cocoa에선 불가능함)
- Intel에서 (i386, amd64) 리눅스와 FreeBSD 32 비트와 64 비트
- 그 외 DOS, OS/2와 같은 플랫폼에서는 RLT과 FCL을 기반으로 한 프리 파스칼 프로그램만 이용 가능하며, 이와 같은 플랫폼용으로 된 라자루스나 LCL 은 없다.
32-bit Windows용 그리고 64-bit Windows 용 소프트웨어 개발에서조차 큰 차이가 있다. 그렇다면 라자루스에서 지원하는 모든 플랫폼을 대상으로 소프트웨어 개발 시 직면할 차이는 얼마나 많을 것이며, 프리 파스칼은 또 어떻겠는가. 일반적으로 64 비트 운영체제는 32 비트 프로그램도 지원하지만 그 반대는 적용되지 않는다. 물론 32 비트 프로그램이 64 비트 시스템에서 2기가 바이트의 메모리만 어드레싱(address)할 수는 있을 것이다.
Windows 32/64 API
윈도우는 여전히 데스크톱 PC에서 지배적인 운영체제이다 (보통은 Windows 32만 연관시키곤 하지만 본 저서의 내용 대부분은 Windows 64에도 동일하게 적용된다).
따라서 대체로 윈도우는 플랫폼 독립적인 프로그래밍 프로젝트에서 여러 대상 플랫폼 중에 속한다. 윈도우는 경로 구분자로 백슬래시 "\" 를 사용한다. 배치 파일은 (.bat와 .cmd 확장자로 된) DOS와 같은 구문으로 된 작업을 자동화시키는 데에 사용된다. 드라이브 문자(drive letter)는 디스크 드라이브를 가리킬 때 사용된다. 예를 들어, C:\Windows 는 C 드라이브에 위치한 Windows 디렉터리를 일컫는다.
모든 드라이브에는 드라이브 문자가 있다. 보통 A와 B는 플로피 디스크 드라이브용으로 예약되어 있으며, C는 주 하드 디스크를 나타낸다. D 이상은 추가 하드 디스크, 네트워크 드라이브, CDROM, DVD 드라이브, 펜(pen) 드라이브 등을 의미한다. 파일명 확장자는 주로 파일의 타입으로 구성되며, 파일의 실행 여부를 나타낸다. 확장자가 .exe 인 파일은 실행 파일이며, .bat 는 배치 파일, 라이브러리는 주로 .dll이란 확장자를 가진다. 하지만 실행 파일에 사용되는 확장자는 훨씬 더 많다. 예를 들어, .scr 는 화면 보호기의 확장자, .cpl 파일은 제어판 애플릿의 확장자로, 구체적 호출 프로시저를 가진 매우 특별한 DLL이다.
윈도우에서 디렉터리와 파일명은 대.소문자를 구별하지 않는다 (C:\Windows 는 C:\WINDOWS 와 동일하다). 프로그램이 실행되는 동안 윈도우는 .exe 파일과 사용 중인 모든 DLL에 쓰기 접근을 못하도록 막는다. 특정 옵션을 설정하지 않는 한 프로그램이 파일을 열면 그 파일로의 접근도 금한다.
윈도우에서 네이티브 저수준 API는 Windows API이다. 윈도우는 모놀리식 설계로 되어 있기 때문에 이 API는 매우 거대하며, 엄청난 양의 함수를 제공한다. 많은 기본 라이브러리들이 거의 모든 윈도우 버전에서 동일하다. 새로 발매된 라이브러리는 새 기능을 가지지만 기존 기능이 사라지는 경우는 드물다. 하지만 특정 윈도우 버전에서만 이용할 수 있는 API 기능이 몇 가지 있다. 예를 들어, 유니코드 지원은 NT 계열에서만 이용할 수 있다.
마이크로소프트사는 최근 Windows XP 이상 버전만 지원한다. 많은 새 라이브러리들이 Windows Vista가 도입된 이후로만 이용할 수 있게 되었다. 새 기능을 사용하기 전에는 운영체제 버전을 먼저 확인하는 것이 좋겠다. 주요 API 루틴은 프리 파스칼 RTL 내 Windows 유닛에서 선언된다. 추가 API는 다른 유닛에서 찾을 수 있는데, 관련 내용은 표 5.4를 참고한다.
API Name | 유닛(들) | 설명 |
Shell | shellapi shfolder shlobj |
가상 객체를 조직한다. 예: 원격 프린터, 데스크톱 또는 휴지통을 계층적 명칭공간(hierarchical namespace)에 조직한다.
메인 루틴: ShelExecute, SHGetPathFromIDList, SHGetSpecialFolderLocation. 주 인터페이스부: IShellLink. |
Common Controls | commctrl | 표준 윈도우 컨트롤. 예: 툴바, 이미지리스트, 리스트뷰, 트리뷰, 버튼, 에디트, 리스트상자, 콤보상자 등. |
Common Dialogs | commdlg | 열기, 저장하기, 인쇄하기 대화창 |
COM | activex comconst comobj |
다른 프로그래밍 언어 간에 클래스 내보내기/가져오기를 위한 메커니즘. 이러한 클래스들은 인터페이스로서 전달되고 GUID라는 유일한 식별자를 가진다.
메인 루틴: CreateComObject, CoInitialize, CoUninitialize. |
Multimedia | mmsystem | wav와 midi 음향의 재생 및 녹음에 사용된다.
메인 루틴: PlaySound |
Richedit | richedit winver |
Windows Richedit 컨트롤
윈도우 버전 확인을 단순화시킨다. 메인 루틴: CheckWin32Version |
Other libraries | flatsb imagehlp wininet |
그 외 사용 빈도가 낮은 API. |
표 5.4: 가장 중요한 구성요소(constituent)를 포함한 Windows API와 프리 파스칼 유닛 |
특정 함수에 대한 문서는 읽고 싶다면 구글에 접속하여 검색창에 site:msdn.microsoft.com<function name>를 입력하라. 그러면 Microsoft Developer Network(마이크로소프트 개발자 네트워크)에 해당 페이지가 열릴 것이다. 하지만 델파이에 포함된 도움말 파일(help file) 등 Windows API에 관한 오프라인 문서도 있다.
사용할 API가 무엇이며 어떻게 작동하는지가 명확해지면 라자루스 IDE를 이용해 어떻게 루틴이 파스칼로 선언되었는지를 볼 수 있겠다.
이를 위해선 소스 코드에 루틴의 이름을 입력하여 오른쪽 마우스를 클릭한 후 나타나는 컨텍스트 메뉴에서 Find Declaration 을 선택한다.
성공적으로 작업하려면 함수 선언을 포함하는 유닛을 현재 유닛의 uses 절에서 명명해야 한다. 아니면 소스에 유닛 이름을 입력하고 점을 붙인 후 ("shellapi.") 잠시 기다린다. 그러면 해당 유닛 내 모든 루틴을 모은 리스트가 제공될 것이다. 루틴 이름의 첫 문자를 입력하면 리스트가 줄어든다. 몇 개의 주요 유닛의 이름을 익히는 것이 편리하다는 사실을 알게 될 것이다 (표 5.4 참고)!
다음 예제에서는 Windows API의 호출을 살펴볼 것인데, 이 특별한 경우와 같이 플랫폼 독립적 파스칼 라이브러리에서 이용할 수 없는 루틴을 호출하고자 한다. 프로그램은 현재 사용자의 데스크톱에 C:\Windows\Calc.exe (윈도우 계산기)로의 핫키 단축키를 생성한다. 이를 위해선 CreateComObject 를 이용해 ShellLink COM 객체를 생성해야 한다. 이후에는 단축키에 관해 필요한 정보가 입력되고 SHGetSpecialFolderLocation 은 현재 사용자의 데스크톱 폴더 위치를 제공한다. 하드 디스크의 단축키를 저장하기 위해 IShellLink..Save 가 호출된다.
(*================================================================ *)
(* createshortcut.lpr *)
(* Creates a link to the Windows calculator in the desktop *)
(* =============================================================== *)
program createshortcut;
{$ifdef fpc}
{$mode delphi}
{$endif}
uses SysUtils, Windows, ShlObj, ActiveX, ComObj;
var IObject : IUnknown;
ISLink : IShellLink;
IPFile : IPersistFile;
PIDL : PItemIDList;
InFolder : array[0..MAX_PATH] of Char;
TargetName: String;
LinkName : WideString;
begin
CoInitialize(NIL);
TargetName := 'c:\windows\calc.exe'; // In the system encoding!
(* Creates an instance from IShellLink *)
IObject := CreateComObject(CLSID_ShellLink);
ISLink := IObject as IShellLink;
IPFile := IObject as IPersistFile;
ISLink.SetPath(PChar(TargetName));
ISLink.SetWorkingDirectory(PChar(ExtractFilePath(TargetName)));
(* Locates the Desktop folder *)
SHGetSpecialFolderLocation(0, CSIDL_DESKTOPDIRECTORY, PIDL);
SHGetPathFromIDList(PIDL, InFolder);
LinkName := InFolder + '\Link created with Free Pascal.lnk';
(* Creates the link *)
IPFile.Save(PWChar(LinkName), False);
CoUninitialize;
end.
여기서 윈도우 특정적 코드를 어느 정도 설명할 필요가 있겠다. 문자열은 C 프로그램에서와 같이 항상 널 종료(null-terminated) 포맷으로 전달된다. 널 종료란 첫 #0 문자를 검색하면 문자열 길이를 찾을 수 있음을 의미한다.
ANSI 문자열도 널 종료되므로, PChars로 쉽고 간단하게 변환할 수 있다.
이러한 수법은 IShellLink.SetPath 와 IShellLink.SetWorkingDirectory 에서도 사용된다. Windows API로부터 문자열을 얻는다면 메모리가 먼저 할당되어야 한다. 이를 위해선 SetLength 를 이용해 ANSI 문자열의 올바른 길이를 설정하거나 GetMem 를 이용해 메모리를 얻는 방법이 있다.
GetMem 저장 공간은 항상 다시 FreeMem 을 이용해 해제해야(release) 한다. Windows API는 이 작업을 대신해주지 않는다.
예제는 메모리 공간을 예약하는 세 번째 방법, 즉 로컬 배열의 선언을 통한 (InFolder) 방식을 보여준다. 대부분 Windows API 함수들 또한 파라미터에 메모리 청크(chunk)의 크기를 요한다. SHGetPathFromList 의 경우 문자열은 MAX_PATH 크기여야 한다.
Windows API에서 유니코드가 가능한 함수 내 유니코드 코딩은 UTF16 로 이루어진다. 해당 인코딩에서 각 문자의 너비는 2~4 바이트까지 가능하다. 파스칼 타입인 Widestring과 Widechar가 이를 지원한다.
경험상 widestring는 윈도우에서 참조 계수(reference-counted)를 이용하지 않는다 (ansistring과 달리). 하지만 IPersistFile.Save 로의 호출에서 볼 수 있듯이 PWChar 로 그리고 PWChar 로부터의 타입 캐스팅(typecast)은 작동한다.
GetMem 를 이용해 widestring에 메모리를 할당할 때는 GetMem 가 크기를 문자수가 아닌 바이트로 세기 때문에 문자열의 수에 2를 곱해야 함을 주목한다.
LCL이 모든 문자열을 UTF-8로 예상하듯, UTF16 widestring은 UTF8Decode와 UTF8Encode를 이용해 변환되어야 한다.
그림 5.1은 Windows XP의 디렉터리 구조를 보여준다. Program Files 와 Documents and Settings 와 같은 일부 폴더들은 윈도우 버전이 발매된 국가에 따라 이름이 다르다.
영문 Program Files 이 독일어 버전에선 Programme , Documents and Settings 는 Dokumente und Einstellungen 이라 부른다. Windows Vista 부터는 Windows XP에 비해 많이 달라졌기 때문에 Vista/Win7에서 시스템 디렉터리 다수의 이름이 변경되었다.
운영체제 의존적 디렉터리의 이름이 서로 다르기 때문에 이러한 경로는 절대로 소프트웨어로 하드코딩(hard-coded)될 수 없으므로 이번 예제에서도 SHGetSpecialFolderLocation 을 사용한다.
디렉터리/CSID | Win XP에서 영문 | Win XP에서 독일어 | Vista/Windows 7 |
데스크톱 폴더 CSIDL_DESKTOP_DIRECTORY |
C:\Documents and Settings\Username\Desktop | C:\Dokumente und Einstellungen\Username\Desktop | C:\Users\Username\Desktop |
사용자 특정적 구성 파일 CSIDL_LOCAL_APPDATA |
C:\Documents and Settings\Username\Local Settings\Application Data | C:\Dokumente und Einstellungen\Username\Lokale Einstellungen\Anwendungsdaten | C:\Users\Username\AppData Local\ |
애플리케이션 CSIDL_PROGRAM_FILES |
C:\Program Files | C:\Programme | C:\Program Files (x86) |
"시작 메뉴"의 내용 CSIDL_PROGRAMS |
C:\Documents and Settings\Username\Start Menu\Programs menu\Program | C:\Dokumente und Einstellugen\Username\Start Programs | C:\Users\Username\AppData \Roaming\Microsoft\StartMenu |
윈도우 운영체제 폴더 CSIDL_WINDOWS |
C:\Windows | C:\Windows | C:\Windows |
표 5.5: 다양한 윈도우 버전과 로케일(locale)에서 특수 디렉터리와 그 이름. 사용자 로컬 문서에 대한 CSIDL_MYDOCUMENTS와 같은 상수들이 있으며, 새 윈도우 버전마다 새 디렉터리를 추가할 수도 있다. |
현재 운영체제에 해당하는 표 5.5의 값은 작은 라자루스 프로그램에서 쉽게 얻을 수 있다. 이 프로그램에 할 일이란 빈 폼에 메모 필드를 위치시키고 오브젝트 인스펙터로 창에 대한 OnCreate 이벤트를 할당하기만 하면 된다.
uses
Windows, LCLProc, ShlObj;
procedure TForm1.FormCreate(Sender: TObject);
var PIDL : PItemIDList;
Folder: array[0..MAX_PATH] of Char;
begin
SHGetSpecialFolderLocation(0, CSIDL_DESKTOPDIRECTORY, PIDL);
SHGetPathFromIDList(PIDL, Folder);
Memo1.Lines.Add('Desktop: ' + SysToUTF8(Folder));
SHGetSpecialFolderLocation(0, CSIDL_LOCAL_APPDATA, PIDL);
SHGetPathFromIDList(PIDL, Folder);
Memo1.Lines.Add('Application data for the current user: ' + SysToUTF8(Folder));
SHGetSpecialFolderLocation(0, CSIDL_MYDOCUMENTS , PIDL);
SHGetPathFromIDList(PIDL, Folder);
Memo1.Lines.Add('User documents: '+ SysToUTF8(Folder));
SHGetSpecialFolderLocation(0, CSIDL_PROGRAM_FILES, PIDL);
SHGetPathFromIDList(PIDL, Folder);
Memo1.Lines.Add('Program files: ' + SysToUTF8(Folder));
SHGetSpecialFolderLocation(0, CSIDL_PROGRAMS, PIDL);
SHGetPathFromIDList(PIDL, Folder);
Memo1.Lines.Add('Start menu: '+ SysToUTF8(Folder));
SHGetSpecialFolderLocation(0, CSIDL_WINDOWS, PIDL);
SHGetPathFromIDList(PIDL, Folder);
Memo1.Lines.Add('Windows Folder: ' + SysToUTF8(Folder));
end;
Windows CE, 특수 사례
라자루스 버전 0.9.24부터는 네이티브 Windows CE 애플리케이션을 파스칼로 개발할 수 있다. Windows CE는 (WinCE) 매우 유연한 운영체제라, 많은 애플리케이션에서 사용되고 있다-처음에는 주로 오토모티브(automotive) 애플리케이션에 주로 사용되었으나, 현재엔 PDA (PocketPC), 스마트폰(윈도우 모바일), 소형 컴퓨터 (TabletPC)에서 사용된다. 이 모든 경우에서 운영체제는 동일하지만 이렇게 다양한 하드웨어에서 이용할 수 있는 리소스에는 큰 차이가 있다. 일부 애플리케이션에는 GUI가 없고, 일부는 터치화면으로 제한된 GUI를 허용하며, 매우 간단한 일부 폰들은 터치화면 없이 GUI를 가진 반면 TabletPC용 GUI는 표준 PC의 GUI와 상당히 비슷하다. Windows CE에 실행할 프로그램을 생성할 때는 이러한 차이점들을 고려해야 한다.
라자루스 컴포넌트 라이브러리(LCL)는 Windows CE 애플리케이션을 작성하거나 기존 애플리케이션을 Windows CE로 또는 그로부터 이식하도록 돕는 여러 메커니즘을 제공한다.
Java의 모바일용 J2ME와 같은 기타 플랫폼 독립적 개발 환경들은 모바일과 데스크톱 애플리케이션에 대해 완전히 다른 API를 가진 반면, LCL를 이용하면 항상 동일한 API를 이용할 수 있다. 이러한 경우 모바일 플랫폼에서 행위를 제어하기 위해 스위치를 사용해야 한다. 이번 절에서는 이러한 스위치와 Windows CE에 특정적인 세부내용을 다루고자 한다.
Windows CE API는 일반 Win32 API와 유사하며, 많은 인터페이스들이 완전히 동일하다. 두 운영체제의 주요 프로퍼티들은 동일하기 때문에 여기서는 둘의 차이점에 중점을 두겠다. 첫 번째 차이는 Windows CE가 유니코드 텍스트 문자열만 사용한다는 점이다. Ansi API는 구식이다.
데스크톱 버전에서와 마찬가지로 메인 API는 Windows 유닛에 위치하며, 추가 API는 구분된 라이브러리에 위치한다. Windows CE와 Windows의 메인 API는 동일할지 모르지만 특수 API가 매우 다른 경우도 종종 있다. 게다가 장치마다 API의 복잡성에 따라 이용성이 매우 다를 수 있다.
API Name | 유닛 | 설명 |
AYG Shell | aygshell | agyshell.dll 라이브러리로부터의 잡다한(miscellaneous) 셸 루틴. 해당 라이브러리의 많은 버전을 http://hpcmonex.net/izemize.htm에서 다운로드할 수 있다.
메인 루틴: SHRecognizeGesture, SHFindMenuBar, SHCreateMenuBar |
Bluetooth | bt_api, bthapi, bthutil |
블루투스 클라이언트/서버 API. |
Synchronization | cesync | Windows CE용 ActiveSync 동기화 모듈. |
Common Controls | commctrl | 툴바, 이미지리스트, 리스트뷰, 트리뷰, 버튼, 에디트, 리스트상자, 콤보상자 등 표준 윈도우 컨트롤. |
Common Dialogs | commdlg | 열기, 저장하기, 인쇄하기 대화창. |
Control Panel | cpl | 제어판 확장 DLL 정의. |
GPS | gpsapi | GPS Intermediate Driver API. |
OLE | oleauto | oleau32 WinCE API에 대한 선언. |
Phone API | phone, tapi | Telephone API. |
PIM | pim | PIM 데이터를 관리한다 (연락처, 달력, 업무, 노트). |
Shell | shellapi | 가상 객체를 조직한다. 예: 원격 프린터, 데스크톱 또는 휴지통을 계층적 명칭공간(hierarchical namespace)에 조직한다. 메인 루틴: SHGetSpecialFolderLocation, SHGetPathFromIDList |
Notification LED | nled | 알림 LED API. 다른 운영체제 LED와 달리 장치가 꺼져있을 때도 ON 또는 깜빡이도록 설정할 수 있다. 하드웨어 지원을 필요로 한다. |
SMS | sms | SMS 메시지의 전송/수신용. |
표 5.6: 프리 파스칼로 Windows CE API를 노출하는 유닛 |
사전 요구사항과 준비사항
Windows CE 애플리케이션의 개발을 시작하기 전에 어느 정도 준비해야 할 사항이 있다. 라자루스 프로그램을 테스트하기 위해선 WinCE가 실행되는 하드웨어, 또는 개발 PC에 장치 에뮬레이터(device emulator)가 필요하다. 마이크로소프트는 에뮬레이터를 설치하려면 Windows XP SP2 혹은 Windows Server 2003이 필요하다고 명시한다.
4개의 파일은 아래에서 다운로드 할 수 있다:
마이크로소프트사의 Windows Mobile OS Images가 있는 Pocket-PC-Emulator Standalone Device Emulator 1.0는 아래에서 다운로드한다.
먼저 V1Emulator.zip 파일을 (실제 에뮬레이터) 압축해제 후 설치한다. 이 파일은 Windows 2000과 Windows XP에 이용할 수 있다. 마이크로소프트사에서 발표하는 에뮬레이터의 업데이트마다 변경 대상이 되므로 설치 세부내용을 모두 여기서 다룰 수는 없다. 위의 웹사이트에서 efp.msi 파일도 다운로드해야 하지만 아직 설치는 하지 않는다 (추후 설치). Windows Vista 이상 버전에서는 에뮬레이터 버전 1을 설치할 수 없으며, 버전 3이 필요하다. (Windows XP/2003에서는 버전 1이나 3 중 원하는 대로 사용할 수 있다). 아래 주소의 마이크로소프트 서버 Microsoft Device Emulator 3.0 – Eigenstandige Version 페이지에서 vs_emulator.exe (Win32) 또는 vs_emulator_x64_vista.exe 파일의 독일어 버전 3을 다운로드할 수 있다.
웹사이트에 주어진 링크가 작동하지 않는다고 하면서 Virtual Machine Network Driver(가상 머신 네트워크 드라이버)를 설치해야 한다는 오류가 있다. 대신 Virtual PC를 다운로드 할 수 있다.
에뮬레이터는 공식적으로 Windows XP SP2 이상에만 적합하도록 선언되었다. 이유는 에뮬레이터 자체뿐만 아니라 Virtual PC 2007도 필요하기 때문이다. 오래된 Windows 2000을 사용하길 원한다 하더라도 작업은 가능하다. Virtual PC 2004 SP1에도 드라이버가 있으며, 다음 단계에서 설치하게 될 efp.msi 에서도 수락될 것이다. 해당 프로그램은 아래의 마이크로소프트 다운로드 섹션에서 이용 가능하다.
아니면 구글에서 Virtual PC 2004를 검색하도록 한다.
Windows XP 혹은 Vista와 작업을 원할 경우 무료로 이용 가능한 Virtual PC 2007를 설치할 필요가 있는데, 아래 주소에서 다운로드 후 설치한다.
위의 두 에뮬레이터 모두 필요한 드라이버가 포함되어 설치될 것이다.
이제 첫 번째 웹사이트에서 다운로드한 efp.msi (에뮬레이터 이미지)를 설치할 수 있다.
중요: 이 파일은 Vista에서 설치해야 한다. 파일을 얻으려면 에뮬레이터 버전 3.0을 다운로드 한 후 웹사이트에서 버전 1.0로 전환한다. 이제 완전히 기능하는 PocketPC 에뮬레이터가 라자루스를 이용해 애플리케이션을 개발할 준비가 되어 있을 것이다. 정보 교환을 가능하게 하려면 호스트 PC에 ActiveSync를 설치해야 하는데, 이는 아래 주소에서 찾을 수 있다:
ActiveSync 4.2 (혹은 이상) 버전을 다운로드하고 설치하라. 4.2 버전에 필요한 파일은 setup.exe, 4.5버전에 필요한 파일은 setup.msi이다. 구성 설정은 건너뛰어도 좋다. ActiveSync는 Windows Vista/7과 호환이 불가함을 명심하라. 후에 발매된 Windows는 동일한 기능을 제공하는 Windows Mobile Device Center를 필요로 한다. 이제 에뮬레이터를 위한 공유 폴더를 설치해야 한다. 에뮬레이터 메뉴 File→Configure 를 통해 호스트 PC에서 데이터 교환을 위한 디렉터리를 선택해야 한다. 이 디렉터리는 WinCE에 실행되는 실행 파일 이미지를 라자루스가 저장하는 위치이다. 호스트 상에 공유 폴더(Shared Folder)로 정의된 디렉터리는 에뮬레이터 익스플로러에서 (Menu Start→Programs) 외장 카드(Storage Card)로 이용할 수 있을 것이다.
그 곳에서 발견한 실행 파일을 더블 클릭하면 시작할 수 있다.
라자루스 애플리케이션을 에뮬레이터에서 사용하려면 직접 실행해야 하는데, 이를 위해선 윈도우 프로그램 메뉴 Windows Mobile Emulator Images→PocketPC 를 선택한다. 명령 행으로부터 ActiveSync를 통해 gdb 디버거를 이용하는 방법도 있다. 이러한 작동을 위해선 에뮬레이터 또는 장치를 ActiveSync로 연결해야 한다. 이후 윈도우 명령 행이 열리고 arm-wince-Target을 가리킨다.
gdb --tui C:\MyProgram\MyWinceAp\MyApp.exe
위의 명령은 gdb의 GUI를 호출할 것이다. 애플리케이션을 시작하기 위해선 r 문자를 입력하고 [Enter]를 눌러 승인하면 실행 파일이 \gdb 디렉터리 내 장치로 복사되어 실행된다.
이제 개발 환경의 설치를 시작할 수 있다. Windows CE 애플리케이션을 위한 라자루스 빌드 환경을 설치하는 방법은 한 가지 이상이 있다. 안정적 Addon Installer 혹은 Snapshot Installer 를 통해 설치하거나, Subversion을 이용해 Windows CE LCL 인터페이스를 LCL로 수동으로 넣는 방법이 있다. 가장 간단한 방법은 안정적 Addon Installer를 사용하는 것이다. 이를 위해선 먼저 라자루스의 일반 32-bit Windows 버전을 제시된 디렉터리에 (보통 C:\Lazarus) 설치하라. 이 버전은 (후에 필요한 다음 아카이브 또한) 웹사이트에 실린 연결 리스트에서 쉽게 얻을 수 있다: http://www.lazarus.freepascal.org
버전 0.98.2 은 Lazarus-0.9.28.2-fpc-2.2.4-win32.exe, 그리고 Lazarus-0.9.28.2-fpc-2.2.4-cross-arm-wince-win32.exe를 함께 설치하면 편할 것이다. 설치가 끝나면 컴파일러는 두 개의 대상 타입, 즉 Win32와 WinCE ARM 을 작성할 수 있게 된다. 최근에는 Win64용 Addon 아카이브도 이용할 수 있다 (버전이 빌드된 방식에 따라).
전체 버전 또는 Addon 중 무엇을 다루는지는 설치 아카이브의 크기와 파일명에 포함된 -cross- 로 식별할 수 있다.
설치가 끝나면 라자루스는 ARM WinCE 애플리케이션을 컴파일할 수 있다.
새로 설치된 라자루스 IDE라면 Project→Project Options 를 선택하여 좌측 트리뷰(treeview)에 Paths 를 클릭한다. 그리고 Paths 페이지에서 컴파일러 옵션을 설정하는데 (그림 5.4 참고) LCL Widget Type (various) 라는 라벨이 붙은 콤보상자를 클릭하여 드롭다운 리스트에서 WinCE(beta) 를 선택하면 된다.
동일한 창에서 Code generation 노드를 클릭하여 대상 플랫폼을 명시하라 (그림 5.5 참고).
다음으로 사용자는 컴파일 설정을 확인할 수 있다. 이를 위해선 대화창의 하단에 [Test] 버튼을 누른다 (그림 5.6 참고). 현재 개발자 버전을 처리하기 위한 프로시저는 위에서 논한 과정과 정확히 동일하다. 이는 http://www.lazarus.freepascal.org 의 Daily Snapshots 아래의 링크 리스트에서 찾을 수 있다.
사용자는 아카이브와 컴파일러의 버전을 (안정 버전과 개발자 버전) 혼합해선 안 된다. 하지만 둘을 구분된 작업 디렉터리에 설치하는 것은 가능하다.
라자루스 크로스 컴파일러 버전은 추가 바이너리 유닛만 설치할 수 있다. 이는 ARM 프로세서에 Windows CE 용 GNU binutils 를 설치하는 것만큼 중요하다 (그림 5.8 참고). Binutils는 컴파일러 (as), 링커 (ld), 디버그 정보 스트리퍼 (strip), 리소스 컴파일러 (windres), 디버거 (gdb), 아카이버를 (ar) 포함한다. 이들이 없이는 어떤 프로그램도 컴파일할 수 없다. 이들은 대상 플랫폼 의존적이며, 네이티브 운영체제로 작업할 때와 (짧은 이름 버전) 크로스 컴파일을 실행할 때 (이름에 대상의 접두사가 포함된 버전) 필요하다.
프리 파스칼을 이용한 크로스 컴파일은 라자루스에 관한 본 저서의 주제가 아니며, 라자루스로 정확한 툴 체인을 개발하기란 꽤 복잡하다. (tool chain 은 컴파일이나 연결을 포함해 전체 해석 과정에 필요한 프로그램의 시퀀스를 의미한다.) 라자루스에서 필요한 툴은 프리 파스칼 컴파일러, 리소스 컴파일러, 어셈블러, 링커로 충분하다.
스트립(strip)은 필수사항은 아니지만 매우 유용한 과정인데, 부호(symbol)를 제거하면 프로그램이 약간이 아니라 본래 크기의 10%만큼 줄어들 수 있기 때문이다. 윈도우에 Windows CE용 안정적 툴 체인을 얻는 방법에는 두 가지가 있다.
ftp://ftp.freepascal/pub/fpc/contrib/cross
위의 주소와 그 하위디렉터리에서 바이너리 파일 전체 집합을 이용할 수 있다. 이 중 일부는 꽤 오래되었음에도 불구하고 별다른 문제가 되지 않는다. 이 방법이 아니라면 아래 주소에서 binutils 2.2.0용 소스 코드를 다운로드해야 한다.
http://ftp.gnu.org/binutils/
이러한 유틸리티들의 컴파일, 특히 유닉스에서의 컴파일에 관한 정보는 아래를 참고한다:
http://sources.redhat.com/binutils/
윈도우 상에서는 MingW 컴파일러를 이용해 Cygwin 에서 컴파일도 가능하다.
바이너리 파일은 그 네이티브 이름뿐만 아니라 크로스 컴파일러 이름으로 저장하는 것이 중요한데, cpu 플랫폼 이름을 예로 들 수 있겠다. 따라서 대상 플랫폼인 "FreeBSD/i386"을 위한 "as"라는 이름의 파일은 -어떤 호스트 플랫폼이든- i386-freebsd-as라고 명명해야 한다.
성공적인 컴파일을 위해선 위의 파일들 외에도 RTL, FCL, LCL로부터의 파일이 필요한데, 예를 들자면 프리 파스칼 자체의 유닛과 라자루스의 유닛이 모두 필요하다. 그렇게 되면 라자루스 설치 크기가 상당히 증가할 것이다. 따라서 라자루스를 이용할 플랫폼을 위한 크로스 컴파일의 유용성을 고려하는 것이 중요하다. 어쩌면 두 개의 플랫폼에 라자루스를 따로 설치하는 편이 효율적일지도 모른다. 컴파일러는 어차피 무료이지 않은가!
Windows CE용 LCL
앞서 언급하였듯 Windows CE는 Windows와 다르며, 운영체제의 모든 폼이 다른 기능을 가진다. 라자루스는 이를 전역 애플리케이션 객체의 프로퍼티에 분류한다 (Forms 유닛에 정의). 이러한 전역 객체의 ApplicationType 프로퍼티는 LCL을 강제로 특정 모드로 만들 것이다. Application.ApplicationType이 취할 수 있는 값은 표 5.7에 실려 있다. 라자루스 0.9.28까지 atKeyPadDevice 라는 애플리케이션 타입은 atSmartphone 이라 불렸으며, atHandheld 라는 함수적 타입(functional type)도 있었다. 후자는 0.9.29 버전에서 제거되었고, atSmartphone 타입은 휴대전화 기능과 전혀 관련이 없었을 뿐더러 마이크로소프트사에서 할당한 이름은 혼동만 야기하였으므로 atKeyPadDevice 로 재명명되었다. PDA는 주로 스마트폰보다 더 많은 버튼을 가지고 있기 때문에 atPDA 로 설정하는 편이 나았다. atKeyPadDevice 는 입/출력 성능이 매우 제한된 바코드 주사기(bar code scanner)와 같은 장치를 설명한다.
WinCE 인터페이스에 가장 중요한 함수 ApplicationType 는 창의 생성을 제어한다. PDA 또는 스마트폰의 화면은 대부분 폼에 너무 작기 때문에 이동 가능한 창이 필요하지 않다. 대부분 BorderStyle 값의 결과는 창 크기를 무시하고 전체화면으로 표시할 것이다.
애플리케이션 타입은 여러 장치 타입을 지원할 수 있도록 변경 가능하지만 이러한 변경은 Application.Initialize 전에 실행되어야 한다. 예를 들어, 추후에는 창을 닫은 후 혹은 다시 열 때 변경할 수 있겠다. 보통 ApplicationType 는 atDefault 로 설정되고, LCL 는 올바른 값을 예상할 것이다.
버전 | 설명 |
atDefault | 모든 LCL 애플리케이션에 나타나는 Application.Initialize 로의 호출에서 Application.ApplicationType이 atDefault 로 설정되면 라자루스는 하드웨어가 어떤 애플리케이션 타입에 응답하는지 자동으로 감지하여 ApplicationType 을 감지된 모드로 변경할 것이다. |
atPDA | 터치화면이나 작은 화면의 PDA 또는 스마트폰. 전체 작업 영역을 차지하도록 폼은 주로 최대화되며, 커스텀 위치설정(positioning) 크기설정(sizing)은 무시된다. 기본 해상도로 된 Windows CE는 240 픽셀 너비이며, 높이는 장치마다 변경할 수 있지만 320 픽셀 정도가 알맞다. 일부 장치에서는 화면이 지원하는 한 더 큰 크기로 지정할 수도 있다. |
atSmartphone, atKeyPadDevice |
단순한 폰과 일부 GPS 장치 및 바코드 주사기. 이러한 타입의 장치는 매우 제한되고 화면이 작아 터치 화면이나 다른 위치 결정 장치가 없다. 제한된 네비게이션 기능이라 함은 애플리케이션을 가능한 한 간소화해야 함을 의미한다. 예를 들어, WindowsCE에서는 최상위 메뉴가 2개만 가능하며, 대부분의 폼은 전체의 작은 화면을 채우기 위해 라자루스가 자동으로 최대화시킨다. |
atDesktop | 큰 화면과 위치 결정 장치가 있는 하드웨어용 애플리케이션 모드. 데스크톱 컴퓨터뿐만 아니라 노트북과 태블릿 PC용으로 사용되기도 한다. Windows CE 태블릿은 해당 모드에서 실행되며 Win32 라자루스 인터페이스와 매우 유사하게 작동한다. |
표 5.7: LCL에서 이용 가능한 애플리케이션 타입 |
애플리케이션 타입 | 테두리 (border)스타일 | 행위 | 창 상태 | 캡션 바 |
atDesktop | 모두 가능 | 윈도우 데스크톱에서와 같음. | 윈도우 데스크톱에서와 같음. | 윈도우 데스크톱에서와 같음. |
atPDA, atKeyPadDevice |
bsDialog | 커스텀 크기설정을 수락. X, Y 위치는 무시되고 항상 상단 좌측 모서리로 설정되지만 상단에 최후 작업 표시줄의 유무를 고려함. | 적용사항 없음. | (ok) 닫기 버튼이 있는 구분된 제목 표시줄. |
atPDA, atKeyPadDevice |
bsNone | 크기와 위치의 전적인 제어를 허용. | 적용사항 없음. | 존재하지 않음. |
atPDA, atKeyPadDevice |
bsSizable, bsSingle, bsToolWin, bySizeToolWin | 커스텀 크기 및 위치를 거부하고 항상 메뉴를 제외한 전체 작업 영역을 채우도록 설정됨. 작업 영역에는 항상 작업 표시줄이 제외됨. | 적용사항 없음. | (ok) 닫기 버튼이 있는 작업 표시줄로 통합. |
표 5.8: Windows CE에서 애플리케이션 타입이 창의 행동을 제어하는 방법 |
program MyProgram;
uses Forms, Interfaces, MyUnit,
begin
Application.ApplicationType := atSmartphone;
Application.Initialize;
Application.CreateForm(TMainWindow, vMainWindow);
Application.Run;
end.
WinCE 용 LCL에서 많이 사용하는 항목을 한 가지 더 들자면, 제목 표시줄에 위치한 버튼들이다. WinCE에는 사전 정의된 닫기 버튼은 없지만 창을 최소화하는 (X) 버튼이 있다. 버튼에는 "X" 자가 있기 때문에 (데스크톱 애플리케이션에서 창을 닫는 버튼과 유사) 많은 애플리케이션 개발자들은 (X) 버튼이 폼(창)을 닫는 기능을 할 것이라 생각한다. 이러한 오해를 막기 위해 LCL WinCE 인터페이스는 창을 닫는 (기본 값으로) (ok) 버튼을 표시하여 창을 최소화하는 (X) 버튼과 혼동되지 않도록 한다.
그렇지만 이러한 행위는 WinCEWidgetset 의 WinCETitlePolicy 프로퍼티를 이용해 변경할 수 있다. WinCETitlePolicy 프로퍼티에 대해 기본 값을 변경하여 이러한 기본적인 창 행위를 변경할 수는 있지만, 메인 창에서 (X)를 클릭하더라도 애플리케이션이 닫히지 않으므로 개발자들은 (X) 버튼을 허용하는 옵션과 관련해 특별한 주의를 기울여야 한다.
개발자로서 당신은 프로그램의 두 번째 인스턴스가 시작될 때 사용자에게 경고할 뿐 아니라 본래 인스턴스를 보고 새 인스턴스 닫을 수 있는 옵션을 제공할 책임이 있다. 그렇지 않을 시, 사용자는 이전 인스턴스를 최소화시키기만 했다는 사실을 인식하지 못하여 프로그램의 불필요한 중복 인스턴스가 장치의 메모리를 차지하게 될 것이다.
값(value) | 설명 |
tpAlwaysUseOKButton | 기본 값으로 (ok) 닫기 버튼이 모든 폼에 제공된다. |
tpOKButtonOnlyOnDialogs | 모든 폼에 (X) 최소화 버튼이 제공되나, bsDialog 테두리 스타일로 된 폼은 예외로서 (ok) 닫기 버튼이 있을 것이다. |
tpControlWithBorderIcons | biMinimize 가 폼의 BorderIcons에 포함될 경우 (X) 최소화 버튼이 표시되며, 포함되지 않는다면 (ok) 버튼이 표시될 것이다. |
표 5.9: WinCEWdigetset.WinCETitlePolicy 프로퍼티로 가능한 값 |
아래는 캡션 바 버튼의 기본 값 설정을 변경하는 방법에 대한 예제이다.
속성(attribute)은 Application.Initialize 를 호출하기 전에 변경되어야 한다.
호출 후에 변경할 경우 어떠한 효과도 나타나지 않는다.
program mywinceapp;
uses Forms, Interfaces, MyUnit,{$ifdef WinCE} WinCEInt;{$endif}
begin
{$ifdef WinCE}
WinCEWidgetset.WinCETitlePolicy := tpOKButtonOnlyOnDialogs;
{$endif}
Application.Initialize;
Application.CreateForm(TMainWindow, vMainWindow);
Application.Run;
end;
Linux, FreeBSD, 그리고 기타 Unix 시스템
Linux, FreeBSD, Solaris, 그리고 기타 많은 Unix 시스템들 간에는 엄청난 유사점들이 있기 때문에 여기서 함께 논하고자 한다. 보통 리눅스 프로그램은 변경 없이 재컴파일만으로도 FreeBSD를 비롯해 다른 유닉스 변형(variant)에서 실행될 것이다. 이는 커널로의 직접 호출이나 /dev 내 장치 이름과 같은 시스템 특정적 수법을 사용하지 않았을 때만 작동한다. 모든 유닉스 시스템들은 인터넷 URL과 마찬가지로 디렉터리 경로에 "/" 를 구분자로 이용한다. 윈도우와 유닉스의 가장 큰 차이 중 하나는 파일명 확장자가 파일 타입을 나타내지 않는다는 점이다. 윈도우에서는 확장자로 문서를 인식하는 것이 보통이다. 반면 유닉스의 실행 파일에는 확장자가 없다. 라이브러리에는 .so 확장자가 있다. 윈도우와 반대로 유닉스 파일 이름은 대.소문자를 구별한다. 단, 유닉스에서 외부(foreign) 파일 시스템으로 (FAT 또는 NTFS 포맷으로 된 파일 시스템) 접근한 경우는 제외한다.
카테고리 | 사용자 접근 권한 | 설명 |
/ | 읽기 | 루트 디렉터리. |
/home | 읽기 | 각 사용자마다 하나의 폴더를 보유하며, 사용자 계정을 이름으로 한다. 사용자의 문서 폴더가 될 것이다. |
/media and /mnt | 읽기/쓰기 | CD-ROM, DVD, 펜드라이브 등과 같은 장치가 저장되는 장소이다. 장소 이름은 리눅스 배포판에 따라 변경된다. |
/etc | 읽기 | 전역 구성 데이터. |
/proc | 읽기 | IRQ (인터럽트 번호), I/O 포트, 실행 중인 프로세스 등 리눅스 커널에 관한 정보가 있는 가상 파일들이 포함된 가상 디렉터리. |
/root | - | 루트 사용자의 홈(home) 디렉터리. |
/tmp | 읽기/쓰기 | 임시 파일을 위한 전역적 위치. |
/usr | 읽기 | 프로그램 데이터와 리소스 파일을 보유한다. |
/usr/bin | 읽기 | 제3자 소프트웨어에 의해 설치된 전역적으로 접근 가능한 실행 파일을 보유한다. |
/usr/share | 읽기 | 프로그램들은 읽기전용 리소스 파일이 있는 하위 디렉터리를 여기에 생성한다. |
/usr/share/man | 읽기 | 매뉴얼 페이지 (콘솔 기반의 도움말 시스템). |
표 5.10: 보통 리눅스 시스템에서 가장 중요한 디렉터리 |
유닉스에서 디렉터리 계층구조는 윈도우와 상당히 다르다. 드라이브 문자가 없으며, 모든 드라이브는 동일한 계층구조의 부분이다. 드라이브를 시스템에 추가하려면 디렉터리에 마운트해야 한다. 디스크상의 모든 파일은 그 디렉터리에 위치하는 것처럼 어드레싱(address)된다. 윈도우에 비해 "파일"이란 개념은 확장되며, 사운드 카드와 같은 장치들도 파일인 것처럼 어드레싱되고, 스피커는 쓰기 명령으로 어드레싱된다.
유닉스에서는 디렉터리 또한 파일이라 크기를 결정할 수 없거나 변경 일자가 유효하지 않은 개념인 경우가 발생할 것이다. 따라서 파일 시스템을 검색하는 프로그램을 생성 시 이를 이해하는 것이 중요하다. 윈도우와 달리, 유닉스에서는 파일의 접근을 잠그지 않는 것이 보통이다. 따라서 실행 가능 프로그램들은 실행 중에 업데이트가 가능하며, 얼마나 많은 프로그램이든 동시에 파일을 열 수 있다.
심볼릭 링크(symbolic link; 또는 symlink)도 윈도우보다 훨씬 더 광범위한 범위까지 이용할 수 있다. 심볼릭 링크는 다른 파일을 가리키는 파일이다. 링크가 읽기용으로 열려 있다면 사실상 링크의 대상이 열린 셈이다. 디렉터리도 심볼릭 링크가 될 수 있다. 심볼릭 링크의 개념은 FPC 컴파일러에서 현명하게 사용된다. 리눅스에서 /usr/bin/ppc386 파일은 사실 (i386용 프리 파스칼 컴파일러로 보이는) 실제 컴파일러의 링크에 불과하다. 이는 /usr/lib/fpc/<Compierversion>/ 디렉터리에 위치하는데, 완전 설치를 중복할 필요 없이 프리 파스칼의 다중 버전을 동시에 설치 및 사용 가능하게 해준다. 사용자는 단순히 링크만 변경하여 컴파일러 버전을 전환시킬 수 있다.
리눅스/유닉스는 하드 링크(hard link)도 지원하여 드라이브에 있는 파일을 외부 세계에서 하나 이상의 이름으로 표시되도록 해준다. 심볼릭 링크와 달리 하드 링크는 마운트 지점 경계(mount point boundaries)를 넘을 수 없다. FileUtil 유닛은 이러한 링크를 해결하는 ProgramDirectory 함수를 제공한다. 표 5.10은 개발자들에게 가장 중요한 리눅스 파일 시스템 디렉터리를 열거한다.
FreeBSD에서 디렉터리 계층구조는 리눅스와 또 약간 다르다. /proc 이 선택적이며 다른 데이터를 포함할 수도 있다. 프로그램에 가장 중요한 설치 경로는 /usr가 아니라 /usr/local 이다. 하지만 리눅스에도 예외가 있는데 이는 표 5.10 에 소개되어 있다. 일부 배포판에서 중요한 추가 경로로 /opt를 들 수 있는데, 이 경로는 추가 프로그램 패키지를 설치하는 데에 사용된다.
무료 유닉스 배포판들은 몇 가지 GUI를 특징으로 하는데, 그 중 가장 중요한 것으로 KDE 3, KDE 4, Gnome 2, Xfce 4를 들 수 있다. Gnome 2와 Xfce 4는 본래 Gimp용으로 개발되었던 GUI toolkit Gtk 2를 대개 기반으로 한다. KDE 3은 Qt3을, KDE 4는 Qt4를 기반으로 한다. Qt는 현재 Nokia가 인수한 Trolltech 에서 개발한 것이다. 툴킷 중 하나를 기반으로 한 프로그램들은 사용자가 다른 GUI를 선택한다 하더라도 실행될 테지만 필수 라이브러리는 여전히 설치되어 있어야 한다. 라자루스는 Gtk 1, Gtk 2, Qt 4 중 하나를 선택할 수 있도록 제공한다. IDE 는 개발 중인 프로그램 외 다른 툴킷을 이용할 수도 있다. 라자루스는 오래된 Gtk 버전 1부터 0.9.26 버전까지 사용하도록 사전 구성되었고, 버전 0.9.28부터는 Gtk 2가 사용된다.
하지만 Gtk 1로의 연결은 권하지 않는데, 많은 배포판에서 더 이상 자동으로 설치되지 않기 때문이다. 따라서 사용자는 추후 수동으로 설치해야 할 것이다. 현재 테마도 설치되지 않아 외관이 이상할 것이다 (하지만 테마는 라자루스와 함께 전달할 수 있다). Gtk 1은 유니코드를 지원하지 않기 때문에 현재 시스템 언어만 가능할 것이다. Qt는 C++로 작성되고 FPC는 이를 직접적으로 어드레싱 할 수 없으므로 Qt 4의 경우 추가 라이브러리가 설치되어야 한다. Gtk 2 애플리케이션들은 문제없이 작동할 것이다.
프로그램이 기반으로 하는 툴킷을 개발 PC 뿐만 아니라 대상 시스템에도 설치하는 것이 중요하다. 표 5.11은 각 툴킷에 대한 최소 요구사항을 보여준다.
라자루스 | 최소 FPC 버전 | Gtk | Qt 4 | Win32 | Carbon |
0.9.24 | 2.2.0 | Gtk 1.18 이상 | Qt 4.2 이상 | Windows 98 이상 | Mac OS X 10.4 이상 |
0.9.26 | 2.2.2 | Gtk 1.18 이상 | Qt 4.3 이상 | Windows 98 이상 | Mac OS X 10.4 이상 |
0.9.28 | 2.2.4 | Gtk 2.6 이상 | Qt 4.5 이상 | Windows 98 이상 | Mac OS X 10.4 이상 |
0.9.29(개발 중) | 2.4.0 | Gtk 2.6 이상 | Qt 4.5 이상 | Windows 98 이상 | Mac OS X 10.4 이상 |
표 5.11: 라자루스에 필요한 툴킷과 FPC의 최소 요구 버전 |
Qt 4 기본(basics)
Qt는 객체 지향 API이다. 다른 프로그래밍 언어로부터 그러한 API를 사용하려면 절차적 포맷(procedural format)으로 내보내야 한다. 이를 위해선 모든 메소드, 생성자, 소멸자, 필드, 프로퍼티를 절차적 방식으로 내보내는 프로그램을 작성해야 한다. 호출 규칙은 두 컴파일러에서 처리할 수 있는 방식이어야 한다. 플랫폼 독립적인 GCC로 컴파일된 Qt와 같은 라이브러리의 경우 cdecl 호출 규칙으로 해결한다.
Qt 라이브러리는 한 언어로부터 (C++) 객체를 다른 언어를 (Pascal) 위한 오파크(opaque) 타입으로 만든다. 즉, 객체들이 인터페이스 라이브러리에서 제공하는 함수를 통해서만 어드레싱됨을 의미한다.
모든 메소드는 Self 라는 숨겨진 파라미터(hidden parameter)를 소유한다는 점을 이해하는 것이 중요하다. 메소드를 호출하면 컴파일러는 자동으로 메소드 클래스의 인스턴스에 대한 포인터를 제공한다. API를 절차적 형태로 평준화(flatten) 시에는 이러한 숨겨진 파라미터를 고려해야만 한다.
예를 들어, Qt 툴킷 내 gmainwindow.h 라는 파일에서 아래 C++ 헤더를 고려해보자:
class Q_GUI_EXPORT QMainWindow : public QWidget {
public:
explicit QMainWindow(QWidget *parent = 0,
Qt::WindowFlags flags = 0 );
~QMainWindow();
QWidget * centralWidget () const;
void setCentralWidget ( QWidget *widget );
};
이는 다음 4개의 메소드로 QMainWindow 클래스를 선언한다: 생성자; 소멸자; centralWidget 프로퍼티에 대한 setter/getter 쌍(pair). 4개의 메소드는 아래와 같이 평준화(flatten)될 수 있다 (gmainwindow.hb 파일에서 찾을 수 있다).
C_EXPORT QMainWindowH QMainWindow_create(QWidgetH parent, unsigned int flags);
C_EXPORT void QMainWindow_destroy(QMainWindowH handle);
C_EXPORT QWidgetH QMainWindow_centralWidget(QMainWindowH handle);
C_EXPORT void QMainWindow_setCentralWidget(QMainWindowH handle, QWidgetH widget);
C++ 인터페이스 파일도 필요할 것이다. 이번 경우 gmainwindow.cppb가 호출된다:
QMainWindowH QMainWindow_create(QWidgetH parent, unsigned int flags){
return (QMainWindowH) new QMainWindow((QWidget*)parent,
(Qt::WindowFlags)flags);
}
void QMainWindow_destroy(QMainWindowH handle) {
delete (QMainWindow *)handle;
}
QWidgetH QMainWindow_centralWidget(QMainWindowH handle) {
return (QWidgetH)((QMainWindow *)handle)->centralWidget();
}
void QMainWindow_setCentralWidget(QMainWindowH handle, QWidgetH widget) {
((QMainWindow *)handle)->setCentralWidget((QWidget*)widget); }
변형된 헤더 폼에 의해 위의 내용은 파스칼 프로그램으로 가져오기(import)되고, 별도로 gt4.pas 인터페이스 파일도 필요할 것이다.
function QMainWindow_create(parent: QWidgetH = NIL;
flags: QtWindowFlags = 0): QMainWindowH; cdecl;
external QtIntf name 'QMainWindow_create';
procedure QMainWindow_destroy(handle: QMainWindowH); cdecl;
external QtIntf name 'QMainWindow_destroy';
function QMainWindow_centralWidget(handle: QMainWindowH): QWidgetH;
cdecl; external QtIntf name 'QmainWindow_centralWidget';
procedure QMainWindow_setCentralWidget(handle: QMainWindowH; widget:
QWidgetH); cdecl; external QtIntf name 'QmainWindow_setCentralWidget';
하지만 메소드를 호출할 때와 동일한 방식으로 클래스를 생성하고 소멸(release)하기 위해 파스칼 구조를 사용하거나 오브젝트 파스칼로부터 C++ 객체를 직접 사용하지는 못한다. C++ 구조의 평준화(flattening) 때문에 우리 소프트웨어는 위에 논한 API 바인딩의 절차적 변형(procedural variant)을 훨씬 더 많이 사용해야 할 것이다. 그러한 절차적 코드는 다음과 같은 모양을 한다:
program qttest;
uses qt4;
var MyMainWindow: QMainWindowH;
MyLabel : QLabelH;
w : WideString;
begin
QApplication_Create(@argc, argv);
w := 'FPC/Qt4 Hello world';
MyMainWindow := QMainWindow_create;
MyLabel := QLabel_create;
QLabel_setText(MyLabel, @w);
QMainWindow_setCentralWidget(MyMainWindow, MyLabel);
QWidget_resize(MyMainWindow, 400, 200);
QWidget_Show(MyMainWindow);
QApplication_Exec;
end.
다음과 같은 큰 프로그램을 쓸 수 없다는 건 분명하다.
API로의 절차적 접근이 성공하면 절차적 바인딩에 다른 레이어(layer)를 추가함으로써 객체 지향을 복구시킬 수 있다. 절대적으로 필요한 것은 아니지만 이 방법이 API의 사용을 수월하게 해주는 것은 사실이다. 하지만 소규모와 중간 규모의 프로그램을 위한 개발 효율성을 상당히 향상시키고, 코드 관리에도 도움을 줄 것이다.
큰 애플리케이션의 경우 자신의 요구에 더 잘 부합하도록, 절차적 API를 둘러싼 고유의 완전한 객체 지향 래퍼(wrapper)를 생성하는 것이 유용하다. 이는 라자루스를 위한 Qt 4 인터페이스에서 이루어졌다. 상세한 정보는 아래 파일을 참고한다.
라자루스가 설치된 디렉토리에서 아래 2개의 파일.
lazarus/lcl/interfaces/qt/qtobjects.pas
lazarus/lcl/interfaces/qtwidgets.pas
C++과 오브젝트 파스칼의 차이점들 중 문제를 야기할 수 있는 한 가지 차이점은 C++에서 다중 상속과 관련해 발생한다. 오브젝트 파스칼에선 다중 상속이란 개념이 없기 때문에 특별한 조치를 요하지 않는다. 각 클래스는 가상 메소드의 어드레스가 있는 테이블(table)을 가지는데, 이를 가상 메소드 테이블(Virtual Method Table)이라 부른다. 오브젝트 파스칼에선 각 클래스가 그러한 테이블을 하나씩만 가진다. C++에서는, 하나 이상의 클래스로부터 상속된 클래스는 하나 이상의 VMT를 가질 수 있다. 파스칼로부터 다수의 조상을 가진 클래스를 어드레싱 할 수 있도록 만들기 위해선 절차적 인터페이스의 평준화된(flattened) 일반 메소드 내부에 Self 포인터가 올바른 메소드 테이블을 가리켜야 한다. Qt에서 예를 들자면, QObject 와 QPaintDevice 에서 상속되는 QWidget 을 들 수 있겠다. QWidget 을 가리키는 포인터가 있는데 이를 QPaintDevice 를 예상하는 함수로 넘기고 싶다면, 타입캐스트(typecast)가 필요할 것이다. 하지만 이런 특수 타입의 타입캐스트는 C++ 언어 구성 요소로서 오브젝트 파스칼에서는 이용할 수가 없다. 따라서 추가적으로 바인딩(binding)에 구현되어야 하는 helper 함수가 필요하다. 아래는 QWidget_to_QPaintDevice 의 구현이다:
QPaintDeviceH QWidget_to_QPaintDevice(QWidgetH handle) {
return (QPaintDeviceH)(QPaintDevice *)(QWidget *)handle; }
타입캐스트(QPaintDevice*)는 (QWidget*) 내 포인터가 동일한 클래스의 다른 VMT를 가리키도록 변경한다.
파스칼에서 Q4로 접근을 가능하게 하려면 Q4를 설치해야 한다. 리눅스와 BSD의 경우, 운영체제 또는 배포판에 포함된 패킷 관리 툴을 이용하면 된다. 윈도우나 MacOS X의 경우, http://qt.nokia.com/downloads#lgpl 에서 다운로드할 수 있다.
선컴파일된 QT4 파스칼 라이브러리로 프로그램을 실행하기 위해선 (작은) 프레임워크만 다운로드하면 된다. 그 외에는 인터페이스 DLL만 있으면 된다. Win32와 Windows CE 버전은 라자루스 SVN 저장소에서 파일로 찾을 수 있다.
http://svn.freepascal.org/svn/lazarus/binaries/i386-win32/qt/libqt4intf.dll
아니면 필요 시 라자루스의 Win32 버전과 함께 설치할 수도 있다. DLL에 관한 추가 정보와 소스 코드는 FPC QT 바인딩의 본래 주소에서 이용 가능하다.
http://users.telenet.be/Jan.Van.hijfte/qtforfpc/fpcqt4.html
사용자가 직접 해당 C++ 바인딩 라이브러리를 컴파일하려면 완전한 프레임워크와 Qt 소스 코드를 다운로드 해야 할 것이다.
MacOS X API
일반적으로 애플 하드웨어에서만 실행되는 MacOS X는 진정한 유닉스 운영체제라 할 수 있다. 따라서 앞서 리눅스에 대해 논한 대부분이 적용된다. 다른 유닉스 시스템과의 유사성 때문에 유닉스 콘솔 애플리케이션을 변경하지 않고도 MacOS X에서 실행될 것이다.
하지만 GUI 애플리케이션은 또 다른 문제이다. MacOS X는 이와 관련해 고유의 규칙을 가진다. MacOS X 설치 DVD에는 X11가 포함되어 있으므로 리눅스 애플리케이션도 재컴파일 후 실행 가능하지만 종종 네이티브 MacOS X 소프트웨어 및 시스템과 상호작용 시 문제가 발생하곤 한다. 따라서 이러한 접근법은 소규모 혹은 인하우스(in-house) 애플리케이션에만 적용된다. 상용 소프트웨어의 경우 네이티브 플랫폼으로 전체 이식(full port)이 필요할 것이다. MacOS X에 관한 첫 번째 특성은 우리가 직접 개발해야 한다는 점이다. 윈도우에서처럼 인스톨러를 제공하거나 리눅스처럼 계층구조식 패키지가 아니라 "애플리케이션 번들"이라 불리는 형태로 이용할 수 있어야 하는데, 이것은 사실상 애플리케이션이 필요로 하는 모든 데이터를 포함한 디렉터리이다. 사용자는 시스템 내 원하는 위치에 설치할 수 있다. MacOS X에서는 개발된 소프트웨어가 이미 번들을 가지는지 여부를 라자루스 단독으로 결정할 것이다. 번들이 없다면 라자루스가 번들을 하나 생성할 것이다.
사용자는 보통 MacOS X에서 발견되는 또 다른 개체(entity), 즉 프레임워크를 항상 유념해야 한다. 프레임워크는 사용자가 자신의 파스칼 프로그램 소스로 연결할 수 있는 특별 선발된 패키지(packed package)로서, 이를 위해 사용자는 프리 파스칼 명령 행 명령어:
-k-frame –k<Frameworkname>
위의것을 이용하거나, 컴파일러 지시어 {$R Linkframework <Frameworkname>}를 이용할 수 있다.
여러 프레임워크가 있는데, 그 중 일부는 서브 프레임워크를 가진다.
가장 중요한 프레임워크는 Carbon, Cocoa, OpenGL 로, Carbon 프레임워크는 Mac 운영체제의 기본 C API를 포함하며, 프리 파스칼 버전 2.2.2부터 MacOSAll 유닛에 위치한다 (오래된 컴파일 버전에서는 FPCMacOSAll 유닛에 위치).
Cocoa는 좀 더 새로 나온 Objective C API로, Carbon을 이용해 컴파일된다. Cocoa 프레임워크는 3가지 작은 프레임워크로 구성된다: Foundation, Appkit, CoreData. 프레임워크 이름으로 된 유닛을 프로그램 내 uses 절에 추가하여 접근할 수 있다. 유닛은 타입 정의, Objective C 클래스, 일부 내보내진 루틴을 포함한다.
프레임워크 | 설명 |
Foundation | 기본 비시각적 클래스. 예: NSObject, NSString, NSAutoreleasePool 등 |
Appkit | GUI 소프트웨어 개발을 위한 클래스. 예: NSApplication, NSWindow, NSView, NSButton 등 |
CoreData | 그래프 관리, 실행취소(undo)와 복구(redo), 객체 존속(object persistence)을 위한 하부구조 제공 (디스크상의 파일과 같이 영구 저장소로부터 객체를 가져오거나 객체를 저장). |
표 5.12: Cocoa 서브 프레임워크 |
프리 파스칼로 Cocoa 프로그램을 개발하기 위해서는 PasCocoa 프로젝트의 현재 버전과 서브 프레임워크를 다운로드해야 한다:
http://wiki.lazarus.freepascal.org/PasCocoa
각 서브 프레임워크는 동일한 이름으로 된 구분된 유닛을 가지는데, 내용은 표 5.13를 참고한다.
정의 | 설명 |
CoreFoundation | Carbon과 Cocoa 모두에 유용한 기본적 데이터 타입과 필수 서비스. |
CarbonSound | 오래된 음향 API, QuickTime Audio에도 기본. Mac OS X 10.5 부터 사라졌으며, 애플사는 CoreAudio의 사용을 권장, Mac OS X 10.5 이전 버전을 지원해야 할 경우 QuikTime Audio를 권장. |
CommonPanels | 색상 선택과 폰트 대화창을 지원. 가장 중요한 함수는 PickColor, FPShowHideFontPanel, SetFontInfoForSelection이다. 64 비트에선 지원되지 않음. |
Help | 애플 help 시스템에 API를 제공. 크로스 플랫폼 애플리케이션의 경우 크로스 플랫폼 help 시스템을 사용하는 편이 나음. |
HIToolbox | Mac OS X 10.2에 도입된 새 Carbon 사용자 인터페이스 API. 주요 타입은 HIObject, HIToolbox, HIView이다. 64 비트에선 지원되지 않음. |
HTMLRendering | HTML 렌더링 라이브러리. Mac OS X 10.4부터 사라졌으며, 애플사는 대신 Web Kit Objective-C API를 권장. |
ImageCapture | 카메라나 스캐너와 같은 이미지 캡처 장치와 통신하는 API. 가장 중요한 함수는 ICAGetDeviceList, ICACopyObjectPropertiesDictionary, ICARegisterForEventNotification, ICAOpenSession, ICAObjectDownLoadFile, ICACloseSession 이다. |
Ink | 태블릿이나 스타일러스(stylus)와의 특별 상호작용을 위한 API. 대부분은 사용 시 표준 마우스 루틴으로 충분하다. |
NavigationServices | 표준 대화창을 위한 API. 예: 파일 열기, 파일 저장하기, 디렉터리 열기, 음량 설정하기 등. |
OpenScripting | 애플 특정적 상호 통신 매커니즘(IPC). 크로스 컴파일 애플리케이션의 경우 소켓, 파이프, 프리 파스칼의 simpleipc 유닛과 같은 크로스플랫폼 IPC를 사용하는 편이 나음. |
Carbon 애플리케이션이 인쇄를 위한 사용자 인터페이스를 표시하는 데에 사용. 페이지 설정 대화창, 인쇄 대화창, 인쇄 상태 대화창을 제공한다. 실제 인쇄를 진행하려면 CorePrinting과 함께 사용해야 한다. 64 비트에서는 이용할 수 없다. | |
SecurityHI | 보안관련 대화창을 제공한다. 64 비트에서는 이용할 수 없다. |
SpeechRecognition | 음성 인식 관리자(Speech Recognition Manager)와의 상호작용. |
표 5.13: Carbon 서브 프레임워크 |
FPC 2.5.1에서 컴파일러는 Objective C 클래스로 직접 접근을 소개하였다. 이는 라자루스를 위한 실제 Cocoa 위젯 셋을 생성할 수 있는 방도를 마련해주었다. 하지만 안정적 FPC 2.4.0 버전의 경우 아직 가능하지 않으므로, 여기서는 PasCocoa 프로젝트만 다루도록 하겠다. 모든 서브 프레임워크는 사용자의 프로젝트 내 uses 절과 작업 유닛에 MacOSAll 유닛을 추가함으로써 접근한다.
MacOS X 10.6 버전까지는 HFS를 파일 시스템으로 사용하는데, 이 파일은 리눅스나 BSD 파일 시스템과 비교할 때 약간의 차이가 있다. 무엇보다 윈도우와 마찬가지로 대·소문자를 구별하지 않는다. 하지만 이것을 구성할 수는 있으며, 일부 OS X 서버에선 이미 구성해왔다. 두 번째로 OS X는 파일 명에 일반화된 UTF-8를 사용한다. A 움라우트(Ä)는 유니코드와 다른 방법으로 인코딩 할 수 있다 (그리고 리눅스의 ext2+ 파일 시스템이 둘을 구별할 것이다). HFS는 모든 a-움라우트를 하나의 인코딩으로 일반화시킨다. 따라서 파일 이름의 비교를 위해선 항시 FileUtil 함수인 CompareFilenames 를 사용하는 것이 최선의 방법이다.
디렉터리 | 사용자 접근성 | 설명 |
/ | 읽기 | 루트 디렉터리. |
/Users | 읽기 | 사용자마다 사용자와 동일한 이름으로 된 폴더를 보유한다. 사용자의 문서 폴더가 될 것이다. |
/Applications | 읽기 | 네이티브 애플리케이션 번들을 이 디렉터리에 드래그하여 설치해야 한다. |
/Library | 읽기 | 제3자 개발자들이 제공한 데이터 파일과 프레임워크를 보유한다. |
/System/Library | 읽기 | 애플 데이터 파일과 프레임워크를 보유한다. |
/Developer | 읽기 | 개발자 툴이 설치될 때 생성되며, 애플리케이션, 툴, 문서, 그 외 MacOS X 소프트웨어 개발에 필요한 리소스를 포함한다.
애플 라이브러리에 대한 오리지널 C 및 Objective-C 헤더는 /Developer/SDKs에 위치한다. |
/tmp | 읽기/쓰기 | 임시 파일을 위한 전역적 위치. |
/usr | 읽기 | /usr/local 디렉터리를 제외하고 애플 유닉스 소프트웨어를 위해 예약된다. |
/usr/lib | 읽기 | 유닉스 동적 연결 라이브러리의 위치. |
/usr/local/bin | 읽기 | 일반 사용자 실행 가능한 유닉스 파일의 일반 접근을 위한 위치. |
/usr/local/share | 읽기 | 유닉스 애플리케이션은 이 곳에 읽기전용 리소스 파일을 저장하기 위한 하위디렉터리를 생성해야 한다. |
표 5.14: MacOS X에서 가장 중요한 디렉터리 |
MacOS X 디렉터리는 이중적 특성을 지닌다. 유닉스 파일 시스템이지만 일반 사용자에겐 보이지 않는다. 예를 들어, /usr과 같은 표준 디렉터리가 Finder에 (Mac 파일 관리자) 표시되지 않는다. 이러한 디렉터리는 터미널(terminal)에서 접근할 수 있다. 유닉스 애플리케이션은 이곳에 설치된다.
Mac 표준 폴더의 이름은 명령 행에 로컬화되지 않은 (unlocalized) 방식으로 표시되지만(영문)-따라서 그 이름은 고정됨-Finder에서는 시스템 언어로 표시된다. 표 5.14는 MacOS X에 가장 중요한 디렉터리를 소개한다.
오래된 MacOS X 버전
MacOS X에서 추가 옵션 없이 컴파일된 애플리케이션은 현재 메인 버전을 포함한 그 이상의 메인 버전에서만 작동이 보장된다. 따라서 MacOS X 10.5.6 에서 프로그램을 컴파일 한다고 가정하면, MacOS X 10.5.x 또는 그 이상 버전에서만 작동할 것이라 확신할 수 있겠다. 이런 특이한 행위의 원인은 메인 운영체제 버전에 대해 버전화되어 현재 버전에 직접 연결되는 SDK와 관련이 있다. 다행히 애플 개발 툴은 보통 운영체제의 적어도 바로 이전 버전까지 작동하는 SDK와 함께 전달된다. 오래된 SDK로 연결하려면 Project ⇒ Project Options 대화창에 가서 Other 을 클릭한 후 (Compiler Options 아래에 노드 중 하나) Custom options 메모에 링커에 대한 다음 지시어를 입력한다:
k-macosx version min klO.4
XR/Developer/SDKs/MacOSX10.4u.sdk/
여기에 최소 버전을 하나 더 입력하거나 다른 SDK의 경로를 입력하는 방법도 있다.
Cocoa 프레임워크
Cocoa API와 오브젝트 파스칼 애플리케이션의 결합은 2007년 이후부터 가능해졌다. Objective C와 오브젝트 파스칼 간 브리지는 PasCocoa라고 부른다. Objective C는 클래스 생성, 메소드와 내보내기 등록에 필요한 모든 함수를 포함하는 C 라이브러리를 갖고 있기 때문에 이 브리지를 전환하는 것이 가능하며, Objective C 코드를 위한 추가 라이브러리의 오버헤드(overhead) 또는 외부 라이브러리를 필요로 하지 않는다. 이것이 가능한 이유는 두 프로그램이ㅡ하나는 파스칼로 작성되었고 다른 하나는 Objective C로 작성ㅡ동일한 라이브러리 내 함수를 호출하기 때문이다.
OS X 용어 중 프레임워크란 특별한 방법으로 묶인 라이브러리를 의미한다. 프리 파스칼 컴파일러는 추가 명령 행 파라미터로 가리켜야 한다. 사용할 스위치는 –k-framework –k<framework_name>이다. 프레임워크는 3개의 작은 프레임워크로 구성되는데, 그 이름은 Foundation, Appkit, CoreData 이다 (표 5.12 참고). 이 3개의 서브 프레임워크는 프레임워크와 이름이 동일한 유닛을 프로그램의 uses 절에 추가함으로써 접근한다.
유닛은 타입 정의, Objective C 클래스, 내보내진 루틴을 포함한다. Cocoa는 프리 파스칼 2.2.2 버전부터 MacOS All 유닛에 위치한 Carbon C API와 주로 함께 사용된다 (이전 버전들은 FPCMacOSAll 유닛에 위치). 애플리케이션과 명령 행 지시어 -k-framework -kcocoa -k-lobjc를 연결 시 (또는 Project Options 대화창 Linking 페이지의 Options: (-k) 편집 필드에 동일한 내용을 입력) Cocoa로 직접 연결할 수 있다. Cocoa는 모든 MacOS X 버전에서 이용할 수 있기 때문에 라이브러리의 이용 가능성은 전혀 문제가 되지 않는다.
Cocoa에서 모든 클래스, 데이터 타입, 프로시저 이름은 "NS"로 시작되는데, NextSTEP에서 파생됨을 의미한다. 프레임워크에서 대부분 호출 또한 서브 프레임워크 Foundation 내의 NSObject 기반 클래스에서 파생되는데, 오브젝트 파스칼 클래스들이 주로 TObject 에서 파생되는 것과 마찬가지이다. Objective C는 프로퍼티가 별로 없다는 점에서 파스칼과 다르다. 프로퍼티 대신 getter/setter 쌍을 제공한다.
파스칼 소스 코드 예제에서 예상하는 NSString 대신 CFStringRef 의 정의를 찾은 경우, Carbon 타입의 별명(alias) 정의가 편의상 PasCocoa 에서 선언되었기 때문일 것이다.
가장 중요한 Cocoa 클래스
여기서는 가장 중요한 기반 Cocoa 클래스와 가장 중요한 메소드를 간략하게 소개하겠다. 검색엔진을 이용해 애플 문서에서 이와 관련된 세부내용을 찾을 수 있을 것이다. 사용자는 구글 검색 문자열에 site:developer.apple.com NSApplication 을 포함시켜 애플 개발자 사이트로 검색을 제한할 수 있다.
Cocoa 항목 | 설명 |
NSObject | Cocoa 계층구조에서 기본 객체. 파스칼 개발자들의 작업을 용이하게 하는 많은 메소드들이 이에 추가되었다. |
Constructor Create; | 새 파스칼 클래스와 그에 연결된 새 Objective C 클래스를 생성한다. |
Constructor CreateWithHandle (aHandle: objc.id); |
새 Objective C 객체를 생성하지 않고, 대신 파라미터에 표시된 것을 사용한다. |
Property Handle: objc.id; | Objective C 객체. |
Procedure AddMethods; | ObjectiveC에 의한 호출을 위한 메소드를 등록하기 위해 서브클래싱(subclassing) 도중에 덮어쓰인다. |
Procedure AddMethod (aName, aParameters: String; aPointer: Pointer); |
메소드를 등록한다. |
Function CreateClassDefinition(const name, superclassName: String): Boolean; |
Objective C 런타임에 클래스를 등록한다. |
Class NSAutoreleasePool | PasCocoa가 사용하는 각 쓰레드(thread)는 다른 Objective C 호출을 어드레싱하기 전에 NSAutoreleasePool을 생성해야 한다.
객체는 쓰레드 끝에서 파괴되어야 한다. |
Class NSResponder | Cocoa 이벤트 시스템을 구현한다. |
Class NSApplication | 애플리케이션을 나타낸다. 공통 애플리케이션 객체를 리턴한다. |
Constructor sharedApplication; | 이를 사용해 전역 변수 NSApp를 초기화해야 한다. |
Procedure run; | 애플리케이션이 닫힐 때만 끝이 나는 이벤트 루프로 점프한다. |
Procedure terminate; | 프로그램을 끝낸다. |
Class NSView | 자식 컨트롤 요소를 추가하기 위한 기반 클래스. |
Procedure addSubview (_aView: objc.id {NSView}); | 이것에 자식 요소를 추가한다. |
Class NSControl | 3개의 기본 GUI 함수를 제공하는 추상 클래스: 화면에 그리기; 사용자 입력에 반응하기; 액션 알림(action notice) 전송하기. |
Constructor initWithFrame(_frameRect: NSRect); | 처음에 size_frameRect를 이용해 컨트롤 요소의 새 인스턴스를 생성한다. |
Procedure setStringValue(_aString: CFStringRef); | 컨트롤 요소가 포함하는 데이터에 대한 getter와 setter 메소드. |
Procedure setIntValue(anlnt: Integer); Function stringValue: CFStringRef; Function intValue: Integer; |
예를 들어 NSTextField에서 이것은 화면에 표시되는 문자열이다. |
Class NSWindow | 화면창. 해당 창 클래스는 NSView 에서 파생되지 않았으므로 어떤 자식 요소도 직접 받을 수 없다. 연관된 NSView 는 contentView를 통해 설정될 것이다. 이것은 컨트롤 유닛을 추가할 수 있는 곳이다. |
Procedure orderFrontRegardless; | 창을 Z-order에 최상으로 설정한다. |
Function contentView: objc.id {NSView}; | 창과 관련된 NSView 를 리턴한다. |
Constructor initWithContentRect styleMask backing defer(_contentRect: NSRect; windowStyle:LongWord;_bufferingType: NSBackingStoreType; _flag: LongBool); |
새 NSWindow 객체를 생성한다._contentRect는 화면상의 위치로, windowStyle은 NSBorderlessWindowMask 이거나 다른 창 스타일 상수 ORed와의 조합이 될 수 있다. _bufferingType은 NSBackingStoreBuffered 이어야 하며, deferCreation 은 LongBool(NO)이어야 한다. |
Function title: CFStringRef; Procedure setTitle( aString: CFStringRef); |
창 제목에 대한 getter/setter 한쌍. |
Class NSButton | 버튼 컨트롤 요소. |
Class NSTextField | 창에 텍스트를 표시하기 위한 컨트롤 요소. |
표 5.15: 선택된 PasCocoa 클래스와 메소드 |
Cocoa는 Objective C와 파스칼 코드를 결합하기 위해 많은 기법을 필요로 한다. 여기에는, Cocoa 클래스 인스턴스 생성하기; 클래스 메소드 호출하기; 내보내진 함수 호출하기; Cocoa 클래스 서브클래싱하기; 파생 클래스의 메소드 덮어쓰기가 포함된다.
Cocoa 클래스 인스턴스화하기
클래스의 인스턴스가 파생되면 클래스가 다시 파괴될 때까지 그 메소드를 호출할 수 있다. Objective C 로 클래스를 생성하기 위해선 메모리를 먼저 예약한 후에야 클래스를 초기화할 수 있다. 파스칼에서는 이 두 단계를 생성자(constructor)에서 함께 처리할 수 있다. 따라서 연결하는(bridging) 파스칼이 이를 따르도록 만들기 위해 모든 PasCocoa 생성자가 파스칼 생성자로 변경된다.
그 외에도 사용자는 파스칼 객체로의 참조를 Objective C 객체와 교환할 수 없다. 하지만 그 중 하나를 이용할 수 있다면 나머지는 항상 얻을 수 있다. Objective C 객체에 해당하는 각 파스칼 객체는 해당 객체로의 링크를 나타내는 Handle 프로퍼티를 가진다. 반대로 NSObject.CreateWithHandle 은 우리가 해당 파스칼 객체에 대한 Objective C 객체를 쿼리할 수 있도록 해준다.
아래 코드는 NSAutoreleasePool 를 생성하기 위해 일반적인 Objective C 소스가 어떻게 파스칼로 변환되어야 하는지를 보여준다. 먼저, Objective C 코드는 아래와 같다:
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[]) {
// Create the pool object
NSAutoreleasePool* pool=[[NSAutoreleasePool alloc] init];
// Call release method from object pool
[pool release];
}
그리고 아래는 그에 상응하는 오브젝트 파스칼 버전이다:
program MyPasCocoaApp;
uses Foundation;
var pool: NSAutoreleasePool;
begin
pool := NSAutoreleasePool.Create;
pool.Free;
end.
클래스 메소드와 프로시저 내보내기
클래스 메소드는 일반 오브젝트 파스칼 코드에서와 같이 호출된다. 아래 예제는 NSApplication 으로부터 sharedApplication 클래스 메소드를 호출한다. 이러한 루틴은 전역적 애플리케이션 변수를 리턴할 것이다. NSApp 변수는 AppKit 유닛에 이미 정의되어 있기 때문에 다시 정의해선 안됨을 명심하라. 하지만 프로그램 시작 시 초기화되지는 않는다.
uses AppKit;
begin
NSApp := NSApplication.sharedApplication;
// ... further code here
Objective C는 C의 상위집합(superset)이므로, Objective C의 모든 절차적 코드는 C 소스 코드이기도 하다. 프로시저는 C에서와 동일한 방식으로 호출되며, 적절한 선언이 있는 C 루틴과 동일한 방식으로 파스칼에서 사용할 수 있다.
Cocoa 클래스가 서브클래싱에 사용되는 방식과 메소드를 덮어쓰는 방식을 이해하기 위해 예제 애플리케이션을 분석하고자 한다. 간단한 서브클래싱은 전혀 복잡하지 않다. 그저 파스칼 클래스를 선언하고 우리가 부모 객체가 되길 원하는 Cocoa 클래스를 명시하기만 하면 된다. Objective C로 작성된 코드는 여기서 선언된 메소드에 접근할 수 없다. Objective C가 자신의 메소드를 인식할 수 있으려면 특정 구문으로 선언된 후 등록되어야 한다. 성공적으로 등록이 끝난 후에만 자신의 메소드의 레이아웃이 실제 Objective C 메소드에 들어맞을 것이다.
TMyController = class(NSObject)
public
// Additional binding functions
constructor Create; Override;
procedure AddMethods; Override;
// Objective C methods
class procedure doShowStatusitem(_self: objc.id; cmd: SEL;
sender: objc.id); CDecl; Static;
class procedure doHideStatusitem(_self: objc.id; cmd: SEL;
sender: objc.id); CDecl; Static;
class Procedure doClose(_self: objc.id; cmd: SEL; sender: objc.id);
CDecl; Static;
class function applicationShouldTerminateAfterLastWindowClosed(
_self: objc.id; cmd: SEL; theApplication: objc.id): Cbool;
CDecl; Static;
end;
이번 예제에서 doShowstatusitem, doHideStatusitem, doClose 선언은 파스칼 메소드로서, Objective C로부터의 접근에 준비된 상태이다. 예를 들어, 버튼에 이벤트 콜백(event callback)을 위해 이 과정이 필요하다. applicationShouldTerminateAfterLasWindowClosed 는 NSApplication 으로부터 오는 신호를 위해 핸들러를 구현한다. 메소드는 클래스 메소드여야 한다는 점을 명심하길 바란다. 다시 말하면, 파스칼로부터 Self (자기) 참조에 접근하기란 불가능함을 의미한다. 하지만 또 다른 _self 를 이용할 수 있다. 이 식별자는 Objective C 클래스와 파스칼 클래스를 제공한다. 가령 우리가 파스칼 클래스로 접근하기 위해 전역 변수를 이용할 수 있는데, NSObject.CreateWithHandle 를 호출함으로써 objc.id에 리턴된 파스칼 클래스를 얻으면 된다.
소스 코드의 다음 섹션은 TMyController.AddMethods 의 올바른 구현을 보여준다. 해당 프로시저는 메소드를 등록할 것이다. 또한 TMyController.Create 생성자를 표시할 것이다. 그러면 지연된(deferred) 생성자가 호출되기 전에 현재 클래스를 새 Objective C 클래스로서 등록하고, 이는 AddMethods 를 호출할 것이다.
마지막으로 프리 파스칼로 작성된 완전한 Cocoa 애플리케이션에 대한 소스를 제공할 것이다. 이 예제 프로그램은 결국 하나의 창을 생성한 후 "Hello Cocoa!"라는 텍스트가 있는 NSTextField 타입의 컨트롤 요소를 표시할 것이다.
프로그램에서 첫 호출은 NSAutoreleasePool.Create 이다. 이는 프로그램의 모든 쓰레드에 호출되어야 하고, 다른 모든 Cocoa 루틴보다 먼저 호출되어야 한다. 그러면 Cocoa 객체에 대해 일반 기억장치 관리자(storage manager)를 생성한다. 이후 NSApplication.shared.Application 에 의해 전역 변수 NSApp 가 초기화되고, 이는 프로그램 초기화를 완료한다. 다음 단계는 NSWindow.initWithcontentRect 를 이용해 프로그램 창을 생성하고 NSWindow.setTitle 을 이용해 제목을 설정한다. 이후에 비슷한 방식으로 NSTextField.initWithFrame 을 이용해 텍스트 필드가 초기화된다.
Cocoa 에서 창은 자식 컨트롤 요소를 허용하는 NSView 의 자식이기 때문에 창의 NSView는 NSWindow.contentView 로 설정된다. 해당 메소드는 Objective C 클래스를 리턴하며, 리턴된 클래스는 NSView.CreateWithHandle 을 이용해 호출용으로 변환된다 (MainWindow.contentView).
창에 대한 NSView 를 갖게 되면 NSView.addSubview(TextField) 를 이용해 텍스트 필드를 입력할 수 있다. 다음 단계는 NSWindow.orderFrontRegardless 를 이용해 메인 창을 가리킨 후 마지막으로 NSApp.run을 호출하여 애플리케이션이 끝날 때까지 프로그램을 이벤트 루프로 가져간다.
가장 쉬운 프로그램 컴파일 방법은 lpi 프로젝트 파일을 로딩하여 라자루스 내부에서 프로그램을 생성하는 것이다. PasCocoa는 라자루스 패키지에 묶여 올바른 경로를 설정한다. 그림 5.14는 해당 프로그램이 화면에 어떻게 나타날 것인지를 보여준다.
{'v@:@' adds methods to the class.
Notes on the parameter string:
The first parameter is the result (v = void), followed by self and _cmd (@ id = and: = SEL)
and at the end of "sender"(@ = id)}
procedure TMyController.AddMethods;
begin
AddMethod(Str doShowStatusItem, 'v@:@', Pointer(doShowStatusitem));
AddMethod(Str doHideStatusitem, 'v@:@', Pointer(doHideStatusitem));
AddMethod(Str doClose, 'v@:@', Pointer(doClose));
AddMethod(Str applicationShouldTerminateAfterLastWindowClosed,
'v@:@',Pointer(applicationShouldTerminateAfterLastWindowClosed));
end;
constructor TMyController.Create;
var fileName: CFStringRef;
begin
// The class registered before the NSObject constructor is registered
// in the Objective-C run-time
if not CreateClassDefinition(ClassNameO, Str_NSObject)
then WriteLn('Failed to create objc class');
Inherited Create;
end;
AddMethods 메소드는 각 메소드가 등록되도록 NSObject.AddMethod 를 호출하고, 메소드의 파라미터와 함께 문자열을 전달해야 한다. 이 문자열은 4개의 부분으로 구성된다. 첫 번째 부분은 함수의 리턴 값 타입에 대한 코드이고, 두 번째는 클래스 식별자로서 _self, 세 번째는 _cmd, 즉 클래스 셀렉터 (내부 Objective C 식별자), 마지막 부분은 파라미터 free를 나타낸다. Objective C 클래스를 표시하기 위해 _self에 대한 하위 문자열(substring)은 항상 @ 가 되며, _cmd에는 셀렉터의 부호로 항상 콜론이 사용되어야 한다. 표 5.16은 가장 공통적으로 사용되는 값을 소개하며, 완전한 리스트는 아래에서 이용할 수 있다:
http://wiki.lazarus.freepascal.org/PasCocoa#Type_Encodings
하위 문자열 | Pascaltype | 유닛 | 설명 |
v | - | - | 파라미터 존재 유무를 가리킨다. |
b | cbool | ctypes | C와 호환 가능한 부울 형(Boolean type) |
@ | id | objc | Objective C 클래스 인스턴스 |
: | SEL | objc | Objective C 셀렉터 |
i | cint | ctypes | C와 호환 가능한 정수 |
I | cuint | ctypes | C와 호환 가능한 unsigned integer |
? | Pointer | - | 일반 포인터 |
표 5.16: Cocoa 데이터 타입을 프리 파스칼에 등록하기 |
GetAppConfigDir(True) | C:\Documents and Settings\All Users\Application Data\configtest\ |
GetAppConfigDir(False) | C:\Documents and Settings\Username\Local Settings\Application Data\configtest\ |
GetAppConfigFile(True) | C:\Documents and Settings\All Users\Application Data\configtest\configtest.cfg |
GetAppConfigFile(False) | C:\Documents and Settings\Username\Local Settings\Application Data\configtest\conftest.cfg |
아래는 운영체제(Windows)의 독일어 버전에 관한 정보이다.
GetAppConfigDir(True) | C:\Dokumente und Einstellungen\All Users\Anwendungsdaten\configtest\ |
GetAppConfigDir(False) | C:\Dokumente und Einstellungen\Username\Lokale Einstellungen\Anwendungsdaten\configtest\ |
GetAppConfigFile(True) | C:\Dokumente und Einstellungen\All Users\Anwendungsdaten\sysfolder\configtest.cfg |
GetAppConfigFile(False) | C:\Dokumente und Einstellungen\Username\Lokale Einstellungen \Anwendungsdaten\sysfolder\conftest.cfg |
함수가 True로 호출되면 윈도우에서 전역적 구성이 저장되는 디렉터리를 보고할 것이다. 로컬 구성은 현재 사용자 계정의 디렉터리에 저장될 것이다.
FPC 2.2.4 버전이 실행되는 Vista의 경우는 또 다르다. 이런 경우 시스템 로케일(system locale)에 따른 차이가 없다. Vista를 시작으로 마이크로소프트사는 애플사의 MacOS X처럼 사용자로 하여금 로컬 파일명을 사용하고 있다고 생각하게끔 만들었다.
GetAppConfigDir(True) | C:\ProgramData\configtest\ |
GetAppConfigDir(False) | C:\Users\martin\AppData\Local\configtest |
GetAppConfigFile(True) | C:\ProgramData\configtset\configtset.cfg |
GetAppConfigFile(False) | C:\Users\martin\AppData\Local\configtest\configtest.cfg |
program simplewindow;
{$ifdef fpc}{$mode delphi}{$endif}
uses
{$ifdef ver2_2 0}
FPCMacOSAll,
{$else}
MacOSAll,
{$endif}
objc, ctypes, AppKit, Foundation;
const
Str_Window_Title = 'Pascal Cocoa Example';
Str_Window_Message = 'NSTextField Control';
var
//classes
pool : NSAutoreleasePool;
MainWindow: NSWindow;
MainWindowView: NSView;
TextField : NSTextField;
CFTitle, CFMessage: CFStringRef; (* strings *)
MainWindowRect, TextFieldRect: NSRect; (* sizes *)
begin
// Creates an autorelease pool for that thread (Each thread must have one).
pool := NSAutoreleasePool.Create;
// Creates the application object NSApp
NSApp := NSApplication.sharedApplication;
// creates a simple window
MainWindowRect.origin.x := 300.0;
MainWindowRect.origin.y := 300.0;
MainWindowRect.size.width := 300.0;
MainWindowRect.size.height := 100.0;
MainWindow := NSWindow.initWithContentRect_styleMask_backing_defer(
MainWindowRect, NSTitledWindowMask OR NSClosableWindowMask OR
NSMiniaturizableWindowMask OR NSResizableWindowMask,
NSBackingStoreBuffered, LongBool(NO));
// initialize the window title
CFTitle := CFStringCreateWithPascalString(NIL, Str_Window_Title,
kCFStringEncodingUTF8);
MainWindow.setTitle(CFTitle);
// Adds a string to the NSTextField
CFMessage := CFStringCreateWithPascalString(NIL, Str_Window_Message,
kCFStringEncodingUTF8);
TextFieldRect.origin.x := 50.0;
TextFieldRect.origin.y := 50.0;
TextFieldRect.size.width := 200.0;
TextFieldRect.size.height := 25.0;
TextField := NSTextField.initWithFrame(TextFieldRect);
TextField.setBordered(LongBool(NO));
TextField.setDrawsBackground(LongBool(NO));
TextField.setStringValue(CFMessage);
MainWindowView := NSView.CreateWithHandle(MainWindow.contentView);
MainWindowView.addSubview(TextField.Handle);
MainWindow.orderFrontRegardless; // window to bring forward
NSApp.run; // places the proram in the event loop
pool.Free; // autorelease thread pool is freed
end.
파스칼 프로그래머들에게 가장 큰 단점은 아무래도 이용 가능한 프로그래밍 예제들 거의 대부분이 Objective C로 작성되었기 때문에 사용하기 전에 파스칼로 변환해야 한다는 점일 것이다. 그 외에도 대부분 파스칼 프로그래머들은 사용되는 메소드를 등록시켜야 하는 점을 처음엔 어색해한다. 2010년 봄부터 프리 파스칼 2.5.1 버전은 Cocoa에 대한 네이티브 지원을 제공한다. 이는 Objective Pascal 이라 불리는데, Cocoa를 이용한 작업을 훨씬 수월하게 해준다. 하지만 Pascocoa와 마찬가지로 단점도 있기 마련인데, 바로 LCL 컴포넌트 프레임워크를 사용할 수 없다는 점이다. 따라서 이 프로그램은 여전히 실험 단계라고 보면 되겠다.
32 비트와 64 비트
플랫폼 독립적 프로그래밍은 운영체제나 아키텍처가 다르다고 해서 중단되지 않는다. 애플리케이션이 32 비트와 64 비트 프로세서에서 작동하길 원하거나 32 비트 시스템에서 64 비트 시스템으로 애플리케이션을 복사하길 원한다면 유념해야 할 몇 가지 중요 사항들이 있다.
개발자들이 자주 하는 오해 중 하나는 포인터의 크기가 항상 4 바이트로 같을 것이라는 생각이다. 이는 대부분 32 비트 시스템에선 사실일지 모르지만 64 비트 프로세서에서 포인터의 크기는 8 바이트이다. 즉, 포인터를 정수로 타입캐스트하는 것이 불가능함을 의미한다. 개발자들이 좀 더 수월하게 이용할 수 있도록 프리 파스칼은 포인터와 동일한 크기가 보장되는 두 가지 정수형을 제공하는데, PtrInt 와 PtrUInt 가 그것이다. PtrInt 는 32 비트 플랫폼에선 32 비트의 부호 있는 정수형(signed integer)이고, 64 비트 플랫폼에선 64 비트의 부호 있는 정수형이다. PtrUInt 는 이와 비슷한 부호 없는 정수형(unsigned integer)이다.
아래 예제 코드는 32 비트 플랫폼에서만 작동할 것이다:
function ObjectToInteger(AObject: TObject): Cardinal;
begin
Result := Cardinal(AObject);
end;
아래는 64 비트 플랫폼에서도 작동하도록 수정된 버전이다:
function ObjectToInteger(AObject: TObject): PtrUInt;
begin
Result := PtrUInt(AObject);
end;
정수형은 크기가 4 바이트로, 64 비트 운영체제에서도 동일하다.
따라서 컴파일러는 내부에 사용되지 않은 공간이 있는 레코드 및 클래스 변수를 생성할 수 있는데, 이러한 정렬(alignment)은 프로세서가 데이터로 더 빠르게 접근하도록 허용하기 때문이다. 이를 피하기 위해 packed 속성을 이용할 수 있다.
type
TFixedSizeRecord = packed record // no gaps between variables
i: Integer;
j: Integer;
end;
해당 레코드의 크기는 항상 8 바이트이다.
64 비트와 호환되는 코드를 작성하려면 아래 ifdef 구조를 사용할 수도 있다.
{$ifdef CPU64}
// 64-Bit-Code
{$else}
// 32-Bit-Code
{$endif}