12.프린터 출력하기

본장은 화면 출력을 떠나서 이제 프린터로 넘어가는 부분입니다. 프린터는 일반 모니터와 형태가 다르기 때문에 출력을 하고자 할경우에는 새로운 구성이 필요합니다. 프린터와 모니터 둘다 DC를 이용하는 것은 같으나 모니터는 가로가 크고 프린터는 세로가 크다는 것이 차이입니다. 또한 프린터는 각 모델마다 dpi가 다르기 때문에 출력해야할 방법또한 다릅니다. 본장에서는 현재 설정되어 있는 프린터와 이프린터에 등록되어 있는 정보를 얻고 이것을 이용하여 글자와 그림등을 출력하는 방법을 설명합니다.

설정된 프린터수와 이름 얻기

보통 일반적으로 한 대의 컴퓨터에는 한 대의 프린터가 연결되어 있는 경우가 기본입니다. 그러나 LAN을 이용한 환경에서는 여러대의 프린터가 연결될수 있고 때로는 프린터 포트 1,2(LPT1, LPT2)에 두 대가 설치 되었을경우도 있습니다. 프린터에 데이터를 출력하고자 할 경우 최우선적으로 알아내어야 할 것이 프린터가 있나 없느나와 프린터가 몇 개가 설치되어 있으며 어느 프린터에 인쇄를 할것인가입니다. 실제 이것을 아는방법중 가장 무식한 방법은 윈도우디렉토리의  win.ini파일에서 [Devices]항목을 찾아서 이곳에 설정되어 있는 데이터를 읽는것입니다.

[Devices]

HP DeskJet 870 Series=HPRDJC03,\\Sparrow\hp 870k

HP LaserJet 5  XL (KOREA)=HPHXLAA,\\Condor\hp laserjet5

만약 위와 같은 내용이 win.ini파일에 있다면 컴퓨터에 HP DeskJet 870 Series프린터와 HP LaserJet 5 XL (KOREA)라는 프린터가 연결되어 있으며 이프린터는 Sparrow라는 컴퓨터와 Condor이라는 컴퓨터에 연결되어 있고 현재 네트워크 프린터를 사용하고 있다는 것을 알수가 있습니다.

이렇게 하는 방법보다 더 간편한 방법이 EnumPrinters 함수를 사용하는 것입니다.

이함수는 다음과 같습니다.

BOOL EnumPrinters(

DWORD Flags, // 프린터 정보 얻기 위한 명령 플러그

LPTSTR Name, // 프린터 이름

DWORD Level, // 프린터 정보의 특별한 타입

//PRINTER_INFO_1~PRINTER_INFO_5 까지의 정보를 설정하는 버퍼 포인터

LPBYTE pPrinterEnum,

DWORD cbBuf, // pPrinterEnum이 가르키는 포인터의 크기

LPDWORD pcbNeeded, //필요한 바이트의 수

LPDWORD pcReturned // 리턴되는 프린터 정보

);

Flags는 프린터 정보를 얻기 위한 명령 플러그입니다. 이플러그에 설정되는 값은 다음과 같습니다.

PRINTER_ENUM_LOCAL: 연결되어 있는 프린터

PRINTER_ENUM_NAME  : 이름으로 설정된 프린터

PRINTER_ENUM_LOCAL를 설정하면 EnumPrinters는 연결되어 있는 프린터를 순차적으로 돌아가면서 나머지 인자에 설정된 정보에 의한 내용을 수행합니다.

PRINTER_ENUM_NAME:를 설정하면  EnumPrinter는 연결되어 있는 특정 프린터에 대한 정보를 얻습니다.

Name는 프린터 객체 이름을 말합니다. Level은 프린터 정보를 얻고자 할 때 전달하는 구조체에 해당하는 타입을 설정하는데 값은 1,2,4,5로 설정할수 있습니다. 1로 설정되면 PRINTER_INFO_1 구조체의 값이 pPrinterEnum 곳에 설정되고 2일경우에는 PRINTER_INFO_2 가 4일경우에는 PRINTER_INFO_4 5일경우에는 PRINTER_INFO_5의구조체에 정보를 넣어서 설정됩니다. PRINTER_INFO_1 구조체는 프린터 이름을 중심으로 설정되며 PRINTER_INFO_2는 프린터 드라이브 이름과 포트 이름을 중심으로 설정되면 PRINTER_INFO_4 프린터 이름과 서버 이름으로 PRINTER_INFO_5는 프린터 이름과 포트 이름을 중심으로 설정됩니다. 즉 Level에 값을 정하고 pPrinterEnum 에 적당한 구조체를 설정함으로써 정보를 얻을수 있습니다. pcbNeeded  본 정보를 저장하기 위해서 필요한 메모리 정보를 pcReturned 에는 결과 정보가 설정됩니다. 예를 들어서 현재 설정되어 있는 프린터의 수와 필요한 메모리 크기를 얻고자 한다면 다음과 같이 할수 있습니다.

//연결된 프린터의 필요한 데이터 크기와 컴퓨터 수를 얻는다.

EnumPrinters (PRINTER_ENUM_LOCAL,

NULL, 5, (LPBYTE) "", 0, &dwNeeded, &dwReturned) ;

위와 같이 하면 dwNeeded는 PRINTER_INFO_5형태로 저장할 메모리 크기와 dwReturned는 현재 연결된 프린터의 수가 설정이 됩니다.

위와 같이 하여 PRINTER_INFO_5의 구조체 포인터를 만들고 이변수를 dwNeeded만큼 메모리를 할당해 준다음 EnumPrinters함수를 실행시키면 이구조체에 설정되어 있는 프린터 정보를 일련의 배열로 설정합니다.

//필요한 메모리 설정

szInfo=GlobalAlloc(GHND,dwNeeded);

//이메모리를 PRINTER_INFO_5구조체에 할당

printinfo=(LPPRINTER_INFO_5)GlobalLock(szInfo);

//프린터 정보를 얻는다.

EnumPrinters (PRINTER_ENUM_LOCAL,

NULL, 5, (LPBYTE) printinfo, dwNeeded,

&dwNeeded, &dwReturned);

예를 들어서 프린터가 두 개가 연결되어 있다고 가정을 하다면 printinfo에는 첫 번째 프린터 정보가 printinfo+1이 두 번째 프린터 정보가 설정되는 것입니다.

PRINTER_INFO_5구조체의 맴버중에서 pPrinterName 변수가 바로 프린터 이름입니다.

프린터 정보 얻기

프린터로 문자나 그림을 출력하고자 할 경우 프린터 정보를 얻어야 합니다. 즉 가로 출력할 수 있는 픽셀과 세로의 픽셀수 또한 가로의 크기 세로의 크기등을 얻어야 합니다. 이이유는 많은 프린터들이 출력되는 픽셀이 다르기 때문입니다. 픽셀수가 작은 프린터와 픽셀수가 많은 프린터에 같은 픽셀의 크기로 (즉 MM_TEXT 모드로) 출력을 한다면 픽셀수가 작은 프린터에서 글자의 크기는 크게 나올것입니다. 가로 픽셀 및 용지의 가로크기를 얻어서 문자나 그림을 비례적으로 얻기 위해서는 먼저 프린터의 성능이 어떤가의 정보가 필요합니다. 이런 정보를 얻기 위해서는 1차적으로 프린터 DC를 얻어야 합니다. 프린터 DC를 얻을 때 정보를 위한 DC는 CreateIC 함수를 이용합니다.

