dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.03k stars 1.16k forks source link

IOException in ZipArchiveEntry.OpenInUpdateMode when printing #9418

Open bgrainger opened 2 months ago

bgrainger commented 2 months ago

Description

This is a repost of https://github.com/dotnet/wpf/issues/6842 with a repro.

In a WPF application ported to .NET 8.0 from .NET Framework 4.7.2, printing a complex document throws an exception, and nothing prints. (The Microsoft Print to PDF driver writes a 0-byte file.)

Reproduction Steps

Clone https://github.com/bgrainger/Bug6842. Build and run the app. Press the Print button. (If your default printer is "Print to PDF" or similar, specify a file name to print to when the Save dialog appears.) The application crashes.

The minimal code to repro is:

// Create a FlowDocument with an embedded image
FlowDocument flowDocument = new FlowDocument
{
    Blocks =
    {
        new Paragraph
        {
            // ** this line triggers the exception below **
            Background = new ImageBrush(new BitmapImage(new Uri("pack://application:,,,/sample.bmp"))),

            Inlines =
            {
                new Run { Text = "test" },
            },
        },
    }
};

// create a temp XPS document
var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());
var tempDocument = new XpsDocument(tempFilePath, FileAccess.ReadWrite);
var writer = XpsDocument.CreateXpsDocumentWriter(tempDocument);

// Write the FlowDocument to the XPS document
writer.Write(((IDocumentPaginatorSource) flowDocument).DocumentPaginator);

// Get a default print ticket from the default printer
LocalPrintServer localPrintServer = new LocalPrintServer();
PrintQueue printQueue = LocalPrintServer.GetDefaultPrintQueue();
PrintTicket printTicket = printQueue.DefaultPrintTicket;

// Create an XpsDocumentWriter object for the print queue.
XpsDocumentWriter printWriter = PrintQueue.CreateXpsDocumentWriter(printQueue);

// ** this throws the exception **
printWriter.Write(tempDocument.GetFixedDocumentSequence(), printTicket);

// avoiding the temporary document prints successfully
// xpsDocumentWriter.Write(((IDocumentPaginatorSource) flowDocument).DocumentPaginator, printTicket);

Expected behavior

The FlowDocument (with an embedded image) is printed to the selected printer.

Actual behavior

The application crashes and nothing is printed.

