본문 바로가기

TechLog

닷넷에서 텍스트를 프린터로 출력하기

닷넷에서 텍스트를 프린터로 출력하기

닷 넷 프레임워크에서는 텍스트를 그대로 프린터에 출력하는 기능(Raw 데이터 출력이라고 함)을 지원하지 않습니다. GDI+에서 DrawText()등의 메서드를 이용하여 출력할 수는 있지만, 때때로 텍스트만을 출력해야 하거나, 특수한 프린터(감열 영수증 프린터 등)를 사용할 때에는 Raw 데이터 출력이 필요할 때가 있습니다. 그런 기능을 하는 클래스를 하나 만들어 보았습니다.

 

 

안녕하세요 : )

 

닷넷 프레임워크에서 아무리 찾아봐도 Raw 프린트 기능을 지원하는 클래스가 없길래, WinAPI를 사용해서 하나 만들어 보았습니다.

 

그럼 시작합니다 : )

 

 

전체 소스 코드

 

 

 

 

using System;
using System.Runtime.InteropServices;

namespace System.IO
{
/// <summary>
/// Printer
에 대한 요약 설명입니다.
/// </summary>
public class Printer
{
  public Printer()
  {
   //
   // TODO:
여기에 생성자 논리를 추가합니다.
   //
  }

  [DllImport("winspool.drv")]
  private static extern bool OpenPrinter(
   [In]string pPrinterName,
   [In,Out]ref System.IntPtr hPrinter,
   [In]byte[] pDefault
   );
 
  [DllImport("winspool.drv")]
  private static extern bool ClosePrinter(
   [In,Out]System.IntPtr hPrinter
   );
  
  [DllImport("winspool.drv")]
  unsafe private static extern bool WritePrinter(
   [In]System.IntPtr hPrinter,
   [In,Out]string pBuf,
   [In]int cbBug,
   ref int pcWritten
   );

  [DllImport("winspool.drv")]
  unsafe private static extern int StartDocPrinter(
   [In]System.IntPtr hPrinter,
   [In]int nLevel,
   [In]ref DOC_INFO_1 pDocInfo
   );

  [DllImport("winspool.drv")]
  unsafe private static extern bool StartPagePrinter(
   [In]System.IntPtr hPrinter
   );
  
  [DllImport("winspool.drv")]
  unsafe private static extern bool EndPagePrinter(
   [In]System.IntPtr hPrinter
   );

  [DllImport("winspool.drv")]
  unsafe private static extern bool EndDocPrinter(
   [In]System.IntPtr hPrinter
   );