HDC CreateIC(

LPCTSTR lpszDriver, // 드라이버 이름

LPCTSTR lpszDevice, // 디바이스 이름

LPCTSTR lpszOutput, // 포트 또는 파일명

CONST DEVMODE *lpdvmInit // 설정할 초기 모드값

);

예를 들어서 □HP DeskJet 870 Series□ 라는 프린터의 DC를 얻고자 한다면 다음과 같습니다.

PrintDc=CreateIC(NULL,□HP DeskJet 870 Series□,NULL,NULL);

만약 EnumPrinters함수에 의해서 얻어진 정보 값 printinfo를 이용하여 첫 번째 프린터 DC를 얻고자 한다면 다음과 같이 할수 있습니다.

PrintDc=CreateIC(NULL,printinfo->pPrinterName,NULL,NULL);

이와 같이 하여 프린터 DC를 얻고 난다음 GetDeviceCaps를 이용하여 여러 정보를 얻을수 있습니다. 표1은 GetDevieceCaps에 의해서 얻을수 있는 리스트들입니다.

(표 1) GetDeviceCap의 nIndex리스트

(표시작)

설정값        내용

HORZSIZE 밀리미터의 가로크기

VERTSIZE 밀리미터의 세로크기

HORZRES  수평 픽셀수

VERTRES  수직 픽셀수

LOGPIXELSX  가로방향으로 로직컬 인치당 픽셀수

LOGPIXELSY  세로 방향으로 로직컬 인치당 픽셀수

BITSPIXEL 각픽셀에 대한 컬러 비트수

PLANES  컬러 플레인

NUMBRUSHES 장치에 설정된 브러쉬 수

NUMPENS 장치에 설정된 펜의 수

NUMFONTS  장치에 설정된 폰트의 수

NUMCOLORS  장치의 색상 테이블의 항목수

ASPECTX  선그리기를 윈한 장치 픽셀의 상대적인 폭

ASPECTY  선그리기를 위한 장치 픽셀의 상대적인 높이

ASPECTXY 선그리기를 위한 장치 픽셀의 대각선 크기

CLIPCAPS 장치의 클리핑 특성을 나타내는 플래그

(표끝)

이외에도 많은 정보들이 있습니다. 나머지는 GetDeviceCaps 함수를 도움말에서 찾아서 확인해보시기 바랍니다.

예를 들어서 프린터의 가로 픽셀수를 얻고자 한다면 위에서 얻은 프린터 DC PrintDc와 HORZSIZE를 이용하여 다음과 같이 하여 얻을수 있습니다.

dwInfodata=GetDeviceCaps(PrintDc,HORZSIZE);

워드 프로세서에서는 모든 문자와 크기를 밀리미터 또는 인치 단위로 사용하고 있습니다. 화면에서는 이것이 일정비율로 크게 또는 작게 나와도 상관은 없으나 프린터 할경우에는 이 단위가 정확하게 출력되어야 합니다.이렇게 정확하게 출력하기 위해서는 LOGPIXELSX 플러그 등을 사용하여 정보를 얻고 이정보에 맞게 글자와 그림을 맞추어야 합니다. 이때 GetDeviceCaps는 매우 유용하게 사용됩니다.

프린터 정보 대화상자

프린터에 대한 정보에 대한 공용 대화상자는 PrinterProperties 함수를 이용합니다.이함수의 형은 다음과 같습니다.

BOOL PrinterProperties(

HWND hWnd, // 부모 윈도우

HANDLE hPrinter // 프린터 핸들

);

이함수는 프린터 정보를 알려주는 대화상자를 출력하는 함수입니다. 여기에서 프린터 핸들이라는 인자가 있습니다. 즉 프린터 핸들이 있어야만 이함수를 가동할수 있습니다. 프린터 핸들을 얻기 위해서 OpenPrinter 함수를 상요합니다.

BOOL OpenPrinter(

LPTSTR pPrinterName, //프린터 디바이스 이름

LPHANDLE phPrinter, // 프린터 핸들

LPPRINTER_DEFAULTS pDefault // 구조체 정보 포인터

);

OpenPrinter에 위에서 우리가 얻었던 프린터 드라이버 이름을 설정하면 phPrinter에 핸들이 넘어옵니다. OpenPrinter함수를 이용하여 프린터를 열었다면 ClosePrinter를 실행시켜 프린터를 닫아 주어야 합니다. 다음은 프린터 정보 대화상자를 실행하는 예입니다.

//프린터를 오픈하고

if (OpenPrinter ((printinfo+iPrinterNumber)->pPrinterName, &hPrint, NULL))

{

//대화상자 실행후

PrinterProperties (hwnd, hPrint) ;

//프린터를 닫는다.

ClosePrinter (hPrint) ;

}

위와 같이 했을 때 프린터에 대한 정보를 얻을수 있는 대화상자가 실행됩니다. 이대화상자는 프린터 제조업체가 제공한 드라이버를 설정했을때는 다르게 나타납니다. 그림 1은 HP LaserJet 5가 설치되었을 때 실행되는 등록정보 대화상자입니다.

(그림 1) HP LaserJet5 프린터 등록정보 대화상자

프린터 정보 얻기 예제 ExPrinterChk

ExPrinterChk는 현재 설정되어 있는 프린터의 정보를 출력하고 연결된 프린터의 개수를 출력하며 해당프린터의 등록정보 대화상자를 출력하는 예제입니다.

(프로그램 소스)

//프린터 정보 얻기 예제

//ExPrinterChk.c

#include <windows.h>

#include "resource.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

BOOL CALLBACK DialogProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam);

HINSTANCE hInst;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "ExPrinterChk" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = "MENU" ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hInst=hInstance;

