如何在C#中编组一个IntPtr的异常

时间:2012-01-27 21:57:42

标签: c# c .net clr marshalling

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

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

有更好的方法吗?

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

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

我的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);
    }
}

2 个答案:

答案 0 :(得分:5)

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

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

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

答案 1 :(得分:0)

您想要序列化

作为附注,您的陈述:

  

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

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

Exception是非blittable,您可能不会按照您描述的方式对其进行编组,但它可以序列化为字节,从而使interop成为可能。我已经阅读了你的另一个问题:

Implications of throwing exception in delegate of unmanaged callback

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

  • 托管控制台应用程序 - 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;
            }
        }
    }
    
  • Unnamaged类库 - 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. DllImport的dll名称和方法入口点应该被修改为您的真实版本,我没有操纵受损的名称。

  2. Unmanaged Helper只是对示例非托管方法StoreExceptionRetrieveException的互操作的演示;你不必像我如何处理它们那样编写代码,你可能想以自己的方式进行设计。

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

  4. 如果您想用C而不是C ++构建代码,您必须更改Compile As选项(Visual Studio)并将MyDll.cpp重命名为MyDll.c;还要注意,错位的名字会像_StoreException@8;使用DLL导出查看器或十六进制编辑器查找确切的名称。