본문 바로가기

Application Programming Interface/Windows API

RAW형 데이터의 프린터 출력을 위한 Windows API 호출 과정

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

 프린터로 문서를 출력하기 위한 Windows API 호출 과정은 다음과 같다.

  1. 컴퓨터에 장착된 프린터를 찾아 그 중 하나를 선택한다. (EnumPrinters)
  2. 선택한 프린터를 열어 그 핸들을 얻는다. (OpenPrinter)

1. EnumPrinters 함수를 사용하여 프린터 정보 얻기

 EnumPrinters 함수는 다음과 같이 선언되어 있다.

BOOL EnumPrinters(
    _In_  DWORD   Flags,
    _In_  LPTSTR  Name,
    _In_  DWORD   Level,
    _Out_ LPBYTE pPrinterEnum,
    _In_  DWORD   cbBuf,
    _Out_ LPDWORD pcbNeeded,
    _Out_ LPDWORD pcReturned);

(참조: https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd162692(v=vs.85).aspx)

Level별 PRINTER_INFO 구조체

 EnumPrinters 함수를 다루기에 앞서, Windows API에서 제공하는 프린터 정보 관련 구조체를 설명한다.

 Windows API는 프린터 정보의 상세 정도에 따라 PRINTER_INFO_1, PRINTER_INFO_2, PRINTER_INFO_3, PRINTER_INFO_4, PRINTER_INFO_5, PRINTER_INFO_6등의 구조체를 제공한다. PRINTER_INFO_의 뒤에 붙는 숫자는 레벨(level)이라 부르며, Windows API에서 프린터에 대한 여러 정보 중 어떤 것을 선택적으로 얻을 지에 대한 옵션으로 쓰인다.

PRINTER_INFO_1

PRINTER_INFO_1는 가장 간단한 정보(프린터 이름, 프린터에 대한 설명)를 나타낸다.

typedef struct _PRINTER_INFO_1 {
    DWORD Flags;
     LPTSTR pDescription;
    LPTSTR pName;
    LPTSTR pComment;
} PRINTER_INFO_1, *PPRINTER_INFO_1;

자세한 내용은 MSDN 레퍼런스(https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd162844(v=vs.85).aspx)를 참고한다.

PRINTER_INFO_2

PRINTER_INFO_2는 프린터에 대한 상세한 정보(서버, 물리적 위치, 포트 번호 등)를 전달한다.

typedef struct _PRINTER_INFO_2 {
    LPTSTR pServerName;
    LPTSTR pPrinterName;
    LPTSTR pShareName;
    LPTSTR pPortName;
    LPTSTR pDriverName;
    LPTSTR pComment;
    LPTSTR pLocation;
    LPDEVMODE pDevMode;
    LPTSTR pSepFile;
    LPTSTR pPrintProcessor;
    LPTSTR pDatatype;
    LPTSTR pParameters;
    PSECURITY_DESCRIPTOR pSecurityDescriptor;
    DWORD Attributes;
    DWORD Priority;
    DWORD DefaultPriority;
    DWORD StartTime;
    DWORD UntilTime;
    DWORD Status;
    DWORD cJobs;
    DWORD AveragePPM;
} PRINTER_INFO_2, *PPRINTER_INFO_2;

자세한 내용은 MSDN 레퍼런스(https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd162845(v=vs.85).aspx)를 참고한다.

PRINTER_INFO_3

PRINTER_INFO_3은 프린터의 보안 설정을 전달한다.

typedef struct _PRINTER_INFO_3 {
    PSECURITY_DESCRIPTOR pSecurityDescriptor;
} PRINTER_INFO_3, *PPRINTER_INFO_3;

