智取天狼星计划(二)

英雄虎胆施巧计,豪杰妙手赚名单

阅读前文:《智取天狼星计划(一)》

叶冲在屋中来回踱步,思考着如何才能有效获取打印时产生的 SPL 文件。

如果通过传统的方法使用 Timer 控件定时监视打印作业的产生,似乎并不科学也不专业。

打印动作并非时刻都会有,况且如果打印作业时间较长,如何把握好打印的开始和结束是个不太容易控制的问题。

万一打印过程中产生了延迟,程序必定会出错而失效,那么就会痛失获取文件的良机。

叶冲抓了一把头发,心想这可如何是好?


屋外渐渐传来了打更的声音,此时已经是半夜子时。

叶冲依然在屋中边思考边来回踱步,却仍然不得要领。

随着打更的声音越来越近,映衬在这寂静的夜晚中显得格外响亮。

哎?叶冲先是一愣,随后慢慢露出了一丝笑容,心中念道:“有了!”

每天晚上都有打更之人经过门前,而到了门前才能清晰地听到打更的声音。

这就好比只要等待系统产生打印动作信号,就可以及时获取打印状态和作业文档了!

终于想到了,太棒了!

叶冲此时有些兴奋,高兴得差点叫出声。

那么有没有这样的打印信号等待功能呢?

时间紧迫,不赶快试一试怎么知道呢!

很快,他查找整理出了下面的内容。


行动四:监视打印队列,获取打印文档

1、先要初始化打印选项,后面会用到。
' ****** 初始化打印选项 ******
With PrintOptions1
    .Version = 2 ' 固定设定为2
    .Count = 2 ' 作业信息和打印机信息

    ' 获取需要的打印事件
      With PrinterNotifyOptions(0)
        .Type = PRINTER_NOTIFY_TYPE
        ReDim pFieldsPrinter(0 To 19) As Integer

        pFieldsPrinter(0) = PRINTER_NOTIFY_FIELD_PRINTER_NAME
        pFieldsPrinter(1) = PRINTER_NOTIFY_FIELD_SHARE_NAME
        pFieldsPrinter(2) = PRINTER_NOTIFY_FIELD_STATUS
        .Count = (UBound(pFieldsPrinter) - LBound(pFieldsPrinter)) + 1
        .pFields = VarPtr(pFieldsPrinter(0))
      End With

    ' 获取需要的作业事件
    With PrinterNotifyOptions(1)
        .Type = JOB_NOTIFY_TYPE
        ReDim pFieldsJob(0 To 22) As Integer
        pFieldsJob(0) = JOB_NOTIFY_FIELD_PRINTER_NAME
        pFieldsJob(1) = JOB_NOTIFY_FIELD_MACHINE_NAME
        pFieldsJob(2) = JOB_NOTIFY_FIELD_PORT_NAME
        pFieldsJob(3) = JOB_NOTIFY_FIELD_USER_NAME
        pFieldsJob(4) = JOB_NOTIFY_FIELD_NOTIFY_NAME
        pFieldsJob(5) = JOB_NOTIFY_FIELD_DATATYPE
        pFieldsJob(6) = JOB_NOTIFY_FIELD_PRINT_PROCESSOR
        pFieldsJob(7) = JOB_NOTIFY_FIELD_PARAMETERS
        pFieldsJob(8) = JOB_NOTIFY_FIELD_DRIVER_NAME
        pFieldsJob(9) = JOB_NOTIFY_FIELD_DEVMODE
        pFieldsJob(10) = JOB_NOTIFY_FIELD_STATUS
        pFieldsJob(11) = JOB_NOTIFY_FIELD_STATUS_STRING
        pFieldsJob(12) = JOB_NOTIFY_FIELD_DOCUMENT
        pFieldsJob(13) = JOB_NOTIFY_FIELD_PRIORITY
        pFieldsJob(14) = JOB_NOTIFY_FIELD_POSITION
        pFieldsJob(15) = JOB_NOTIFY_FIELD_SUBMITTED
        pFieldsJob(16) = JOB_NOTIFY_FIELD_START_TIME
        pFieldsJob(17) = JOB_NOTIFY_FIELD_UNTIL_TIME
        pFieldsJob(18) = JOB_NOTIFY_FIELD_TIME
        pFieldsJob(19) = JOB_NOTIFY_FIELD_TOTAL_PAGES
        pFieldsJob(20) = JOB_NOTIFY_FIELD_PAGES_PRINTED
        pFieldsJob(21) = JOB_NOTIFY_FIELD_TOTAL_BYTES
        .Count = (UBound(pFieldsJob) - LBound(pFieldsJob)) + 1
        .pFields = VarPtr(pFieldsJob(0))
    End With

     .lpPrintNotifyOptions = VarPtr(PrinterNotifyOptions(0))
