繁体   English   中英

如何在C#中封送IntPtr的例外

[英]How to marshal an Exception to an IntPtr in C#

我想在非托管C程序集中保留一个指向托管Exception对象的指针。

我尝试了很多方法。 这是我发现的唯一通过初步测试的产品。

有没有更好的办法?

我真正想做的是在ExceptionWrapper构造函数和析构函数中处理alloc和free方法,但是结构不能具有构造函数或析构函数。

编辑:回复:为什么我要这样:

我的C结构具有一个函数指针,该函数指针设置有作为非托管函数指针封送的托管委托。 托管代表使用外部设备执行一些复杂的测量,并且在这些测量过程中可能会发生异常。 我想跟踪发生的最后一个事件及其堆栈跟踪。 现在,我只保存异常消息。

我应该指出,托管委托不知道它正在与C DLL进行交互。

public class MyClass {
    private IntPtr _LastErrorPtr;

    private struct ExceptionWrapper
    {
        public Exception Exception { get; set; }
    }

    public Exception LastError
    {
        get
        {
            if (_LastErrorPtr == IntPtr.Zero) return null;
            var wrapper = (ExceptionWrapper)Marshal.PtrToStructure(_LastErrorPtr, typeof(ExceptionWrapper));
            return wrapper.Exception;
        }
        set
        {
            if (_LastErrorPtr == IntPtr.Zero)
            {
                _LastErrorPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ExceptionWrapper)));
                if (_LastErrorPtr == IntPtr.Zero) throw new Exception();
            }

            var wrapper = new ExceptionWrapper();
            wrapper.Exception = value;
            Marshal.StructureToPtr(wrapper, _LastErrorPtr, true);
        }
    }

    ~MyClass()
    {
        if (_LastErrorPtr != IntPtr.Zero) Marshal.FreeHGlobal(_LastErrorPtr);
    }
}

这行不通。 您正在非托管内存中隐藏对Exception对象的引用。 垃圾收集器在此处看不到它,因此无法更新引用。 当C稍后将指针吐出时,GC压缩了堆之后,引用将不再指向该对象。

您需要使用GCHandle.Alloc()固定指针,以使垃圾收集器无法移动对象。 并且可以将AddrOfPinnedObject()返回的指针传递给C代码。

如果C代码长时间保持该指针,那将是非常痛苦的。 下一种方法是给C代码一个句柄 创建一个Dictionary<int, Exception>来存储异常。 具有一个递增的静态整数。 那就是您可以传递给C代码的'handle'值。 这并不完美,当程序添加了超过40亿个异常并且计数器溢出时,您会遇到麻烦。 希望您实际上不会有那么多例外。

您要序列化

附带说明一下,您的声明:

我真正想做的是在ExceptionWrapper构造函数和析构函数中处理alloc和free方法,但是结构不能具有构造函数或析构函数。

是不正确的。 C#中的struct 可以并且确实具有构造函数,只是不允许用户显式声明无参数构造函数 也就是说,例如,您可以声明一个接受Exception的构造函数。 对于在托管代码中未广泛使用的析构函数,如果您的类将容纳一些非托管资源,则应实现IDisposable

Exception是不可传播的,您可能无法按照您描述的方式将其编组,但是可以将其序列化为字节进位,从而使互操作成为可能。 我已经阅读了您的另一个问题:

在非托管回调的委托中引发异常的含义

