dotnet/wpf · error · COMException

STG_E_CANTSAVE

STG_E_CANTSAVE

Error message

The only IPersistFile operation supported by the component is Load.

What it means

IPersistFile.Save on XpsFilter is deliberately unimplemented: a filter is read-only, so the explicit interface method immediately throws COMException with STG_E_CANTSAVE and the message SR.FilterIPersistFileIsReadOnly. There is no code path that saves; the operation is unsupported by design.

Solutions

  1. Do not call Save on the filter; treat it as load-only
  2. Gate Save calls behind a capability check / feature flag for writable persistence
  3. Use the actual storage (XPS writer APIs) to produce files instead of the filter

Example fix

// before
((IPersistFile)filter).Save(path, true);
// after
// XpsFilter is read-only; write XPS via XpsDocument instead
using (var doc = new XpsDocument(path, FileAccess.Write)) { /* write */ }
Defensive patterns

Strategy: try-catch

Try / catch

try { ((IPersistFile)filter).Save(path, true); }
catch (COMException ex) when (ex.HResult == NativeMethods.STG_E_CANTSAVE)
{ /* read-only component: use writer APIs instead */ }

Prevention

When it happens

Trigger: Calling IPersistFile.Save (or SaveCompleted patterns that assume writability) on an XpsFilter instance obtained as IPersistFile.

Common situations: Generic persistence plumbing that round-trips every IPersistFile component; test harnesses exercising all interface members.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/d2e353f228f7f0c8. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/IO/Packaging/XpsFilter.cs:459

        /// <summary>
        /// Saves a copy of the object into the specified file.
        /// </summary>
        /// <param name="pszFileName">
        /// A zero-terminated string containing the absolute path 
        /// of the file to which the object is saved.
        /// </param>
        /// <param name="fRemember">
        /// Indicates whether pszFileName is to be used as the current working file. 
        /// </param>
        /// <remarks>
        /// On the odd chance that this link is still valid when it's needed, 
        /// expected error codes are described at
        /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/html/da9581e8-98c7-4592-8ee1-a1bc8232635b.asp
        /// </remarks>
        void IPersistFile.Save(string pszFileName, bool fRemember)
        {
            throw new COMException(SR.FilterIPersistFileIsReadOnly, NativeMethods.STG_E_CANTSAVE);
        }

        /// <summary>
        /// Notifies the object that it can write to its file.
        /// </summary>
        /// <param name="pszFileName">
        /// The absolute path of the file where the object was previously saved. 
        /// </param>
        /// <remarks>
        /// On the odd chance that this link is still valid when it's needed, 
        /// expected error codes are described at
        /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/html/da9581e8-98c7-4592-8ee1-a1bc8232635b.asp
        /// This function should always return S_OK when Save is not supported.
        /// </remarks>
        void IPersistFile.SaveCompleted(string pszFileName)
        {
            return; // return S_OK
        }

View on GitHub (pinned to 81131a70a4)