简体   繁体   English

如何在C#中封送IntPtr的例外

[英]How to marshal an Exception to an IntPtr in C#

I want to keep a pointer to a managed Exception object in an unmanaged C assembly. 我想在非托管C程序集中保留一个指向托管Exception对象的指针。

I've tried a bunch of ways. 我尝试了很多方法。 This is the only one I've found that passes my very preliminary tests. 这是我发现的唯一通过初步测试的产品。

Is there a better way? 有没有更好的办法?

What I'd really like to do is handle the alloc and free methods in the ExceptionWrapper constructor and destructor, but structs can't have constructors or destructors. 我真正想做的是在ExceptionWrapper构造函数和析构函数中处理alloc和free方法,但是结构不能具有构造函数或析构函数。

EDIT: Re: Why I would like this: 编辑:回复:为什么我要这样:

My C structure has a function pointer that is set with a managed delegate marshaled as an unmanaged function pointer. 我的C结构具有一个函数指针,该函数指针设置有作为非托管函数指针封送的托管委托。 The managed delegate performs some complicated measurements using external equipment and an exceptions could occur during those measurements. 托管代表使用外部设备执行一些复杂的测量,并且在这些测量过程中可能会发生异常。 I'd like to keep track of the last one that occurred and its stack trace. 我想跟踪发生的最后一个事件及其堆栈跟踪。 Right now, I'm only saving the exception message. 现在,我只保存异常消息。

I should point out that the managed delegate has no idea it's interacting with a C DLL. 我应该指出,托管委托不知道它正在与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);
    }
}

This doesn't work. 这行不通。 You are hiding a reference to the Exception object in unmanaged memory. 您正在非托管内存中隐藏对Exception对象的引用。 The garbage collector cannot see it there so it cannot update the reference. 垃圾收集器在此处看不到它,因此无法更新引用。 When the C spits the pointer back out later, the reference won't point the object anymore after the GC has compacted the heap. 当C稍后将指针吐出时,GC压缩了堆之后,引用将不再指向该对象。

You'll need to pin the pointer with GCHandle.Alloc() so the garbage collector cannot move the object. 您需要使用GCHandle.Alloc()固定指针,以使垃圾收集器无法移动对象。 And can pass the pointer returned by AddrOfPinnedObject() to the C code. 并且可以将AddrOfPinnedObject()返回的指针传递给C代码。

That's fairly painful if the C code holds on that pointer for a long time. 如果C代码长时间保持该指针,那将是非常痛苦的。 The next approach is to give the C code a handle . 下一种方法是给C代码一个句柄 Create a Dictionary<int, Exception> to store the exception. 创建一个Dictionary<int, Exception>来存储异常。 Have a static int that you increment. 具有一个递增的静态整数。 That's the 'handle' value you can pass to the C code. 那就是您可以传递给C代码的'handle'值。 It is not perfect, you'll run into trouble when the program has added more than 4 billion exceptions and the counter overflows. 这并不完美,当程序添加了超过40亿个异常并且计数器溢出时,您会遇到麻烦。 Hopefully you'll never actually have that many exceptions. 希望您实际上不会有那么多例外。

You want serialization . 您要序列化

As a side note, your statement of: 附带说明一下,您的声明:

What I'd really like to do is handle the alloc and free methods in the ExceptionWrapper constructor and destructor, but structs can't have constructors or destructors. 我真正想做的是在ExceptionWrapper构造函数和析构函数中处理alloc和free方法,但是结构不能具有构造函数或析构函数。

is not true. 是不正确的。 struct s in C# can have and do have constructor(s), just not allowing user to declare parameterless constructor explicitly . C#中的struct 可以并且确实具有构造函数,只是不允许用户显式声明无参数构造函数 That is, for example, you can declare a constructor which accepts an Exception . 也就是说,例如,您可以声明一个接受Exception的构造函数。 For destructors, which is not widely used in managed code, you should implement IDisposable if your class will hold some unmanaged resources. 对于在托管代码中未广泛使用的析构函数,如果您的类将容纳一些非托管资源,则应实现IDisposable

Exception is non-blittable, you may not marshalling it the way you described, but it can be serialized as byte arry thus makes interop possible. Exception是不可传播的,您可能无法按照您描述的方式将其编组,但是可以将其序列化为字节进位,从而使互操作成为可能。 I've read your another question: 我已经阅读了您的另一个问题:

Implications of throwing exception in delegate of unmanaged callback 在非托管回调的委托中引发异常的含义

and take the some of the usage from your code. 并从代码中获取一些用法。 Lets's to have two projects, one for managed and the other for the unmanaged code. 让我们有两个项目,一个用于托管项目,另一个用于非托管代码。 You can create them with all the default settings but note the bitness of the executable images should be set the same. 您可以使用所有默认设置创建它们,但请注意,可执行映像的位数应设置为相同。 There are just one file per project need to be modified: 每个项目只有一个文件需要修改:

  • Managed console application - Program.cs: 托管控制台应用程序-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 class library - MyDll.cpp: 未命名的类库-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; } 

With these code you can serialize the exception first and then deserialize it at any later time for retrieving the object that represents the original exception - just the reference would not be as same as the original, so are the objects it referenced. 使用这些代码,您可以先对异常进行序列化,然后在以后的任何时间反序列化以检索表示原始异常的对象-只是引用与原始对象不同,引用的对象也与原始对象不同。

I'd add some note of the code: 我要添加一些代码注释:

  1. The dll name and method entry point of DllImport should be modified as your real build, I didn't manipulate the mangled names. DLL名称和DllImport方法入口点应作为您的真实版本进行修改,我没有操纵错误的名称。

  2. Unmanaged Helper is just a demonstration of the interop with the example unmanaged methods StoreException and RetrieveException ; Unmanaged Helper只是使用示例非托管方法StoreExceptionRetrieveException的互操作的演示; you don't have to write the code like how I deal with them, you might want to design in your own way. 您不必像我处理代码那样编写代码,您可能希望以自己的方式进行设计。

  3. If you want to deserialize the exception within the unmanaged code, you might want to design your own formatter rather than BinaryFormatter . 如果要在非托管代码中反序列化异常,则可能要设计自己的格式化程序,而不是BinaryFormatter I don't know an existing implementation of Microsoft's specification in non-cli C++. 我不知道Microsoft规范在非CLI C ++中的现有实现。


In case you want to build the code in C instead of C++, you'll have to change the Compile As option(Visual Studio) and rename MyDll.cpp to MyDll.c; 如果要使用C而不是C ++生成代码,则必须更改“ Compile As选项(Visual Studio),并将MyDll.cpp重命名为MyDll.c; also note the mangled names would be like _StoreException@8 ; 还请注意, _StoreException@8名称类似于_StoreException@8 ; use DLL Export Viewer or hex editors to find the exact name. 使用DLL导出查看器或十六进制编辑器查找确切名称。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM