本文告诉大家一个最简单步骤让 RenderTargetBitmap 出现 COMException 提示

只需要在界面添加一个 ListView 绑定图片,然后在后台不断刷新列表就可以

        <ListView Margin="10,10,10,10" ItemsSource="{Binding DeagernereDechuno}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid Margin="10,10,10,10">
                        <Image Source="{Binding}"></Image>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

在后台创建 DeagernereDechuno 列表

        public ObservableCollection<ImageSource> DeagernereDechuno { get; set; }=new ObservableCollection<ImageSource>();

在 Load 之后调用函数 WarwairJorkasou 不断截图

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;


            Loaded += (s, e) =>
            {
                 WarwairJorkasou();
            };
        }

在 WarwairJorkasou 调用循环进行截图,很快就可以看到下面提示

 System.Runtime.InteropServices.COMException: MILERR_WIN32ERROR

异常堆栈

System.Runtime.InteropServices.COMException (0x88980003): MILERR_WIN32ERROR (异常来自 HRESULT:0x88980003)
    System.Windows.Media.Imaging.RenderTargetBitmap.FinalizeCreation()
    System.Windows.Media.Imaging.RenderTargetBitmap..ctor(Int32 pixelWidth, Int32 pixelHeight, Double dpiX, Double dpiY, PixelFormat pixelFormat)

截图的代码

        private async void WarwairJorkasou()
        {
            var ran = new Random();

            while (true)
            {
                await Task.Delay(10).ContinueWith(_ =>
                {
                    DeagernereDechuno.Clear();
                    var n = ran.Next(int.MaxValue / 10);
                    for (int i = n; i < n + 1000; i++)
                    {
                        try
                        {
                            DrawingVisual drawingVisual = new DrawingVisual();
                            DrawingContext drawingContext = drawingVisual.RenderOpen();

                            var text = new FormattedText(i.ToString(),
                                CultureInfo.GetCultureInfo("zh-cn"),
                                FlowDirection.LeftToRight,
                                new Typeface("Verdana"),
                                36, Brushes.Black);
                            drawingContext.DrawText(text,
                                new Point(0, 0));
                
                            drawingContext.Close();

                            var image = new RenderTargetBitmap((int) text.Width, (int) text.Height, 96, 96, PixelFormats.Pbgra32);
                            image.Render(drawingVisual);

                            DeagernereDechuno.Add(image);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e);
                        }
                    }
                }, TaskScheduler.FromCurrentSynchronizationContext());
            }
        }

运行程序大概在 300M 左右就会出现 COMException 提示

System.Runtime.InteropServices.COMException (0x88980003): MILERR_WIN32ERROR (Exception from HRESULT: 0x88980003)

RenderTargetBitmap throws COM exception when created too fast: MILERR_WIN32ERROR (Exception from HRESULT: 0x88980003)

代码请看 https://github.com/dotnet-campus/wpf-issues/tree/master/RenderTargetBitmapThrowsCOMExceptionWhenCreatedTooFast

已经报告官方,请看 Known issus: WPF will throw COM Exception when create RenderTargetBitmap too fast · Issue #3067 · dotnet/wpf

在 WPF 里面炸掉的代码如下

        internal override void FinalizeCreation()
        {
            try
            {
                using (FactoryMaker myFactory = new FactoryMaker())
                {
                    SafeMILHandle renderTargetBitmap = null;
                    HRESULT.Check(UnsafeNativeMethods.MILFactory2.CreateBitmapRenderTarget(
                        myFactory.FactoryPtr,
                        (uint)_pixelWidth,
                        (uint)_pixelHeight,
                        _format.Format,
                        (float)_dpiX,
                        (float)_dpiY,
                        MILRTInitializationFlags.MIL_RT_INITIALIZE_DEFAULT,
                        out renderTargetBitmap));

                    Debug.Assert(renderTargetBitmap != null && !renderTargetBitmap.IsInvalid);

                    BitmapSourceSafeMILHandle bitmapSource = null;
                    HRESULT.Check(MILRenderTargetBitmap.GetBitmap(
                        renderTargetBitmap,
                        out bitmapSource));
                    Debug.Assert(bitmapSource != null && !bitmapSource.IsInvalid);

                    lock (_syncObject)
                    {
                        _renderTargetBitmap = renderTargetBitmap;
                        bitmapSource.CalculateSize();
                        WicSourceHandle = bitmapSource;

                        // For the purpose of rendering a RenderTargetBitmap, we always treat it as if it's
                        // not cached.  This is to ensure we never render and write to the same bitmap source
                        // by the UCE thread and managed thread.
                        _isSourceCached = false;
                    }
                }

                CreationCompleted = true;
                UpdateCachedSettings();
            }
            catch
            {
                _bitmapInit.Reset();
                throw;
            }
        }

从上面代码可以看到是调用 COM 炸了,也就是修框架层还是解决不动

有小伙伴 elyoh 告诉我,也许是 GDI 对象的问题,这个方法每次都需要申请一定量的 GDI 对象,但是这个方法没有立刻释放这些 GDI 资源。通过 GDI Objects 文档可以知道,限制每个会话能打开的 GDI 对象是 65535 个,在注册表中设置了每个进程可以注册 10,000 个,一个可以让程序跑得更远的方法是不断调用垃圾回收,尽管这个调用会降低性能

GC.Collect();
GC.WaitForPendingFinalizers();

但实际上小伙伴说的内容不对,原因是这个进程没有使用那么多的 GDI 对象,通过任务管理器可以看到


本文会经常更新,请阅读原文: https://dotnet-campus.github.io//post/WPF-%E4%BD%BF%E7%94%A8-RenderTargetBitmap-%E5%BF%AB%E9%80%9F%E6%88%AA%E5%9B%BE%E5%87%BA%E7%8E%B0-COMException-%E6%8F%90%E7%A4%BA.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 lindexi (包含链接: https://dotnet-campus.github.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系