System.IO.Compression.dll!System.IO.Compression.ZipArchiveEntry.OpenInUpdateMode() Line 714 C#  Symbols loaded.
System.IO.Compression.dll!System.IO.Compression.ZipArchiveEntry.Open() Line 342 C#  Symbols loaded.
System.IO.Packaging.dll!System.IO.Packaging.ZipStreamManager.Open(System.IO.Compression.ZipArchiveEntry zipArchiveEntry, System.IO.FileAccess streamFileAccess) Line 78 C#  Symbols loaded.
System.IO.Packaging.dll!System.IO.Packaging.ZipPackagePart.GetStreamCore(System.IO.FileMode streamFileMode, System.IO.FileAccess streamFileAccess) Line 28  C#  Symbols loaded.
System.IO.Packaging.dll!System.IO.Packaging.PackagePart.GetStream(System.IO.FileMode mode, System.IO.FileAccess access) Line 154    C#  Symbols loaded.
WindowsBase.dll!MS.Internal.IO.Packaging.PackagePartExtensions.GetSeekableStream(System.IO.Packaging.PackagePart packPart, System.IO.FileMode mode, System.IO.FileAccess access) Line 62    C#  Symbols loaded.
PresentationCore.dll!System.IO.Packaging.PackWebResponse.CachedResponse.GetResponseStream() Line 652    C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ImageSourceTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, System.Type destinationType) Line 245  C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.VisualSerializer.WriteBitmap(string attribute, System.Windows.Media.ImageSource imageSource) Line 647   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.VisualSerializer.BrushToString(System.Windows.Media.Brush brush, System.Windows.Rect bounds) Line 772   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.VisualSerializer.FindBrush(System.Windows.Media.Brush brush, System.Windows.Rect bounds) Line 273   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.VisualSerializer.WriteBrush(string attribute, System.Windows.Media.Brush brush, System.Windows.Rect bounds) Line 903    C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.VisualSerializer.System.Windows.Xps.Serialization.IMetroDrawingContext.DrawGeometry(System.Windows.Media.Brush brush, System.Windows.Media.Pen pen, System.Windows.Media.Geometry geometry) Line 1923   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.DrawingContextFlattener.DrawGeometry(System.Windows.Media.Brush brush, System.Windows.Media.Pen pen, System.Windows.Media.Geometry geometry) Line 281   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.VisualTreeFlattener.DrawingWalk(System.Windows.Media.Drawing d, System.Windows.Media.Matrix drawingToWorldTransform) Line 697   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.VisualTreeFlattener.DrawingWalk(System.Windows.Media.Drawing d, System.Windows.Media.Matrix drawingToWorldTransform) Line 763   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.VisualTreeFlattener.StartVisual(System.Windows.Media.Visual visual) Line 638    C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachVisualSerializer.SerializeTree(System.Windows.Media.Visual visual, System.Xml.XmlWriter resWriter, System.Xml.XmlWriter bodyWriter) Line 111   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachUIElementCollectionSerializer.SerializeUIElements(System.Windows.Xps.Serialization.SerializableObjectContext serializableObjectContext) Line 138   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachSerializer.SerializeObject(System.Windows.Xps.Serialization.SerializablePropertyContext serializedProperty) Line 176   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachSerializer.SerializeProperties(System.Windows.Xps.Serialization.SerializableObjectContext serializableObjectContext) Line 341  C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.XpsOMFixedPageSerializer.PersistObjectData(System.Windows.Xps.Serialization.SerializableObjectContext serializableObjectContext) Line 114   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachSerializer.SerializeObject(object serializedObject) Line 112   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachSerializer.SerializeObject(object serializedObject) Line 112   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachPageContentCollectionSerializer.SerializePageContent(object pageContent) Line 140  C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachPageContentCollectionSerializer.SerializePageContents(System.Windows.Xps.Serialization.SerializableObjectContext serializableObjectContext) Line 116   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachSerializer.SerializeObject(System.Windows.Xps.Serialization.SerializablePropertyContext serializedProperty) Line 176   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachSerializer.SerializeProperties(System.Windows.Xps.Serialization.SerializableObjectContext serializableObjectContext) Line 341  C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.XpsOMFixedDocumentSerializer.PersistObjectData(System.Windows.Xps.Serialization.SerializableObjectContext serializableObjectContext) Line 106   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachSerializer.SerializeObject(object serializedObject) Line 112   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachSerializer.SerializeObject(object serializedObject) Line 112   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachDocumentReferenceCollectionSerializer.SerializeDocumentReferences(System.Windows.Xps.Serialization.SerializableObjectContext serializableObjectContext) Line 86    C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachSerializer.SerializeObject(System.Windows.Xps.Serialization.SerializablePropertyContext serializedProperty) Line 176   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachSerializer.SerializeProperties(System.Windows.Xps.Serialization.SerializableObjectContext serializableObjectContext) Line 341  C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.XpsOMDocumentSequenceSerializer.PersistObjectData(System.Windows.Xps.Serialization.SerializableObjectContext serializableObjectContext) Line 52 C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.ReachSerializer.SerializeObject(object serializedObject) Line 112   C#  Symbols loaded.
ReachFramework.dll!System.Windows.Xps.Serialization.XpsOMSerializationManager.SaveAsXaml(object serializedObject) Line 126  C#  Symbols loaded.
System.Printing.dll!System::Windows::Xps::XpsDocumentWriter::SaveAsXaml(System::Object^ serializedObject, bool isSync) Line 2291    C++ Symbols loaded.
Bug6842.dll!Bug6842.MainWindow.PrintButton_Click(object sender, System.Windows.RoutedEventArgs e) Line 69   C#  Symbols loaded.
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) Line 206   C#  Symbols loaded.
PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args) Line 2340 C#  Symbols loaded.
PresentationFramework.dll!System.Windows.Controls.Primitives.ButtonBase.OnClick() Line 72   C#  Symbols loaded.
PresentationFramework.dll!System.Windows.Controls.Button.OnClick() Line 273 C#  Symbols loaded.
PresentationFramework.dll!System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(System.Windows.Input.MouseButtonEventArgs e) Line 504   C#  Symbols loaded.
PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeHandler(System.Delegate handler, object target) Line 335  C#  Symbols loaded.
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) Line 206   C#  Symbols loaded.
PresentationCore.dll!System.Windows.UIElement.ReRaiseEventAs(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args, System.Windows.RoutedEvent newEvent) Line 2286    C#  Symbols loaded.
PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeHandler(System.Delegate handler, object target) Line 335  C#  Symbols loaded.
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) Line 206   C#  Symbols loaded.
PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args) Line 2340 C#  Symbols loaded.
PresentationCore.dll!System.Windows.UIElement.RaiseTrustedEvent(System.Windows.RoutedEventArgs args) Line 457   C#  Symbols loaded.
PresentationCore.dll!System.Windows.Input.InputManager.ProcessStagingArea() Line 805    C#  Symbols loaded.
PresentationCore.dll!System.Windows.Input.InputProviderSite.ReportInput(System.Windows.Input.InputReport inputReport) Line 105  C#  Symbols loaded.
PresentationCore.dll!System.Windows.Interop.HwndMouseInputProvider.ReportInput(nint hwnd, System.Windows.Input.InputMode mode, int timestamp, System.Windows.Input.RawMouseActions actions, int x, int y, int wheel) Line 1438  C#  Symbols loaded.
PresentationCore.dll!System.Windows.Interop.HwndMouseInputProvider.FilterMessage(nint hwnd, MS.Internal.Interop.WindowMessage msg, nint wParam, nint lParam, ref bool handled) Line 527 C#  Symbols loaded.
PresentationCore.dll!System.Windows.Interop.HwndSource.InputFilterMessage(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled) Line 1592 C#  Symbols loaded.
WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled) Line 299   C#  Symbols loaded.
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) Line 103 C#  Symbols loaded.
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler) Line 36 C#  Symbols loaded.
WindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs) Line 1339 C#  Symbols loaded.
WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(nint hwnd, int msg, nint wParam, nint lParam) Line 341    C#  Symbols loaded.
[Native to Managed Transition]      Annotated Frame
[Managed to Native Transition]      Annotated Frame
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) Line 2143 C#  Symbols loaded.
PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) Line 2406 C#  Symbols loaded.
PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) Line 1693    C#  Symbols loaded.
Bug6842.dll!Bug6842.App.Main()  Unknown Symbols loaded.