자세한 내용은 MSDN 레퍼런스(https://msdn.microsoft.com/en-us/library/windows/desktop/dd162846(v=vs.85).aspx)를 참고한다.

PRINTER_INFO_4

PRINTER_INFO_4는 프린터의 위치 정보(원격에 있는지 로컬에 있는지에 대한 여부와 그 주소)를 나타낸다.

typedef struct _PRINTER_INFO_4 {
    LPTSTR pPrinterName;
    LPTSTR pServerName;
    DWORD Attributes;
} PRINTER_INFO_4, *PPRINTER_INFO_4;

자세한 내용은 MSDN 레퍼런스(https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd162847(v=vs.85).aspx)를 참고한다.

PRINTER_INFO_5

PRINTER_INFO_5PRINTER_INFO_4보다 더 상세한 정보를 전달한다.

typedef struct _PRINTER_INFO_5 {
    LPTSTR pPrinterName;
     LPTSTR pPortName;
    DWORD  Attributes;
    DWORD  DeviceNotSelectedTimeout;
    DWORD  TransmissionRetryTimeout;
} PRINTER_INFO_5, *PPRINTER_INFO_5;

자세한 내용은 MSDN 레퍼런스(https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd162848(v=vs.85).aspx)를 참고한다.

PRINTER_INFO_6

PRINTER_INFO_6은 프린터의 상태(오프라인/온라인, 인쇄중인지 여부, 용지 걸림 여부 등)를 전달하기 위한 구조체이다.

typedef struct _PRINTER_INFO_6 {
    DWORD dwStatus;
} PRINTER_INFO_6, *PPRINTER_INFO_6;

자세한 내용은 MSDN 레퍼런스(https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd162849(v=vs.85).aspx)를 참고한다.

PRINTER_INFO_7

PRINTER_INFO_7GetPrinterSetPrinter에서 사용할 각종 상태 플래그를 전달하는 구조체이다.

typedef struct _PRINTER_INFO_7 {
    LPTSTR pszObjectGUID;
    DWORD  dwAction;
} PRINTER_INFO_7, *PPRINTER_INFO_7;

자세한 내용은 MSDN 레퍼런스(https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd162850(v=vs.85).aspx)를 참고한다.

PRINTER_INFO_8

PRINTER_INFO_8은 전역 범위에서 설정된 용지 방향 및 해상도 관련 정보를 전달하는 구조체이다.

typedef struct _PRINTER_INFO_8 {
    LPDEVMODE pDevMode;
} PRINTER_INFO_8, *PPRINTER_INFO_8;

자세한 내용은 MSDN 레퍼런스(https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd162851(v=vs.85).aspx)를 참고한다.

PRINTER_INFO_9

PRINTER_INFO_9PRINTER_INFO_8와 같은 내용이지만, 사용자 계정 별로 설정된 정보를 전달하는 구조체이다.

typedef struct _PRINTER_INFO_9 {
    LPDEVMODE pDevMode;
} PRINTER_INFO_9, *PPRINTER_INFO_9;

자세한 내용은 MSDN 레퍼런스(https://msdn.microsoft.com/en-us/library/windows/desktop/dd162852(v=vs.85).aspx)를 참고한다.

컴퓨터에 장착된 프린터 열거하기

 프린터를 열거하는 방법은 다음의 단계로 진행된다.

  1. EnumPrinters로 프린터 정보를 보관할 메모리의 크기 얻기
  2. 위 단계에서 얻은 크기만큼 메모리 할당
  3. 할당한 메모리를 EnumPrinters로 전달하여 프린터 정보를 복사하고 그 갯수를 얻기

 첫 번째 과정은 프린터 정보가 열거된 배열을 얻기 위해 몇 바이트가 필요한지를 묻는 과정이다. 이를 위해 지정할 EnumPrinters의 매개변수는 Flags, Level, pcbNeeded, pcReturned이며 나머지는 0 내지는 NULL로 지정한다.

 Flags는 비트 플래그로서 다음 상수들을 OR(|) 조합하여 사용한다.

PRINTER_ENUM_LOCAL
로컬에 설치된 프린터들의 목록을 가져온다.
PRINTER_ENUM_NAME
Name 매개변수로 지정한 이름을 갖는 프린터를 모두 가져온다.
PRINTER_ENUM_SHARED
공유된 프린터들의 목록을 가져온다.
PRINTER_ENUM_CONNECTIONS
이전에 연결했던 적이 있는 프린터들의 목록을 가져온다.
PRINTER_ENUM_NETWORK
네트워크 위치에 있는 프린터들의 목록을 가져온다. (레벨 1에 한함)
PRINTER_ENUM_REMOTE
네트워크 위치에 있는 프린터 및 프린터 서버들의 목록을 가져온다. (레벨 1에 한함)
PRINTER_ENUM_CATEGORY_3D
3D 프린터들의 목록을 가져온다,
PRINTER_ENUM_CATEGORY_ALL
3D 프린터를 포함해서 모든 프린터들의 목록을 가져온다.

 Level 1을 지정해서 컴퓨터에 연결된 프린터들의 기본 정보를 얻는 과정은 다음과 같다. 먼저, 프린터 정보를 얻기 위해 몇 바이트의 메모리가 필요한지를 묻는 과정이다. 두 개의 변수가 우선 선언되는데, cbPrinterInfo1는 필요한 메모리의 크기를, nPrinterInfo1는 컴퓨터에 연결된 프린터의 갯수를 나타낸다.

DWORD cbPrinterInfo1 = 0;
DWORD nPrinterInfo1 = 0;

EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 1, NULL, 0, &cbPrinterInfo1, &nPrinterInfo1);
/* TODO: Something */

그러면 cbPrinterInfo1 변수를 통해 몇 바이트의 메모리가 필요한지 그 값이 반환된다. 이 때, nPrinterInfo1의 값은 의미가 없으므로 무시한다. cbPrinterInfo1를 통해 요구한 바이트만큼 메모리로부터 동적할당 받는다.

HLOCAL hLocal = NULL;
LPPRINTER_INFO_1 lpPrinterInfo1 = NULL;
DWORD cbPrinterInfo1 = 0;
DWORD nPrinterInfo1 = 0;

EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 1, NULL, 0, &cbPrinterInfo1, &nPrinterInfo1);

