LazarusCompleteGuide:8.4

From 흡혈양파의 번역工房
Revision as of 13:32, 10 March 2013 by Onionmixer (talk | contribs) (LCG 8.4 페이지 추가)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

장치와의 통신

ISA와 PCI 보드와 병렬 포트는 병렬 프로토콜을 통해 컴퓨터와 통신한다. 직렬포트와 USB 장치는 직렬 프로토콜을 사용한다. 병렬 프로토콜은 데이터를 블록으로 보내므로 8, 16, 32 또는 64 비트 폭이 가능하며, 하드웨어에도 동일한 수의 병렬 와이어(wire)를 필요로 한다. 반면 직렬 프로토콜은 하나 또는 두 개의 데이터 라인을 사용한다 (양측이 데이터를 동시에 전송할 수 있는 full duplex 연결에서 두 개의 데이터 라인). 각 방향에 하나의 데이터 라인이 사용되는데 여기서는 한 번에 하나의 상태만 (1 비트) 전송된다. 청크(chuk) 또는 블록 내 데이터 전송에 직렬 인터페이스를 사용하는 라이브러리와 소프트웨어는 추후 데이터 처리가 수월해진다.


병렬 하드웨어로 데이터를 전송하고 그로부터 수신하는 방법에는 3가지가 있다: 하드웨어 포트를 통해, 인터럽트를 통해, 그리고 DMA를 (직접 메모리 접근) 통한 방법이다. 하드웨어 인터페이스 포트를 이용하는 첫 번째 방법은 BIOS와 운영체제를 통해 소프트웨어로 I/O 포트의 어드레스를 전송하는 작업이 수반된다. 소프트웨어가 이 어드레스로 데이터를 전송하면 장치에서 이용 가능해진다. 그리고 소프트웨어가 장치로부터 데이터를 읽을 때는 즉시 이용 가능하며, 입력의 처리에 시간 지연이 없다. 하드웨어가 데이터를 제공하길 애플리케이션이 기다리는 동안 소프트웨어는 루프를 실행하여, 데이터를 이용할 수 있는지 검사하기 위해 이따금씩 하드웨어를 폴링(polling)한다. 이는 세 가지 통신 모델 중 가장 단순한 방법이다.


인터럽트를 이용할 경우, 하드웨어는 데이터를 이용할 수 있을 때 소프트웨어로 신호를 보낸다. 이는 폴링(polling) 루프에서 대기를 불필요하게 만들며, 새 데이터로 더 재빠르게 반응할 수 있게 된다.


가장 빠른 통신 방법은 DMA 를 사용하는 것이다. 이런 경우, 하드웨어는 컴퓨터 메모리로 직접 접근할 수 있으며, 데이터 자체에 보관 및 꺼낼 수 있다. 따라서 프로세서 시간이 소요되지 않는다. 이 방법은 하드 디스크, CDROM 드라이브, 그 외 대량 저장 장치와 같이 많은 양의 데이터를 처리해야 하는 장치에서 가장 흔히 사용된다. 현대 운영체제는 인터럽트 서비스 루틴과 DMA 의 구현에 대해 장치 드라이버를 필요로 하는데 보통 이는 꽤 복잡하다.


가장 간단한 접근법은 병렬 포트를 사용하는 방법인데, 본장의 뒷부분에서 논하겠다. 직렬 프로토콜은 병렬 프로토콜보다 훨씬 더 복잡한 하드웨어를 필요로 하므로, 대부분의 직렬 인터페이스는 특수 논리 회로나 내장 마이크로컨트롤러(microcontroller)로 작업한다. 라자루스는 플랫폼 독립적인 직렬 통신 라이브러리 Synaser 를 제공한다.

기술 데이터 전송 속도 하드웨어 복잡성
직렬 포트 매우 느림 (<105 Bit/s) 평균
병렬 포트 느림 (~106 Bit/s) 쉬움
ISA 카드 평균 (~107 Bit/s) 평균
USB 평균 (~107 Bit/s) 복잡
PCI 카드 매우 빠름 (>109 Bit/s) 매우 복잡
표 8.9: 하드웨어 접근 프로토콜의 개요


병렬 포트

