我的 C++在某些机器上,库无法捕获它引发的异常

发布于 2025-01-11 12:53:02 字数 7258 浏览 0 评论 0原文

我有一个简单的 C#.NET Windows 窗体应用程序。 它动态加载 C++ DLL(用 RAD Studio 创建)并调用其方法。

有时,库会抛出异常并捕获它们,以便它向我的应用程序返回有意义的错误。 在某些机器上,这种行为会使整个应用程序崩溃,因为显然库没有捕获异常(我的应用程序也没有捕获异常)。这怎么可能?

应用程序和DLL都是64位架构。如果我用 32 位构建就没有问题。

它运行的机器装有 Windows Server 2008 R2 Enterprise。

它不起作用的计算机具有 Windows Server 2019 Datacenter 和 Windows 10。

这是我的应用程序:

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace TestMyLib
{
    public partial class FMain : Form
    {
        public static readonly string LIBRARY_NAME = "MyLib.dll";
        private IntPtr handle;

        private void btnMM489_Click(object sender, EventArgs ea)
        {
            try
            {
                //load library
                string libPath = Path.Combine(Directory.GetCurrentDirectory(), "lib", LIBRARY_NAME);
                handle = NativeDlls.LoadLibraryEx(libPath, IntPtr.Zero, NativeDlls.LoadLibraryFlags.LOAD_WITH_ALTERED_SEARCH_PATH);
                if (handle == IntPtr.Zero)
                    throw new Exception(string.Format("Failed to load library '{0}'. {1}", LIBRARY_NAME, Marshal.GetLastWin32Error()));

                //call some lib functions
                //...

                //call a function (_FailingFunction)
                //that we know will launch an exception inside its try/catch
                Type delegateFunType = typeof(_FailingFunction);
                IntPtr funAddr = NativeDlls.GetProcAddress(handle, delegateFunType.Name);
                dynamic fun = Convert.ChangeType(Marshal.GetDelegateForFunctionPointer(funAddr, delegateFunType), delegateFunType);

                //********* THIS LINE MAKES THE APPLICATION CRASH *************
                fun.Invoke();

                //...

                MessageBox.Show("Success!");
            }
            catch (ExternalException e) { MessageBox.Show(e.ErrorCode + Environment.NewLine + e.Message + Environment.NewLine + e.StackTrace); }
            catch (Exception e) { MessageBox.Show(e.Message + Environment.NewLine + e.StackTrace); }
        }

        //Given delegate type, return the pointer to the DLL function,
        //which can be used to invoke such function
        private dynamic GetFunctionPtr(Type delegateFunType)
        {
            IntPtr funAddr = NativeDlls.GetProcAddress(handle, delegateFunType.Name);
            if (funAddr == IntPtr.Zero)
                throw new Exception(string.Format("Failed to load function {0} from library {1}: error code {2}",
                    delegateFunType.Name, LIBRARY_NAME, Marshal.GetLastWin32Error()));
            var fun = Convert.ChangeType(Marshal.GetDelegateForFunctionPointer(funAddr, delegateFunType), delegateFunType);
            return fun;
        }

        //delegates to MyLib.dll methods
        //...
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate bool _FailingFunction();

        public FMain() { InitializeComponent(); }
    }

    /// <summary>
    /// Static class containing some Windows API methods for handling
    /// native (unmanaged) DLLs.
    /// </summary>
    public static class NativeDlls
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags);

        [Flags]
        public enum LoadLibraryFlags : uint { LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 }

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
    }
}

如您所见,它调用我的库的函数 _FailingFunction。这样的函数会抛出一个明显的异常,应该捕获它:

bool FailingFunction()
{
    try
    {
        //****** THROW EXCEPTION! *******
        throw Exception("An error occured!");
        return true;
    }
    catch (Exception &E)
    {
        //****** HERE WE SHOULD CATCH IT... *******
        return false;
    }
}   

在 Windows 事件查看器中,崩溃生成此事件:

Applicazione: TestMyLib.exe
Versione framework: v4.0.30319
Descrizione: il processo è stato terminato a causa di un'eccezione non gestita.
Informazioni sull'eccezione: System.AccessViolationException
   in DynamicClass.CallSite.Target(System.Runtime.CompilerServices.Closure, System.Runtime.CompilerServices.CallSite, System.Object, Int32, System.String, System.String, System.Text.StringBuilder, Int32)
   in System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid6[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]](System.Runtime.CompilerServices.CallSite, System.__Canon, Int32, System.__Canon, System.__Canon, System.__Canon, Int32)
   in TestMyLib.FMain.btnMM489_Click(System.Object, System.EventArgs)
   in System.Windows.Forms.Control.OnClick(System.EventArgs)
   in System.Windows.Forms.Button.OnClick(System.EventArgs)
   in System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs)
   in System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32)
   in System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
   in System.Windows.Forms.ButtonBase.WndProc(System.Windows.Forms.Message ByRef)
   in System.Windows.Forms.Button.WndProc(System.Windows.Forms.Message ByRef)
   in System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
   in System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
   in System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)
   in System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
   in System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
   in TestMyLib.Program.Main()