hwnd = CreateWindow (szAppName,

"ExPrinterChk:프린터 정보 얻기 예제",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LPPRINTER_INFO_5 printinfo;

DWORD  dwNeeded, dwReturned ;

HANDLE szInfo;

int iPrinterNumber;

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

char temp[200];

int i;

static HDC PrintDc;

DWORD dwInfodata;

HANDLE  hPrint ;

switch (iMsg)

{

case WM_CREATE :

//연결된 프린터의 필요한 데이터 크기와 컴퓨터 수를 얻는다.

EnumPrinters (PRINTER_ENUM_LOCAL,

NULL, 5, (LPBYTE) "", 0, &dwNeeded, &dwReturned) ;

//필요한 메모리 설정

szInfo=GlobalAlloc(GHND,dwNeeded);

//이메모리를 PRINTER_INFO_5구조체에 할당

printinfo=(LPPRINTER_INFO_5)GlobalLock(szInfo);

//프린터 정보를 얻는다.

EnumPrinters (PRINTER_ENUM_LOCAL,

NULL, 5, (LPBYTE) printinfo, dwNeeded,

&dwNeeded, &dwReturned);

iPrinterNumber=0;

PrintDc=CreateIC(NULL,(printinfo+iPrinterNumber)->pPrinterName,NULL,NULL);

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

i=0;

wsprintf(temp,"설정되어 있는 프린터 %d 개",dwReturned);

TextOut(hdc,20,10,temp,strlen(temp));

i++;

wsprintf(temp,"현재 프린터:%s",(printinfo+iPrinterNumber)->pPrinterName);

TextOut(hdc,20,40+i*20,temp,strlen(temp));

i++;

dwInfodata=GetDeviceCaps(PrintDc,HORZSIZE);

wsprintf(temp,"수평크기(HORZSIZE): %d",dwInfodata);

TextOut(hdc,20,40+i*20,temp,strlen(temp));

i++;

dwInfodata=GetDeviceCaps(PrintDc,VERTSIZE);

wsprintf(temp,"수직크기(VERTSIZE): %d",dwInfodata);

TextOut(hdc,20,40+i*20,temp,strlen(temp));

i++;

dwInfodata=GetDeviceCaps(PrintDc,HORZRES);

wsprintf(temp,"가로크기(HORZRES): %d",dwInfodata);

TextOut(hdc,20,40+i*20,temp,strlen(temp));

i++;

dwInfodata=GetDeviceCaps(PrintDc,VERTRES);

wsprintf(temp,"세로크기(HORZRES): %d",dwInfodata);

TextOut(hdc,20,40+i*20,temp,strlen(temp));

i++;

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_COMMAND:

switch(LOWORD(wParam))

{

case ID_SETPRINTER:

DialogBox(hInst,(LPSTR)IDD_PRINTERSET,hwnd,DialogProc);

DeleteDC(PrintDc);

PrintDc=CreateIC(NULL,

(printinfo+iPrinterNumber)->pPrinterName,NULL,NULL);

InvalidateRgn(hwnd,NULL,FALSE);

break;

case ID_PRINTPROPERTY:

//프린터를 오픈하고

if (OpenPrinter ((printinfo+iPrinterNumber)->pPrinterName, &hPrint, NULL))

{

//대화상자 실행후

PrinterProperties (hwnd, hPrint) ;

//프린터를 닫는다.

ClosePrinter (hPrint) ;

}

break;

}

break;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

//모달 대화상자 프로시저

BOOL CALLBACK DialogProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam)

{

static HWND listwnd;

int num,i;

char temp[80];

switch(iMsg)

{

case WM_INITDIALOG:

listwnd=GetDlgItem(hwnd,IDC_LIST);

for(i=0;i<(int)dwReturned;i++)

{

wsprintf(temp,"%s",(printinfo+i)->pPrinterName);

SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)temp);

}

break;

case WM_COMMAND:

switch(LOWORD(wParam))

{

case IDOK:

EndDialog(hwnd,TRUE);

return TRUE;

case IDCANCEL:

EndDialog(hwnd,TRUE);

return TRUE;

case IDC_LIST:

if(HIWORD(wParam)==LBN_SELCHANGE)

{

num=SendMessage(listwnd,LB_GETCURSEL,0,0);

iPrinterNumber=num;

}

break;

}

break;

}

return FALSE;

}

(프로그램 소스끝)

(그림 2)ExPrinterChk출력 결과

텍스트 출력하기

프린터로 텍스틀 출력을 하고자 할 경우 프린터 DC를 얻고 프린터 DC에 글자를 출력하면 됩니다. 프린터 DC를 얻고자 할경우에 사용하는 함수는 CreateDC입니다. CreaeDC는 프린터 디바이스 이름을 설정하여 만들 수 있습니다. 예를 들어 프린터 드라이버 이름이 HPC라고 할 경우 다음과 같이 하면됩니다.

HDC  *PrintDc;

PrintDc=CreateDC(NULL,"HPC",NULL,NULL);

이프린터 디바이스 이름은 EnumPrinters 함수를 이용해서 얻은 이름을 말합니다. 위의 예제와 연계시켜서 프린터 DC를 얻고자 한다면 다음과 같이 할수 있습니다.

PrintDc=CreateDC(NULL,(printinfo+iPrinterNumber)->pPrinterName,

NULL,NULL);

CreateDC는 프린터 뿐만아니라 모든 영역의 DC를 얻고하 할 때 사용할수 있는 함수입니다. 예를 들어 모니터 전체의 DC를 얻고자 한다면 다음과 같이 하면 됩니다.

hdc= CreateDC("DISPLAY",NULL,NULL,NULL);

PrintDc를 받아서 이곳에 화면에 출력하듯 문자를 출력하면 됩니다. 이때 전처리 작업이 있습니다. 프린터는 화면과 달리 인쇄시작이 있고 페이지 시작이 있으며 페이지 끝이 있습니다. 즉 인쇄를 시작하기 위해서는 인쇄시작이라는 명령을 DC에 보내야 합니다. 이때 사용하는 함수가 StartDoc입니다.

int StartDoc(

HDC hdc, // 프린터 디바이스 DC

CONST DOCINFO *lpdi // 도큐먼트 정보

);

DOCINFO 구조체는 현재 프린터 되는 정보를 설정하는 구조체입니다 이구조체는 다음과 같습니다.

typedef struct {    // di

int     cbSize;

LPCTSTR lpszDocName; //도큐먼트 이름

LPCTSTR lpszOutput;  //출력 버퍼

LPCTSTR lpszDatatype;   //윈도우 95에서만사용

DWORD   fwType;         // 윈도우 95에서만 사용

} DOCINFO;

실제 프린터로 출력을 할 경우 lpszDocName에 만 현재 프린터 내용이 무엇이라는 것을 알리는 정보만 설정하고 나머지는 사용하지 았습니다. 다음은 DOCINFO에 정보를 설정하고 StartDoc를 실행한 예입니다.

static DOCINFO docinfo={sizeof(DOCINFO),"Print Exam",NULL};

StartDoc(PrintDc,&docinfo);

위와 같이하면 프린터 관리자에서 보면 문서이름에 "Print Exam"이 등록되면서 프린터에 데이터를 출력하는 준비가 완료된것입니다.

이렇게 데이터 출력이 완료 되면 바로 페이지 시작을 알리는 StartPage 함수를 실행하고 이함수에 화면에 문자를 출력하듯 출력하면 인쇄가 됩니다. 이렇게 한후에 페이지가 끝나게 되면 EndPage를 사용하여 폼피트 명령을 설정하여 한페이지를 종료하게 됩니다.

StartPage(PrintDc);//페이지 시작알림

프린터 DC에 출력

EndPage(PrintDc); //페이지 종료 알림

모든 프린터가 종료 되면 EndDoc함수를 이용하여 도큐먼트 인쇄가 종료 되었다는 것을 알려줍니다.

EndDoc(PrintDc);

예를 들어서 2장의 데이터를 출력하고자 한다면 다음과 같이 하면됩니다.

StartDoc(PrintDc,&docinfo);//도큐먼트 시작

StartPage(PrintDc);//페이지 시작

: 첫 번째 페이지 내용을 기록한다.

EndPage(PrintDc);//첫번째 페이지 종료

StartPage(PrintDc);//페이지 시작

: 두 번째 페이지 내용을 기록한다.

EndPage(PrintDc);//두번째 페이지 종료

EndDoc(PrintDc);//인쇄 종료

프린터에 출력을 할경우는 화면에 출력하는것과는 다르게 해야 합니다. 지금까지 사용한 예제에서 화면에 출력할 때 보통 문자의 세로를 20도트 정도를 띄면서 한라인을 개행하면서 출력하였습니다. 다음과 같은 문장을 보신 기억이 있을것입니다.

for(k=0;k<100;k++)