데스크톱 컴퓨터는 아무리 최신 제품이라 하더라도 대부분 표준 병렬 포트를 가지고 있다. 마더보드에 병렬 인터페이스가 없다면 병렬 포트가 있는 USB 어댑터나 PCI 포트를 추가하여 설치하는 수도 있다. 많은 프린터들은 (네트워크 혹은 USB 인터페이스를 사용하지 않는 이상) 병렬 포트를 통해 PC로 연결되어 있다. 오래된 컴퓨터는 물론이고 고가의 최신 프린터도 마찬가지다. 병렬 포트는 인쇄 이외의 용도에도 사용 가능하다. 우리는 PC 상에서 하드웨어 인터페이스를 통해 데이터를 전송/수신함으로써 직접 통신에 집중할 것이다 (ISA나 PCI 카드와 같은 다른 병렬 장치와의 통신 또한 하드웨어 포트의 교환을 통해 가능한데, 이는 커스텀 하드웨어 개발에 종종 사용된다). 표준 병렬 포트는 입출력에 데이터 핀 8개, 장치의 상태를 읽는 상태 핀 5개, 명령을 전송하는 제어 핀 4개를 포함한다. 이는 특수 상태 핀을 제공하지 않는 다른 프로토콜에 비해 인터페이스를 훨씬 수월하게 만든다. 병렬 포트는 주로 3BCh~3BFh, 378h~37Fh, 278h~27Fh의 포트를 통해 어드레싱된다. 운영체제는 병렬 포트의 어드레스를 제공할 것이다. 윈도우에서 어드레스는 제어판 ▷ 장치 관리자의 LPT 포트 프로퍼티에서 찾을 수 있다. 하드웨어 어드레스가 열거되어 있을 것이다.

그림 8.3: Windows XP 제어판에 표시된 병렬 포트에 관한 정보


병렬 포트로부터 출력은 주로 일반 TTL 수준으로, 1일 때 +5V, 0일 때 0V이다. 핀에서 이용 가능한 전류는 포트에 따라 좌우되는데 주로 핀당 4mA~20mA 사이 값이다 (소스와 싱크). 몇 개의 LED와 같이 매우 간단한 하드웨어를 연결하거나 핀을 직접 읽기 위해서는 포트 핀으로부터 직접 전류를 얻는 것이 가능하다. 좀 더 복잡한 하드웨어의 경우, 외부 전원으로 버퍼를 추가하여 병렬 포트에 부하(load)를 최소화할 것을 권장한다.


X86 컴퓨터에서 병렬 인터페이스를 통해 통신하려면 두 개의 특수 어셈블러 연산 부호(opcode)가 필요하다: IN과 OUT. 하지만 Windows 9x 계열만 (95, 98, ME) 이러한 호출로 직접 접근을 허용한다. Windows NT 계열은 장치 드라이브에서만 사용을 허용한다. 이에 대한 해결책이 inpout 32.dll 라이브러리의 폼에 있다 (64-bit Windows용 버전도 존재). 해당 무료 오픈 소스 소프트웨어는 http://iogix4u.net/ 에서 다운로드할 수 있다. 시스템에서 Windows NT를 발견하면 커널 장치 드라이버 HWInterface.sys를 압축해제하여 설치할 것이다. Windows 9x이 발견되면 어셈블러 연산 부호가 사용되어 하드웨어로 직접 어드레싱할 것이다.

핀 번호 (DB25) 신호이름 방향 레지스터 비트 하드웨어에서 반대로 되는가(inverted)?
1 nStrobe Out Control-0
2 Data0 In/Out Data-0 아니오
3 1 In/Out Data-1 아니오
4 2 In/Out Data-2 아니오
5 Data3 In/Out Data-3 아니오
6 Data4 In/Out Data-4 아니오
7 Data5 In/Out Data-5 아니오
8 Data6 In/Out Data-6 아니오
9 Data7 In/Out Data-7 아니오
10 nAck In Status-6 아니오
11 Busy In Status-7
12 Paper-Out In Status-5 아니오
13 Select In Status-4 아니오
14 Linefeed Out Control-1
15 nError In Status-3 아니오
16 nInitialize Out Control-2 아니오
17 nSelect-Printer Out Control-3
18-25 Ground
표 8.10: 25-way 병렬(parallel) 커넥터


n으로 시작하는 신호 이름은 active low를 의미한다. 핀은 컴퓨터 측면에서 보이는 대로 센다. 일부 핀은 병렬 포트 하드웨어에서 반대로 된다(inverted). 즉, 높은 수준을 (+5V) 적용 시 소프트웨어가 0으로 읽는 결과가 발생함을 의미한다.


