.NET 外部非托管库函数引发无法捕获的异常

Posted

技术标签:

【中文标题】.NET 外部非托管库函数引发无法捕获的异常【英文标题】:.NET external unmanaged library function throws uncatchable exception 【发布时间】:2011-02-09 10:47:54 【问题描述】:

我在我的 WindowsCE .NET Compact Framework 3.5 应用程序中使用 OpenNetCF 库。 问题是当我调用它的一个函数(NetworkInterface.GetAllNetworkInterfaces() 具体来说)时,它会随机抛出无法捕获的异常并使我的应用程序崩溃:

异常代码:0x80000002 异常地址:0x03F928C4 在 NativeMethods.WZCDeleteIntfObj(INTF_ENTRY& Intf) 在 INTF_ENTRY.Dispose() 在 GetAllNetworkInterfaces()

我已经在网络上搜索过,OpenNetCF 社区的错误跟踪器中报告了这个错误。不幸的是,还没有人修复它,我很确定没有人会修复它。 我有源代码,所以可能我必须自己做。如果我能设法捕获异常,而不会使应用程序崩溃,我会很高兴。

抛出异常的方法实际上是通过以下方式导入的native方法:

//---------------------------------------- // WZCDeleteIntfObj: 清除一个 INTF_ENTRY 对象,它是 // 在任何 RPC 调用中分配。 // // 参数 // pIntf // [in] 指向要删除的 INTF_ENTRY 对象的指针 [DllImport("wzcsapi.dll")] 公共静态外部无效 WZCDeleteIntfObj( 参考 INTF_ENTRY Intf);

如何从这个方法中捕获异常?

【问题讨论】:

0x80000002 等同于“内存不足”,这是一个非常奇怪的错误。发送到 delete 的 intf 对象此时是否真的有效(即尚未删除)? 这是第三方代码,我得看看。它可能会尝试删除不存在的对象。但是问题是这个异常怎么可能不能被捕获呢? 【参考方案1】:

我在 Windows 紧凑框架中使用 OpenNetCF 库时遇到了类似的问题。无法捕获的本机异常是随机抛出的,它使整个应用程序崩溃。在调查此问题的可能原因时,我可以知道这可能是由于内存泄漏导致系统内存不足或数据未对齐。

在调用OpenNETCF.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces() 方法之前,我调用了GC.Collect() 来回收所有不可访问的内存。 这解决了这个问题,之后我没有得到任何本机异常。

GC.Collect() 方法强制系统尝试回收最大数量的可用内存。所有对象,无论它们在内存中存在多长时间,都将被考虑收集;但是,不会收集托管代码中引用的对象。

【讨论】:

【参考方案2】:

我倾向于将所有 PInvoke 调用设为私有,并将它们包装在一个执行异常捕获的公共方法中:

class MyWrappingClass
    [DllImport("wzcsapi.dll")]
    private static extern void WZCDeleteIntfObj(ref INTF_ENTRY Intf);

    public void useWZCDeleteIntfObj(ref INTF_ENTRY Intf) //you may wish to use a better method name
        //you may wish to put some guards here, to check that Intf is valid before passing it to the native library.
        try
             WZCDeleteIntfObj( Intf );
        catch(ExternalException e) //try to catch more specific exception types, such as SEHException.
            //handle the exception
        
    

然后任何托管代码使用:

MyWrappingClass wrapper = new MyWrappingClass();
wrapper.useWZCDeleteIntfObj( Intf );

MyWrappingClass 还可以方便地放置帮助方法和抽象出一些底层 PInvoke 调用的方法,从而更轻松地处理原生库。

【讨论】:

在这种情况下它不会有帮助。使用 WZCDeleteIntfObj 方法的代码尝试使用空的 catch 语句捕获异常:try catch 正如我所说,异常没有被捕获。 啊,好的。我建议您在调用函数之前检查输入变量和软件其他相关部分的状态,以确保它们处于有效状态。关于您收到的具体错误,恐怕我不熟悉。【参考方案3】:

尝试使用托管 C++ 将异常捕获为 C++/C 异常,然后作为 CLR 异常重新抛出。当 pInvoke 变得太难时,托管 C++ 通常是一个不错的选择。