hLocal = LocalAlloc(LHND, cbPrinterInfo1); assert(hLocal != NULL);
lpPrinterInfo1 = LocalLock(hLocal); assert(lpPrinterInfo1 != NULL);
/* TODO: Something */
LocalUnlock(hLocal);
LocalFree(hLocal);

 할당된 메모리의 주소를 제공하여 다시 EnumPrinters 함수를 호출한다. 이 때는 Flags, Level, pcbNeeded, pcReturned와 함께 pPrinterEnum, cbBuf에도 매개변수를 지정한다.

HLOCAL hLocal = NULL;
LPPRINTER_INFO_1 lpPrinterInfo1 = NULL;
DWORD cbPrinterInfo1 = 0;
DWORD nPrinterInfo1 = 0;

EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 1, NULL, 0, &cbPrinterInfo1, &nPrinterInfo1);

hLocal = LocalAlloc(LHND, cbPrinterInfo1); assert(hLocal != NULL);
lpPrinterInfo1 = LocalLock(hLocal); assert(lpPrinterInfo1 != NULL);
EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 1, lpPrinterInfo1, cbPrinterInfo1, &cbPrinterInfo1, &nPrinterInfo1);
LocalUnlock(hLocal);
LocalFree(hLocal);

 위와 같이 실행하면 동적할당한 메모리에 프린터에 대한 정보가 복사된다. 우선 그 목록을 출력해 본다.

HLOCAL hLocal = NULL;
LPPRINTER_INFO_1 lpPrinterInfo1 = NULL;
DWORD cbPrinterInfo1 = 0;
DWORD nPrinterInfo1 = 0;
DWORD dwIndex = 0;

EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 1, NULL, 0, &cbPrinterInfo1, &nPrinterInfo1);

hLocal = LocalAlloc(LHND, cbPrinterInfo1); assert(hLocal != NULL);
lpPrinterInfo1 = LocalLock(hLocal); assert(lpPrinterInfo1 != NULL);

EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 1, lpPrinterInfo1, cbPrinterInfo1, &cbPrinterInfo1, &nPrinterInfo1);
for (dwIndex = 0; dwIndex < nPrinterInfo1; dwIndex++)
{
    printf("%d: %S\n", (int)i, lpPrinterInfo1[i].pName);
    printf("- Description: %S\n", lpPrinterInfo1[i].pDescription);
    printf("- Comment: %S\n", lpPrinterInfo1[i].pComment);
}

LocalUnlock(hLocal);
LocalFree(hLocal);

 컴퓨터에 프린터가 설치되어 있다면 아래 결과와 같이 각 프린터에 맞는 정보가 출력될 것이다.

 전체 소스코드는 다음과 같다.

#include <windows.h>
#include <assert.h>
#include <locale.h>
#include <stdio.h>