라이브러리는 매우 단순하며 사용이 쉽다. Inp32 와 Out32 함수만을 가진다. Inp32 는 주어진 포트에서 병렬 장치로부터 바이트를 읽고, Out32 는 바이트를 전송한다. 우리는 이 라이브러리를 어떻게 사용하는지 예제에서 설명하고자 한다.


리눅스와 다른 유사 유닉스 체제에서 병렬 통신에 대한 해결책은 달라 보이므로 병렬 통신을 위한 플랫폼 독립적 애플리케이션에서는 항상 {$ifdef} 지시어를 사용해야 할 것이다.


유닉스에서 우리는 ioperm 를 호출하여 포트를 어드레싱하기 (어드레스 범위) 위한 권한을 설정함으로써 시작해야 한다. 이 루틴은 LibC 의 부분이다. 이를 이용하기 위해선 자신의 프로그램 uses 절에 clib unit이 포함되어 있어야 한다. 접근 권한이 설정되고 나면 유닛 포트를 이용해 데이터를 읽고 쓸 수 있다. 윈도우에서 이것은 IN과 OUT 어셈블러 명령어를 사용할 것이다.


아래 병렬 통신에 대한 예제 프로그램에는 장치의 포트 어드레스와 전송할 값에 관한 정보를 나타내는 텍스트 필드가 있다. 또한 데이터의 전송과 수신을 시작하는 버튼이 위치한다. 이 프로그램은 병렬 장치와의 작업 방식을 설명하는 데 유용하지만 새 하드웨어나 간단한 장치를 테스트하는 데 사용되기도 한다.

그림 8.4: 병렬 통신을 위한 애플리케이션 실행 모습


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

uses  Classes, SysUtils, FileUtil, LResources, Forms,
      Controls, Graphics, Dialogs, StdCtrls, ExtCtrls
 {$IFDEF WIN32}
   , Windows
 {$ENDIF}
 {$IFDEF Unix}
   , clib, ports  // for the library and port operations
 {$ENDIF}
 ;
type  { TformParallel }
 (* Address represents the address of the port you want to access Out32 sends data to the port
    specified by Address Inp32 returns a byte from the port specified by Address *)

  TInp32 = function(Address: SmallInt): SmallInt; StdCall;
  TOut32 = procedure(Address: SmallInt; Data: SmallInt); StdCall;
  TformParallel = class(TForm)
    buttonSend: TButton;
    buttonReceive: TButton;
    editPort: TEdit;
    editSend: TEdit;
    editReceived: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    procedure buttonReciveClick(Sender: TObject);
    procedure buttonSendClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    Inpout32: THandle;
    Inp32: TInp32;
    Out32: TOut32;
  end;

var formParallel: TformParallel;

{$ifdef Unix}
function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer):
 Integer; CDecl; external clib;
{$endif}

implementation

{ TformParallel }
%%nextpage
procedure TformParallel.FormCreate(Sender: TObject);
// Under Windows the helper library is loaded dynamically
begin
  {$ifdef Win32}
  {$ifdef CPUX86}
  Inpout32 := LoadLibrary('inpout32.dll');  // this only for 32-bit Windows
  {$else}
  Inpout32 := LoadLibrary('inpoutx64.dll'); // In 64-bit Windows use:
  {$endif}                                  // inpoutx64.dll
  if Inpout32 <> 0 then begin
    Inp32 := TInp32(GetProcAddress(Inpout32, 'Inp32'));
    if @Inp32 = NIL then Caption := 'Error';
    Out32 := TOut32(GetProcAddress(Inpout32, 'Out32'));
    if @Out32 = NIL then Caption := 'Error';
  end else
    Caption := 'Error';
  {$endif}
end;

procedure TformParallel.buttonSendClick(Sender: TObject);
  var  PortAddress: Integer; Data: Byte;
begin
  PortAddress := StrToInt(editPort.Text);
  Data := StrToInt(editSend.Text);
  {$ifdef Win32}
  Out32(PortAddress, Data);
  {$endif}
  {$ifdef Unix}
  ioperm(PortAddress, 8, 1);
  port[PortAddress] := Data;
  {$endif}
end;

procedure TformParallel.buttonReceiveClick(Sender: TObject);
  var  PortAddress: Integer;
begin
  PortAddress := StrToInt(editPort.Text);
  {$ifdef Win32}
  editReceived.Text := IntToHex(Inp32(PortAddress), 2);
  {$endif}
  {$ifdef Unix}
  editReceived.Text := IntToHex(Port[PortAddress], 2);
  {$endif}
end;

procedure TformParallel.FormDestroy(Sender: TObject);
begin
  {$ifdef Win32}
  FreeLibrary(Inpout32);
  {$endif}
