Barkod Printer, Seri Port, USB, C# üzerine… – 3

Bundan önceki ilk 2 makalede sırasıyla Barkod yazıcılara ve C# içinden erişime değinmiştik. Ülkemizde yaygın olarak bulunan Zebra ve Argox yazıcılar için, barkod komut dili olan PPLA PPLB ZPL gibi diller ile tasarım yapmayı ve bu tasarımları barkod yazıcıya göndermeyi anlatmıştım.

Bu makalede ise  C# içinden  USB ile bağlı olan yazıcıya, data göndermek üzere gliştirilmiş kütüphaneyi ve C# içinden kullanımını göstereceğim.

aşağıdaki kodu DirectPrint.cs olarak kaydedip, var olan projenize item olarak eklemeniz yeterlidir.

// DirectPrint.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;

namespace VYP
{
	public class RawPrinterHelper
	{
		// Structure and API declarions:
		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
		public class DOCINFOA
		{
			[MarshalAs(UnmanagedType.LPStr)]
			public string pDocName;
			[MarshalAs(UnmanagedType.LPStr)]
			public string pOutputFile;
			[MarshalAs(UnmanagedType.LPStr)]
			public string pDataType;
		}
		[DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
		public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);

		[DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
		public static extern bool ClosePrinter(IntPtr hPrinter);

		[DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
		public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);

		[DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
		public static extern bool EndDocPrinter(IntPtr hPrinter);

		[DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
		public static extern bool StartPagePrinter(IntPtr hPrinter);

		[DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
		public static extern bool EndPagePrinter(IntPtr hPrinter);

		[DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
		public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);

		// SendBytesToPrinter()
		// When the function is given a printer name and an unmanaged array
		// of bytes, the function sends those bytes to the print queue.
		// Returns true on success, false on failure.
		public static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount)
		{
			Int32 dwError = 0, dwWritten = 0;
			IntPtr hPrinter = new IntPtr(0);
			DOCINFOA di = new DOCINFOA();
			bool bSuccess = false; // Assume failure unless you specifically succeed.

			di.pDocName = "Mavis RAW Document (Barcode)";
			di.pDataType = "RAW";

			// Open the printer.
			if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero))
			{
				// Start a document.
				if (StartDocPrinter(hPrinter, 1, di))
				{
					// Start a page.
					if (StartPagePrinter(hPrinter))
					{
						// Write your bytes.
						bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
						EndPagePrinter(hPrinter);
					}
					EndDocPrinter(hPrinter);
				}
				ClosePrinter(hPrinter);
			}
			// If you did not succeed, GetLastError may give more information
			// about why not.
			if (bSuccess == false)
			{
				dwError = Marshal.GetLastWin32Error();
			}
			return bSuccess;
		}

		public static bool SendFileToPrinter(string szPrinterName, string szFileName)
		{
			// Open the file.
			FileStream fs = new FileStream(szFileName, FileMode.Open);
			// Create a BinaryReader on the file.
			BinaryReader br = new BinaryReader(fs);
			// Dim an array of bytes big enough to hold the file's contents.
			Byte[] bytes = new Byte[fs.Length];
			bool bSuccess = false;
			// Your unmanaged pointer.
			IntPtr pUnmanagedBytes = new IntPtr(0);
			int nLength;

			nLength = Convert.ToInt32(fs.Length);
			// Read the contents of the file into the array.
			bytes = br.ReadBytes(nLength);
			// Allocate some unmanaged memory for those bytes.
			pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
			// Copy the managed byte array into the unmanaged array.
			Marshal.Copy(bytes, 0, pUnmanagedBytes, nLength);
			// Send the unmanaged bytes to the printer.
			bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, nLength);
			// Free the unmanaged memory that you allocated earlier.
			Marshal.FreeCoTaskMem(pUnmanagedBytes);
			return bSuccess;
		}