int main(int argc, char * argv[])
{
    HLOCAL hPrinterInfo1 = NULL;
    LPPRINTER_INFO_1 lpPrinterInfo1 = NULL;
    DWORD cbPrinterInfo1 = 0;
    DWORD nPrinterInfo1 = 0;
    
    // Wide Char를 출력하기 위한 로케일 설정
    setlocale(LC_ALL, "");

    // 프린터 정보를 열거하기 위해 메모리에 몇 바이트가 필요한지를 측정함.
    EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 1, NULL, 0, &cbPrinterInfo1, &nPrinterInfo1);

    // 필요한 만큼의 메모리를 동적 할당
    assert(hPrinterInfo1 = LocalAlloc(LHND, cbPrinterInfo1));
    assert(lpPrinterInfo1 = LocalLock(hLocal));

    // 프린터 정보를 메모리로 복사하기
    assert(EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 1, (LPBYTE)lpPrinterInfo1, cbPrinterInfo1, &cbPrinterInfo1, &nPrinterInfo1));

    // 프린터 개수만큼 반복하며 정보 출력
    for (i = 0; i < nPrinterInfo1; i++)
    {
        printf("%d: %S\n", (int)i, lpPrinterInfo1[i].pName);
        printf("- Description: %S\n", lpPrinterInfo1[i].pDescription);
        printf("- Comment: %S\n", lpPrinterInfo1[i].pComment);
    }

    // 메모리 해제
    lpPrinterInfo1 = NULL;
    LocalUnlock(hLocal);
    LocalFree(hLocal);

    return 0;
}

OpenPrinter/ClosePrinter, StartDocPrinter/EndDocPrinter, StartPagePrinter/EndPagePrinter를 호출하기

 OpenPrinter/ClosePrinter를 사용하여 위에서 열거한 프린터 중 하나를 골라 프린터를 열고 닫는다. 프린터를 연 후에는 StartDocPrinter/EndDocPrinter 함수로 문서를 구분하고, 한 문서에 대해 StartPagePrinter/EndPagePrinter 함수로 페이지를 구분한다. 호출 순서는 OpenPrinter - { StartDocPrinter - { StartPagePrinter - EndPagePrinter } - EndDocPrinter } - ClosePrinter의 순이며, 괄호는 반복 호출이 가능함을 의미한다.

Level별 DOC_INFO 구조체

 PRINTER_INFO 구조체와 마찬가지로 문서 정보도 그 내용에 따라 몇 개의 레벨로 분화된다.

DOC_INFO_1

DOC_INFO_1 구조체는 일반적인 문서 정보를 프린터에게 전달한다.

typedef struct _DOC_INFO_1 {
    LPTSTR pDocName;
    LPTSTR pOutputFile;
    LPTSTR pDatatype;
} DOC_INFO_1;

 자세한 내용은 MSDN 레퍼런스(https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd162471(v=vs.85).aspx를 참조한다.)

DOC_INFO_2

DOC_INFO_2 구조체는 프린터와 데이터 교환을 할 것인지 여부를 전달한다. dwMode0이면 일반적인 출력을 수행하고, DI_CHANNEL이면 WritePrinterReadPrinter 함수를 통해 데이터를 교환한다.