  public static void PrintText(string strPrintText)
  {
   System.IntPtr handle = System.IntPtr.Zero;
   if(System.Drawing.Printing.PrinterSettings.InstalledPrinters.Count == 0)
   {
    throw new Exception("
시스템에 프린터가 없음");
   }
   
   //
무조건 첫번째 프린터를 가져온다
   string printername = System.Drawing.Printing.PrinterSettings.InstalledPrinters[0];
   DOC_INFO_1 pd = new DOC_INFO_1();
   pd.pDatatype = "RAW";
   pd.pDocName = "Printer Class"; //
프린터 대기열에 보일 문서의 이름
   pd.pOutputFile = "";
 
   //
기록된 바이트 수
   int dwWritten = 0;
   //
기록될 글자의 갯수. 일반 String에서는 한글도 길이가 1로 취급되는 것에 주의
   int dwBytesOfText =
    System.Text.Encoding.GetEncoding("euc-kr").GetByteCount(strPrintText);

   OpenPrinter(printername, ref handle, null);
   StartDocPrinter(handle, 1, ref pd);
   StartPagePrinter(handle);
   
   WritePrinter(handle, strPrintText, dwBytesOfText, ref dwWritten);
   EndPagePrinter(handle);
   EndDocPrinter(handle);

   ClosePrinter(handle);
  }

  public struct DOC_INFO_1
  {
   public string     pDocName;
   public string     pOutputFile;
   public string     pDatatype;
  }
}
}

 

 

 

 

 

핸들을 마샬링하기

 

이 클래스는 WinAPI를 사용하고 있다. (WinAPI를 가져오는 방법에 대해서는 예전에 소개했던, C#에서 Native DLL(WinAPI) 사용하기를 참고) 예전 내용과 다른 점이라면, 파라메터에 LPHANDLE 타입의 변수가 있다는 점이다. 예를 들어 OpenPrinter() WinAPI 함수의 원형은 다음과 같다 :

 

BOOL OpenPrinter(

  LPTSTR pPrinterName,         // printer or server name

  LPHANDLE phPrinter,          // printer or server handle

  LPPRINTER_DEFAULTS pDefault  // printer defaults

);

 

LPHANDLE HANDLE에 대한 포인터 타입이지만, 닷넷에서는 변수에 대한 포인터이든 변수이든 알아서 마샬링을 해 준다. 대신에, 그 타입에 적절한 마샬링 방법을 정해 주거나, 적절한 타입의 변수를 대응시켜주어야 한다. MSDN에 나와있는 Platform Invoke Data Types는 다음과 같다 :

 

Unmanaged type in Wtypes.h

Unmanaged C language type

Managed class name

Description

HANDLE

void*

System.IntPtr

32 bits

BYTE

unsigned char

System.Byte

8 bits

SHORT

short

System.Int16

16 bits

WORD

unsigned short

System.UInt16

16 bits

INT

int

System.Int32

32 bits

UINT

unsigned int

System.UInt32

32 bits

LONG

long

System.Int32

32 bits

BOOL

long

System.Int32

32 bits

DWORD

unsigned long

System.UInt32

32 bits

ULONG

unsigned long

System.UInt32

32 bits

CHAR

char

System.Char

Decorate with ANSI.

LPSTR

char*

System.String or System.StringBuilder

Decorate with ANSI.

LPCSTR

Const char*

System.String or System.StringBuilder

Decorate with ANSI.

LPWSTR

wchar_t*

System.String or System.StringBuilder

Decorate with Unicode.

LPCWSTR

Const wchar_t*

System.String or System.StringBuilder

Decorate with Unicode.

FLOAT

Float

System.Single

32 bits

DOUBLE

Double

System.Double

64 bits

 

(자세한 내용은 MSDN Library에서 Platform Invoke, Data Types 항목을 참고하기 바란다)

 

위 표에서 보면 HANDLE에 대응되는 타입으로 System.IntPtr을 제시하고 있다. OpenPrinter() WinAPI에서 사용되는 LPHANDLE HANDLE에 대한 포인터이지만, 포인터인 것과는 상관없이 마샬링이 가능하므로, System.IntPtr 타입으로 파라메터를 선언해서 함수를 호출할 수 있게 된다.

 

구조체와 관련된 부분은 코드를 보면 이해할 수 있을 것이다.

 

 

WinAPI 함수와 호환을 위한 문자열 관련 코드

 

그리고 또 짚고 넘어갈 부분으로 다음 코드가 있다 :

 

int dwBytesOfText =       System.Text.Encoding.GetEncoding("euc-kr").GetByteCount(strPrintText);

 

위 코드는 프린트될 문자열의 바이트 길이를 구하는 코드이다. WritePrinter() WinAPI 함수가 프린트될 문자열의 길이를 요구하기 때문에 필요한 것인데, 위와 같이 GetEncoding() 메서드로 euc-kr에 대한 인코더를 구해서 직접 바이트 수를 구하는 이유는, 이렇게 하지 않으면 한글 문자에 대한 바이트 수가 제대로 전달되지 않기 때문이다. WinAPI 기반 함수에서는 한글로 된 한 글자가 2바이트를 차지하기 때문에, 그냥 string에서 Length 속성을 가져다가 dwBytesOfText 값으로 넘겨주면 한글도 한 바이트로 처리되므로 나중에 출력이 올바로 되지 않을 수 있다.

 

예를 들자면, "abcd"같은 경우 문자열의 길이로 4가 나와야 하지만, "가나다라"의 경우엔 8이 리턴되어야 출력될 문자열의 바이트 수가 맞기 때문에 (물론 euc-kr 인코딩 기준에서) 올바른 출력이 가능해진다.

 

 

정리

 

프 린터 클래스 하나를 만드는데 pinvoke에 문자열 인코딩까지 나와버렸습니다. WinAPI에 접근하는 것은 역시 그렇게 녹록하지는 않네요. pinvoke에 관련해서 HANDLE 까지 나오긴 했습니다만, 이것 말고도 배열에 관련된 내용이라든가, COM 객체를 호출할 때 VARIANT 타입을 주고받는 내용 등 몇 가지가 더 있습니다. 필수적인 것은 아니지만, 익혀두시면 도움이 될 내용이므로 미리 봐 두시는 것도 좋을 것 같습니다.

( 아니면 Kenial이 계속 아티클을 올릴 때까지 기다리시는 것도..^^; )

 

그럼 : )