After a few days of searching the net on how exactly I can go about printing an arbitrary string to an arbitrary printer on windows, I finally came up with this code.
LPBYTE pPrinterEnum;
DWORD pcbNeeded, pcbReturned;
PRINTER_INFO_2 *piTwo = NULL;
HDC printer;
EnumPrinters(PRINTER_ENUM_LOCAL,NULL,2,NULL,0,&pcbNeeded,&pcbReturned);
pPrinterEnum = new BYTE[pcbNeeded];
if (!EnumPrinters(PRINTER_ENUM_LOCAL,NULL,2,pPrinterEnum,pcbNeeded,&pcbNeeded,&pcbReturned)) {
qDebug() << "In Print, could not enumerate printers";
} else {
piTwo = ((PRINTER_INFO_2*)pPrinterEnum);
for (int i = 0; i < pcbReturned; i++) {
QString name = QString::fromWCharArray(piTwo[i].pPrinterName);
if (this->m_printer_path == name) {
const WCHAR * driver = L"WINSPOOL\0";
printer = CreateDC(NULL,piTwo[i].pPrinterName,NULL,NULL);
}
}
}
if (printer == 0) {
qDebug() << "No Printer HDC";
return;
} else {
qDebug() << "Printer seems okay!";
}
qDebug() << "Starting Document";
DOCINFO di;
memset( &di, 0, sizeof( di ) );
di.cbSize = sizeof( di );
WCHAR * text = new WCHAR[ba.length()];
QString(ba).toWCharArray(text);
StartDoc(printer,&di);
qDebug() << "Writing text";
TextOut(printer,0, 0, text, ba.length());
qDebug() << "Text Written";
EndPage(printer);
qDebug() << "Page ended";
DeleteDC(printer);
qDebug() << "DC Deleted";
Some basic caveats:
1) I cannot use QPrinter. I need to write raw text, no postscript. 2) I do not know the name of the printer until the user sets it, and I do not know the size of the string to print until the user creates it.
Additional information:
a) The printer works, I can print from Notepad, Chrome, just about everything to the printer that I want. b) I am willing to implement just about any hack. Ones like write it to a text file and issue the copy command don't seem to work, that is, I get a failed to initialize device error.
This works: notepad /P Documents/test_print.txt This does not work: copy Documents\test_print.txt /D:EPSON_TM_T20 copy Documents\test_print.txt /D \MYCOMPUTER\epson_tm_t20 (leads to access denied, printer is shared) print Documents\test_print.txt (Unable to initialize device)
I have tried just about every recommended way to print a text file from the command line, just doesn't work. I have installed, reinstalled driver, added printer, mucked with ports and done it all again.
Obviously there is something simple about windows printing that I am missing due to inexperience.
What I want to accomplish is:
1) Best Scenario( Directly write text to the printer) 2) Second best scenario (Write text to a file, then execute some program to print it for me) Notepad adds an annoying amount of space to the bottom of the printout wasting paper.
Since the program is for end users, I have to find a way to do this automagically for them, so I can't expect them to click checkbox a in tab 36 after running command obscure_configuration from a powershell.
Any help would be greatly appreciated.
/Jason
UPDATE
This is the working code, before I go through an spruce it up a bit, which prints the contents of a QByteArray to a thermal printer.
qDebug() << "Executing windows code";
BOOL bStatus = FALSE;
DOC_INFO_1 DocInfo;
DWORD dwJob = 0L;
DWORD dwBytesWritten = 0L;
HANDLE hPrinter;
wchar_t * name = new wchar_t[this->m_printer_path.length()+1];
this->m_printer_path.toWCharArray(name);
name[this->m_printer_path.length() + 1] = 0;
qDebug() << "opening printer";
bStatus = OpenPrinter(name,&hPrinter, NULL);
if (bStatus) {
qDebug() << "Printer opened";
DocInfo.pDocName = L"My Document";
DocInfo.pOutputFile = NULL;
DocInfo.pDatatype = L"RAW";
dwJob = StartDocPrinter( hPrinter, 1, (LPBYTE)&DocInfo );
if (dwJob > 0) {
qDebug() << "Job is set.";
bStatus = StartPagePrinter(hPrinter);
if (bStatus) {
qDebug() << "Writing text to printer";
bStatus = WritePrinter(hPrinter,ba.data(),ba.length(),&dwBytesWritten);
EndPagePrinter(hPrinter);
} else {
qDebug() << "could not start printer";
}
EndDocPrinter(hPrinter);
qDebug() << "closing doc";
} else {
qDebug() << "Couldn't create job";
}
ClosePrinter(hPrinter);
qDebug() << "closing printer";
} else {
qDebug() << "Could not open printer";
}
if (dwBytesWritten != ba.length()) {
qDebug() << "Wrong number of bytes";
} else {
qDebug() << "bytes written is correct " << QString::number(ba.length()) ;
}
Note: I do owe an apology to Skizz, what he wrote was actually helpful in debugging the fundamental issue. The characters in the QByteArray are preformatted specifically for the printer, the problem is, they contain several NULL bytes. When trying to send them to the printer, this causes TextOut to truncate the text, only printing the first few lines. Using WritePrinter, as suggested in the answer ignores null bytes and accepts a void * and a length, and just puts it all there.
Further, his response recommending the use of PrintDlg did work to fectch the correct printer HDC, the issus is that, the user first chooses a printer once, and then doesn't need to choose it each time they print, because they will be printing alot (It's a Point of Sale).
The problem with getting the printer HDC from the string name was due to not adding the all important NULL byte to wchar_* which was solved this way:
wchar_t * name = new wchar_t[this->m_printer_path.length()+1];
this->m_printer_path.toWCharArray(name);
name[this->m_printer_path.length() + 1] = 0;
In the above, m_printer_path is a string representation of the name of the printer taken from Print Manager.
Because the string has all the formatting necessary for the printer, there's no need to worry about new lines, or any formatting.
All three answers to this question were actually very helpful in implementing the final working solution, and I have voted up each answer, and I appreciate the time each person took in responding.