3

在网上搜索了几天关于如何将任意字符串打印到 Windows 上的任意打印机之后,我终于想出了这段代码。

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";

一些基本的警告:

1) 我不能使用 QPrinter。我需要写原始文本,没有后记。2)在用户设置之前我不知道打印机的名称,并且在用户创建它之前我不知道要打印的字符串的大小。

附加信息:

a) 打印机工作正常,我可以从记事本、Chrome 以及几乎所有我想要的打印机打印。b)我愿意实施任何黑客攻击。将其写入文本文件并发出复制命令似乎不起作用,也就是说,我得到了一个初始化设备错误的失败。

这有效:notepad /P Documents/test_print.txt 这无效:复制 Documents\test_print.txt /D:EPSON_TM_T20 copy Documents\test_print.txt /D \MYCOMPUTER\epson_tm_t20(导致访问被拒绝,打印机被共享)打印文档\test_print.txt(无法初始化设备)

我已经尝试了几乎所有推荐的从命令行打印文本文件的方法,但都不起作用。我已经安装,重新安装了驱动程序,添加了打印机,弄乱了端口并再次完成了这一切。

显然,由于缺乏经验,我缺少一些关于 Windows 打印的简单内容。

我想要完成的是:

1)最佳方案(直接将文本写入打印机) 2)次佳方案(将文本写入文件,然后执行一些程序为我打印)记事本在打印输出的底部增加了令人讨厌的空间浪费纸张。

由于该程序是针对最终用户的,我必须找到一种自动为他们执行此操作的方法,因此我不能指望他们在从 powershell 运行命令 obscure_configuration 后单击选项卡 36 中的复选框 a。

任何帮助将不胜感激。

/杰森

更新

这是工作代码,在我稍微整理一下之前,它将 QByteArray 的内容打印到热敏打印机。

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()) ;
}

注意:我确实应该向 Skizz 道歉,他写的内容实际上有助于调试基本问题。QByteArray 中的字符是专门为打印机预先格式化的,问题是,它们包含几个 NULL 字节。当试图将它们发送到打印机时,这会导致 TextOut 截断文本,只打印前几行。正如答案中所建议的,使用 WritePrinter 会忽略空字节并接受 void * 和长度,然后将其全部放在那里。

此外,他推荐使用 PrintDlg 的回复确实可以找到正确的打印机 HDC,问题是,用户首先选择打印机一次,然后不需要每次打印时都选择它,因为他们将打印很多(这是一个销售点)。

从字符串名称获取打印机 HDC 的问题是由于没有将所有重要的 NULL 字节添加到 wchar_* ,这是通过这种方式解决的:

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;

在上面,m_printer_path 是从 Print Manager 中获取的打印机名称的字符串表示形式。

因为字符串具有打印机所需的所有格式,所以无需担心换行或任何格式。

这个问题的所有三个答案实际上对实施最终的工作解决方案非常有帮助,我对每个答案都投了赞成票,我感谢每个人花时间做出回应。

4

5 回答 5

5

大多数现代打印机不会对提供的数据执行任何形式的布局处理。因此,向打印机发送一系列字符充其量只是以某种默认字体打印出页面侧面的一行文本。回车也可能有效。

现代打印机通常做的是使用打印机理解的预处理数据打印页面,并定义在何处打印以及如何打印。所有这些预处理都在主机 PC 上完成,并将结果发送到打印机。这就是您通常安装打印机驱动程序的原因 - 这些驱动程序获取用户数据(无论是简单的文本文件还是 DTP 页面)并将其转换为打印机可以理解的语言。

这样做的结果是,将原始文本发送到打印机可能无法正常工作。

然后你就遇到了拥有多台具有不同属性和语言的打印机的问题。

因此,在 Windows 中,所有这些都被抽象为打印机设备上下文对象。这与图形设备上下文具有相同的界面,但您创建它的方式不同。

Win32 API 有一个通用对话框让用户选择打印机。使用PrintDlgEx函数允许用户选择打印机。然后使用返回的 DC 将文本绘制到页面上。

于 2012-09-11T15:00:17.823 回答
3

有几篇 MSDN 文章描述了如何将原始数据(打印机控制代码等)发送到打印机。

于 2012-09-11T21:43:42.370 回答
1

你有正确的想法(尽管你应该有StartPageEndDoc呼吁匹配)。问题是TextOut只绘制一行文本。它不会将长字符串分成多行等。您需要这样做(或查找代码来执行此操作)。

如果您知道文本将始终适合单个页面,您可能会用调用替换您TextOutDrawTextEx,这可以进行基本的换行、制表符扩展等。

于 2012-09-11T16:36:50.383 回答
1

为什么不试试 QPrint.. 它使用 Generic Text Only 驱动程序打印原始文本

QString prn("^XA^FO121,41^A0N,19,15^FDABC DEFGHIJKLMNOPQRSTUVWXYZ^FS^XZ");
QPrinter printer(QPrinterInfo::defaultPrinter());  //the default printer is "Generic / Text Only"

QTextDocument doc(prn);
doc.print(&printer);
于 2014-01-09T23:57:03.970 回答
0

M在 C++ 中尝试以下代码:

 #include<fstream>
Class PrinterDriver{
Private:
fstream print("PRN")
Public:
Void Print(char a[]){
print >>a;}
Char GetPrinterStatus[](){
char c[];
print<<c;
return c;}};

了解它(关键)

于 2013-10-14T07:19:47.473 回答