End With
 


2、告诉系统我需要等待打印信号,就像每天在门口等待打更的声音一样。

这里要用到一个系统函数:WaitForSingleObject

它的作用简单地说就是一直等待,直到打印作业信号出现,之后才继续后面的程序执行。


' ****** 设定内核等待打印事件信号 ******
Dim mhPrinter AS Long
Dim pDefault As PRINTER_DEFAULTS
With pDefault
    .pDatatype = vbNullString
    .pDevMode = 0
    .DesiredAccess = PRINTER_ACCESS_USE
End With

If mhPrinter <> 0 Then Call ClosePrinter(mhPrinter)

res = OpenPrinter("Microsoft Print to PDF", mhPrinter, pDefault)

Dim mEventHandle As Long
mEventHandle = FindFirstPrinterChangeNotificationLong(mhPrinter, 0, 0, VarPtr(PrintOptions))

' 无限期等待打印作业信号,程序会运行到这里停止,直到打印事件发生才会继续
Call WaitForSingleObject(mEventHandle, INFINITE)
 


3、打印信号来啦,终于等到了,那么就开始获取打印作业信息吧。
' ****** 一旦有打印动作产生,则获取打印队列信息 ******
Dim lpPrintInfoBuffer As Long
Dim pdwChange As Long
Call FindNextPrinterChangeNotificationByLong(mEventHandle, pdwChange, PrintOptions1, lpPrintInfoBuffer)

Dim mData As PRINTER_NOTIFY_INFO
' mData包含一个有效的PRINTER_NOTIFY_INFO结构体'
Call CopyMemoryPRINTER_NOTIFY_INFO(mData, lpPrintInfoBuffer, Len(mData))

If mData.dwCount > 0 Then
    ReDim aData(1 To mData.dwCount) As PRINTER_NOTIFY_INFO_DATA
    Call CopyMemoryPRINTER_NOTIFY_INFO_DATA(aData(1), lpPrintInfoBuffer + Len(mData), Len(aData(1)) * mData.dwCount)
    'Debug.Print aData(1).Field
    'Debug.Print aData(1).id
    'Debug.Print aData(1).Type
    'Debug.Print aData(1).Reserved
    'Debug.Print aData(1).adwData(0)
    'Debug.Print aData(1).adwData(1)

    ' 在此可记录打印信息
    List1.AddItem strPrinterName & " | " & aData(1).Type & " - " & aData(1).Field
 


' ****** 如果打印结束,则获取打印文档并记录动作信息等 ******
Dim booPrinted As Boolean, intCount As Integer
Do While (booPrinted = False)

    Set clsPrinterInfo = New CPrinterInfo
    clsPrinterInfo.DeviceName = strPrinterName '"Microsoft Print to PDF"

    Dim clsPrinterJobInfo As CPrinterJobInfo
    Dim strStatusText As String, strDatatype As String, strDocument As String, strMachineName As String, strNotifyName As String
    Dim strPagesPrinted As String, strTotalPages As String, strSize As String, strSubmitted As String, strPortName As String
    Dim strJobId As String, strTime As String
    Dim strSplFile As String, strExt As String 'SPL文件

    For Each clsPrinterJobInfo In clsPrinterInfo.Jobs

        With clsPrinterJobInfo
            strStatusText = .StatusText
            strDatatype = .StatusText
            strDocument = .Document
            strMachineName = .MachineName
            strNotifyName = .NotifyName
            strPagesPrinted = .PagesPrinted
            strTotalPages = .TotalPages
            strSize = FormatBytes(.Size)
            strSubmitted = CStr(clsPrinterJobInfo.Submitted)
            strPortName = clsPrinterInfo.PortName
            strJobId = CStr(.JobId)
            strTime = CStr(.Time)
        End With

        ' 1.判断是否是printed状态
        If UCase(strStatusText) = "PRINTED" Then

            ' 2.复制SPL文件
            Dim lngResult As Long, strSource As String, strTarget As String

            strExt = strJobId & ".SPL" '"69.SPL"
            strSplFile = findSplFile(strSpoolPath, strExt)
            strSource = strSpoolPath & "\" & strSplFile
            strTarget = strSplCopyTo & "\" & strSplFile

            lngResult = CopyFileEvenIfOpen(strSource, strTarget)

            If lngResult = 0 Then
                MsgBox "sccuess!"
            Else
                MsgBox "failed!"
            End If

            ' 3.删除相应JobId的打印作业
            clsPrinterInfo.Jobs.ControlCancel strJobId
            clsPrinterInfo.Refresh

        End If

    Next

Loop