并从代码中获取一些用法。 让我们有两个项目,一个用于托管项目,另一个用于非托管代码。 您可以使用所有默认设置创建它们,但请注意,可执行映像的位数应设置为相同。 每个项目只有一个文件需要修改:

  • 托管控制台应用程序-Program.cs:

     using System.Diagnostics; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.InteropServices; using System.IO; using System; namespace ConsoleApp1 { class Program { [DllImport(@"C:\\Projects\\ConsoleApp1\\Debug\\MyDll.dll", EntryPoint = "?return_callback_val@@YGHP6AHXZ@Z")] static extern int return_callback_val(IntPtr callback); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] delegate int CallbackDelegate(); static int Callback() { try { throw new Exception("something went wrong"); } catch(Exception e) { UnmanagedHelper.SetLastException(e); } return 0; } static void Main() { CallbackDelegate @delegate = new CallbackDelegate(Callback); IntPtr callback = Marshal.GetFunctionPointerForDelegate(@delegate); int returnedVal = return_callback_val(callback); var e = UnmanagedHelper.GetLastException(); Console.WriteLine("exception: {0}", e); } } } 

     namespace ConsoleApp1 { public static class ExceptionSerializer { public static byte[] Serialize(Exception x) { using(var ms = new MemoryStream { }) { m_formatter.Serialize(ms, x); return ms.ToArray(); } } public static Exception Deserialize(byte[] bytes) { using(var ms = new MemoryStream(bytes)) { return (Exception)m_formatter.Deserialize(ms); } } static readonly BinaryFormatter m_formatter = new BinaryFormatter { }; } } 

     namespace ConsoleApp1 { public static class UnmanagedHelper { [DllImport(@"C:\\Projects\\ConsoleApp1\\Debug\\MyDll.dll", EntryPoint = "?StoreException@@YGHHQAE@Z")] static extern int StoreException(int length, byte[] bytes); [DllImport(@"C:\\Projects\\ConsoleApp1\\Debug\\MyDll.dll", EntryPoint = "?RetrieveException@@YGHHQAE@Z")] static extern int RetrieveException(int length, byte[] bytes); public static void SetLastException(Exception x) { var bytes = ExceptionSerializer.Serialize(x); var ret = StoreException(bytes.Length, bytes); if(0!=ret) { Console.WriteLine("bytes too long; max available size is {0}", ret); } } public static Exception GetLastException() { var bytes = new byte[1024]; var ret = RetrieveException(bytes.Length, bytes); if(0==ret) { return ExceptionSerializer.Deserialize(bytes); } else if(~0!=ret) { Console.WriteLine("buffer too small; total {0} bytes are needed", ret); } return null; } } } 
  • 未命名的类库-MyDll.cpp:

     // MyDll.cpp : Defines the exported functions for the DLL application. // #include "stdafx.h" #define DLLEXPORT __declspec(dllexport) #define MAX_BUFFER_LENGTH 4096 BYTE buffer[MAX_BUFFER_LENGTH]; int buffer_length; DLLEXPORT int WINAPI return_callback_val(int(*callback)(void)) { return callback(); } DLLEXPORT int WINAPI StoreException(int length, BYTE bytes[]) { if (length<MAX_BUFFER_LENGTH) { buffer_length=length; memcpy(buffer, bytes, buffer_length); return 0; } return MAX_BUFFER_LENGTH; } DLLEXPORT int WINAPI RetrieveException(int length, BYTE bytes[]) { if (buffer_length<1) { return ~0; } if (buffer_length<length) { memcpy(bytes, buffer, buffer_length); return 0; } return buffer_length; } 

使用这些代码,您可以先对异常进行序列化,然后在以后的任何时间反序列化以检索表示原始异常的对象-只是引用与原始对象不同,引用的对象也与原始对象不同。

我要添加一些代码注释:

  1. DLL名称和DllImport方法入口点应作为您的真实版本进行修改,我没有操纵错误的名称。

  2. Unmanaged Helper只是使用示例非托管方法StoreExceptionRetrieveException的互操作的演示; 您不必像我处理代码那样编写代码,您可能希望以自己的方式进行设计。

  3. 如果要在非托管代码中反序列化异常,则可能要设计自己的格式化程序,而不是BinaryFormatter 我不知道Microsoft规范在非CLI C ++中的现有实现。


如果要使用C而不是C ++生成代码,则必须更改“ Compile As选项(Visual Studio),并将MyDll.cpp重命名为MyDll.c; 还请注意, _StoreException@8名称类似于_StoreException@8 ; 使用DLL导出查看器或十六进制编辑器查找确切名称。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM