如何发送文件到网络打印机,并打印(使用C#)?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何发送文件到网络打印机,并打印(使用C#)?相关的知识,希望对你有一定的参考价值。

如题,我有一个文件,比如说word或者pdf,已经排好版了,想要进行打印。
还有一个局域网上的打印机,ip地址是192.168.0.254,名称是:Posteck 2108,请问如何采用C#编程的方式把这个文件发送到打印机的打印队列中进行打印?

问题
.Net Framework 1.1给我们提供了一个PrinterSettings类,以提供指定有关文档打印方式的信息,其中包括打印文档的打印机。其中的静态属性InstalledPrinters可以使我们获取安装在计算机上所有打印机的名称。
但是可惜的是,该属性仅仅能够提供已安装的打印机的名称。对于获取该打印机的相关信息(如打印机类型等)却无能为力。问题就产生了,由于客户无法提供打印机的SDK,所以对打印机的筛选(处于商业目的,客户要求软件只能在使用他们的打印机时才能输出)只能通过打印机驱动的辨认来实现。

----------------------------------------------

解决方案一 使用WMI获取打印机信息

WMI,全称Windows Management Instrumentation。是可伸缩的系统管理结构,它采用一个统一的、基于标准的、可扩展的面向对象接口。WMI 为您提供与系统管理信息和基础 WMI API 交互的标准方法。WMI 主要由系统管理应用程序开发人员和管理员用来访问和操作系统管理信息。

.Net Framework中System.Management类提供了对WMI的支持,其中ManagementObjectSearcher用于根据指定的查询或枚举检索 ManagementObject 或 ManagementClass 对象的集合。

/**//// <summary>
/// Code 1:WMI搜索示例
/// <summary>
/// <param name="strDrivername">驱动名称</param>
/// <returns>返回找到的打印机列表</returns>
/// <remarks>strDrivername支持”%“以及”_“通配符查询,类似于SQL语句中的查询<remarks>
public StringCollection GetPrintsWithDrivername( string strDrivername )

StringCollection scPrinters = new StringCollection();
string strcheck = "";
if( strDrivername !="" && strDrivername != "*" )
strcheck = " where DriverName like \'" + strDrivername + "\'";
string searchQuery = "SELECT Name FROM Win32_Printer" + strcheck;
ManagementObjectSearcher searchPrinters =
new ManagementObjectSearcher(searchQuery);
ManagementObjectCollection printerCollection = searchPrinters.Get();

foreach(ManagementObject printer in printerCollection)

string printname = printer.Properties["Name"].Value.ToString();
scPrinters.Add(printname);

searchPrinters.Dispose();
printerCollection.Dispose();

return scPrinters;


问题看上去基本解决了,运行程序的确是获得了正确的打印机列表。可是用户用了一段时间后发现,有的时候打印机无法正确获得,看来DOTNET调用WMI稳定性的确有点问题啊。。。。。。

WMI本身功能还是相当强大的,通过VBS基本可以涵盖WINDOWS最基本的操作。详细可以参加MSDN的文档。

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/wmi_start_page.asp

-------------------------------------------

解决方案二 使用WIN32API获取打印机

转来转去,又回到WIN32API上来了,无奈啊。。。。。。怪不得C++依然这么吃香 啊。。。。。

.Net给我们提供了DllImport来操作非托管的DLL(发现C#如此的强啊~~~~暗自偷笑)。

主要使用到winspool.drv中的EnumPrinters函数,代码如下:

[DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumPrinters ([MarshalAs(UnmanagedType.U4)] PRINTER_ENUM flags,
[MarshalAs(UnmanagedType.LPStr)] string sName,
uint iLevel,
IntPtr pPrinterDesc,
uint iSize,
[MarshalAs(UnmanagedType.U4)] ref uint iNeeded,
[MarshalAs(UnmanagedType.U4)] ref uint iReturned
);

说明:Marshal属性提供了对托管代码与非托管代码见数据封送。

EnumPrinters 的 WIN32 API的定义如下:

BOOL EnumPrinters(
DWORD Flags, // printer object types
LPTSTR Name, // name of printer object
DWORD Level, // information level
LPBYTE pPrinterEnum, // printer information buffer
DWORD cbBuf, // size of printer information buffer
LPDWORD pcbNeeded, // bytes received or required
LPDWORD pcReturned // number of printers enumerated
);

问题又来啦,EnumPrinters通过Level来获取PRINTER_INFO,而能获得打印机驱动的是PRINTER_INFO_2,而C#中又没有PRINTER_INFO_2结构。

查了半天资料,网上基本上都是PRINTER_INFO_1的定义,而PRINTER_INFO_2不同与PRINTER_INFO_1,其中还包括DEVMODE结构,非托管的结构套结构,偶开始飘了~~~~

最后发现与其在C#中定义结构来对应非托管的结构,还不如直接用类来替代。所以定义了两个类

PRINTER_INFO_2以及DEVMODE(注:由于PRINTER_INFO_2中只用到了DEVMODE结构来接收打印机驱动的信息,所以只定义了这个类,对于其他类都没有做具体实现)。

在PRINTER_INFO_2中,对于所有的DWORD类型数据,全部对应到Int32类型上面,而对于所有LPTSTR、LPDEVMODE以及PSECURITY_DESCRIPTOR一律对应到IntPtr指针类型。

为了获取非托管中的数据,使用了一下函数获取打印机信息

.

PRINTER_INFO_2 pi = new PRINTER_INFO_2();
//把数据从非托管内存传送到到托管内存

for(int i = 0; i < numPrinters; i++)

Marshal.PtrToStructure( prInfo, pi ); //prInfo是由上面EnumPrinters获得的打印机

string driver = Marshal.PtrToStringAuto( pi.pDriverName );

if ( printerdriver == "" || driver.ToLower().IndexOf( printerdriver ) != -1)


// 做相关处理


prInfo = new IntPtr(prInfo.ToInt32() + Marshal.SizeOf(typeof(PRINTER_INFO_2))); // 获取下一个打印机信息段开始

.

问题至此基本解决。但C#中对非托管函数的调用,以及相互之间的数据封装还是一个比较难的地方,有空还需要整理一下。

文章来源:http://spaces.msn.com/sharkoo/Blog/cns!D8E832CE4545AF!158.entry

补充:在2.0中,fixed关键字可以用于定义一个固定大小的数组缓存,而不是像1.x中那样还需要定义一个数字大小。但这种方式只能用于结构(struct)而不能用于类(class)的定义。

参考资料:http://hi.baidu.com/youlix/blog/item/04709923ab408843ac34de2f.html

参考技术A 你真的不觉得麻烦?你是搞程序的想自己开发开发实现自己的创意?

如何将文本文件发送到打印机

【中文标题】如何将文本文件发送到打印机【英文标题】:How to send text file to printer 【发布时间】:2012-12-27 11:01:27 【问题描述】:

正如我在一些指纹手册中看到的,我们可以将文本文件发送到打印机。意味着我们可以在文本编辑器中编写程序,并使用通信程序使用一些传输命令将整个程序作为文本文件发送到打印机。 因为在我的主机中,在 D:/ 中有一个名为 myfile.txt 的文件,其中包含闲置数据

10 PRPOS 200,200
20 DIR 3
30 ALIGN 5
40 PRIMAGE “GLOBE.1”
50 PRINTFEED
RUN

如何将此文件发送到打印机并执行打印图像的说明。 请给我一些代码参考。

【问题讨论】:

这不是almost the same question吗? 【参考方案1】:

有几种方法可以从命令行执行此操作。例如:

type foo.txt > lpt1:

或者

copy foo.txt lpt1:

或者

print foo.txt

或者

notepad /p foo.txt

如果您需要以编程方式执行此操作,您可以使用 system() 函数或 CreateProcess() 执行任何这些命令。

【讨论】:

【参考方案2】:

如果您使用 Intermec 手持设备并连接到蓝牙打印机,您应该能够打开 COM6 的串行端口并发送文件。什么编程语言?那里应该有很多串口通信代码示例。

【讨论】:

【参考方案3】:

我对 Intermec PM4i 标签打印机的体验就像坐过山车一样,但我知道我有一个可以工作的应用程序。

我通过 generictext 驱动程序尝试了 Windows 打印机管道。它确实可以在记事本中使用,但很少有角落案例。

直接从记事本打印效果很好,直到我尝试了带有很长文本行的 QRCODE 图像。图像没有打印出来。将 qrcode 文本制作为几个字符,并且相同的脚本运行良好。

INPUT OFF
NASC 1252
BF OFF
FT "Swiss 721 Bold BT",12,0,100 
PP 50,500:PT "Text line goes here"
PP 400,400:AN 7:BARSET "QRCODE",1,1,7,2,4
PB "ABC123 aabbcc....very long text goes here...I mean about 200 chars or more"
PRINTFEED

就像记事本将文本剪切到右侧边框并且命令字符串被破坏了。我做了一个打印首选项 A3-landscape,它接受更长的文本,但仍然不足以满足所有用例。

所有打印机都有一个物理最大打印宽度,但不应在指纹/直接协议脚本文件中考虑它。毕竟我们不是按原样打印这些文本,而是向打印机提交命令。

我的解决方案是创建 Java 应用程序,它打开一个原始 TCP 套接字到 11.22.33.44:9100 地址并写入文本行,这些行以 NL(#10) 终止。工作正常。我做的另一个帮助工具是 Delphi app.exe 从 Windows 打印机对象读取 IP 地址。我可以从 Excel 应用程序“直接”提交标签打印输出。

最终用户编辑 Excel 数据行并单击“打印标签”按钮 vba 宏使用 $FIELD1 查找和替换替换项解析指纹模板文件 文件已写入 %wintemp%/intermec_script.txt 文件夹 调用 app.exe 读取用户选择的打印机的 IP 地址 调用java应用提交intermec_script.txt到IP:PORT socket

我应该在 Delphi 中创建相同的套接字提交应用程序以删除 javavm 依赖项,但这个解决方案对我的用例来说更快。我对 Java 的熟悉程度超过了我的 Delphi 技能水平。

【讨论】:

以上是关于如何发送文件到网络打印机,并打印(使用C#)?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 golang 在网络打印机上打印文件或格式化文本

如何在python中将打印作业发送到打印机

如何列出网络上的所有打印机?

如何使用我的网络应用程序发送剪纸命令?

无法在网络打印机上使用 cmd 打印 .prn 文件

如何在 Flutter 中打印到热敏网络打印机?