2010-08-30 115 views
3

我已经将COM接口IPreviewHandler导入到WinForms应用程序中,并使用它来显示各种类型文档的预览(我在注册表中查找相应预览处理程序的GUID,然后使用Activator.CreateInstance(guid)实例的具体COM类IPreviewHandler引发无法捕捉的异常

这工作奇妙的绝大多数文件类型 - 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文件。有什么办法可以捕捉到这个错误并且优雅地处理它吗?更好的是,有什么方法可以克服它并让处理程序起作用?

+0

没有类型库。你是如何“导入”接口声明的? – 2010-08-30 20:28:48

+0

@Hans Passant:使用'[ComImport]'和'[Guid]'属性进行手动声明。看到我的博客上的源代码:http://www.brad-smith.info/blog/archives/79 – 2010-08-30 23:21:02

回答

6

我无法使GetPreviewHandlerGUID()识别.txt文件,而必须直接注入GUID。当您使用Project + Properties,Debug时,可以看到出现了什么问题,勾选启用非托管代码调试。

调试器现在将在问题和显示器

`STATUS_STACK_BUFFER_OVERRUN遇到

调用堆栈的顶部看起来像这样停止:

[email protected]() + 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 
[email protected]() + 0x13d bytes 

的问题所在在StreamInCallback()函数中。它由RichTextBox调用,用于显示预览(msftedit.dll)来加载文件。这个回调函数中的代码有一个bug,它破坏了用来检测堆栈帧由于缓冲区溢出而损坏的'canary'。

这是微软用来防止病毒通过缓冲区溢出注入自身的对策措施的一部分。 Visual Studio中用于C/C++语言的/ GS编译选项。一旦检测到,CRT就会很快终止程序。发生这种情况时没有例外,堆栈无法安全地展开,因为它已被泄露。因此,CLR无法捕捉到例外。

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

+0

谢谢,您的回答肯定会对此有所了解。我发现Windows TXT预览处理程序实际上与Windows 7上的.txt扩展名没有关联,仅在Vista上。也许MS意识到允许第三方应用程序托管这个多功能处理程序是一个坏主意?我可能只是在我的应用程序中将CLSID列入黑名单。 – 2010-08-31 02:11:51

+0

多好的答案。布拉德利史密斯,你是否为txt文件编写自己的处理程序?或者你如何解决这个问题? – SwissCoder 2011-07-18 11:52:00

+0

不可捕捉的异常似乎也被抛出* .reg * .bat和* .vbs文件。 – 2015-01-23 13:55:26

0

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

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

毫不意外,这是行不通的。我也非常肯定,按照定义,CLR不能捕获除System.Exception之外的任何东西。如果发生一些低级异常,'Marshal'应该抛出一个'COMException'或一个'Win32Exception'。 – 2010-08-30 08:33:57

+0

我同意 - 应该有COMException。仅供参考,我曾在J Ritcher书中读过CLR没有要求异常对象是System.Exception类型,但语言(C#,Vb.NET)不支持它。 针对您的问题的另一个长镜头 - 如果您使用的是.NET4,请检查您是否遇到了损坏的状态异常 - 因为它们需要使用HandleProcessCorruptedStateExceptions属性标记您的代码。 – VinayC 2010-08-30 08:47:57

+0

不幸的是,仍然生活在.NET 2.0的土地上。可能尝试在一个针对.NET 4的独立项目中尝试... – 2010-08-30 09:01:18

1

我有同样的问题,我能通过编译x64而不是AnyCPU来获得TXT PreviewHandler。

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

在Visual Studio 2010

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

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

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); 

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

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