并且此事件:

Nome dell'applicazione che ha generato l'errore: TestMyLib.exe, versione: 1.0.1.2, timestamp: 0xf5550cd3
Nome del modulo che ha generato l'errore: KERNELBASE.dll, versione: 10.0.17763.2183, timestamp: 0x8e097f91
Codice eccezione: 0xc0000005
Offset errore 0x0000000000039329
ID processo che ha generato l'errore: 0x846c
Ora di avvio dell'applicazione che ha generato l'errore: 0x01d82e13c8a73c68
Percorso dell'applicazione che ha generato l'errore: C:\Users\Me\Desktop\TestMyLib\TestMyLib.exe
Percorso del modulo che ha generato l'errore: C:\Windows\System32\KERNELBASE.dll
ID segnalazione: 250ac398-95c1-4ef7-b999-c69d50653af1
Nome completo pacchetto che ha generato l'errore: 
ID applicazione relativo al pacchetto che ha generato l'errore: 

I have a simple C#.NET Windows Forms application.
It dynamically loads a C++ DLL (made with RAD Studio) and calls its methods.

Sometimes the library throws exceptions and catches them, so that it returns a meaningful error to my application. On some machines this behaviour makes the whole application crash, because apparently the exception is not catched by the library (and neither by my application). How can it be possible?

Apllication and DLL have 64-bit architecture. There are no problems if I build in 32-bit.

The machine where it works have Windows Server 2008 R2 Enterprise.

The machines where it doesn't work has Windows Server 2019 Datacenter and Windows 10.

This is my application:

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace TestMyLib
{
    public partial class FMain : Form
    {
        public static readonly string LIBRARY_NAME = "MyLib.dll";
        private IntPtr handle;

        private void btnMM489_Click(object sender, EventArgs ea)
        {
            try
            {
                //load library
                string libPath = Path.Combine(Directory.GetCurrentDirectory(), "lib", LIBRARY_NAME);
                handle = NativeDlls.LoadLibraryEx(libPath, IntPtr.Zero, NativeDlls.LoadLibraryFlags.LOAD_WITH_ALTERED_SEARCH_PATH);
                if (handle == IntPtr.Zero)
                    throw new Exception(string.Format("Failed to load library '{0}'. {1}", LIBRARY_NAME, Marshal.GetLastWin32Error()));

                //call some lib functions
                //...

                //call a function (_FailingFunction)
                //that we know will launch an exception inside its try/catch
                Type delegateFunType = typeof(_FailingFunction);
                IntPtr funAddr = NativeDlls.GetProcAddress(handle, delegateFunType.Name);
                dynamic fun = Convert.ChangeType(Marshal.GetDelegateForFunctionPointer(funAddr, delegateFunType), delegateFunType);

                //********* THIS LINE MAKES THE APPLICATION CRASH *************
                fun.Invoke();

                //...

                MessageBox.Show("Success!");
            }
            catch (ExternalException e) { MessageBox.Show(e.ErrorCode + Environment.NewLine + e.Message + Environment.NewLine + e.StackTrace); }
            catch (Exception e) { MessageBox.Show(e.Message + Environment.NewLine + e.StackTrace); }
        }

        //Given delegate type, return the pointer to the DLL function,
        //which can be used to invoke such function
        private dynamic GetFunctionPtr(Type delegateFunType)
        {
            IntPtr funAddr = NativeDlls.GetProcAddress(handle, delegateFunType.Name);
            if (funAddr == IntPtr.Zero)
                throw new Exception(string.Format("Failed to load function {0} from library {1}: error code {2}",
                    delegateFunType.Name, LIBRARY_NAME, Marshal.GetLastWin32Error()));
            var fun = Convert.ChangeType(Marshal.GetDelegateForFunctionPointer(funAddr, delegateFunType), delegateFunType);
            return fun;
        }

        //delegates to MyLib.dll methods
        //...
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate bool _FailingFunction();

        public FMain() { InitializeComponent(); }
    }

    /// <summary>
    /// Static class containing some Windows API methods for handling
    /// native (unmanaged) DLLs.
    /// </summary>
    public static class NativeDlls
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags);

        [Flags]
        public enum LoadLibraryFlags : uint { LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 }

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
    }
}

As you see, it invokes function _FailingFunction of my library. Such function throws an obvious exception and should catch it:

bool FailingFunction()
{
    try
    {
        //****** THROW EXCEPTION! *******
        throw Exception("An error occured!");
        return true;
    }
    catch (Exception &E)
    {
        //****** HERE WE SHOULD CATCH IT... *******
        return false;
    }
}   

In the Windows Events Viewer the crash generates this event:

Applicazione: TestMyLib.exe
Versione framework: v4.0.30319
Descrizione: il processo è stato terminato a causa di un'eccezione non gestita.
Informazioni sull'eccezione: System.AccessViolationException
   in DynamicClass.CallSite.Target(System.Runtime.CompilerServices.Closure, System.Runtime.CompilerServices.CallSite, System.Object, Int32, System.String, System.String, System.Text.StringBuilder, Int32)
   in System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid6[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]](System.Runtime.CompilerServices.CallSite, System.__Canon, Int32, System.__Canon, System.__Canon, System.__Canon, Int32)
   in TestMyLib.FMain.btnMM489_Click(System.Object, System.EventArgs)
   in System.Windows.Forms.Control.OnClick(System.EventArgs)
   in System.Windows.Forms.Button.OnClick(System.EventArgs)
   in System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs)
   in System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32)
   in System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
   in System.Windows.Forms.ButtonBase.WndProc(System.Windows.Forms.Message ByRef)
   in System.Windows.Forms.Button.WndProc(System.Windows.Forms.Message ByRef)
   in System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
   in System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
   in System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)
   in System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
   in System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
   in TestMyLib.Program.Main()

and this event:

Nome dell'applicazione che ha generato l'errore: TestMyLib.exe, versione: 1.0.1.2, timestamp: 0xf5550cd3
Nome del modulo che ha generato l'errore: KERNELBASE.dll, versione: 10.0.17763.2183, timestamp: 0x8e097f91
Codice eccezione: 0xc0000005
Offset errore 0x0000000000039329
ID processo che ha generato l'errore: 0x846c
Ora di avvio dell'applicazione che ha generato l'errore: 0x01d82e13c8a73c68
Percorso dell'applicazione che ha generato l'errore: C:\Users\Me\Desktop\TestMyLib\TestMyLib.exe
Percorso del modulo che ha generato l'errore: C:\Windows\System32\KERNELBASE.dll
ID segnalazione: 250ac398-95c1-4ef7-b999-c69d50653af1
Nome completo pacchetto che ha generato l'errore: 
ID applicazione relativo al pacchetto che ha generato l'errore: 

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

可是我不能没有你 2025-01-18 12:53:02

错误 c0000005 是 Windows 中“访问冲突”(又名 SIGBUS)的说法,是由于尝试读取或写入无效地址而引起的。

这是我们从您的日志中看到的一件事,但也许这是一个转移注意力的事情,而且似乎与您发布的代码无关。但是您可以捕获它 - 如果您发现需要 - 但不能使用 try ... catch。相反,您需要使用 Windows 的结构化异常处理 (SEH),您将找到详细描述 此处。基本上,您需要做的是:;

__try
{
    some_code_i_dont_trust ();
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
    handle_the_low_level_exception ();     // you might `throw` here, for example
}

您还应该查阅该链接来决定要使用哪些编译器开关,因为这些开关会影响编译器在引发异常时为您(或不为您)清理资源的方式。有关更多信息,请此处

实际上,对我来说最好的方法是什么并不明显,但是如果您将所有潜在的硬件异常包装在 __try ... __ except 中,那么,从 Hans Passant 发布的内容来看,您'最好不要使用 /EHa

除以零实际上也是一个“低级”(也称为硬件)异常,因此可以以相同的方式捕获和处理。再说一次,try ... catch 不会看到它。

Error c0000005 is Windows speak for 'access violation' (aka SIGBUS) and is caused by trying to read from or write to an invalid address.

So that's one thing we see from your logs, but maybe that's a red-herring and certainly doesn't seem to be related to the code you posted. But you can catch it - if you find you need to - but not with try ... catch. Instead, you need to use Windows' Structured Exception Handling (SEH), which you will find described in detail here. Basically, what you need to do is:;

__try
{
    some_code_i_dont_trust ();
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
    handle_the_low_level_exception ();     // you might `throw` here, for example
}

You should also consult that link to decide what compiler switches you want to use, as these affect the way that the compiler cleans up resources for you (or not) when an exception is thrown. More on that here.

It's actually not obvious to me what the best approach is, but if you wrap all potential hardware exceptions in __try ... __except then, from what Hans Passant posted, you're probably best off not using /EHa.

Divide-by-zero is actually also a 'low level' (aka hardware) exception, so can be trapped and handled in the same way. Again, try ... catch won't see it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文