64位Office中VBA调用SetPrinterA致Word崩溃问题求助
你在适配64位Office VBA代码时遇到的崩溃问题,大概率是API声明或结构体内存布局不兼容导致的,我帮你梳理几个关键问题和修复方案:
1. 修正SetPrinterA的API声明
你可能错误地把Level参数声明为LongPtr了——Win32 API里SetPrinter的Level参数是DWORD类型(32位无符号整数),不管32位还是64位系统,这个参数始终是32位,应该用VBA的Long类型,而非指针大小的LongPtr。正确的64位兼容声明如下:
Private Declare PtrSafe Function SetPrinter Lib "winspool.drv" Alias "SetPrinterA" ( _ ByVal hPrinter As LongPtr, _ ByVal Level As Long, _ pPrinter As Any, _ ByVal Command As Long _ ) As Long
2. 更新Printer信息结构体的指针成员
崩溃的核心原因往往是结构体内存布局在64位下错位。比如你如果使用PRINTER_INFO_2这类结构体,里面的所有指针类型成员(比如pServerName、pPrinterName、pShareName等)必须从原来的Long改成LongPtr——64位系统下指针是8字节,而Long仅4字节,类型不匹配会直接导致内存读写越界,触发程序崩溃。
举个PRINTER_INFO_2的64位兼容示例:
Type PRINTER_INFO_2 pServerName As LongPtr pPrinterName As LongPtr pShareName As LongPtr pPortName As LongPtr pDriverName As LongPtr pComment As LongPtr pLocation As LongPtr pDevMode As LongPtr pSepFile As LongPtr pPrintProcessor As LongPtr pDatatype As LongPtr pParameters As LongPtr pSecurityDescriptor As LongPtr Attributes As Long Priority As Long DefaultPriority As Long StartTime As Long UntilTime As Long Status As Long cJobs As Long AveragePPM As Long End Type
3. 确保结构体内存分配大小正确
如果你的代码用GlobalAlloc或LocalAlloc分配结构体内存,64位下因为指针成员变长,结构体总大小会增加,一定要重新计算分配的字节数。你可以用LenB函数获取修正后结构体的正确大小:
Dim structSize As Long structSize = LenB(PRINTER_INFO_2)
4. 验证打印机句柄的有效性
调用SetPrinter前,必须确保hPrinter是通过OpenPrinter正确获取的有效句柄,64位下OpenPrinter的声明也要适配:
Private Declare PtrSafe Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" ( _ ByVal pPrinterName As String, _ phPrinter As LongPtr, _ pDefault As Any _ ) As Long
调用后要检查返回值,确保打印机成功打开,避免传递无效句柄导致崩溃。
5. 可选:改用Unicode版本的SetPrinterW
64位Office默认使用Unicode字符串,SetPrinterA是ANSI版本,需要额外做字符串转换,容易出错。建议直接改用SetPrinterW,声明如下:
Private Declare PtrSafe Function SetPrinter Lib "winspool.drv" Alias "SetPrinterW" ( _ ByVal hPrinter As LongPtr, _ ByVal Level As Long, _ pPrinter As Any, _ ByVal Command As Long _ ) As Long
使用时结构体里的字符串直接用StrPtr传递即可,无需额外转换:
pi2.pPrinterName = StrPtr("MyPrinter")
最后,调用SetPrinter后一定要检查返回值,如果返回0,用Err.LastDllError获取系统错误码,能帮你快速定位剩余问题:
Dim result As Long result = SetPrinter(hPrinter, 2, pi2, 0) If result = 0 Then Debug.Print "错误码:" & Err.LastDllError End If
内容的提问来源于stack exchange,提问作者William James

