我想在非托管C程序集中保留指向托管Exception
对象的指针。
我尝试了很多方法。这是我发现的唯一通过我的初步测试的人。
有更好的方法吗?
我真正想做的是处理ExceptionWrapper
构造函数和析构函数中的alloc和free方法,但是struct不能有构造函数或析构函数。
编辑:回复:为什么我要这样:
我的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);
}
}
答案 0 :(得分:5)
这不起作用。您正在隐藏对非托管内存中的Exception对象的引用。垃圾收集器无法在那里看到它,因此它无法更新引用。当C稍后将指针退出时,在GC压缩堆之后,引用将不再指向对象。
您需要使用GCHandle.Alloc()固定指针,以便垃圾收集器无法移动对象。并且可以将AddrOfPinnedObject()返回的指针传递给C代码。
如果C代码长时间持有该指针,那将是非常痛苦的。接下来的方法是给C代码一个句柄。创建Dictionary<int, Exception>
以存储异常。有一个你增加的静态int。这是你可以传递给C代码的'句柄'值。它并不完美,当程序添加超过40亿个异常并且计数器溢出时,您将遇到麻烦。希望你永远不会有那么多例外。
答案 1 :(得分:0)
您想要序列化。
作为附注,您的陈述:
我真正想做的是处理ExceptionWrapper构造函数和析构函数中的alloc和free方法,但结构不能有构造函数或析构函数。
并非都是如此。 C#中的struct
可以和做具有构造函数,只是不允许用户明确声明无参数构造函数。也就是说,例如,您可以声明一个接受Exception
的构造函数。对于在托管代码中未广泛使用的析构函数,如果您的类将拥有一些非托管资源,则应实现IDisposable
。
Exception
是非blittable,您可能不会按照您描述的方式对其进行编组,但它可以序列化为字节,从而使interop成为可能。我已经阅读了你的另一个问题:
Implications of throwing exception in delegate of unmanaged callback
并采用代码中的一些用法。让我们有两个项目,一个用于托管,另一个用于非托管代码。您可以使用所有默认设置创建它们,但请注意可执行映像的位数应设置为相同。每个项目只需要修改一个文件:
托管控制台应用程序 - 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类库 - 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 ++中Microsoft's specification的现有实现。
如果您想用C而不是C ++构建代码,您必须更改Compile As
选项(Visual Studio)并将MyDll.cpp重命名为MyDll.c;还要注意,错位的名字会像_StoreException@8
;使用DLL导出查看器或十六进制编辑器查找确切的名称。