		public static bool SendStringToPrinter(string szPrinterName, string szString)
		{
			IntPtr pBytes;
			Int32 dwCount;
			// How many characters are in the string?
			dwCount = szString.Length;
			// Assume that the printer is expecting ANSI text, and then convert
			// the string to ANSI text.
			pBytes = Marshal.StringToCoTaskMemAnsi(szString);
			// Send the converted ANSI string to the printer.
			SendBytesToPrinter(szPrinterName, pBytes, dwCount);
			Marshal.FreeCoTaskMem(pBytes);
			return true;
		}
	}

}

VS içindeki Projeniz şuna benzer bir şekilde görünür…

Artık, nereden isterseniz erişebileceğiniz çok düzgün bir sınıfımız oldu. SendBytesToPrinter içinde, yazdırma yöneticisine gönderilecek döküman adı bilgisini görebilirsiniz. Siz barkod bastır komutu verdiğinizde, Windows yazdırma yöneticinde bu isim görünecektir. Dolayısıyla projenize uygun şık bir isim girebilirsiniz.

Programınızdan kullanımı ise, bir önceki makalede gösterildiği gibidir. Yine de kısaca tekrar etmek gerekirse

RawPrinterHelper.SendStringToPrinter(printerName, barStrings);

şeklindeki bir yapı işinizi görür. Buradaki printerName, windows sürücüleri ile kurulu yazıcınızın adıdır. barstrings ise göndereceğiniz barkod komutlarıdır.

Benzer şekilde, paralel port kullanan nokta vuruşlu yazıcılara da direk USB üzerinden aynı sınıfı kullanarak erişebilirsiniz.

Barkod Printer, Seri Port, USB, C# üzerine… – 2

makalenin ilk versiyonunda (http://www.mavis.com.tr/blog/?p=233) Barkod printer üzerine genel bir giriş yapmıştık.

Barkod yazıcıya anlayacağı dilden (PPLA, PPLB, ZPL ..) etiket tasarlamanın en kolay yolu, genelde barkod yazıcının kendi CD si ile birlikte gelen tasarım programını kullanmaktır. (PPLA PPLB komutlarını açıklayan PDF dosyayı buradan indirebilirsiniz)  Etiket görsel olarak istenilen şekilde tasarlanır. Yazıcıdan çıktı denemeleri alınır. En ideal etiket tasarımında karar verildikten sonra, yazıcıya gönderilecek dosyanın kaynak kodu elde edilir. Bazı programlarda PPLA / PPLB kodunu göster seçeneği var, bazı programlarda ise bunu yapmak için Windows arabiriminde “Print To File” işaretlenerek, yazıcıya göndereceği veriyi bir dosyaya yazdırması sağlanır.

Yukarıdaki resim, Zebra yazıcılar ile birlikte ücretsiz gelen ZebraDesigner programında Print To File işleminin yapılmasını gösterir. Etiket, görsel olarak istenildiği gibi tasarlanır. Yazıcıya gönderme işleminde, görsel tasarım dışında, baskı tipi (termal transfer, direk baskı – ribon-), renk doygunluğu (density) etiket boy ve boşluk bilgileri gibi başlık (Header) bilgileri de yer alır. Bunları tek tek kodlamak için ciddi bir Programmers Guide hatmedilmelidir. (Kısmen eğlenceli de olsa, çoğu kez zaman kaybından başka bişey de değildir)

“Print To File” seçeneği işaretlenerek oluşturulmuş örnek bir barkod tasarım bilgisi :

Q160,019
q831
rN
S1
D7
ZT
JB
OD
R335,0
N
GW104,104,6,48,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïÿÿÿÿÿÿÿÿÿÿ¿
A152,24,2,2,1,1,N,"ZD:01.01.10"
A152,48,2,2,1,1,N,"QC:235-167"
A152,72,2,2,1,1,N,"01.06.2010"
A152,96,2,2,1,1,N,"03L131051BH"
P1

ilk başta yer alan satırlar (1..10 arası) barkodun başlık bilgileri, geri kalan yazılacak verilere ilişkin koordinatlar ve data ları içerir. GW ile başlayan satır, etikette bir grafik olduğunu gösterir. Bu örnekte ben grafik kullandım, çünkü C# içinden  grafiği düzgün bastırabilmek için kodda bazı düzenlemeler yapmak gerekmektedir.

Gerçek hayatta, her barkod üzerindeki bilgiler değişeceği için, etiketin üzerindeki bu bilgileri de değiştirerek yazıcıya göndermek gerekmektedir. Bu yüzden orijinal etiket dosyasını aşağıdaki gibi değiştirdim.

Q160,019
q831
rN
S1
D7
ZT
JB
OD
R335,0
N
GW104,104,6,48,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïÿÿÿÿÿÿÿÿÿÿ¿
A152,24,2,2,1,1,N,"*LINE1*"
A152,48,2,2,1,1,N,"*LINE2*"
A152,72,2,2,1,1,N,"*LINE3*"
A152,96,2,2,1,1,N,"*LINE4*"
P1

amacım; ** arasında geçen değişkenleri *LINE1* gibi, program ile RunTime anında değiştirebilmek.

Bunun için, Print To File yaparak elde ettiğim dosyayı, edit edip (not defteri vb. bir programla) yukarıdaki gibi düzenleyerek, bildiğim bir yere (diyelim ki C:\PrnTmp klasörüne) bildiğim bir isimle (mesela Template.prn) kaydedelim.

Şimdi, bu dosyayı okuyup, dinaik olarak değiştirip yazıcıya gönderen C# kodum aşağıdaki gibi olacaktır.

		public void PrintBarcode(String L1, String L2, String L3, String L4)
		{
			String tempFile = "C:\\PrnTmp\\Template.prn";
			String PrinterName = "ZDesigner TLP 2844";
			StreamReader SR = new StreamReader(tempFile, Encoding.Default);
			String all = SR.ReadToEnd();
			SR.Close();

			all = all.Replace("*Line1*", L4);
			all = all.Replace("*Line2*", L3);
			all = all.Replace("*Line3*", L2);
			all = all.Replace("*Line4*", L1);

			RawPrinterHelper.SendStringToPrinter(PrinterName, all);

		}

Bu rutin, verdiğim 4 değişkeni orijinal barkod tasarımında yerine koyar ve bu verdiğim değerlere göre barkod basar.
Grafiklerin doğru basılabilmesi için, StremReader nesnesi, Encoding.Default parametresi ile çağırılmalıdır.

Buradaki, RawPrinterHelper USB bağlantı üzerinden direk barkod basan sınıfın adıdır. Bir sonraki makalede bu sınıftan bahsetmeyi düşünüyorum. Tabii, seri ya da paralel port üzerinden yazıcıya erişmek için bu sınıfı kullanmaya gerek olmadığını belirtmeye gerek yok sanırım.

Makalenin son bölümünde, bu sınıf açık kaynak kodu ile birlikte yer almaktadır.

Makalenin Son Bölümü : Bölüm – 3

Barkod Printer, Seri Port, USB, C# üzerine… – 1

Herhangi bir uygulama içinde barkod basmak gerektiğinde, ilk akla gelen yöntem, ihtiyaca uygun bir barkod yazıcı kullanmaktır.

Barkod yazıcı kullanarak barkod basmak gerektiğinde kullanılabilecek 2 ana yöntem vardır

  1. Yazıcıyı windows sürücüleri ile yüklemek ve herhangi bir windows yazıcısı gibi kullanmak
  2. Yazıcının desteklediği PPLA, PPLB, ZPL gibi barkod basma komut setlerini kullanarak yazıcı ile konuşmak

1. yöntem, yazıcıyı windowsa herhangi bir yazıcı gibi tanıtmaktır. Yazıcı ile beraber gelen CD vb. ortamdan sürücüleri yüklenir. Muhtemelen yardımcı programlar ve belki birkaç barkod hazırlama utility si de yüklenecektir. Genelde tek başına çalışacak şekilde tasarlanmış tüm bu programlar, programcının direk olarak işine yaramaz. Programcı, kendi programından barkod basma ihtiyacı hissettiğinde, zaten windows tarafından tanınmış olan barkod yazıcıya windows yazdırma yöneticisini kullanarak erişir. Bu yöntem, her ne kadar jenerik bir yöntem olsa da, dezavantajları vardır. Bunlara değinirsek;

Windows yazıcısı olarak kullanılan barkod yazıcı, barkod basma yeteneğini kaybeder. Herhangi bir lazer ya da inkjet yazıcı nasıl davranırsa o da öyle davranır. Tek farkı, normal yazıcılardan farklı olarak sensörleri olduğundan, barkod bitimi, sağ – sol boşlukları, etiket uzunluğu gibi değerleri hala işlevsel olmaya devam edecektir. Fakat asıl farklılık katan yeteneğini, yani barkod basma işlevini yitirmemiş olsa bile kullanamaz duruma gelmiştir. Bu durumda yapılabilecek en kolay yöntem, bir True Type Barkod fontu kulanmaktır. Parayla satılan ya da ücretsiz dağıtılan Code39 fontu internette aratılarak kolayca bulunabilir. (Bulamayanlar ya da test edilmiş bir tanesi ile çalışmak isteyenler bize mail atabilir – info@mavis.com.tr) Bundan sonrası programcının çıktı alacağı form üzerine Code39 fontu ile oluşturulmuş label (etiket) vb. koyması ve bunların çıktısını almaya kalıyor. (Bu yazının konusu olmadığından ve de oldukça basit bir iş olduğundan ayrıntıya girmeye burada son veriyorum.)

Windows yazıcısı olarak kullanmanın diğer bir dezavantajı, basit bir etiket basmak için bile, araya windows sürücülerini sokmak, grafik ya da form tasarımı yapmak (ya da kod ile runtime anında oluşturmak)  ve basılacak barkodu windows yazdırma yöneticisine teslim etmektir. Basit bir barkod için, (tasarıma göre değişmekle birlikte) çok fazla raw data yazdırma yöneticisine gönderilir. Yazdırma yöneticisi o an meşgulse, bunları kuyruğa atar  ki çoğu kez hızlı bir işlem olması beklenen barkod basma işi, duruma göre bir sonraki bahara kalabilir.

Diğer bir sorun, barkod yazıcının verdiği mesajları alamamaktır. Barkod yazıcı normalde, etiket başarıyla basıldı, etiket sonu, etiket bitti, ribbon bitti vb. gibi bir çok hata mesajını anlık olarak döndürür. Fakat araya windows sürücü modeli girdiğinden, programcı bu bilgilerin hiçbirine erişemez. Belirli bir süre sonra, windows görev çubuğunda bir ünlem işareti görebilir (eğer şansı varsa). Otomasyon projeleri gibi, çok hızlı, realtime çalışan uygulamalarda bu model akla bile getirilmemelidir.

Yazı dizisinin 2. bölümünde, barkod tasarlama ve tasarlanmış bu barkodu C# içinden dinamik olarak değiştirip yazıcıya gönderme konusuna yer verdim. (http://www.mavis.com.tr/blog/?p=412)

Makalenin son bölümünde ise, USB yazıcılara direk erişim ve tıpkı seri/paralel port ile erişimlerde olduğu gibi direk veri gönderme teknikleri anlatılacaktır.

Mavis VYP, tüm barkod yazıcılarla çalışabilen son derece güçlü bir barkod sınıfına sahiptir. USB, Seri, Paralel herhangi bir porttan, tüm printerlar ile çalışan uygulamamızın, USB printer arabirimini burada sunmuş olacağım. Böylece Mavis VYP nin engine kısmına ve dolayısıyla teknolojik altyapısına daha detaylı bakmış olacağız.