繁体   English   中英

PrintDocument.Print导致Win32Exception操作成功完成

[英]PrintDocument.Print results in Win32Exception The operation completed successfully

我试图在C#.NET 3.5应用程序中打印到网络打印机并获得此异常:

操作成功完成

造成它的原因是什么,它如何解决?

System.ComponentModel.Win32Exception: The operation completed successfully
   at System.Drawing.Printing.PrinterSettings.GetHdevmodeInternal()
   at System.Drawing.Printing.PrinterSettings.GetHdevmode(PageSettings pageSettings)
   at System.Drawing.Printing.PrintController.OnStartPrint(PrintDocument document, PrintEventArgs e)
   at System.Windows.Forms.PrintControllerWithStatusDialog.OnStartPrint(PrintDocument document, PrintEventArgs e)
   at System.Drawing.Printing.PrintController.Print(PrintDocument document)
   at System.Drawing.Printing.PrintDocument.Print()
  • 该帐户有权使用网络打印机进行打印。 为Everyone设置权限以进行打印。
  • 打印机已被删除并重新创建。
  • 假脱机与直接打印到打印机的设置已经双向切换。
  • 机器上的其他打印机工作正常
  • 网络上的其他客户端和同一台计算机上的应用程序可以毫无问题地打印到此打印机。

为了缩小问题范围,我创建了一个简单的控制台应用程序。 作为普通用户运行,应用程序打印。 当运行作为服务帐户,它犯错服务帐户

在此输入图像描述

决议以我的问题是卸载,导致该问题的驱动程序,并安装一个较旧的驱动程序。

神秘的消息是由.NET Framework中的pinvoke代码中的错误引起的。 失败的基础winapi调用是DocumentProperties()函数 它的pinvoke声明如下所示:

[DllImport("winspool.drv", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int DocumentProperties(...);

SetLastError属性是错误的。 从MSDN链接可以看出,该函数通过返回负值来指示失败。 并且没有记录设置GetLastError()返回的错误代码。

这个错误的结果是框架将调用Marshal.GetLastWin32Error()来获取错误代码,并且将获得一个随机值,因为DocumentProperties()没有设置它。 0不太可能,这会产生“操作成功完成”异常消息。

所以你需要忽略异常消息; 当然,这是非常无益的。 不幸的是,这个winapi函数属于一类函数,就像大多数GDI函数一样,只产生“它不起作用”的返回代码。 它没有提示在何处寻找问题。 这个怪癖有一个不错的原因:当你调用DocumentProperties()时,Windows本身做的很少; 大多数工作都是由打印机驱动程序完成的。 在winapi中没有用于打印的错误代码集。 一切皆有可能:打印机驱动程序不是微妙的代码块。 打印机驱动程序的工作是告诉您有关问题的信息。 他们应该通过弹出自己的窗口来实现这一目标。 理论上他们无论如何; 如今市场细分市场的激烈竞争并没有留下很多钱来支付这些天的优秀程序员工资。

当您从服务打印时,这当然不起作用。 没有任何方法可以看到这样的弹出窗口,这是微软强烈反对从服务打印的核心原因。 您和您客户的IT员工都没有机会诊断问题。 阅读此博客文章 ,了解有关从服务中使用PrintDocument其他说明。

没有人喜欢得到这样的建议,但写作就在墙上。 不要这样做

要添加@ HansPassant的答案,以下是引发异常的确切代码:

Microsoft Reference Source, PrinterSettings.cs

private IntPtr GetHdevmodeInternal(string printer) {
    // Create DEVMODE
    int modeSize = SafeNativeMethods.DocumentProperties(NativeMethods.NullHandleRef, NativeMethods.NullHandleRef, printer, IntPtr.Zero, NativeMethods.NullHandleRef, 0);
    if (modeSize < 1) {
        throw new InvalidPrinterException(this);
    }
    IntPtr handle = SafeNativeMethods.GlobalAlloc(SafeNativeMethods.GMEM_MOVEABLE, (uint)modeSize); // cannot be <0 anyway
    IntPtr pointer = SafeNativeMethods.GlobalLock(new HandleRef(null, handle));

    //Get the DevMode only if its not cached....
    if (cachedDevmode != null) {
        Marshal.Copy(cachedDevmode, 0, pointer, devmodebytes);
    }
    else  {
        int returnCode = SafeNativeMethods.DocumentProperties(NativeMethods.NullHandleRef, NativeMethods.NullHandleRef, printer, pointer, NativeMethods.NullHandleRef, SafeNativeMethods.DM_OUT_BUFFER);
        if (returnCode < 0) {
            throw new Win32Exception();  // <--------
        }
    }

没有代码和一个说你一切都好的例外很难回答。 所以我只想提出一些想法来追查问题

  1. 在本地打印机上测试它以确保应用程序正常工作。
  2. 尝试使用记事本或类似的东西打印到联网打印机
  3. 双重三重检查应用程序正在运行的用户是否具有打印到网络打印机的权限。
  4. 在另一台联网打印机上测试(在为该打印机执行2和3之后)

我也有这个问题。 在我的例子中,我使用一个单独的线程来打印报告,并通过“ManualResetEvent”将其与主线程同步。 (我已经开发出这种方式,因为.Net强制将打印逻辑划分为“PrintPage”方法......)。

由于打印机驱动程序和此多线程环境之间的不兼容性(我无法解释其来源)而发生错误。

我通过将我的打印逻辑方法签名从“void”更改为“IEnumerable”来解决了这个问题,并突破了yield return,这是一种类似于Unity3d游戏引擎的“CoRoutines”的方法。 这样,就不必创建多个线程,并且我的打印代码会被组织起来。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM