Skip to content

WPF 使用 RenderTargetBitmap 快速截图出现 COMException 提示

Updated: at 12:43,Created: at 06:58

本文告诉大家一个最简单步骤让 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 对象,通过任务管理器可以看到


知识共享许可协议

原文链接: http://blog.lindexi.com/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

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