Как вывести исключение в IntPtr в C # - PullRequest
1 голос
/ 28 января 2012

Я хочу сохранить указатель на управляемый объект Exception в неуправляемой сборке C.

Я пробовал несколько способов. Это единственный, который я нашел, который проходит мои предварительные тесты.

Есть ли лучший способ?

Что я действительно хотел бы сделать, так это обработать методы alloc и free в конструкторе и деструкторе ExceptionWrapper, но структуры не могут иметь конструкторов или деструкторов.

РЕДАКТИРОВАТЬ: Re: Почему я хотел бы это:

Моя структура 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 ]

5 голосов
/ 28 января 2012

Это не работает. Вы скрываете ссылку на объект Exception в неуправляемой памяти. Сборщик мусора не может видеть его там, поэтому он не может обновить ссылку. Когда C позже выплевывает указатель обратно, ссылка больше не будет указывать объект после того, как GC сжал кучу.

Вам нужно закрепить указатель с помощью GCHandle.Alloc (), чтобы сборщик мусора не мог переместить объект. И может передать указатель, возвращенный AddrOfPinnedObject (), в код на языке C.

Это довольно болезненно, если код C долго удерживает этот указатель. Следующий подход состоит в том, чтобы дать коду C дескриптор . Создайте Dictionary<int, Exception> для хранения исключения. Имейте статический int, который вы увеличиваете. Это значение дескриптора, которое вы можете передать в C-код. Это не идеально, вы столкнетесь с проблемами, когда программа добавит более 4 миллиардов исключений и переполнение счетчика. Надеюсь, у вас никогда не будет столько исключений.

0 голосов
/ 04 января 2018

Вы хотите сериализация .

В качестве примечания, ваше заявление:

Что я действительно хотел бы сделать, это обработать методы alloc и free в конструкторе и деструкторе ExceptionWrapper, но структуры могут это сделать 'у него нет конструкторов или деструкторов.

не соответствует действительности.struct s в C # может иметь и делать иметь конструктор (ы), просто не позволяя пользователю явно объявить конструктор без параметров .То есть, например, вы можете объявить конструктор, который принимает Exception.Для деструкторов, которые не используются широко в управляемом коде, вы должны реализовать IDisposable, если ваш класс будет содержать некоторые неуправляемые ресурсы.

Exception не является blittable, вы не можете упорядочить его так, как вы описали, но он может быть сериализован, так как байтовый массив делает возможным взаимодействие.Я прочитал ваш другой вопрос:

Последствия создания исключения в делегате неуправляемого обратного вызова

и некоторые из них используются в вашем коде.Давайте иметь два проекта, один для управляемого и другой для неуправляемого кода.Вы можете создать их со всеми настройками по умолчанию, но обратите внимание, что битность исполняемых образов должна быть одинаковой.Для каждого проекта требуется изменить только один файл:

  • Управляемое консольное приложение - 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 - это просто демонстрация взаимодействия с примерами неуправляемых методов StoreException и RetrieveException;вам не нужно писать код, например, как я с ними справляюсь, возможно, вы захотите создать свой собственный дизайн.

  3. Если вы хотите десериализовать исключение в неуправляемом коде, вы, возможно, захотите создать свой собственный модуль форматирования, а не BinaryFormatter.Я не знаю существующую реализацию спецификации Microsoft в не-C ++ C ++.


Если вы хотите построить код на C вместо C ++, вам придется изменить параметр Compile As (Visual Studio) и переименовать MyDll.cpp вMyDll.c;также обратите внимание, что искаженные имена будут похожи на _StoreException@8;используйте DLL Export Viewer или шестнадцатеричные редакторы, чтобы найти точное имя.

...