现在的位置: 首页 > 综合 > 正文

Windows 下直接控制打印端口

2013年10月06日 ⁄ 综合 ⁄ 共 3958字 ⁄ 字号 评论关闭

近日在完成最后的 POS 打印模块时有所斩获。POS 机的小票打印是一个不太好控制的内容,原因是小票的长度从来不固定,它要根据用户购买的货品种类,以及附加信息的不同(例如可能需要打印折扣/优惠/会员等汇总信息);用报表+自定义纸张也许可以解决,不过长度的精确计算是件不容易实现的事,且动态自定义纸张要求当前用户拥有管理员或打印机操作员的权限,受到的限制颇多;另一方面,POS 打印机通常都自带字库,而且只能用直接输出控制码的方法才能控制钱箱接口的脉冲输出,所以用直接输出字符和控制码到打印机的方法才是 POS 中最合理的解决方案。

起初是采用 Fopen / Fwrite 等指令来直接输出的,在打印机联机的时候都很正常;然而,一旦打印机未开机或出现故障时,这种方法就出现问题了!不像 DOS 下直接输出到打印机,它可以通过 On Error 陷阱加 PrintStatus 函数来检测到打印错误和状态,超时长短也可以在 Config.fp 中通过 Timeout 设置项来控制;然而 Windows 下用 Fwrite 函数时的打印超时错误却从不出现,一旦打印机关闭或出错就使程序陷入无穷的等待状态,至少在我可以忍耐的时间内不会返回。

是否在 Windows 下就真的无法检测到打印超时了呢?关于这个问题,我想很多人都碰到过,然而却从来没有人找到过解决的办法,就算找到了方法也从来没人公布过,这是我在网络上经过无数次的搜索后得出的结论。

仔细分析过 Windows 中的输入输出控制函数后可以发现,通过 CreateFile 可以打开一个 DOS LPT 设备端口,进而用 WriteFile 就可以直接将数据输出到打印机中,但是它与使用 Fopen / Fwrite 的效果是一样的,也同样会在打印超时时陷入等待状态;但是,熟悉 win32 api 的好处是可以利用系统提供所有功能!api 中除了使用 WriteFile 这种同步输出方式以外,还提供了一种用于异步输出方式的函数 WriteFileEx,这个函数在它的最后一个参数中需要提供一个回调函数,当使用它来启动输出后,不像 WriteFile 需要一直等待输出完成才返回,而是立即返回,在它输出成功后,系统会调用你提供的回调函数来通知你操作完成。当然要得到回调通知你还需要让你的程序进入一种可等待的状态,而不能让 vfp 代码一直运行下去,否则系统是没有机会来启动回调的,这可以通过像 SleepEx, WaitForSingleObjectEx, WaitForMultipleObjectsEx, MsgWaitForMultipleObjectsEx, SignalObjectAndWait 等函数来让系统进入可中断等待状态,这些函数通常用于线程同步。这里我使用了 SleepEx,在调用 WriteFileEx 后,立即调用 SleepEx 进入可中断等待状态,让它最多等待 5 秒(自定的超时值),如果系统在 5 秒内执行 WriteFileEx 成功,会调用回调函数并发送一个消息让 SleepEx 提前退出。上面就是不让输出进入无穷等待状态的方法,下面是这一部分的示例代码:

