如何使用SafeHandle或变通方法获取GetFunctionPointerForDelegate方法

时间:2010-01-10 00:20:18

标签: c# .net interop pinvoke

这是导致MarshalDirectiveException的代码示例。可以找到SafeHandle的良好解释here


[SuppressUnmanagedCodeSecurity]
private delegate SafeHandle testDelegate();

[SuppressUnmanagedCodeSecurity]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
public static SafeHandle test(){
    FileStream fs=new FileStream("a.txt", FileMode.Create);
    return fs.SafeFileHandle;
}

private static void Main(){
    MethodInfo methodInfo = typeof (Program).GetMethod("test", BindingFlags.Static | BindingFlags.Public);
    Delegate delegateInstance = Delegate.CreateDelegate(typeof (testDelegate), methodInfo);

    //System.Runtime.InteropServices.MarshalDirectiveException
    //Cannot marshal 'return value': SafeHandles cannot be returned from managed to unmanaged.
    IntPtr fcePtr = Marshal.GetFunctionPointerForDelegate(delegateInstance);

    // alternatively for method parameter
    // throws System.Runtime.InteropServices.MarshalDirectiveException 
    // Cannot marshal 'parameter #1': This type can only be marshaled in restricted ways."

    // alternatively for HandleRef
    // System.Runtime.InteropServices.MarshalDirectiveException
    // Cannot marshal 'parameter #1': HandleRefs cannot be marshaled ByRef or from unmanaged to managed.
}

简单地说,裸体手柄,作为int或IntPtr接收可能会泄漏,当异常被抛出之前被包裹到适当的Dipose模式。将裸柄返回到本机代码时,在本机代码使用句柄之前,往往会对其进行g收集。我有兴趣学习如何以足够的安全性来解决这个问题。特别回归的手柄让我很担心。这些仅仅是为了简洁起见,我实际上并不使用File句柄。我宁愿从SafeHandle继承我自己。


[DllImport("mydll")]
public static extern void naked(IntPtr nakedHandle);

private static void Main(){
    IntPtr intPtr = getHandle();
    naked(intPtr);
}

private static IntPtr getHandle(){
    FileStream fs = new FileStream("myfile", FileMode.CreateNew);
    IntPtr ha = fs.Handle;
    return ha;
    // at this point, fs is garbage collected. 
    // ha is pointing to nonexistent or different object.
}

2 个答案:

答案 0 :(得分:1)

处理此问题的典型方法是在调用非托管函数之前将数据固定在托管代码中。 Here是固定数据和调用非托管调用的示例。

更新:根据评论,您可以使用HandleRef使对象引用保持活动状态。然后你仍然可以将“Handle”传递给你的PInvoke调用。这是一个对我有用的例子:

  [DllImport("kernel32.dll", SetLastError=true)]
  static extern bool ReadFile(HandleRef hFile, byte[] lpBuffer,
     uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);

  private static HandleRef getHandle()
  {
     FileStream fs = new FileStream("myfile", FileMode.OpenOrCreate, FileAccess.ReadWrite);
     return new HandleRef(fs, fs.SafeFileHandle.DangerousGetHandle());
  }

  private static void Main()
  {
     HandleRef intPtr = getHandle();
     GC.Collect();
     GC.WaitForPendingFinalizers();
     GC.Collect();
     System.Threading.Thread.Sleep(1000);

     const uint BYTES_TO_READ = 10;
     byte[] buffer = new byte[BYTES_TO_READ];
     uint bytes_read = 0;        

     bool read_ok = ReadFile(intPtr, buffer, BYTES_TO_READ, out bytes_read, IntPtr.Zero);
     if (!read_ok)
     {
        Win32Exception ex = new Win32Exception();
        string errMsg = ex.Message;
     }
  }

几乎忘了清理这里:

  IDisposable is_disposable = intPtr.Wrapper as IDisposable;
  if (is_disposable != null)
  {
     is_disposable.Dispose();
  }

答案 1 :(得分:1)

这只是一个错误,没有任何安全的句柄可以避免.NET包装器类被垃圾收集和最终确定。它是P / Invoke中一个非常常见的陷阱,另一个经典案例是传递包装回调并忘记保留对委托对象的引用的委托。

变通方法很简单:在最后一刻之前不要使用Handle,垃圾收集器将在调用堆栈上看到FS引用。或者将FS存储在比呼叫更长的对象的字段中。或者P / Invoke DuplicateHandle这样可以毫不费力地完成包装。