{

TextOut(hdc,0,k*20,buff[i],strlen(buff[i]):

}

위의 문장은 모니터에서 출력을 할 경우 한문장과 한문장이 보기좋게 띄어서 출력이 됩니다. 프린터도 이럴것이다하고 출력을 한다면 아마 요즘 나오는 프린터는 글자들이 전부 겹쳐져서 출력이 될것입니다. 그이유는 프린터에 출력되는 폰트의 크기와 실제 모니터에 출력되는 폰트의 크기가 다르기 때문입니다. 보통 프린터에 출력되는 폰트는 모니터폰트에 약 3배정도는 큰 높이를 가지고 있습니다. 이것도 프린터 기종에 따라 다르기 때문에 프린터에 설정되어 있는 폰트의 평균 폭을 얻어서 이폭만큼 띄어서 출력하는 것이 좋습니다. 프린터에 설정되어 있는 폰트의 평균 폭을 얻고자 할 경우 GDI에서 설명한 GetTextMetrics를 사용하시면 됩니다.

GetTextMetrics (PrintDc, &tm) ;

cy = tm.tmHeight + tm.tmExternalLeading ;

위의 예는 PrintDc 즉 프린터 DC 에 설정되어 있는 텍스트 정보를 얻고 폰트의 세로 높이를 cy에 설정한 것입니다. 이렇게 설정을 한후에 그 값만큼 개행하여 출력을 하면 화면과 같이 글자가 정확하게 띄어져서 출력이 될것입니다.

for(k=0;k<100;k++)

{

TextOut(PrintDc,0,k*cy,buff[i],strlen(buff[i]):

}

실제로 원드 프로세서나 또는 프린터를 중요시하는 일반 어플리케이션 프로그램에서는 도트 단위보다 미터단위 또는 인치 단위로 크기를 계산합니다. GetDeviceCaps을 이용하면 용지의 크기서부터 프린팅하고자 할 때 필요한 모든 정보들이 들어 있습니다. 이정보를 이용하고 맵핑모드를 출력하고자 하는 모드로 전환한다음 폰트또한 출력에 맞게 설정하는 작업이 필요합니다. 이런 작업은 본책에서는 다루지 않습니다. 지금까지 설명을 한 것을 응용한다면 충분히 할수 있기 때문입니다. (이런 작업을 필자는 노가다 작업이라고 합니다. 즉 알고 있는 내용을 가지고 숫자계산을 하면서 프로그래밍하는 것을 의미합니다.)

텍스트 출력하기 예제 ExPrint

ExPrint는 스크롤 윈도우를 설명할 때 사용한 ScrollWnd를 변형시킨것입니다. 이예제는 ExPrint.c를 화면에 출력하고 프린팅을 누르면 데이터가 프린터로 출력됩니다. 한 장에 40줄만 출력하도록 하였습니다.

(프로그램 소스)

//프린터 출력 예제

//ExPrint.c

#include <windows.h>

#include <stdio.h>

#include "resource.h"

LPPRINTER_INFO_5 printinfo;

DWORD  dwNeeded, dwReturned ;

HANDLE szInfo;

int iPrinterNumber;

HDC PrintDc;

char Buff[100][200];

int totalline;

int pos=0;

HINSTANCE hInst;

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

BOOL CALLBACK DialogProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "ExPrint.c" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = "MENU" ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hInst=hInstance;

hwnd = CreateWindow (szAppName,

"프린터 출력예제:ExPrint",

WS_OVERLAPPEDWINDOW | WS_VSCROLL,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

FILE *out;

int i,k;

TEXTMETRIC    tm ;

HANDLE hPrint;

int cy;

static DOCINFO docinfo={sizeof(DOCINFO),"Print Exam",NULL};

switch (iMsg)

{

case WM_CREATE :

out=fopen("ExPrint.c","r");

if(out==NULL)

return 0;

for(i=0;i<100;i++)

{

if(fgets(Buff[i],200,out)==NULL)

break;

Buff[i][strlen(Buff[i])-1]=0x00;

}

totalline=i;

fclose(out);

SetScrollRange(hwnd,SB_VERT,0,totalline,FALSE);

SetScrollPos(hwnd,SB_VERT,pos,TRUE);

EnumPrinters (PRINTER_ENUM_LOCAL,

NULL, 5, (LPBYTE) "", 0, &dwNeeded, &dwReturned) ;

szInfo=GlobalAlloc(GHND,dwNeeded);

printinfo=(LPPRINTER_INFO_5)GlobalLock(szInfo);

EnumPrinters (PRINTER_ENUM_LOCAL,

NULL, 5, (LPBYTE) printinfo, dwNeeded,

&dwNeeded, &dwReturned);

iPrinterNumber=0;

return 0 ;

case WM_SIZE:

SetScrollRange(hwnd,SB_VERT,0,totalline,FALSE);

SetScrollPos(hwnd,SB_VERT,pos,TRUE);

InvalidateRgn(hwnd,NULL,TRUE);

break;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

for(i=pos;i<totalline;i++)

TabbedTextOut(hdc,0,(i-pos)*20,Buff[i],strlen(Buff[i]),1,NULL,0);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_VSCROLL:

switch(LOWORD(wParam))

{

case SB_TOP:

pos=0;

break;

case SB_BOTTOM:

pos=totalline-1;

break;

case SB_LINEUP:

pos-=1;

if(pos<0)

pos=0;

break;

case SB_LINEDOWN:

pos+=1;

if(pos==totalline)

pos=totalline-1;

break;

case SB_PAGEUP:

pos-=10;

if(pos<0)

pos=0;

break;

case SB_PAGEDOWN:

pos+=10;

if(pos>=totalline)

pos=totalline-1;

break;

case SB_THUMBTRACK:

pos=HIWORD(wParam);

break;

}

SetScrollPos(hwnd,SB_VERT,pos,TRUE);

UpdateWindow(hwnd);

InvalidateRgn(hwnd,NULL,TRUE);

break;

case WM_COMMAND:

switch(LOWORD(wParam))

{

case ID_SETPRINTER:

DialogBox(hInst,(LPSTR)IDD_PRINTERSET,hwnd,DialogProc);

DeleteDC(PrintDc);

PrintDc=CreateIC(NULL,

(printinfo+iPrinterNumber)->pPrinterName,NULL,NULL);

InvalidateRgn(hwnd,NULL,FALSE);

break;

case ID_PRINTPROPERTY:

if (OpenPrinter ((printinfo+iPrinterNumber)->pPrinterName, &hPrint, NULL))

{

PrinterProperties (hwnd, hPrint) ;

ClosePrinter (hPrint) ;

}

break;

case ID_PRINT:

PrintDc=CreateDC(NULL,(printinfo+iPrinterNumber)->pPrinterName,NULL,NULL);

if(StartDoc(PrintDc,&docinfo))

{

GetTextMetrics (PrintDc, &tm) ;

cy = tm.tmHeight + tm.tmExternalLeading ;

if(StartPage(PrintDc))

{

k=0;

for(i=0;i<totalline;i++)

{

TextOut(PrintDc,0,k*cy,Buff[i],strlen(Buff[i]));

k++;

if(k>40)

{

k=0;

EndPage(PrintDc);

StartPage(PrintDc);

}

}

EndPage(PrintDc);

}

EndDoc(PrintDc);

}

DeleteDC(PrintDc);

break;

}

break;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

//모달 대화상자 프로시저

BOOL CALLBACK DialogProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam)

{

static HWND listwnd;

int num,i;

char temp[80];

switch(iMsg)

{

case WM_INITDIALOG:

listwnd=GetDlgItem(hwnd,IDC_LIST);

for(i=0;i<(int)dwReturned;i++)

{

wsprintf(temp,"%s",(printinfo+i)->pPrinterName);

SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)temp);

}

break;

case WM_COMMAND:

switch(LOWORD(wParam))

{

case IDOK:

EndDialog(hwnd,TRUE);

return TRUE;

case IDCANCEL:

EndDialog(hwnd,TRUE);

return TRUE;

case IDC_LIST:

if(HIWORD(wParam)==LBN_SELCHANGE)

{

num=SendMessage(listwnd,LB_GETCURSEL,0,0);

iPrinterNumber=num;

}

break;

}

break;

}

return FALSE;

}

(프로그램 소스끝)

(그림 3) ExPrint출력 결과

비트맵 출력하기

비트맵을 프린터로 출력하는 부분은 DC를 받고 이곳에 간단하게 비트맵을 출력하는 방법으로는 제대로 출력이 안됩니다. 지금까지 비트맵출력에 대한 함수로 BitBlt를 설명했습니다. 이함수를 이용하여 프린터 DC에 출력을 하면 빈종이만 나올것입니다. 이유는 간단합니다. BitBlt는 장치 의존적인 비트맵을 출력할 때 사용하는 함수입니다. 즉 장치 디바이스에 일반적인 기본 팔레트가 설정되어 있고 단순히 비트맵 정보만 가지고 있을경우에 출력하는 함수입니다. 비트맵을 출력하기 위해서는 장치 의존적인 비트맵을 장치 독립적인 비트맵으로 바꾸어야 합니다. 이것을 DIB라고 합니다. 실제 DIB는 비트맵 파일 (확장자가 bmp)인 파일을 로드하고 출력할 때 또는 다른 여러 그래픽 파일을 로드하여 출력할 때 많이 사용합니다.

BitBlt는 화면을 위한 함수이지 프린터를 위한 함수가 아니기 때문에 어떤 장치에서도 출력할수 있는 DIB로 장치독립적인 비트맵을 전환시켜야 합니다. 쉽게 보면 HBITMAP핸들을 이용하여 로드하고 출력한 비트맵을 DIB형태로 전환 해야 한다는 것입니다. DIB 형태는 2개의 구조체와 한 개의 비트맵 정보를 가지고 있는 버퍼로 구성됩니다. 이 2개의 구조체가 BITMAPINFO 와 BITMAPINFOHEADER입니다.

먼저 BITMAPINFOHEADER의 구조체는 다음과 같습니다.

typedef struct tagBITMAPINFOHEADER{ // bmih

DWORD  biSize; //BITMAPINFOHEADER의 크기

LONG   biWidth; // 비트맵의 가로크기

LONG   biHeight; //비트맵의 세로크기

WORD   biPlanes; //비트맵 정보 플랜 보통 1입니다.

WORD   biBitCount //한픽셀당 사용되는 비트수

DWORD  biCompression; //압축유무

DWORD  biSizeImage; //이미지의 크기

LONG   biXPelsPerMeter; //픽셀당 미터 X

LONG   biYPelsPerMeter; //픽셀당 미터 Y

DWORD  biClrUsed; //사용되는 색수

DWORD  biClrImportant; //중요한 색

} BITMAPINFOHEADER;

BITMAPINFOHEADER구조체에서 biBitCount는 현재 비트맵의 색상수를 이야기 합니다. 예를 들어서 비트카운트가 8이면 한 개의 픽셀을 출력하기 위해 8비트가 필요한것이고 이것은 2에 8승의 색을 출력할수 있으므로 256칼라입니다. 비트가 24비트일 경우 이것은 트루 칼라이며 비트가 4일 경우 16칼라 비트가 1일경우는 2칼라 즉 흑백이 됩니다. 만약 100x100의 이미지가 있고 biBitCount가 8이라면 총 필요한 바이트 수는 10000 이되며 투르 칼라일경우에는 30000이 되는 것입니다.

BITMAPINFO 구조체는 다음과 같습니다.

typedef struct tagBITMAPINFO {

BITMAPINFOHEADER    bmiHeader; //위의 구조체 정보

RGBQUAD             bmiColors[1];//칼라 값

} BITMAPINFO;

BITMAPINFO는 위에서 설명한 BITMAPINFOHEADER과 RGBQUAD라는 칼라정보구조체를 배열로 가지고 있습니다. RGBQUAD는 다음과 같습니다.

typedef struct tagRGBQUAD { // rgbq

BYTE    rgbBlue; //청색 정보

BYTE    rgbGreen; //녹색 정보

BYTE    rgbRed; //적색 정보

BYTE    rgbReserved; //예약값 사용하지 않음

} RGBQUAD;

RGBQUAD란 색정보를 가지고 있는 구조체입니다. 컴퓨터에서는 청색 녹색 적색을 3원색으로 색을 표현합니다. 이색값을 가지고 있다는 의미입니다. 예를 들어 16칼라일 경우 BITMAPINFO는 RGBQUAD 16개의 배열을 가지고 있고 256일경우에는 256개의 배열을 가지고 있습니다. 그럼 트루칼라일경우에는 몇 개의 배열을 가지고 있을까요? 정답은 한 개도 가지고 있지 않다입니다. 이이유는 컴퓨터에서 사용하는 색에 기인하기 때문입니다. 컴퓨터에서 사용하는 총색은 적색 256값 청색 256값 녹색 256값을 가지고 있습니다. 이 3개의 조합으로 색을 표현하기 때문에 총나올수 있는 색은 256*256*256의 색입니다. 여기에서 256칼라를 가진 그림은 어떤 색을 가지게 될까요? 그냥 위의 총색중 0번부터 255번까지만 가지게 될까요? 만일 256색으로 그림을 그린다고 할 경우 사용하고 싶은 색이 256번 다음 예를들어 1000번쪽에 있다면 256으로는 이색을 표현하지 못하겠네요? 그렇지 않다는게 바로 RGBQUAD의 필요성을 말해줍니다. 즉 컴퓨터가 사용하는 모든 색중에서 현재 그림에 필요한 색을 256개 추출해서 이색정보를 배열로 저장해 놓고 화면에 출력할 경우 1번이라고 하면 색정보 배열번호 1번에서 RGB값을 얻어다 출력을 하게 하는 것입니다. 이것을 팔레트라고 합니다. 그렇기 때문에 트루칼라는 팔레트가 필요없지만 트루칼라 이하는 색을 가지고 출력하는 그림들은 팔레트가 필요한것입니다 현재 그림에서 사용하는 색의 정보를 얻기 위함이지요.

실제 보면 비트맵을 출력하고자 할 경우 2개의구조체라고 하였으나 단1개의 구조체 즉 BITMAPINFO 로 가능합니다. 이안에 또다른 구조체 BITMAPINFOHEADER 가 있기 때문입니다.

장치 독립적인 비트맵을 출력하기 위해서는 BITMAPINFO에 정보를 설정하고 비트맵을 단지 비트정보만 가지고 있는 버퍼로 만들어서 이 두 개를 가지고 출력하면 됩니다.

BITMAPINFO 정보 만들기

HBITMAP 핸들은 보통 자원에서 비트맵을 얻을 때 사용하는 핸들입니다. 이핸들을 이용하여 다음과 같이하여 비트맵을 얻는 것은 이미 설명하였습니다.

hBitmap=LoadBitmap(hInst,"BITMAP");

리소스에 BITMAP로 등록된 비트맵을 hBitmap에 로드한 형태입니다. 이 비트맵에서 비트맵 정보만 얻고자 할 경우 GetDIBits를 사용합니다.

int GetDIBits(

HDC hdc, // 디바이스 컨텍스트 핸들

HBITMAP hbmp, // 비트맵 핸들

UINT uStartScan, // 비트맵에서 얻고자 하는 첫 번째 줄

UINT cScanLines, // 비트맵에서 얻고자하는 라인수

LPVOID lpvBits, // 비트 정보만 저장하는 포인터

LPBITMAPINFO lpbi, // 비트맵 정보 핸들

UINT uUsage //팔레트수 정보

);

hbmp라는 비트맵 핸들과 BITMAPINFO를 넘겨주면 lpvBits에 장치 독립적인 비트맵에서 사용하는 비트맵 정보 형태로 값이 저장됩니다.

GetDIBits를 사용하기 위해서는 먼저 BITMAPINFO 형이 lpbi에 정보를 넣어서 구조체를 만들어야 합니다. 이구조체를 만드는 예는 다음과 같습니다.

hBitmap=LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance,"BITMAP");

