[英]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; }
使用這些代碼,您可以先對異常進行序列化,然后在以后的任何時間反序列化以檢索表示原始異常的對象-只是引用與原始對象不同,引用的對象也與原始對象不同。
我要添加一些代碼注釋:
DLL名稱和DllImport
方法入口點應作為您的真實版本進行修改,我沒有操縱錯誤的名稱。
Unmanaged Helper
只是使用示例非托管方法StoreException
和RetrieveException
的互操作的演示; 您不必像我處理代碼那樣編寫代碼,您可能希望以自己的方式進行設計。
如果要在非托管代碼中反序列化異常,則可能要設計自己的格式化程序,而不是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.