end;

initialization
  {$I parallel_form.lrs}
end.


이 프로그램은 절대 완벽한것이 아니다. 64 비트 애플리케이션이 필요한 경우 다른 드라이버를 사용 및 테스트해야 한다.


직렬 통신

직렬 포트를 통한 통신은 자주 사용되는데, 일반 COM 포터, 심지어 USB 장치에서 인터넷 프로토콜 TCPI/IP나 다른 네트워크 프로토콜에도 사용된다. 이번 절에서는 COM 포트처럼 행동하도록 구성된 USB 장치와 COM 포트를 다룰 것이다.


COM 포트 혹은 직렬 포트는 RS-232 커넥터에서 가장 잘 알려져 있지만 커넥터의 형태와 핀 배치도(pinout)는 하드웨어에 따라 여러 형태를 취할 수 있다:

그림 8.5: 직렬 커넥터의 핀 배치도


컴퓨터는 다수의 COM 포트를 포함할 수 있으며 COM1, COM2, COM3 순으로 번호가 매겨질 것이다. 리눅스에서는 /dev/ttyS0, /dev/ttyS1, /dev/ttyS2 등으로 이용 가능하고, FreeBSD에서 포트는 /dev/ttyu0 순으로 붙여진다. 직렬 장치와 통신하기 위해서는 어떤 포트로 연결되었는지, 그 직렬 프로토콜의 파라미터가 무엇인지를 먼저 알아야 한다. 프로퍼티로는 속도 (초당 비트), 데이터 비트 수, 패리티 비트(parity bit), 정지 비트 (stop bit), 그리고 만약에 있다면 handshake 프로토콜도 해당한다. 이러한 주요 프로퍼티들을 알고 있다면 어떤 직렬 장치로든 연결할 수 있다.