//가로 320 세로 320의 256칼라를 가진 비트맵 정보를 넣을수 있는

//메모리를 만든다.

szDib=GlobalAlloc(GHND,320*320);

//이메모리를 pDib에 락킹시킨다.

pDib=(LPSTR)GlobalLock(szDib);

//bitinfo에 설정할 메모리 핸들을 만든다.

szbitinfo=GlobalAlloc(GHND,sizeof(BITMAPINFOHEADER)+	256*sizeof(RGBQUAD)+sizeof(BITMAPINFO));

//메모리를 bitinfo에 락킹시킨다.

bitinfo=(LPBITMAPINFO)GlobalLock(szbitinfo);

//BITMAPINFO정보를 넣는다.

bitinfo->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);

bitinfo->bmiHeader.biWidth=320;

bitinfo->bmiHeader.biHeight=320;

bitinfo->bmiHeader.biPlanes=1;

bitinfo->bmiHeader.biBitCount=8;

bitinfo->bmiHeader.biCompression=BI_RGB;

bitinfo->bmiHeader.biSizeImage=320*320;

bitinfo->bmiHeader.biXPelsPerMeter= 0;

bitinfo->bmiHeader.biYPelsPerMeter= 0;

bitinfo->bmiHeader.biClrUsed= 0;