其中使用了 vfp2c32.fll 来实现回调函数,可以在这里找到它。
另外,如果系统中已安装了使用本地 LPT1 端口的打印驱动,Windows 仍会将打印输出导向这个驱动程序,有些打印驱动程序会将输出数据放入打印缓存中(跟设置也有关系),这样即使关闭了打印机,输出也同样会成功;所以,最佳的测试方法是删除所有已安装的驱动程序。不过,如果 Windows 打印驱动缓存了打印数据,返回成功的话,也就不会导致进入无穷等待状态了,所以我们也就不用考虑它带来的副作用了,因为我要解决的是进入超时等待的死锁问题。

  1. #define OUTPUT_PORT             'LPT1:'
  2. #define VFP2C_INIT_CALLBACK     0x00000100
  3. #define GMEM_ZEROINIT           0x0040
  4. #define GENERIC_READ            0x80000000
  5. #define GENERIC_WRITE           0x40000000
  6. #define FILE_SHARE_READ         0x00000001
  7. #define FILE_SHARE_WRITE        0x00000002
  8. #define FILE_FLAG_WRITE_THROUGH 0x80000000
  9. #define FILE_FLAG_OVERLAPPED    0x40000000
  10. #define FILE_FLAG_NO_BUFFERING  0x20000000
  11. #define OPEN_EXISTING           3
  12. #define OPEN_ALWAYS             4
  13. m.cPath = JUSTPATH( SYS(16))
  14. SET LIBRARY TO ( m.cPath + '/vfp2c32.fll' ) ADDITIVE
  15. INITVFP2C32( VFP2C_INIT_CALLBACK )
  16. m.oCallBack = CREATEOBJECT( 'MyCallBack')       && 创建回调函数
  17. DECLARE Long CreateFile IN WIN32API ;
  18.   String lpFileName, ;
  19.   Long dwDesiredAccess, ;
  20.   Long dwShareMode, ;
  21.   Long lpSecurityAttributes, ;
  22.   Long dwCreationDisposition, ;
  23.   Long dwFlagsAndAttributes, ;
  24.   Long hTemplateFile
  25. DECLARE Long WriteFileEx IN WIN32API ;
  26.   Long hFile, ;
  27.   String lpBuffer, ;
  28.   Long nNumberOfBytesToWrite, ;
  29.   Long lpOverlapped, ;
  30.   Long lpCompletionRoutine
  31. DECLARE Long CancelIo IN WIN32API ;
  32.   Long hFile
  33. DECLARE Long CloseHandle IN WIN32API Long hObject
  34. DECLARE Long SleepEx IN WIN32API ;
  35.   Long dwMilliseconds, ;
  36.   Long bAlertable
  37. DECLARE Long GlobalAlloc IN WIN32API ;
  38.   Long uFlags, Long dwBytes
  39. DECLARE Long GlobalFree IN WIN32API ;
  40.   Long hMem
  41. m.hh = CreateFile( ;            && 以异步操作方式打开本地打印机端口
  42.   OUTPUT_PORT, ;
  43.   GENERIC_WRITE, ;
  44.   FILE_SHARE_WRITE+FILE_SHARE_READ, ;
  45.   0, ;
  46.   OPEN_EXISTING, ;
  47.   FILE_FLAG_WRITE_THROUGH+FILE_FLAG_OVERLAPPED+FILE_FLAG_NO_BUFFERING, ;
  48.   0 )
  49. IF ( -1 == m.hh )
  50.   MESSAGEBOX( '打开端口失败' )
  51. ELSE
  52.   m.ol = GlobalAlloc( GMEM_ZEROINIT, 4*5 )
  53.   WriteFileEx( m.hh, CHR(13), 1, m.ol, m.oCallback.Address )   && 启动异步写操作
  54.   SleepEx( 5000, 1 )            && 设置 5 秒定为超时
  55.   GlobalFree( m.ol )
  56.   IF ( -1 == m.oCallback.ErrorNo )   && 如果是超时退出
  57.     CancelIo( m.hh )            && 取消输出
  58.   ENDIF
  59.   CloseHandle( m.hh )
  60.   IF ( -1 == m.oCallback.ErrorNo )
  61.     MESSAGEBOX( '打印超时' )
  62.   ELSE
  63.     MESSAGEBOX( '打印正常' )
  64.   ENDIF
  65. ENDIF
  66. m.oCallBack = NULL
  67. SET LIBRARY TO
  68. RETURN
  69. *!* 回调函数实现类
  70. DEFINE CLASS MyCallBack AS Exception
  71.   Address = 0
  72.   ErrorNo = -1
  73.   FUNCTION Init
  74.     This.Address = CreateCallbackFunc( ;
  75.       'CompleteCallback', 'Long', 'Long,Long,Long', This )
  76.   ENDFUNC
  77.   FUNCTION Destroy
  78.     IF ( 0 != This.Address )
  79.       DestroyCallbackFunc( This.Address )
  80.     ENDIF
  81.   ENDFUNC
  82.   
  83.   *!* 回调函数
  84.   FUNCTION CompleteCallback( dwErrCode, dwBytesWritten, lpOverlapped )
  85.     This.ErrorNo = 0       && 回调成功, 表示输出已完成
  86.     RETURN 0
  87.   ENDFUNC
  88. ENDDEFINE

抱歉!评论已关闭.