typedef struct _DOC_INFO_2 {
    LPTSTR pDocName;
    LPTSTR pOutputFile;
    LPTSTR pDatatype;
    DWORD  dwMode;
    DWORD  JobId;
} DOC_INFO_2, *PDOC_INFO_2;

 자세한 내용은 MSDN 레퍼런스(https://msdn.microsoft.com/en-us/library/windows/desktop/dd162472(v=vs.85).aspx를 참조한다.)

DOC_INFO_3

DOC_INFO_3 구조체는 프린터와 데이터 교환을 할 것인지 여부를 전달한다. dwFlagsDI_MEMORYMAP_WRITE 또는 NULL의 값을 가질 수 있다.

typedef struct _DOC_INFO_3 {
    LPTSTR pDocName;
    LPTSTR pOutputFile;
    LPTSTR pDatatype;
    DWORD  dwFlags;
} DOC_INFO_3, *PDOC_INFO_3;

 자세한 내용은 MSDN 레퍼런스(https://msdn.microsoft.com/en-us/library/windows/desktop/dd162473(v=vs.85).aspx를 참조한다.)

프린터를 열고 닫는 함수

 프린터를 여는 함수는 OpenPrinter, 프린터를 닫는 함수는 ClosePrinter이다. 이 과정에서 프린터를 나타내는 핸들(HANDLE)이 사용된다.

BOOL OpenPrinter(
    _In_ LPTSTR pPrinterName,
    _Out_ LPHANDLE phPrinter,
    _In_ LPPRINTER_DEFAULTS pDefault
);
BOOL ClosePrinter(
    _In_ HANDLE hPrinter
);

 pPrinterName은 열려고 하는 프린터의 이름이다. 이 이름은 위의 EnumPrinter 함수를 통해 얻은 PRINTER_INFO_xxx 구조체의 pName 값을 그대로 쓴다.

 phPrinterhPrinter는 프린터에 대한 핸들이다. OpenPrinterphPrinter를 통해 프린터 핸들을 반환한다.

 pDefault는 프린터 설정에 대한 초기 값이다. NULL을 지정해도 되고, MSDN 레퍼런스(https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd162839(v=vs.85).aspx를 참고해서 구조체를 직접 구성해도 된다.

문서의 시작과 끝을 지정하는 함수

 문서의 시작을 알리는 함수는 StartDocPrinter이고 문서의 끝을 알리는 함수는 EndDocPrinter이다.

DWORD StartDocPrinter(
    _In_ HANDLE hPrinter,
    _In_ DWORD  Level,
    _In_ LPBYTE pDocInfo
);
BOOL EndDocPrinter(
    _In_ HANDLE hPrinter
);

 hPrinterOpenPrinter를 통해 얻은 프린터 핸들이다.

 LevelDOC_INFO_xxx 구조체의 레벨이다.

 pDocInfo는 문서 정보의 구조체이다.

페이지의 시작과 끝을 지정하는 함수

 페이지의 시작과 끝을 지정하는 함수는 StartPagePrinterEndPagePrinter이다.

BOOL StartPagePrinter(
    _In_ HANDLE hPrinter
);
BOOL EndPagePrinter(
    _In_ HANDLE hPrinter
);

 

 다음은 RAW 데이터를 프린터로 인쇄하는 예이다. 여기서 RAW 데이터란, 프린터가 인식 가능한 형태의 각종 인쇄 명령어 집합이다. 이 명령어 집합은 프린터 제작사마다 상이하며, HP의 Printer Control Language(PCL) 또는 Adobe의 PostScript가 일반적이다. 이와 관련된 내용은 아래 URL을 참조한다.

  1. Wikipedia: Page Description Language (https://en.wikipedia.org/wiki/Page_description_language)
  2. [MS-RPRN]: Glossary - MSDN - Microsoft (https://msdn.microsoft.com/en-us/library/cc244530.aspx#gt_a04e8872-d39e-4ae8-a870-b2e36b266302
LPBYTE lpDataRAW = NULL;
DWORD cbDataRAW = 0;
DWORD dwDataRAW = 0;

TCHAR szPrinter = NULL;
HANDLE hPrinter = NULL;
HANDLE hDocInfo1 = NULL;
LPDOC_INFO_1 lpDocInfo1 = NULL;

/* 생략: 프린터를 선택해서 그 이름을 szPrinter에 복사하는 과정 */
/* 생략: RAW 데이터를 lpDataRAW에 복사하는 과정 */

// 문서 정보를 위한 메모리를 동적 할당
hDocInfo1 = LocalAlloc(LHND, sizeof(DOC_INFO_1));
lpDocInfo1 = LocalLock(hDocInfo1);

// 문서 정보 세팅
lpDocInfo1->pDocName = TEXT("Untitled-Example1"); // 출력할 문서의 이름 (프린터 대기열에 표시될 이름)
lpDocInfo1->pOutputFile = TEXT(""); // 실제 출력으로 하지 않고, 파일로 인쇄할 경우 그 경로를 지정
lpDocInfo1->pDatatype = TEXT("RAW");

// 프린터 열기
if (OpenPrinter(szPrinter, &hPrinter, NULL))
{
    assert(hPrinter != INVALID_HANDLE_VALUE);

    // 프린터 대기열에 문서 추가
    if (StartDocPrinter(hPrinter, 1, (LPBYTE)lpDocInfo1))
    {
        // 대기열 문서에 페이지 추가
        if (StartPagePrinter(hPrinter))
        {
            // lpDataRAW: 프린터에 전달할 RAW 바이너리
            // cbDataRAW: lpDataRAW의 크기 (바이트)
            // dwDataRaw: WritePrinter가 프린터에 전달한 바이트 수
            WritePrinter(hPrinter, lpDataRAW, cbDataRAW, dwDataRAW);

            assert(cbDataRAW == dwDataRAW);

            EndPagePrinter(hPrinter);
        }
        else
        {
            printf("Error: StartPagePrinter.\n");
        }
        
        EndDocPrinter(hPrinter);
    }
    else
    {
        printf("Error: StartDocPrinter.\n");
    }

    ClosePrinter(hPrinter);
}
else
{
    printf("Error: OpenPrinter.\n");
}