JeffreySu/WeiXinMPSDK · error · ObjectDisposedException

ObjectDisposedException (FinanceLibraryHandle)

Error message

ObjectDisposedException (FinanceLibraryHandle)

What it means

FinanceLibraryHandle was disposed (its native handle freed and zeroed) but GetDelegate was still called on it. ThrowIfDisposed detects _handle == IntPtr.Zero and throws ObjectDisposedException('FinanceLibraryHandle'). The native library cannot be used after Dispose — all SDK operations must happen before it.

Solutions

  1. Ensure Dispose is called only after all finance operations (chat pulls, media downloads) complete.
  2. Keep one long-lived library instance per app and dispose only at process shutdown (e.g. IHostedService.StopAsync after all work drained).
  3. Do not reuse the disposed handle — recreate the API instance if you need to operate again.
  4. Synchronize disposal with worker tasks via await on all in-flight media downloads before Dispose.

Example fix

// before
library.Dispose();
var sdk = library.GetDelegate<NewSdkDelegate>("NewSdk"); // ObjectDisposedException
// after
var sdk = library.GetDelegate<NewSdkDelegate>("NewSdk");
// ... use sdk ...
library.Dispose(); // dispose only when done
Defensive patterns

Strategy: try-catch

Validate before calling

if (library == null || library.IsDisposed) throw new InvalidOperationException("Finance library not initialized");

Type guard

bool IsUsable(FinanceLibraryHandle h) => h is { } && !h.IsDisposed;

Try / catch

try { var d = library.GetDelegate<TDelegate>(name); }
catch (ObjectDisposedException) { library = FinanceLibraryHandle.Load(path); d = library.GetDelegate<TDelegate>(name); }

Prevention

When it happens

Trigger: Calling GetDelegate (or any finance API op) after Dispose() on the FinanceLibraryHandle; using the API from a second scope/thread after an earlier code path disposed the library; DI container disposing a singleton the code still references.

Common situations: Host shutdown releasing the library while a background media-download loop is still running; double-dispose patterns where a finally block disposes and a retry path reuses the object.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of JeffreySu/WeiXinMPSDK@be573f6f94 (2026-09-12). Data as JSON: /api/errors/40a97df4fe2e27b2. Report an issue: GitHub.

Appendix: source

Thrown at src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/MsgAudit/MsgAuditFinanceNativeApi.cs:447

            if (_isWindows)
            {
                WindowsNative.FreeLibrary(handle);
            }
            else if (_useLegacyLibDl)
            {
                LinuxLegacyNative.DlClose(handle);
            }
            else
            {
                LinuxNative.DlClose(handle);
            }
        }

        private void ThrowIfDisposed()
        {
            if (_handle == IntPtr.Zero)
            {
                throw new ObjectDisposedException(nameof(FinanceLibraryHandle));
            }
        }

        private static class WindowsNative
        {
            [DllImport("kernel32", EntryPoint = "LoadLibraryW", CharSet = CharSet.Unicode,
                SetLastError = true)]
            internal static extern IntPtr LoadLibrary(string fileName);

            [DllImport("kernel32", EntryPoint = "GetProcAddress", CharSet = CharSet.Ansi,
                SetLastError = true)]
            internal static extern IntPtr GetProcAddress(IntPtr module, string procedureName);

            [DllImport("kernel32", EntryPoint = "FreeLibrary", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool FreeLibrary(IntPtr module);
        }

View on GitHub (pinned to be573f6f94)