如何在 Go 中实现 Windows 系统下的原生打印机输出

2次阅读

如何在 Go 中实现 Windows 系统下的原生打印机输出

本文介绍在 windows 平台使用 go 语言调用系统打印 api,将 utf-8 编码的纯文本(固定宽度字体)发送至默认打印机的完整实现方案,基于 `github.com/alexbrainman/printer` 封装库,无需外部依赖或渲染引擎。

go 生态中,原生打印支持并非标准库功能,尤其面向 windows 的底层 GDI 打印流程需绕过高级抽象(如 pdf 渲染或 Web 预览),直接对接 windows 打印子系统。对于消息服务器等后台服务场景,要求轻量、可靠、无 GUI 交互——此时推荐使用 github.com/alexbrainman/printer 这一专为 Windows 设计的 Go 绑定库。它封装了 CreateDC, StartDoc, StartPage, WritePrinter, EndPage, EndDoc 等核心 Win32 GDI 打印 API,屏蔽了复杂句柄管理和字符编码转换细节。

以下是一个生产就绪的打印示例(已适配 UTF-8 文本与等宽字体需求):

package main  import (     "fmt"     "log"      prt "github.com/alexbrainman/printer" )  func printToDefault(text string) error {     // 获取系统默认打印机名称     printerName, err := prt.Default()     if err != nil {         return fmt.Errorf("failed to get default printer: %w", err)     }      // 打开打印机句柄     p, err := prt.Open(printerName)     if err != nil {         return fmt.Errorf("failed to open printer %q: %w", printerName, err)     }     defer p.Close() // 确保资源释放      // 启动文档(文档名建议含时间戳或 ID,便于追踪)     docName := fmt.Sprintf("GoPrint-%d", time.Now().unixNano())     if err = p.StartDocument(docName, "TEXT"); err != nil {         return fmt.Errorf("failed to start document: %w", err)     }     defer p.DocumentEnd() // 使用 defer 确保异常时也能结束文档      // 开始新页面     if err = p.StartPage(); err != nil {         return fmt.Errorf("failed to start page: %w", err)     }     defer p.PageEnd() // 同样确保页结束      // 写入 UTF-8 文本(Windows GDI 默认按 OEM 字符集解释字节流)     // ⚠️ 关键:为正确显示中文等 UTF-8 字符,需先转换为系统活动代码页(如 GBK/CP936)     // 以下使用 golang.org/x/text/encoding 演示转换(需 go get)     /*         encoder := encoding.Windows1252.NewEncoder() // 示例:若目标打印机支持 CP1252         encoded, _ := encoder.String(text)         n, err := p.Write([]byte(encoded))     */     // ✅ 更稳妥做法:使用系统默认 OEM 编码(GetOEMCP),或指定打印机支持的代码页     // 实际部署前请确认打印机驱动是否支持 UTF-8 直通(少数现代打印机支持),否则必须转码      n, err := p.Write([]byte(text))     if err != nil {         return fmt.Errorf("failed to write to printer: %w", err)     }     fmt.Printf("Successfully sent %d bytes to printern", n)      return nil }  func main() {     msg := "Hello, 你好,Printer!nThis is monospace text.nLine 3."     if err := printToDefault(msg); err != nil {         log.Fatal(err)     } }

关键注意事项:

  • 字体控制:该库不提供字体设置接口。实际输出字体由打印机驱动默认决定(通常为 Courier 或系统等宽字体)。如需精确控制,需改用 GDI+ 或通过 EMF 记录生成,但会显著增加复杂度。
  • ⚠️ UTF-8 编码兼容性:Windows GDI WritePrinter 接口接收原始字节流,默认按当前线程 OEM 代码页(如 CP437/CP936)解析。若直接传入 UTF-8 字节,可能显示乱码。强烈建议
    • 查询目标打印机规格,确认其是否原生支持 UTF-8(如部分 Zebra、Brother 型号);
    • 否则,使用 golang.org/x/text/encoding 将 UTF-8 字符串显式转为对应 OEM 编码(例如 encoding.TraditionalChinese 对应 CP950);
    • 或调用 Win32 MultiByteToWideChar + WideCharToMultiByte 实现动态代码页转换(需 CGO)。
  • ?️ 权限与上下文:Windows 服务账户默认无交互式桌面会话,无法访问用户级打印机。若程序以 Windows Service 运行,需配置服务登录为“本地系统”并勾选“允许服务与桌面交互”(不推荐),或改用“网络打印协议(IPP/LPD)”对接 CUPS/linux 打印服务器,或通过 Windows 事件日志+计划任务间接触发用户会话打印。
  • ? 错误处理与重试:打印机离线、缺纸、卡纸时 Write 可能返回 ERROR_IO_PENDING 或 ERROR_PRINT_CANCELLED。建议添加超时机制与状态轮询(prt.Status()),并设计幂等重试逻辑。

综上,alexbrainman/printer 是目前 Windows Go 打印最简洁可靠的方案。它直击本质——将文本作为原始字节流交由 Windows 打印子系统处理,完美契合“仅需固定宽度纯文本输出”的轻量需求。只需注意编码转换与运行上下文,即可稳定集成进您的消息服务器。

text=ZqhQzanResources