' 4.清空打印队列,释放缓存
Set clsPrinterJobInfo = Nothing
Set clsPrinterInfo = Nothing
Erase aData
Call FreePrinterNotifyInfoByLong(lpPrintInfoBuffer)
Call FindClosePrinterChangeNotification(mEventHandle)
 


' 使用API的CopyFile,则不管文件是否打开都可以成功复制文件。
Public Declare Function CopyFile Lib "kernel32" Alias "CopyFileA" (ByVal lpExistingFileName As String, ByVal lpNewFileName As String, ByVal bFailIfExists As Long) As Long

    ' 复制文件自定义函数
Public Function CopyFileEvenIfOpen(SourceFile As String, DestFile As String) As Long
    Dim Result As Long
    If Len(Dir(SourceFile)) = 0 Then
        Result = 0
    Else
        Result = CopyFile(SourceFile, DestFile, False)
    End If
    CopyFileEvenIfOpen = Result
End Function
 


叶冲没想到监控打印如此复杂,花了他不少时间查找调试。

还好,最终还是成功地接收到了打印信号,有效地触发了程序,后续的处理也正常完成了。

虽然正常完成了,但是叶冲也考虑到,如果佐藤有多台打印机,那么仅仅靠单线程的代码是不可能完成多台监控的。

那么只有一种办法,没错,就是使用多线程。

参考前文:《三国赛马之VB多线程演示》


打印监控程序顺利获取到了 SPL 文件,并将其移动到了指定的文件夹内。

接下来要如何处理 SPL 文件呢?

叶冲试着双击这些文件,发现并不能直接打开。

他挠了挠头,继续埋头查找起了资料。


行动五:解析识别打印队列文档

SPL 文件,学名叫做打印缓存文件,顾名思义它是用来告诉打印机如何打印内容的文件,所以一般情况下其内容人类是直接看不懂的。

SPL 仅仅是一种通用名称,它有很多种格式,包括诸如 RAWPostscriptEPSSPL-EMFPCL 等等。

甚至同一个 SPL 文件可能同时包含几种格式,比如 PCLPS 两种格式。

既然无法直接查看,那要怎样才能正确打开呢?


也是老天保佑,叶冲偶然间查到了一个网站。

这个网站表面平淡无奇,既没有花里胡哨的图片,也没有引人入胜的视频,却只有一堆堆眼花缭乱、不知所云的文字。

谁曾想到,正是这些无聊的文字中,隐藏着关于打印未公开的秘密文档资料。

网址:http://www.undocprint.org/formats/winspool/spl

网站中有大量的说明资料和工具,叶冲选择了一个叫 SPL Viewer 的工具,这是免费的,似乎比其它的要好用些。

下载链接:https://o8.cn/czTY0s 密码:88gf


通常,一般的物理打印设备产生的SPL缓存打印文件格式都能被这个程序正常识别并打开。


但是也可能有打不开的情况,虽然可以通过转换为其他通用格式,但是由于时间紧,叶冲一时也没有找到完美的解决办法,似乎只有部分商用转换软件可以实现。

官网链接:VeryPDF PCL Conerter

可以预览内容,也可以直接转换成PDF文件。


但是即便是商用软件也有失手的时候,如果格式是 RAW 的话,可能就不会被正确打开了。


RAW 格式的 SPL 文件通常是由于虚拟打印产生的,比如打印成 PDF 文档等。

因为 RAW 格式是与特定设备相关的控制语言,一般为特定的二进制数据格式,可以理解为某台打印设备才能识别的一系列内容,不具备通用性。

不过通常情况下,物理实体打印机不会用到 RAW 格式。


离天亮还有不到2个小时,叶冲彻夜未眠,他不敢有任何怠慢,在总结整理了前面的经验教训后,制作了一个按预想基本可以完成任务的程序。

程序下载链接:https://o8.cn/1gBbRb 密码:dm63


这个程序可以实现最多同时监控四台打印机的功能。

  1. 添加需要监控的打印机
  2. 选择添加的打印机,点击 Save Documnet? 设定为保存打印缓存文件
  3. 点击 Start! 开始监控,随后如果有打印作业产生,程序会自动移动系统打印文件夹中的SPL文件至程序当前的 spl 目录中


监视的打印作业也被妥妥地记录下来了。


叶冲拖着疲惫的身体,靠着顽强的毅志力,迅速打包收拾好了一切。

他心中充满了斗志,准备天亮后背水一战,然而,一切都会按照他预想的顺利进行吗?

叶冲不敢多想,稍稍打了个盹后,整理好行装,背负着光荣而艰巨的使命,就这样迈出家门踏上了征途......


WeChat @网管小贾 | Blog @www.sysadm.cc


提交评论

安全码
刷新

© 2020-present 网管小贾 | 微信公众号 @网管小贾
许可协议:CC-BY-NC 4.0 | 转载文章请注明作者出处及相关链接