Search code examples
c#vb.netf#interopkernel32

Is there a C# & VB compatible System.Void* in F#? (To close a pointer to nonmanaged Alloc?)


    [<DllImport("kernel32")>]
    extern bool CloseHandle(System.Void* handle);
    //System.Void also throws same error
    //extern bool CloseHandle(System.Void handle); 

gives the error:

'System.Void' can only be used as 'typeof' in F#

but

    extern bool CloseHandle(typeof<System.Void> handle);

does not compile. Same error,

"System.Void can only be used as typeof..."

F# void* does compile

    extern bool CloseHandle(void* handle);

but using it in C# throws a design-time convert error

public void CloseBeforeGarbageCollection(IntPtr someAllocIntPtr)
{
    //test unmanaged block
    var result = CloseHandle(someAllocIntPtr.ToPointer());
    return result;
}

'cannot convert from 'void*' to 'System.IntPtr'

though passing the managed IntPtr will compile

//test managed IntPtr
var result = CloseHandle(someAllocIntPtr); //throws error at runtime

but when someAllocIntPtr is the result of Marshal.AllocHGlobal, it throws a runtime exception External component has thrown an exception.. As I understand it, this happens because someAllocIntPtr (as the result of Marshal.AllocHGlobal) is technically a managed pointer TO an unmanaged pointer, not the same as a normal IntPtr. This is noted by Peter Ritchie in a reply to his answer here: System.Runtime.InteropServices.SEHException (0x80004005): External component has thrown an exception

The only way to avoid this runtime Exception is to wrap the handle in a SecureHandle() subclass, but I think that's against the ref-ref\out-out rule on MSDN: CA1021: Avoid out parameters. IE, System.Void* realPointer = someAllocIntPtr.ToPointer() is the ACTUAL pointer (a reference to an unmanaged pointer), or in other words, SecureHandle safeHandle = new SecureHandle(someAllocIntPtr) is actually "a reference of - another reference of - an actual pointer", and should not be passed with out or ref keywords, according to the MSDN article.


Solution

  • I have done a little test in the following way:

    In an f# assembly (dll library) I have the following module:

    module MyWin32
    open System
    open System.Runtime.InteropServices
    
    [<DllImport("kernel32")>]
    extern bool CloseHandle(IntPtr handle);
    
    [<DllImport("kernel32")>]
    extern IntPtr CreateToolhelp32Snapshot(IntPtr flag, IntPtr procId);
    

    In an F# Console Program that has a reference to the above lib I have:

    open System
    open System.Runtime.InteropServices
    open MyWin32
    
    [<EntryPoint>]
    let main argv = 
    
      let handle = CreateToolhelp32Snapshot(IntPtr(4), IntPtr(System.Diagnostics.Process.GetCurrentProcess().Id))
      printfn "%A" handle
      printfn "%b" (CloseHandle handle)
    
      // A HGlobal should always be released by FreeHGlobal
      let intPtr = Marshal.AllocHGlobal(1024)
      Marshal.FreeHGlobal(intPtr)
    

    And in a C# Console program that references the above lib I have:

    using System;
    
    namespace CSTest
    {
      class Program
      {
        static void Main(string[] args)
        {
          var handle = MyWin32.CreateToolhelp32Snapshot(new IntPtr(4), new IntPtr(System.Diagnostics.Process.GetCurrentProcess().Id));
          Console.WriteLine(handle);
          Console.WriteLine(MyWin32.CloseHandle(handle));
    
          Console.ReadLine();
        }
      }
    }
    

    Both the F# and C# test compiles and runs as expeceted. I hope this will help you.

    About void*:

    Changing f# assembly MyWin32 shown above to the following with substution of IntPtr to void* still works for the F# and C# client without any other modfications (The C# metadata code of MyWin32 substitutes void* with IntPtr):

    module MyWin32
    open System
    open System.Runtime.InteropServices
    
    [<DllImport("kernel32")>]
    extern bool CloseHandle(void* handle);
    
    [<DllImport("kernel32")>]
    extern void* CreateToolhelp32Snapshot(IntPtr flag, IntPtr procId);
    

    So the conclusion of the above small tests is that you can use void* in F# as a valid substitution for IntPtr.

    I think one should only use IntPtr.ToPointer() in an unsafe {} section in C#, as pointers only make sense in unsafe mode in C#.