如何在C#中将异常封送到IntPtr
本文关键字:IntPtr 异常 | 更新日期: 2023-09-27 18:21:35
我想在非托管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.Aloc()固定指针,这样垃圾收集器就无法移动对象。并且可以将AddrOfPinnedObject()返回的指针传递给C代码。
如果C代码长时间保持该指针,那就相当痛苦了。下一种方法是给C代码一个句柄。创建一个Dictionary<int, Exception>
来存储异常。有一个递增的静态int。这是可以传递给C代码的"handle"值。这并不完美,当程序添加了超过40亿个异常并且计数器溢出时,您会遇到麻烦。希望你永远不会有那么多例外。
您想要序列化。
作为旁注,您的声明:
我真正想做的是处理ExceptionWrapper构造函数和析构函数中的alloc和free方法,但结构不能有构造函数或析构函数。
不是真的。C#中的struct
可以具有构造函数,而不具有构造函数,只是不允许用户显式声明无参数构造函数。也就是说,例如,您可以声明一个接受Exception
的构造函数。对于在托管代码中没有广泛使用的析构函数,如果类将包含一些非托管资源,则应该实现IDisposable
。
Exception
是不可blitable的,您可能不会按照您描述的方式对其进行编组,但它可以序列化为字节arry,从而使互操作成为可能。我读过你的另一个问题:
在非托管回调的委托中引发异常的含义
并从代码中获取一些用法。让我们有两个项目,一个用于托管代码,另一个用于非托管代码。您可以使用所有默认设置创建它们,但请注意,可执行映像的比特度应设置为相同。每个项目只有一个文件需要修改:
-
托管控制台应用程序-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; }
使用这些代码,您可以先序列化异常,然后在以后的任何时候对其进行反序列化,以检索表示原始异常的对象-只是引用与原始异常不同,它引用的对象也不同。
我想添加一些代码注释:
-
DllImport
的dll名称和方法入口点应该根据您的实际构建进行修改,我没有处理损坏的名称。 -
Unmanaged Helper
只是示例非托管方法StoreException
和RetrieveException
的互操作的演示;你不必像我处理它们那样编写代码,你可能想用自己的方式设计。 -
如果要在非托管代码中反序列化异常,则可能需要设计自己的格式化程序,而不是
BinaryFormatter
。我不知道微软规范在非cli C++中的现有实现。
如果您想用C而不是C++构建代码,则必须更改Compile As
选项(Visual Studio)并将MyDll.cpp重命名为MyDll.C;还要注意,被篡改的名称将类似于_StoreException@8
;使用DLL导出查看器或十六进制编辑器来查找确切的名称。