bitinfo->bmiHeader.biClrImportant= 0;

//팔레트 값을 넣는다.

for(i=0;i<256;i++)

{

bitinfo->bmiColors[i].rgbRed=(BYTE)i;

bitinfo->bmiColors[i].rgbGreen=(BYTE)i;

bitinfo->bmiColors[i].rgbBlue=(BYTE)i;

}

//GetDIBbits를 사용하여 hBitmap에서 DIB를 만든다.

hdc=GetDC(hwnd);

GetDIBits(hdc,hBitmap,0,320,pDib,bitinfo,DIB_RGB_COLORS);

ReleaseDC(hwnd,hdc);

위의 예는 리소스 자원에설정된 가로 320 세로 320의 256 그레이 칼락값을 가진 비트맵을 DIB로 전환한것입니다. 위와 같이 전환하면 pDib에는 DIB정보가 설정됩니다.

DIB 출력하기

DIB를 출력하고자 할경우에사용하는 대표적인 함수는 2개입니다.SetDIBitsToDevice 와 StretchDIBits입니다.

SetDIBitsToDevice는 비트맵 정보를 그대로 DC에 출력하는 것이고 StretchDIBBits는 비트맵 정보를 확대 또는 축소 하여 출력하는 것입니다.

이함수의 형태는 다음과 같습니다.

int SetDIBitsToDevice(

HDC hdc, // 출력할 DC핸들

int XDest, // 출력할 DC의 시작 x좌표

int YDest, // 출력할 DC의 시작 y좌표

DWORD dwWidth, // DIB의 가로크기

DWORD dwHeight, // DIB의 세로크기

int XSrc, // DIB의 출력 시작 x좌표

int YSrc, // DIB의 출력 시작 y좌표

UINT uStartScan, // 첫 번째 출력 라인

UINT cScanLines, // 총 출력라인수

CONST VOID *lpvBits, // DIB정보를 가지고 있는 버퍼

CONST BITMAPINFO *lpbmi, // BITMAPINFO정보 구조체

UINT fuColorUse // 팔레트 정보

);

XDest는 출력할 DC의 x출력 좌표를 의미하며 YDest는 출력할 DC의  y좌표를 의미합니다. 화면 윈도우에 출력한다고 가정했을 경우 값이 (0,0)일경우는 비트맵이 화면의 좌측 상단부터 출력이 됩니다.(MM_TEXT 모드에서)

dwWidth와 dwHeight는 영상의 가로와 세로의 크기이며 XSrc와 YSrc는 DC에 출력할 DIB의 위치를 의미합니다. 이위치가 (0,0) 일겅우에는 비트맵의 모든 정보가 출력이 되나 (0,10)이라고 한다면 비트맵의 상단 10라인은 출력이 되지 않습니다. 다음 uStartScan는 시작라인을 의미하며 cScnaLines는 총 출력라인수를 의미합니다. 예를 들어 320*320의 영상을 그대로 출력한다고 한다면 시작 라인은 0이되고 출력라인수는 320이 되는 것입니다. fuColorUse는  팔레트 정보로 DIB_PAL_COLORS 일경우에는 16비트 배열 조합의 칼라 테이블로써 로직 팔레트를 의미하며 DIB_RGB_COLORS 는 RGB값의 칼라를 의미합니다. 보통 DIB_RGB_COLORS를 많이 사용합니다.

nt StretchDIBits(

HDC hdc, // 출력 DC

int XDest, // 출력 DC의 시작점 X

int YDest, // 출력 DC의 시작점 Y

int nDestWidth, // 출력 DC에서 가로크기

int nDestHeight, // 출력 DC에서 세로크기

int XSrc, // DIB의 시작점 X

int YSrc, // DIB의 시작점 Y

int nSrcWidth, // DIB의 가로크기

int nSrcHeight, // DIB의 세로크기

CONST VOID *lpBits, // DIB 정보 버퍼

CONST BITMAPINFO *lpBitsInfo, // BITMAPINFO

UINT iUsage, // 팔레트 정보

DWORD dwRop // ROP 코드

);

StretchDIBits는 실제 크기가 nSrcWidth와 nSrcHeight인 DIB를 XDest와 YDest에 nDestWidth와 nDestHeight의 크기로 확대 또는 축소하여 출력하는 함수입니다.

dwRop는 ROP코드입니다. 이값들은 도움말을 참조하시기 바랍니다.

프린터로 출력할 경우 StretchDIBits를 많이 사용합니다. 이유는 화면에 보인것과 달리 프린터에 그림을 출력할 경우 매우 작게 나타나기 때문입니다. 예를 들어 320*320의 크기를 화면에 출력할 경우 보기 좋게 적당히 크게 나오나 프린터로 출력하면 매우 작은 형태로 출력됩니다. 이것을 적당히 변경을 하기 위해서는 StretchDIBits를 사용하게 됩니다. StretchDIBits를 이용하여 프린터에 한 장에전체로 그림을 출력하는 예가 다음과 같습니다.

//프린터 정보 DC를 얻고

iPrintInfo=CreateIC(NULL,printinfo->pPrinterName,NULL,NULL);

//가로와 세로의 픽셀수를 cy와 cx에 얻는다.

cy=GetDeviceCaps(iPrintInfo,VERTRES);

cx=GetDeviceCaps(iPrintInfo,HORZRES);

DeleteDC(iPrintInfo);

//출력 프린터 DC를 만들고

PrintDc=CreateDC(NULL,printinfo->pPrinterName,NULL,NULL);

//인쇄시작

if(StartDoc(PrintDc,&docinfo))

{

//페이지 시작

if(StartPage(PrintDc))

{

//그림을 용지 전에체 출력한다.

StretchDIBits(PrintDc,

0,0,cx,cy,0,0,320,320,pDib,bitinfo,DIB_RGB_COLORS,SRCCOPY);

//페이지 종료

EndPage(PrintDc);

}

//인쇄 종료

EndDoc(PrintDc);

}

DeleteDC(PrintDc);

비트맵 출력하기 예제 ExBitPrint

ExBitPrint는 리소의 비트맵을 화면에 출력하고 SetDIBitsToDevice 함수와 StretchDIBits를 이용하여 비트맵을 출력하는 예제입니다. 실제 인쇄를 해보면 SetDIBitsToDevice로 출력하면 그림이 아이콘 모양으로 매우 작게 나오는 것을 보실수 있을것입니다.

(프로그램 소스)

//프린터 출력 예제 ExBitPrint

//ExBitPrint

#include <windows.h>

#include <stdio.h>

#include "resource.h"

LPPRINTER_INFO_5 printinfo;

DWORD  dwNeeded, dwReturned ;

HANDLE szInfo;

static int iPrinterNumber;

HDC PrintDc;

HINSTANCE hInst;

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

BOOL CALLBACK DialogProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "ExBitPrint" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = "MENU" ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hInst=hInstance;

hwnd = CreateWindow (szAppName,

"ExBitPrint",

WS_OVERLAPPEDWINDOW | WS_VSCROLL,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

static HANDLE szDib;

static LPSTR pDib;

LPBITMAPINFO bitinfo;

HANDLE szbitinfo;

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc;

HDC iPrintInfo;

PAINTSTRUCT ps ;

HANDLE hPrint;

static HBITMAP hBitmap;

static DOCINFO docinfo={sizeof(DOCINFO),"Print Exam",NULL};

int i,cx,cy;

switch (iMsg)

{

case WM_CREATE :

EnumPrinters (PRINTER_ENUM_LOCAL,

NULL, 5, (LPBYTE) "", 0, &dwNeeded, &dwReturned) ;

szInfo=GlobalAlloc(GHND,dwNeeded);

printinfo=(LPPRINTER_INFO_5)GlobalLock(szInfo);

EnumPrinters (PRINTER_ENUM_LOCAL,

NULL, 5, (LPBYTE) printinfo, dwNeeded,

&dwNeeded, &dwReturned);

hBitmap=LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance,"BITMAP");

//가로 320 세로 320의 256칼라를 가진 비트맵 정보를 넣을수 있는

//메모리를 만든다.

szDib=GlobalAlloc(GHND,320*320);

//이메모리를 pDib에 락킹시킨다.

pDib=(LPSTR)GlobalLock(szDib);

//bitinfo에 설정할 메모리 핸들을 만든다.

szbitinfo=GlobalAlloc(GHND,sizeof(BITMAPINFOHEADER)+ 256*sizeof(RGBQUAD)+sizeof(BITMAPINFO));

//메모리를 bitinfo에 락킹시킨다.

bitinfo=(LPBITMAPINFO)GlobalLock(szbitinfo);

//BITMAPINFO정보를 넣는다.

bitinfo->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);

bitinfo->bmiHeader.biWidth=320;

bitinfo->bmiHeader.biHeight=320;

bitinfo->bmiHeader.biPlanes=1;

bitinfo->bmiHeader.biBitCount=8;

bitinfo->bmiHeader.biCompression=BI_RGB;

bitinfo->bmiHeader.biSizeImage=320*320;

bitinfo->bmiHeader.biXPelsPerMeter= 0;

bitinfo->bmiHeader.biYPelsPerMeter= 0;

bitinfo->bmiHeader.biClrUsed= 0;

bitinfo->bmiHeader.biClrImportant= 0;

//팔레트 값을 넣는다.

for(i=0;i<256;i++)

{

bitinfo->bmiColors[i].rgbRed=(BYTE)i;

bitinfo->bmiColors[i].rgbGreen=(BYTE)i;

bitinfo->bmiColors[i].rgbBlue=(BYTE)i;

}

iPrinterNumber=0;

//GetDIBbits를 사용하여 hBitmap에서 DIB를 만든다.

hdc=GetDC(hwnd);

GetDIBits(hdc,hBitmap,0,320,pDib,bitinfo,DIB_RGB_COLORS);

ReleaseDC(hwnd,hdc);

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

SetDIBitsToDevice(hdc,

0,0,320,320,0,0,0,320,pDib,bitinfo,DIB_RGB_COLORS);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_COMMAND:

switch(LOWORD(wParam))

{

case ID_SETPRINTER:

DialogBox(hInst,(LPSTR)IDD_PRINTERSET,hwnd,DialogProc);

DeleteDC(PrintDc);

PrintDc=CreateIC(NULL,

(printinfo+iPrinterNumber)->pPrinterName,NULL,NULL);

InvalidateRgn(hwnd,NULL,FALSE);

break;

case ID_PRINTPROPERTY:

if (OpenPrinter ((printinfo+iPrinterNumber)->pPrinterName, &hPrint, NULL))

{

PrinterProperties (hwnd, hPrint) ;

ClosePrinter (hPrint) ;

}

break;

case ID_PRINT:

PrintDc=CreateDC(NULL,(printinfo+iPrinterNumber)->pPrinterName,NULL,NULL);

if(StartDoc(PrintDc,&docinfo))

{

if(StartPage(PrintDc))

{

SetDIBitsToDevice(PrintDc,

0,0,320,320,0,0,0,320,pDib,bitinfo,DIB_RGB_COLORS);

EndPage(PrintDc);

}

EndDoc(PrintDc);

}

DeleteDC(PrintDc);

break;

case ID_PRINTFULL:

//프린터 정보 DC를 얻고

iPrintInfo=CreateIC(NULL,(printinfo+iPrinterNumber)->pPrinterName,NULL,NULL);

//가로와 세로의 픽셀수를 cy와 cx에 얻는다.

cy=GetDeviceCaps(iPrintInfo,VERTRES);

cx=GetDeviceCaps(iPrintInfo,HORZRES);

DeleteDC(iPrintInfo);

//출력 프린터 DC를 만들고

PrintDc=CreateDC(NULL,(printinfo+iPrinterNumber)->pPrinterName,NULL,NULL);

//인쇄시작

if(StartDoc(PrintDc,&docinfo))

{

//페이지 시작

if(StartPage(PrintDc))

{

//그림을 용지 전에체 출력한다.

StretchDIBits(PrintDc,

0,0,cx,cy,0,0,320,320,pDib,bitinfo,DIB_RGB_COLORS,SRCCOPY);

//페이지 종료

EndPage(PrintDc);

}

//인쇄 종료

EndDoc(PrintDc);

}

DeleteDC(PrintDc);

break;

}

break;

case WM_DESTROY :

GlobalUnlock(szDib);

GlobalFree(szDib);

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

//모달 대화상자 프로시저

BOOL CALLBACK DialogProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam)

{

static HWND listwnd;

int num,i;

char temp[80];

switch(iMsg)

{

case WM_INITDIALOG:

listwnd=GetDlgItem(hwnd,IDC_LIST);

for(i=0;i<(int)dwReturned;i++)

{

wsprintf(temp,"%s",(printinfo+i)->pPrinterName);

SendMessage(listwnd,LB_ADDSTRING,0,(LPARAM)temp);

}

break;

case WM_COMMAND:

switch(LOWORD(wParam))

{

case IDOK:

EndDialog(hwnd,TRUE);

return TRUE;

case IDCANCEL:

EndDialog(hwnd,TRUE);

return TRUE;

case IDC_LIST:

if(HIWORD(wParam)==LBN_SELCHANGE)

{

num=SendMessage(listwnd,LB_GETCURSEL,0,0);

iPrinterNumber=num;

}

break;

}

break;

}

return FALSE;

}

(프로그램 소스끝)

(그림 4) ExBitPrint 출력 결과

프린터 취소하기

프린터로 출력하는 도중에 만일 프린터 출력 에러일경우에는 프린터 출력이 종료되어야 합니다. 예를 들어 1000페이지 정도의 데이터를 프린터로 출력한다고 할 경우 프린터 출력 에러가 발생되었는데도 계속적으로 StartPage와 EndPage를 한다면 이상한 글자가 찍힌 종이가 프린터에서 계속 나오게 될것이고 결국 프린터를 끄고 윈도우를 다시시작하는등의 복잡함이 발생됩니다. 이럴 경우 사용하는 방법이 SetAbortProc입니다. 이함수의 형은 다음과 같습니다.

int SetAbortProc(

HDC hdc, // 출력 DC

ABORTPROC lpAbortProc // Abort프로시저

);

SetAbortProc는 프린터 출력시 설정해 주어야 합니다. 이때부터 lpAbortProc함수가 실행되면서 프린터 출력에 에러가 있는지를 확인합니다.

이함수는 일반적으로 다음과 같은 형태를 가지고 있습니다.

BOOL CALLBACK AbortProc(HDC hdcPrint,int iCode)

{

MSG msg;

while(PeekMessage(&msg,NULL,0,0,PM_REMOVE))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return TRUE;

}

프린터로 출력을 하는동안 GDI는 계속적으로 AbortProc를 호출하고 에러가 없을 경우 iCode는 0으로 설정됩니다. 프린터 작업이 이상이 없을경우에는 PeekMessage함수는 TRUE를 리턴하고 따라서 TranslateMessage와 DispatchMessage가 실행됩니다. 만일 PeekMessage함수가 FALSE를 리턴하면 이 루프가 종료되게 됩니다. 이함수와 SetAbortProc를 이용하는 예는 다음과 같습니다.

SetAbortProc(PrintDc,AbortProc);

//인쇄시작

if(StartDoc(PrintDc,&docinfo))

{

//페이지 시작

if(StartPage(PrintDc))

{

밴딩 기법

밴딩이란 큰용량의 데이터를 출력하고자 할 경우 그내용을 잘라서 출력하는 것을 의미합니다. 예를 들어 한페이지의 데이터를 출력하고자 할 경우 이데이터를 가로로 몇 개의 박스로 나누어서 출력을 한는 것입니다. 요즘 나오는 데부분의 프린터는 이밴딩 기법이 필요하지 않습니다. 그이유는 적어도 한페이지 정도는 기억하고 프린팅할수 있게 되어 있으며 설사 이렇게 되어 있지 않더라도 프린터를 제작하는 회사에서 제공하는 프린터 드라이버에 이 밴딩 기법이 내장되어 있기 때문입니다. 허나 도트 매트릭스 프린터나 또는 특수한 프린터의 경우 한페이지의 용량의 데이터를 출력할수 없는 경우도 있기에 이때 사용하는 방법을 설명하고자 합니다.

이밴딩 기법을 이용할 때 사용하는 함수가 ExtEscape함수입니다.

int ExtEscape(

HDC hdc, // 출력 DC

int nEscape, // escape 함수 코드

int cbInput, //input 구조체안에 들어갈 바이트수

LPCSTR lpszInData, //input 데이터 포인터

int cbOutput, // 출력 구조체 안에 들어갈 바이트 수

LPSTR lpszOutData //출력 포인터

);

프린터에 출력할 밴드를 얻고 이것에 의해서 출력하는 예는 다음과 같습니다.

StartDoc(PrintDc,&docinfo);

StartPage(PrintDc);

//밴드를 나누고

ExtEscape(PrintDc,NEXTBAND,0,NULL,sizeof(rect),(LPSTR)&rect);

//나누어진 박스내용이 출력될때까지 실행

while(!IsRectEmpty(&rect))

{

프린터 출력

//에러가 발생하면 탈출한다.

if(ExtEscape(PrintDc,NEXTBAND,0,NULL,sizeof(rect),(LPSTR)&rect)<0)

break;

}

(팁)

프린터로 출력할 때 때로는 내용이 가로로 출력하고자 할경우가 있습니다. 이럴경우에 사용되는 함수 SetLandScape()를 소개합니다. 이 소스는 Visual C++ 의 Help에 서 찾으실수 있을것입니다. 가로로 출력할 때 출력전에 SetLandScape()함수를 실행시키고 프린터 출력하듯이 출력하시면 됩니다.

void SetLandScape()

{

PRINTDLG   pd;

pd.lStructSize = (DWORD) sizeof(PRINTDLG);

if (GetPrinterDeviceDefaults(&pd))

{

DEVMODE FAR* pDevMode =

(DEVMODE FAR*)::GlobalLock(m_hDevMode);

LPDEVNAMES lpDevNames;

LPTSTR lpszDriverName, lpszDeviceName, lpszPortName;

HANDLE hPrinter;

if (pDevMode)

{

pDevMode->dmOrientation = DMORIENT_LANDSCAPE;

lpDevNames = (LPDEVNAMES)GlobalLock(pd.hDevNames);

lpszDriverName

= (LPTSTR )lpDevNames + lpDevNames->wDriverOffset;

lpszDeviceName

= (LPTSTR )lpDevNames + lpDevNames->wDeviceOffset;

lpszPortName

= (LPTSTR )lpDevNames + lpDevNames->wOutputOffset;

OpenPrinter(lpszDeviceName, &hPrinter, NULL);

DocumentProperties(NULL,hPrinter,lpszDeviceName,pDevMode,

pDevMode, DM_IN_BUFFER|DM_OUT_BUFFER);

ClosePrinter(hPrinter);

GlobalUnlock(m_hDevNames);

GlobalUnlock(m_hDevMode);

}

}

}

(팁끝)

(잔소리)

지금까지 공부하셨다면 Win32 SDK 에 대해서는 전반적으로 다 공부하신것입니다. 혹이 이해가 안간다고 하셔도 다시 앞으로는 절대 가지 말기를 부탁드립니다. 이제 뒷부분의 C++과 MFC로의 프로그래밍을 공부하시게 되면 앞부분을 다시 이해할수 있고 정리할수 있는 기회가 됩니다. 또한 MFC는 SDK에서 프로그래밍할 때 복잡한 부분들을 쉽게 처리해주는 다양한 기능이 있기 때문에 때로눈 SDK에서 이해가 안가는 부분이 있었더라도 이것을 극복해주는 기회를 제공할것입니다.

□SDK마스타 하고 MFC로 넘어가야지□ 라고 생각하셨다면 다시한번 고려를 부탁드립니다.

(잔소리끝)