【问题标题】:IPreviewHandler throws uncatchable exceptionIPreviewHandler 抛出无法捕获的异常
【发布时间】:2011-04-05 15:50:35
【问题描述】:

我已将 COM 接口 IPreviewHandler 导入 WinForms 应用程序并使用它来显示各种类型文档的预览(我在注册表中查找相应预览处理程序的 GUID,然后使用 Activator.CreateInstance(guid)实例化特定的 COM 类。

这适用于绝大多数文件类型 - Office 格式、PDF、视频等 - 但是,在我实例化“Microsoft Windows TXT 预览处理程序”{1531d583-8375-4d3f-b5fb-d23bbd169f22} 后,将其初始化为一个包含普通 .txt 文件的流,设置预览窗口的边界,然后最后调用 DoPreview(),我得到一个使用 try...catch 无法捕获的异常:

try {
    Type comType = Type.GetTypeFromCLSID(guid);
    object handler = Activator.CreateInstance(comType);

    if (handler is IInitializeWithStream) {
        Stream s = File.Open(filename, FileMode.Open);
        // this just passes the System.IO.Stream as the COM type IStream
        ((IInitializeWithStream)handler).Initialize(new StreamWrapper(s), 0);
    }
    else {
        throw new NotSupportedException();
    }

    RECT r = new RECT();
    r.Top = 0;
    r.Left = 0;
    r.Right = hostControl.Width;
    r.Bottom = hostControl.Height;

    ((IPreviewHandler)handler).SetWindow(hostControl.Handle, ref r);
    ((IPreviewHandler)handler).DoPreview();    // <-- crash occurs here
}
catch (Exception) {
    // this will never execute
}

当我使用调试器单步执行时,Visual Studio 托管进程崩溃。如果没有调试器,应用程序会在不触发 AppDomain.UnHandledExceptionApplication.ThreadException 事件的情况下崩溃。

我真的不介意我不能使用这种技术预览纯文本文件(Office 格式的工作预览处理程序等足以满足我的应用程序的要求),但我担心我的应用程序崩溃失控应该用户选择一个 .txt 文件。有什么办法可以捕捉到这个错误并优雅地处理它?更好的是,有什么方法可以克服它并让处理程序工作吗?

【问题讨论】:

  • 没有类型库。您是如何“导入”接口声明的?
  • @Hans Passant:使用[ComImport][Guid] 属性的手动声明。源码见我博客:brad-smith.info/blog/archives/79

标签: c# winforms com


【解决方案1】:

这不太可能,但可能是这里的问题 - catch(Exception) 将仅捕获 Exception 类型的异常 - 尝试使用不带任何类型过滤的 catch。

catch(Exception ex) {
   // Normal logging etc
}
catch
{
   // Exception of types other than System.Exception.
}

【讨论】:

  • 毫不奇怪,这不起作用。我也很确定,根据定义,CLR 不能捕获除System.Exception 之外的任何内容。如果发生一些低级异常,Marshal 应该抛出 COMExceptionWin32Exception
  • 我同意 - 应该有 COMException。仅供参考,我在 J Ritcher 的书中读到 CLR 不要求异常对象为 System.Exception 类型,但语言(C#、Vb.NET)不支持它。您的问题的另一个远景 - 如果您使用的是 .NET4,请检查您是否遇到损坏状态异常 - 因为它们需要使用 HandleProcessCorruptedStateExceptions 属性标记您的代码。
  • 不幸的是,仍然生活在 .NET 2.0 领域。可能会在一个以 .NET 4 为目标的独立项目中尝试一下...
  • @VinayC 尝试在 .NET 4 下使用用该属性修饰的方法运行示例。行为没有改变,仍然没有解释地崩溃。
  • 一定是本机代码中的一些讨厌的错误导致进程崩溃 - IMO,只有崩溃转储会给你一些关于这个问题的想法,但我不是这里的专家。就您的问题而言,一种可靠的方法是将预览部分托管在不同的 AppDomain(或其他进程)中
【解决方案2】:

我无法让 GetPreviewHandlerGUID() 识别 .txt 文件,必须直接注入 GUID。使用Project+Properties,Debug,勾选Enable unmanaged code debugging,就可以看到哪里出错了。

调试器现在将停止并显示问题

`遇到STATUS_STACK_BUFFER_OVERRUN

调用栈的顶部是这样的:

kernel32.dll!_UnhandledExceptionFilter@4()  + 0x1a368 bytes 
shell32.dll!___report_gsfailure()  + 0xc8 bytes 
shell32.dll!CRTFPreviewHandler::_StreamInCallback()  + 0x74 bytes   
msftedit.dll!CLightDTEngine::ReadPlainText()  + 0xed bytes  
msftedit.dll!CLightDTEngine::LoadFromEs()  + 0x202b3 bytes  
msftedit.dll!CTxtEdit::TxSendMessage()  + 0x1e25f bytes 
msftedit.dll!_RichEditWndProc@16()  + 0x13d bytes   

问题出在 StreamInCallback() 函数中。它由用于显示预览 (msftedit.dll) 的 RichTextBox 调用以加载文件。此回调函数中的代码有一个错误,它破坏了用于检测堆栈帧因缓冲区溢出而损坏的“金丝雀”。

这是 Microsoft 为防止病毒通过缓冲区溢出注入自身而采取的对策的一部分。 Visual Studio 中用于 C/C++ 语言的 /GS 编译选项。一旦检测到,CRT 会非常迅速地终止程序。发生这种情况时不会引发异常,堆栈不能安全地展开,因为它已被破坏。因此,CLR 无法捕获异常。

此错误是 TXT 文件查看器特有的。除了不使用它之外,您无能为力。向 connect.microsoft.com 报告此错误可能没有用,他们会将其作为“外部”关闭。否则,这是一个微妙的暗示,当您让非托管代码在程序中运行时会发生什么;)

【讨论】:

  • 谢谢,您的回答肯定会对此有所了解。我观察到 Windows TXT 预览处理程序实际上与 Windows 7 上的 .txt 扩展名无关,仅在 Vista 上。也许 MS 意识到允许第三方应用程序托管这个有问题的处理程序是个坏主意?我可能会在我的应用中将该 CLSID 列入黑名单。
  • 多么棒的答案。 Bradley Smith,您是否为 txt 文件编写了自己的处理程序?或者你是如何解决这个问题的?
  • 对于 *.reg *.bat 和 *.vbs 文件似乎也抛出了无法捕获的异常。
【解决方案3】:

我遇到了同样的问题,我能够通过在 x64 中编译而不是 AnyCPU 来使 TXT PreviewHandler 工作。

我在 Windows 7(64 位)上使用 Visual Studio 2010,因此如果您使用的是 32 位操作系统,此答案将不适用。

在 Visual Studio 2010 中

  • 点击Configurations下拉列表
  • 选择Configuration Manager...
  • 单击项目旁边的Platform 单元格
  • 选择New...并选择目标平台x64
  • AnyCPU 复制设置,然后离开。

【讨论】:

    【解决方案4】:

    我想我已经找到了解决这个问题的方法。问题是您正在创建的流要么被垃圾收集器清理,要么被其他东西清理。如果您使用下面代码创建的流调用初始化方法,它应该可以工作:

    System.Runtime.InteropServices.ComTypes.IStream stream;
        byte[] fileData = System.IO.File.ReadAllBytes(filename);
        System.IntPtr hGlobal = System.Runtime.InteropServices.Marshal.AllocHGlobal(fileData.Length);
        System.Runtime.InteropServices.Marshal.Copy(fileData, 0, hGlobal, fileData.Length);
        NativeMethods.CreateStreamOnHGlobal(hGlobal, false, out stream);
        //[DllImport("ole32.dll")]
        //internal static extern int CreateStreamOnHGlobal(IntPtr hGlobal, bool fDeleteOnRelease, out IStream ppstm);
    

    我在明确设置为 32 位 (x86) 并以单线程公寓模式运行的 Windows 窗体应用程序中使用上述代码。

    感谢 Sherlock Homes (http://www.tech-archive.net/Archive/DotNet/microsoft.public.dotnet.framework.interop/2010-09/msg00003.html)

    【讨论】:

      【解决方案5】:

      您遇到此问题的真正原因是您正在创建预览处理程序对象in-process。正确的方法是创建它进程外

      披露以下是我的博客/代码sn-p的广告。

      有关示例,请参阅 https://github.com/GeeLaw/PreviewHost。具体来说,请参阅Line 219 of PreviewHandler.cs,您必须将CLSCTX_LOCAL_SERVER 传递给CoCreateInstance。正如one of my blog entries 中所解释的,Activator.CreateInstance 允许进程内服务器,这不符合预览处理程序的预期,因为它们必须在正确的代理进程中创建,如文档 on MSDN 所述。

      【讨论】:

      • 当您托管预览处理程序时,假设您查看 Excel 或 Word 文件,prevhost.exe 会为您启动吗?因为对于我的尝试(我在这里询问过:stackoverflow.com/questions/62985123/…),prevhost 没有启动,而且我似乎也遇到了一些焦点问题。你遇到过这样的问题吗?
      • @loop123123 "prevhost.exe" 只是 DLL 中实现的预览处理程序的默认代理进程。一些预览处理程序选择实现自己的本地服务器。 Office 预览处理程序就是这样一个例子。在资源管理器中预览 Word 文档时,您将看到“winword.exe”正在运行。这是预期的。
      猜你喜欢
      • 2021-09-02
      • 2015-09-23
      • 2013-08-02
      • 2018-05-30
      • 1970-01-01
      • 2019-01-14
      • 1970-01-01
      • 2012-12-02
      • 2016-10-27
      相关资源
      最近更新 更多