簡體   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