【讨论】:

【参考方案4】:

我遇到了同样的问题。 NativeException 不能在 c# try/catch 中被捕获。至少在 .NET CF 3.5 上没有。

抛出的错误是数据类型未对齐异常。这是一个可能发生的错误,例如ARM 处理器,因为 ARM 架构要求数据在内存中正确对齐。 我查看了 OpenNetCF.Net 代码,但找不到未对齐问题,也许我是 Windows WZZ API 中的错误导致错误。

我最终试图在 C 中捕获异常。本机异常是 SEH 异常。这是在 C 中实现异常的 Windows 方式。 这意味着 C++ try/catch 不会捕获它。 捕获它的方法是使用 __try/__except 块。

所以我最终创建了一个 C++ 项目(代码如下)。在 wzcsapi.dll 中创建一个调用 WZCDeleteIntfObj 的本机 DLL。代码只是捕获并忽略异常。 然后我从 codeplex 检索 OpenNet 代码,并将其修改为使用我的本机 dll 作为 wzcsapi 的 WZCDeleteIntfObj 方法的包装器。 结果看起来很有希望,但我不知道这是否会导致内存泄漏。

WZCWrapper.h

#include "stdafx.h"

#define WZCWrapper_API __declspec(dllexport)

typedef struct

    DWORD   dwDataLen;
    LPBYTE  pData;
 RAW_DATA, *PRAW_DATA;

typedef struct

    LPWSTR          wszGuid;
    LPWSTR          wszDescr;
    ULONG           ulMediaState;
    ULONG           ulMediaType;
    ULONG           ulPhysicalMediaType;
    INT             nInfraMode;
    INT             nAuthMode;
    INT             nWepStatus;
    DWORD           dwCtlFlags;
    DWORD           dwCapabilities;
    RAW_DATA        rdSSID;
    RAW_DATA        rdBSSID;
    RAW_DATA        rdBSSIDList;
    RAW_DATA        rdStSSIDList;
    BOOL            bInitialized;
 INTF_ENTRY, *PINTF_ENTRY;

WZCWrapper.dll

#include "stdafx.h"
#include "WZCWrapper.h"

typedef void (__cdecl *MYPROC)(PINTF_ENTRY);

static HINSTANCE wzcApiDll;
static BOOL initialized;
static MYPROC WZCDeleteIntfObj;

WZCWrapper_API VOID DLL_WZCDeleteIntfObj(PINTF_ENTRY pIntf) 
    if (!initialized)
        wzcApiDll = LoadLibrary(TEXT("wzcsapi.dll"));
        WZCDeleteIntfObj = (MYPROC) GetProcAddress(wzcApiDll, TEXT("WZCDeleteIntfObj"));
        initialized = TRUE;
    
    __try
    
        WZCDeleteIntfObj(pIntf);
    
    __except (GetExceptionCode() == STATUS_DATATYPE_MISALIGNMENT) 
    
    // FreeLibrary(wzcApiDll); // will release dll (we should not use this, keeping the dll open, as we need it again

WZCWrapper.def

LIBRARY "WZCWrapper"
EXPORTS
WZCDeleteIntfObj=DLL_WZCDeleteIntfObj

来自 OpenNetCF.Net 的 WZC.C 的修改部分

//---------------------------------------
// WZCDeleteIntfObj: cleans an INTF_ENTRY object that is
// allocated within any RPC call.
// 
// Parameters
// pIntf
//     [in] pointer to the INTF_ENTRY object to delete
[DllImport("WZCWrapper.dll")]
internal static extern void
    WZCDeleteIntfObj(
    ref INTF_ENTRY Intf);

【讨论】:

以上是关于.NET 外部非托管库函数引发无法捕获的异常的主要内容,如果未能解决你的问题,请参考以下文章

使用 P/Invoke 在托管和非托管回调链上引发异常

C#中如何调用动态链接库DLL

C#:捕获混合托管/非托管进程的所有错误/异常

无法从 main.cpp 中的共享库中捕获异常

非托管 DLL 函数的正确调用约定

托管库和非托管库之间有啥区别?