USB 장치는 일반 COM 포트처럼 취급할 수도 있지만 이를 위해선 행위를 구현하도록 설치된 드라이버가 필요하다. 사용할 USB 칩의 제조업체는 자신이 제공하는 가상 COM 포트 드라이버에 관한 정보를 제공해야 한다. 이에 권장하는 칩은 FTDI의 FT245RL인데, Windows, Linux, MacOS X, Windows CE 와 같은 여러 운영체제에 라이센스 프리(license-free)의 가상 COM 포트를 제공한다 ( http://www.ftdichip.com/FTProducts.htm 참고).


직렬 통신에 좋은 플랫폼 독립 라이브러리로 Synaps 프로젝트의 Synaser 를 들 수 있겠다. 프리 파스칼에 표준으로 딸려 오는 것은 아니지만 아래 주소에서 쉽게 얻을 수 있다:

http://synapse.ararat.cz/doku.php/download


라이브러리는 수정된 BSD 라이센스(modified BSD license)를 따라 제공되어 상업적 및 독점적 프로젝트에 사용을 허용한다. Synaser unit 의 클래스, 메소드, 데이터 타입, 상수에 관한 전체 문서는 http://synapse.ararat.cz/doc/help/synaser.html 에서 찾을 수 있다.


아래 예제는 Synaser release 16을 바탕으로 하여 이 유닛으로 작업해야 하는 모든 것을 보여준다.


직렬연결 테스트 프로그램은 Synaser 유닛과 어떻게 작업해야 하는지를 보여준다. TBlockSerial 클래스의 인스턴스는 창의 OnCreate 이벤트에서 생성된다. 이것은 Synaser 에서 메인 직렬 통신 클래스이다: 그렇지만 클래스의 생성만으로는 전용(dedicated) 함수의 호출을 필요로 하는 장치로 연결하기에 충분치 않다. Free 를 이용해 인스턴스를 다시 해제(release)하는 것이 중요한데 (OnDestroy 메소드에서 또는 프로그램에서 다른 전략적 위치에서) 그렇지 않을 시 연결은 프로그램이 끝난 후에도 개방된 채로 남는다.


사용자가 창에서 Connect(연결) 버튼을 누르면 Connect 메소드가 사용되어 직렬 포트로 연결을 생성할 것이다. 사용할 포트를 명시하는 데에는 편집 컨트롤이 사용된다 (예제에서는 COM1). 이후 Config 메소드를 이용해 직렬 포트의 데이터율, 데이터 비트 수, 패리티 비트, 정지 비트, handshake 프로토콜이 설정된다. 그러면 RecvByte, SendByte 를 비롯해 직렬 인터페이스로 데이터를 전송하고 그로부터 전송 받는 데에 관련된 메소드들을 사용할 준비가 모두 끝난다. Recv* 메소드는 Timeout 파라미터를 가진다. 이는 Timeout 에 명시된 밀리 초를 넘길 때까지 데이터를 기다린다. 데이터가 수신되면 리턴될 것이다.


Connect 버튼은 통신뿐만 아니라 직렬 인터페이스에서 데이터를 이용할 수 있는지 반복 테스트하는 타이머(timer) 또한 활성화시킨다. 데이터를 이용할 수 있다면 memo 창에 작성될 것이다.


아래는 TBlockSerial 클래스 내 메소드 선언부이다:

procedure Connect(comport: String);
procedure Config(baud, bits: integer;
                 parity: char; stop: Integer;
                 softflow, hardflow: Boolean);
function sSendBuffer(buffer: Pointer; length: Integer): Integer;
procedure SendByte(data: Byte);
procedure SendString(data: String);
procedure SendInteger(Data: Integer);
procedure SendStream(const Stream: TStream);
function RecvBufferEx(buffer: Pointer; length: Integer;
                      timeout: Integer): Integer;
function RecvByte(timeout: Integer): byte;
function RecvString(timeout: Integer): String;
function RecvInteger(Timeout: Integer): Integer;
procedure RecvStream(const Stream: IStream; Timeout: Integer);


위의 클래스는 이렇게 중요한 메소드뿐만 아니라 몇 가지 주요 프로퍼티도 제공한다. 장치 이름은 Device에 저장된다. Synaser 는 각 연산이 끝난 후 LastError 와 LastErrorDesc 프로퍼티를 채운다 (fill in). 마지막 연산의 결과는 Last Error 에 저장되는데, 성공 시에는 Null 이, 실패 시에는 오류 코드가 채워진다. 사람이 읽을 수 있는 오류의 설명은 LastErrorDesc 에 작성된다. 오류가 발생 시 Synaser 는 RaiseExcept 프로퍼티를 검사한다. 이 값이 True 일 경우, 오류 정보와 함께 예외가 발생한다. False 일 경우 (기본 값), LastError 와 LastErrorDesc 가 채워지고, 어떤 예외도 발생하지 않는다. 따라서 연산의 성공 여부를 알아내려면 LastError 를 확인하면 될 것이다.

property Device: String Read FDevice;
property LastError: Integer Read FLastError;
property LastErrorDesc: String Read FLastErrorDesc;
property RaiseExcept: Boolean Read FRaiseExcept Write FRaiseExcept Default False;


테스트 프로그램은 직렬 마우스(serial mouse)로 테스트하였고, 그로부터 데이터를 수락할 수 있었다. 직렬 포트 COM1 는 초당 1200 비트, 7 데이터 비트, 패리티 없음, 1 정지 비트, handshake 없음으로 구성되었다.


직렬-USB 컨버터가 연결되었거나 FTDI의 것과 같은 가상 COM 포트 드라이버가 USB 포트에 이용되었을 경우 위와 동일한 프로그램은 USB 장치와도 통신할 수 있다.

그림 8.6: 직렬 통신의 테스트 프로그램 실행 모습
unit comm_form;
{$mode objfpc}{$H+}
interface
uses Classes, SysUtils, FileUtil, LResources, Forms, Controls,
     Graphics, Dialogs, StdCtrls, ExtCtrls, ComCtrls, Synaser;
type { TformComm }
  TformComm = class(TForm)
    buttonConnect: TButton;
    editDevice: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    memoData: TMemo;
    statusBar: TStatusBar;
    timerRead: TTimer;
    procedure buttonConnectClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure timerReadTimer(Sender: TObject);
  private
    connected: Boolean;
    ser: TBlockSerial;
  end;

var formComm: TformComm;
implementation  { TformComm }
procedure TformComm.timerReadTimer(Sender: TObject);
  var Data: Byte;
begin
  if not connected then Exit;
  Data := ser.RecvByte(timerRead.Interval div 2);
  if ser.LastError <> 0 then Exit;
  memoData.Text := memoData.Text + IntToHex(Data, 2) + ' ';
end;

procedure TformComm.FormCreate(Sender: TObject);
begin
  ser := TBlockSerial.Create;
end;

procedure TformComm.buttonConnectClick(Sender: TObject);
begin
  if connected then Exit;
  ser.Connect(editDevice.Text); // specification of COM port
  Sleep(1000);
  ser.Config(1200, 7, 'N', SB1, False, False);
  StatusBar.SimpleText := 'Device: ' + ser.Device + ' Status: ' +
                   ser.LastErrorDesc + ' ' + IntToStr(ser.LastError);
  Sleep(1000);
  if ser.LastError = 0 then connected := True;
end;

procedure TformComm.FormDestroy(Sender: TObject);
begin
  ser.free;
end;

initialization
  {$I comm_form.lrs}
end.


윈도우에서는 직렬 인터페이스와의 통신을 위한 또 다른 전반적인 해결책을 TSerial 에서 찾을 수 있다. 현재 4.4 버전은 Toolbox 잡지 2009년 3-4호에서 발표되었다. 무료 오픈 소스는 아니지만 잡지 구독자라면 어떤 프로그램에서든 사용을 허가한다. TSerial 은 델파이를 위한 컴포넌트 집합이지만 라자루스의 윈도우 버전에서도 설치 가능하다. 하지만 그것으로 작성된 프로그램은 다른 플랫폼으로 전송할 수 없다.


프린터

라자루스는 프린터 접근과 관련해 매우 잘 설계된 플랫폼 독립적 인터페이스를 제공한다. 인쇄 기능은 두 개의 패키지에 구현된다:


Printers4lazide 는 Print 엔트리를 IDE 의 File 메뉴에 추가하여 현재 파일을 인쇄하도록 해준다. 라자루스 0.9.26 버전 이상부터는 현재 선택 부분만 인쇄하는 기능도 제공한다. Printer4lazarus 는 LCL에서 프린터를 지원하기 위한 기본 패키지이다. 이는 시스템 특정적인 인쇄 시스템 부분들을 포함한다. 이 패키지는 인쇄가 활성화된 프로젝트로 추가되어야 한다. 이를 위해선 Project→Project Inspector 메뉴로 가서 [+] 버튼을 (추가하기) 눌러 프로젝트 대화창에서 새 엔트리를 추가한 후, Printers4Lazarus 를 선택하고 마지막으로 OK 를 클릭한다. 인쇄 시스템이 구분된 패키지에 있는 이유는 윈도우와 달리 유닉스에서는 인쇄가 표준 운영체제의 일부가 아니며, 프린터 지원이 없는 그래픽 사용자 인터페이스가 허용될뿐 아니라 흔히 사용되기 때문이다.


인쇄의 기본은 unit Printers 내의 TPrinter 클래스로, LCL의 일부이며, 사용자가 최근 선택한 프린터로 인터페이스를 제공한다. 유닛은 Printer 변수를 포함하는데, 이 변수는 Printer4Lazarus 패키지에서 OSPrinters unit 에 의해 인스턴스화된다. TPrinter 에도 다수의 프로퍼티와 메소드가 있다. 주요 프로퍼티로는 인쇄할 페이지를 나타내는 TCanvas 의 인스턴스를 들 수 있다.


이 객체의 메소드는 텍스트나 그래픽의 인쇄를 시작하고, 페이지 여백(margin)과 용지의 배출 시트(eject sheet)를 설정하도록 해준다. 인쇄 전에 프로그래머는 항상 TPrinterDialog 를 사용자에게 표시해야 하는데, 이는 프린터를 선택하고, 여백과 용지 크기를 설정하며, 다른 구성 항목들을 변경하도록 해준다. (프린터를 선택하기 위해 TPrinterDialog 의 인스턴스가 PrinterDlgs 유닛으로부터 호출된다.) 대화창이 닫히면 Printer 의 설정이 자동으로 업데이트되고, 선택된 프린터에 설정이 적용된다.


인쇄 시 Canvas 는 중요한 역할을 한다. 이는 Printer 객체의 메인 프로퍼티다. Printer.Canvase 는 페이지에 그림과 텍스트를 그리는 역할을 한다. TCanvas 는 그래픽 컨트롤에 대해 논하는 9장에서 다루고 있지만 인쇄에 사용되는 TCanvase 와 동일하다: 이러한 호환성 때문에 화면에 그리기와 프린터에 그리기 간에 그리기 코드(drawing code)를 교환하기가 쉽다. Canvas 의 원점은 용지 좌측 하단 모서리의 (0, 0) 위치다. 프린터는 일반적으로 전체 페이지를 인쇄하지 않음을 명심하는 것이 중요하다: 예를 들자면, TRect 프로퍼티, Printer.PaperSize.PaperRect.WorkRect 에서 결정되는 인쇄 불가 테두리(unprintable border)가 있다.

메소드 효과
procedure Abort; 현재 인쇄 작업을 종료시킨다. 인쇄가 이미 시작되었다면 작업이 중단될 것이다.
procedure Begindoc; 프린터에 데이터를 전송 시 호출해야 하는 첫 번째 메소드여야 하며, 사용자에게 표시된 인쇄 대화창 이후에 사용된다. 이 메소드를 호출하고 나면 Canvas 프로퍼티를 이용해 이미지와 텍스트를 그릴 수 있다. 프린터는 사실 EndDoc이 호출되기 전까진 인쇄를 시작하지 않을 것이다.
procedure EndDoc; 이를 호출해 인쇄 데이터가 완전히 준비되었음을 나타내고, TPrinter 에게 실제 인쇄를 하도록 데이터를 프린터로 전송할 것을 알린다.
procedure NewPage; 깨끗한 Canvas 로 새 인쇄 페이지를 시작한다. PageNumber 프로퍼티를 1씩 증가시킨다.
procedure TPrinter.SetPrinter( aName: String); 현재 프린터를 변경한다. 이용 가능한 프린터가 모두 열거된 Printers 프로퍼티를 확인한다.
표 8.11: TPrinter의 가장 중요한 메소드


프로퍼티 효과
property PaperSize. 현재 선택된 용지의 인쇄 접근가능(print-accessible) 면(surface)이다.
Papername에서 선택된 용지에 따라 변경되는데, 주로 A4 또는 편지형식이다.
property Orientation: 문서의 방향. 가장 일반적인 값은 세로 방향에 poPortrait, 가로 방향에 poLandscape 이다. 일부 프린터는 첫 두 옵션의 역 버전인 poReverseLandscape, poReversePortrait 값을 허용하기도 한다.
property PrinterState: 프린터 상태. psNoDefine - 정의되지 않은 경우; psReady - 인쇄 준비됨; psPrinting – 현재 인쇄 중; psStopped – 현재 인쇄가 중단됨.
property Copies: Integer; 현재 문서 인쇄 매수(copies).
property Printers: 이용 가능한 프린터 리스트.
property Fonts: Tstrings; [read-only] 이용 가능한 폰트 리스트.
property Canvas: Tcanvas; 프린터의 메인 프로퍼티. 인쇄할 이미지와 텍스트를 그려야 하는 면(surface)을 정의한다.
property PageHeight: 캔버스 높이 픽셀.
property PageWidth: 캔버스 너비 픽셀.
property PageNumber: 현재 인쇄 중인 페이지의 번호.
property Aborted: 문서의 인쇄가 운용자 명령에 의해 인쇄 중 중단된 경우 True.
property Printing: 문서가 현재 인쇄되는 중인지 나타낸다.
property Title: String; 현재 프린터의 이름.
property PrinterType: 로컬 프린터에 ptLocal 값, 또는 네트워크 프린터에 ptNetwork 값을 가진다.
property CanPrint: 현재 프린터가 활성화되었다면 True, 비활성화 되었다면 False.
property XDPI: Integer; 현재 프린터의 수평 해상도, 인치당 도트 수(DPI).
property YDPI: Integer; 현재 프린터의 수직 해상도, 인치당 도트 수(DPI).
표 8.12: TPrinter의 가장 중요한 프로퍼티


사용자에게 인쇄 파라미터를 선택할 수 있는 TPrintDialog 를 제시할 때는 운영체제가 자동으로 이러한 기능을 구현하는 경우가 있는 반면 어떤 운영체제는 사용자의 바람을 충족시키기 전에 TPrintDialog 로부터 읽어 와야 함을 항상 명심해야 한다. 가장 흔한 비자동식 설정은 인쇄할 페이지 범위이다. 사용자가 선택할 수 있는 최소 및 최대 페이지 번호를 표시하기 위해서는 TPrintDialog.Execute 를 호출하기 전에 TPrintDialog.MinPage 와 TPrintDialog.MaxPage 를 먼저 설정해야 한다. MinPage 는 주로 1이고, MaxPage 는 보통 현재 문서의 페이지 수가 된다. 대화창을 실행하고 나면 TPrintDialog.PrintRange 가 TPrintDialog.FromPage 와 TPrintDialog.ToPage 를 이용해 사용자가 선택한 페이지 범위를 읽어올 것이다. 일부 기능들은 운영체제에서 자동으로 구현되는데, 인쇄 매수(copies) (운영체제가 인쇄할 문서를 얻어 원하는 매수대로 복사하는), 페이지 정렬 순서를 예로 들 수 있다. Mac OS X에서는 각 시트마다 인쇄할 페이지 번호를 Layout 탭에서 선택할 수도 있다. 이는 사용자가 모두 볼 수 있도록 실행된다.

상수 효과
Collate: Boolean default False 다수 복사본을 인쇄 시 영향을 미친다. False일 경우, 첫 페이지의 모든 복사본이 인쇄된 후 두 번째 페이지의 모든 복사본이 인쇄되는 식이다. True일 경우, 각 순서대로 한 페이지씩 복사되고 이 프로세스가 각 문서 페이지마다 반복된다. 이 프로퍼티는 운영체제가 자동으로 처리한다.
Copies: Integer default 1 인쇄할 복사본 수. 이 프로퍼티는 운영체제가 자동으로 처리한다.
FromPage: Integer default 0 사용자 범위 선택의 시작 페이지에 대한 1을 기반으로 한(1-based) 색인.
MinPage: Integer default 0 이 인쇄에 허용되는 최소 페이지 번호를 사용자에게 표시하는 대화창.
MaxPage: Integer default 0 이 인쇄에 허용되는 최대 페이지 번호를 사용자에게 표시하는 대화창.
Options: TPrintDialogOptions default [ ] 대화창의 모양과 행위를 사용자 설정(customization)하는 옵션의 집합을 포함한다. 가능한 값은 표 8.15에 열거되어 있다.
PrintToFile: Boolean default False 파일로 대신 인쇄 시 정의한다. 윈도우에만 있는 프로퍼티로, 윈도우가 자동으로 처리한다.
PrintRange: TPrintRange default prAllPages 가능한 값은 표 8.16에 설명되어 있다.
ToPage: Integer default 0 사용자의 범위 선택에서 끝 페이지.
표 8.13: TPrinter에서 중요한 상수들


메서드 설명
poPrintToFile 기본 값으로, 윈도우 대화창에서만 "Print to file(파일로 인쇄)" 옵션 표시가 활성화된다. 이 옵션은 당장 인쇄하는 대신 추후 파일을 인쇄할 수 있는 인쇄 .prn 파일을 생성한다. Mac OS X나 Linux 대화창에서 이용할 수 있는 PDF로 인쇄하기 옵션과 혼동되지 않도록 한다.
poPageNums 사용자가 대화창에 인쇄할 페이지 번호를 명시하도록 해준다.
postelection 사용자가 대화창에 인쇄할 페이지 선택을 명시하도록 해준다.
poWarning 사용자가 설치되지 않은 프린터로 인쇄를 시도 시 경고 메시지를 표시하는 윈도우 옵션이다.
poHelp 인쇄에 대한 표준 운영체제 도움말을 제공하는 버튼을 표시한다.
poDisablePrintToFile 사용자가 윈도우 대화창에서 유일한 옵션인 “Print to file”을 선택하지 못하도록 한다. 상세한 내용은 위의 poPrintToFile 를 참고한다.
표 8.14: TPrintDialog의 가장 중요한 프로퍼티


설명
prAllPages 모든 페이지가 인쇄되어야 한다.
prSelection 일부 페이지만 인쇄되어야 한다. 선택된 페이지 범위는 FromPage와 ToPage 프로퍼티에 명시된다.
prPageNums 인쇄해야 할 페이지.
prCurrentPage 현재 페이지만 인쇄되어야 한다.
표 8.15: PrintRange에서 사용 가능한 값


아래 예제는 프린터 여백(printer margin)을 고려하여 메모 컨트롤에서 텍스트를 가져와 페이지 좌측 상단 모서리에 인쇄한다:

uses Printers, PrintersDlgs;

procedure TformPrint.buttonPrintClick(Sender: TObject);
  var PaperWorkRect: TRect;
      PrintDlg     : TPrintDialog;
begin
  Application.Initialize;
  PrintDlg := TPrintDialog.Create(NIL);
  try
    PrintDlg.MinPage := 1; PrintDlg.MaxPage := 1;
    if PrintDlg.Execute then
    begin
      PaperWorkRect := Printer.PaperSize.PaperRect.WorkRect;
      Printer.BeginDoc;
      Printer.Canvas.TextOut(PaperWorkRect.Left, PaperWorkRect.Top,
                               memoPrint.Lines.Text);
      Printer.EndDoc;
    end;
  finally
    PrintDlg.Free;
  end;
end;