2010-01-10 4 views
3

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


[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. 
} 

Проще говоря, голые ручки, полученный в междунар или IntPtr может быть утечкой, когда исключение бросков перед тем, завернутыми в соответствующий Dipose скороговорку. Когда вы возвращаете голый дескриптор в собственный код, он, как правило, собирается g, прежде чем собственный код использует дескриптор. Мне интересно узнать, как решить эту проблему с достаточной степенью безопасности. Специально возвращающаяся ручка беспокоит меня. Это просто примеры для краткости, я не работаю с файловым дескриптором в действительности. Я бы предпочел наследовать мою собственную от 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. 
} 

+0

Что такое intPtr в getHandle()? Я не вижу, где это определено. – SwDevMan81

+0

Я думаю, что могу помочь, но мне нужна дополнительная информация. Вы хотите иметь возможность вызвать управляемую функцию из неуправляемого кода, который принимает дескриптор в качестве первого аргумента, правильно? Передает ли этот вызов право собственности на дескриптор на управляемый код? –

+0

@ SwDevMan81, вы правы, это была опечатка –

ответ

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(); 
    } 
+0

У меня нет проблем с перемещением IntPtr или SafeHandle в память. Проблема в том, что SafeHandle рано устанавливается. Вы можете попросить меня вывести FileStream из getHandle() вместе с дескриптором. И назовите какой-нибудь фиктивный метод на fs после того, как я использую этот дескриптор, чтобы оптимизатор JIT не опустил ссылку на fs. Конечно, это работает. Но это усложняет дизайн любого API/библиотеки более чем приемлемым. –

+0

Хорошо, это имеет смысл. Я обновил свой ответ. Вы должны иметь возможность перейти от IntPtr к HandleRef и заставить его работать так же. Так что «голый» будет проходить в HandleRef, а не в IntPtr (например, мой метод ReadFile PInvoke в моем примере) – SwDevMan81

+0

Я думаю, вы могли бы передать intPtr.Handle вместо изменения определений методов, если это будет боль – SwDevMan81

1

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

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

+0

+1. DuplicateHandle будет моим ответом. – wj32

+0

В моем случае это не окно, но я вижу вашу точку. Но я не уверен, что соглашусь. SafeHandle владеет ссылкой и ссылкой на SafeHandle, естественно, на стек до вызова метода native, потому что он передается маршаллеру. Поэтому он не мог исчезнуть. Я думаю, что это связано с причинами, по которым для маршаллера существует определенная передача. –

+0

Кроме того, если вы дублируете дескриптор, он будет обрабатывать дескрипторы, потому что вызываемый метод обычно не закрывает дескрипторы. Это не хорошо. –

Смежные вопросы