确定打印机是物理设备还是虚拟设备的可移植方法

6
我需要网站的直接打印功能,并且需要能够区分实体打印机和虚拟打印机(文件)。
Coupons.com通过用户安装的本地二进制文件来提供此功能。我希望避免这种方式。
SmartSource.com通过Java小程序实现:
有人知道是如何实现的吗?我浏览了一下Java API,没有发现任何可以区分实体和虚拟打印机的东西,除非看打印机名称(这种方式容易误判)。如果能在Java中实现就更好了,因为我已经知道如何编写Java小程序。如果不行,Flash或Silverlight有办法吗?
谢谢。

回答您的问题,按相反顺序:**(2)** 我需要尝试限制打印仅为一份。**(1)** 我需要做到和 Coupons.com、SmartSource.com 以及其他在线优惠券供应商一样的原因是:提供这些优惠券的制造商不希望人们能够轻易地打印多份优惠券。是的,我知道:任何一个有半个脑子的人都可以将实体打印机的输出带到复印机上,但这个特定的路障(直接打印,禁止文件打印机)现在已经成为这些其他网站的标准,所以制造商期望它(无论是否合理)。 - Mud
1
所以你基本上在问的是“我怎样才能用Java/flash/silverlight构建一个像http://failblog.org/2008/03/13/toll-gate-fail/这样无用的收费站”?我为你接到这样毫无意义的任务和你的雇主的无知和缺乏常识感到遗憾。你有没有可能教育他们呢? - hlovdal
2
我的雇主正试图签约制造商以供应优惠券,从而使我们获得更多的风险资本。制造商是要求这个障碍的人,我们没有权利告诉他们这很愚蠢;他们与其他所有合作伙伴都会提供这个障碍。 从技术角度来看,我并不真的在乎为什么,我只想知道如何。 SmartSource.com使用Java来实现它,所以一定是可以的。 如何实现呢? - Mud
3
两个建议:第一,找一个第三方来反编译该小程序,并以通用的术语告诉您它的工作原理——可以理解原始的JVM指令,而且它必须包含解密字符串的代码。第二,对每张优惠券打印一个独特的条形码,并向您的客户提供一个网络服务,使他们能够确保每张优惠券只能兑换一次——通过提供真正的安全性来区别于那些仅提供安全表演的竞争对手。 - tgdavies
SmartSource.com的小程序似乎带着打印机名称作为参数“call home”,例如:http://smartsource.com/smartsource/SmartSourceFrontController?printerName=PRINTERNAME&jreVersion=1.6.0_22&userId=100081872&_m=ST0012AB2134CD9B56EF7778GH1C91IJA8&entry=printtrack&command=PrintTrackingCommand&pinNumber=14968@@49846528:14968@@49846528&JavaDetectId=null&lookupid=14&trackMode=Update&printId=69755370&,带有cookie "__utmc=83070996"。 - Adrian
显示剩余4条评论
3个回答

6

好的,这是我迄今为止找到的(这并不是详尽无遗的测试,但这是一个有趣的问题需要试图解决)。看起来通过查看PrinterJob类中的validatePage()方法可能会有所帮助。如果打印作业是虚拟的,那么任何尝试设置页面的ImageableArea都将始终返回与默认页面的ImageableArea完全相等的值,而尝试对真实打印机执行相同操作将返回略小的值(以考虑打印机机械固定纸张的边缘)。对于这个问题有用的是,如果在调用validate之前只询问打印机的默认特性,您会得到一个乐观的结果,如果将其与经过验证的响应进行比较,则可以进行简单的if测试。我已经为此编写了一些代码,似乎可以处理桌面上的图像打印机和真实打印机(再次强调,这不是详尽无遗的,但它可以作为一个起点)

import java.awt.print.*;

import javax.print.PrintService;
import javax.print.attribute.Attribute;

public class DetectFilePrinter {

  public static void main(String[] args) {
    PrinterJob job = PrinterJob.getPrinterJob();
    PrintService printer = job.getPrintService();
    System.out.println("Printer Name:"+printer.getName());

    System.out.println(printer.toString());
    PageFormat page = job.defaultPage();
    double default_width = page.getWidth();
    double default_height = page.getHeight();

    Paper paper = new Paper();
    paper.setImageableArea(0, 0, Double.MAX_VALUE, Double.MAX_VALUE);
    page.setPaper(paper);

    PageFormat fixed_page = job.validatePage(page);

    double fixed_width = fixed_page.getImageableWidth();
    double fixed_height = fixed_page.getImageableHeight();

    //So far all of my tested "image printers" return the same 
    //height and width after calling validatePage()
    if(default_height == fixed_height && default_width == fixed_width) {
      System.out.println("This looks like a \"image printer\"");
    } else {
      System.out.println("This looks like a \"real printer\"");
    }
  }
}

杰森,你太棒了。非常聪明的解决方案值得称赞。感谢你超越职责范围。 - Mud
顺便说一下:我写了一个小的测试小程序,在我女朋友的电脑上出现了误检--Microsoft OneNote。我需要再试试看。 - Mud
有意思,如果你想用赏金来尝试得到更好的答案,请随意取消选择我的答案。 - Jason Sperske

1

正如你所说,似乎有一些方法可以获取有关打印机的一些信息:

javax.print.attribute.standard.PrinterMakeAndModel 看起来很有前途。

禁止在任何打印机上使用文件:目标,并在打印机制造商和型号中包含单词 PDF 的任何打印机上打印,根据this list of virtual print software,这可能涵盖了90%的情况。实际上,您不太可能遇到单词 PDF 的误报。

如果此功能不完美,您的客户不太可能注意到;您的竞争对手也很可能有一些可怕的修补程序,因为他们同样清楚,这个功能更多是“安全剧院”,而不是实际安全。


1

我知道在Win32上很容易实现。有一个称为PRINTER_INFO_2的结构体,其中有一个名为pPortName的字段。您可以将其与字符串"FILE"进行比较,以获取与"FILE"端口连接的所有队列。您还可以类似地添加更多的解析逻辑以检测其他虚拟打印机。当您使用打印机句柄调用GetPrinter时,Windows将填充此结构体。现在的问题是如何在.NET中完成它。我看到了一种使用marshalling的方法。我在http://vbcity.com/forums/t/66183.aspx看到了一些代码片段。您还可以查看使用pInvoke的C#互操作http://pinvoke.net/default.aspx/Structures/PRINTER_INFO_2.html

希望这能帮到你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接