Regression?

This is a regression from .NET Framework 4.7.2 and affects my company's product that was ported from that version.

It was present in .NET 6.0 (see https://github.com/dotnet/wpf/issues/6842).

Known Workarounds

Yes; see comment: https://github.com/dotnet/wpf/issues/9418#issuecomment-2239850721.

Impact

Our application can't print on Windows when running on .NET 8.0.

Configuration

dotnet --info
.NET SDK:
 Version:           8.0.303
 Commit:            29ab8e3268
 Workload version:  8.0.300-manifests.34944930
 MSBuild version:   17.10.4+10fbfbf2e

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.19045
 OS Platform: Windows
 RID:         win-x64
 Base Path:   C:\Program Files\dotnet\sdk\8.0.303\

Other information

A possible fix is in this PR: https://github.com/dotnet/wpf/pull/6843.

bgrainger commented 2 months ago

This is a regression from .NET Framework 4.7.2

I had to update the sample app slightly to work correctly under .NET 4.7.2 (but the primary point still stands). The updated code runs under net472 but still crashes under net8.0-windows.

Replace the two lines to create the XpsDocument with:

// create a temporary package
var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Package package = Package.Open(tempFilePath, FileMode.Create, FileAccess.ReadWrite);

// create a temporary XPS document using that package
string tempFilePath2 = Path.GetTempFileName();
PackageStore.AddPackage(new Uri(tempFilePath2), package);
var tempDocument = new XpsDocument(package, CompressionOption.Normal, tempFilePath2);

See https://github.com/bgrainger/Bug6842/commit/409cf4caefd1b323ebc0d6b7db3c2233285994aa.

hongruiyu commented 2 months ago

Suggestion:

If you could write directly to FlowDocument, there is no need to nest another layer of XpsDocument. In this case, just use printWriter.Write(((IDocumentPaginatorSource) flowDocument).DocumentPaginator, printTicket);

Analysis: According to the error message, the XpsDocument is opened multiple times. writer.Write(((IDocumentPaginatorSource)flowDocument).DocumentPaginator); and printWriter.Write(tempDocument.GetFixedDocumentSequence(), printTicket); both need to open the XpsDocument for reading and writing.

I tried using Close() to close the first opening, but it didn't seem to work.

In the official sample program (https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/documents/printing-overview?view=netdesktop-8.0#xpsdocumentwriter), it also seems to avoid opening the XpsDocument file multiple times for reading and writing.

bgrainger commented 2 months ago

The FlowDocument is a simplification for this repro; it's not used by the application where this problem was first discovered (which uses a custom DocumentPaginator and draws directly to a Visual, then saves everything to an XpsDocument so it can show a "print preview" on screen, then prints that XpsDocument).

bgrainger commented 2 months ago

The exception is coming from OpenInUpdateMode. This seemed odd to me, because we should just be "reading" the document to print it, not updating it.

ZipPackage sets the ZipArchiveMode to Update if it's opened with FileAccess.ReadWrite: https://github.com/dotnet/runtime/blob/31bc167803abe8381c6ecdfec1f7935dead2df10/src/libraries/System.IO.Packaging/src/System/IO/Packaging/ZipPackage.cs#L395-L396

That value comes from Package.Open: https://github.com/dotnet/runtime/blob/31bc167803abe8381c6ecdfec1f7935dead2df10/src/libraries/System.IO.Packaging/src/System/IO/Packaging/Package.cs#L854

That value comes from the XpsManager constructor: https://github.com/dotnet/wpf/blob/0be453cffbc6cd30dfc96a0f48c48f7bb0754171/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsManager.cs#L140-L159

Which comes from the XpsDocument constructor: https://github.com/dotnet/wpf/blob/0be453cffbc6cd30dfc96a0f48c48f7bb0754171/src/Microsoft.DotNet.Wpf/src/ReachFramework/Packaging/XpsDocument.cs#L156-L166

Thus, it seems like the bug may be getting triggered from printing an XPS Document that is still open for writing. I updated the code to close and reopen the document. This allows it to be printed successfully: https://github.com/bgrainger/Bug6842/commit/4d5eabc5419fbfa12218c0beed2464eed7585ab6.

I have applied this fix to our complex WPF application and it resolves the bug.

I believe this is still a regression from .NET Framework (and perhaps could manifest in other code paths?) but it is now less serious for us.