Как получить GetFunctionPointerForDelegate для метода с SafeHandle или обходного пути - PullRequest
3 голосов
/ 10 января 2010

Это пример кода, который вызывает исключение MarshalDirectiveException. Хорошее объяснение SafeHandle s можно найти здесь .


[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. При возврате «голого» дескриптора в собственный код, он имеет тенденцию собираться до того, как нативный код использует дескриптор. Мне интересно узнать, как обойти эту проблему с достаточной безопасностью. Меня особенно беспокоит возвращающаяся ручка. Это всего лишь примеры для краткости, в действительности я не работаю с дескриптором файла. Я бы предпочел унаследовать свое от 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 ]

1 голос
/ 10 января 2010

Это просто ошибка, никакой надежный дескриптор не позволит избежать сбора и финализации класса-оболочки .NET. Это довольно распространенная ловушка в P / Invoke, другой классический случай - передача делегата, который оборачивает обратный вызов, и забывание сохранять ссылку на объект делегата.

Обходные пути достаточно просты: не берите дескриптор до самого последнего момента, сборщик мусора увидит ссылку FS в стеке вызовов. Или сохраните ФС в поле объекта, который переживает вызов. Или P / Invoke DuplicateHandle, чтобы обертка могла быть завершена без проблем.

1 голос
/ 10 января 2010

Типичный способ справиться с этим - закрепить данные в управляемом коде перед вызовом неуправляемых функций. Здесь - пример закрепления данных и вызова неуправляемых вызовов.

Обновление: на основании комментария вы можете использовать HandleRef, чтобы сохранить ссылку на объект. Тогда вы все равно можете передать «дескриптор» на ваши вызовы 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();
  }
...