P/Invoke "why" question
-
Here is one for you Win32 experts. When I as a Framework developer need to use a Win API function, I can declare the function with parameters I need rather than with strict type checking. For example, check out the underlined code in SendMessage()
[DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, StringBuilder lParam);
StringBuilder? Hello? What perplexes me is that the API is expecting a pointer but we pass in a .NET StringBuilder. Please explain to a semi-newbie what dark magic allows this to happen. Thank you. Gordon -
Here is one for you Win32 experts. When I as a Framework developer need to use a Win API function, I can declare the function with parameters I need rather than with strict type checking. For example, check out the underlined code in SendMessage()
[DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, StringBuilder lParam);
StringBuilder? Hello? What perplexes me is that the API is expecting a pointer but we pass in a .NET StringBuilder. Please explain to a semi-newbie what dark magic allows this to happen. Thank you. GordonThat one is pretty simple, at least on the surface: all the compiler does is check consistency between your calling code and the prototype(s) you provide (both in C# or VB.NET), as if the native method were a managed one (but with the implementation lacking). The compiler never looks at the DLL file at all, it does not even have to be present while building. At run-time what gets passed is not type-checked at all, so whether it is an array pointer, a StringBuilder reference, or an IntPtr, as long as it points to the actual data, it could work. FYI: there may be some data conversion going on behind the scene, e.g. when the data passed is a string and the native method uses ANSI (8-bit!), the "Marshaling" will convert to/from Unicode (16-bit) for you automaticaally (using a temporary buffer, so also faking the pointer value!). :)
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum
Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.
modified on Monday, August 9, 2010 3:05 PM
-
That one is pretty simple, at least on the surface: all the compiler does is check consistency between your calling code and the prototype(s) you provide (both in C# or VB.NET), as if the native method were a managed one (but with the implementation lacking). The compiler never looks at the DLL file at all, it does not even have to be present while building. At run-time what gets passed is not type-checked at all, so whether it is an array pointer, a StringBuilder reference, or an IntPtr, as long as it points to the actual data, it could work. FYI: there may be some data conversion going on behind the scene, e.g. when the data passed is a string and the native method uses ANSI (8-bit!), the "Marshaling" will convert to/from Unicode (16-bit) for you automaticaally (using a temporary buffer, so also faking the pointer value!). :)
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum
Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.
modified on Monday, August 9, 2010 3:05 PM
Thank you, Luc!
-
Thank you, Luc!
you're welcome. :)
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum
Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.
-
Here is one for you Win32 experts. When I as a Framework developer need to use a Win API function, I can declare the function with parameters I need rather than with strict type checking. For example, check out the underlined code in SendMessage()
[DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, StringBuilder lParam);
StringBuilder? Hello? What perplexes me is that the API is expecting a pointer but we pass in a .NET StringBuilder. Please explain to a semi-newbie what dark magic allows this to happen. Thank you. GordonLuc's given you a brilliant answer! In my experience, anything that takes the same memory footprint can be used. This makes int/uint (4 bytes), or even long/Size (8 bytes) interchangable. In the case of pointers, any reference type (or value type if used with
ref
[orout
depending on the situation]) will work as it's the pointer to the object that's passed not the value pointed to. Whether the value stored at the address can be understood on the native side or managed side once returned is another matter of course!Dave
If this helped, please vote & accept answer!
Binging is like googling, it just feels dirtier. Please take your VB.NET out of our nice case sensitive forum.(Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn) -
Luc's given you a brilliant answer! In my experience, anything that takes the same memory footprint can be used. This makes int/uint (4 bytes), or even long/Size (8 bytes) interchangable. In the case of pointers, any reference type (or value type if used with
ref
[orout
depending on the situation]) will work as it's the pointer to the object that's passed not the value pointed to. Whether the value stored at the address can be understood on the native side or managed side once returned is another matter of course!Dave
If this helped, please vote & accept answer!
Binging is like googling, it just feels dirtier. Please take your VB.NET out of our nice case sensitive forum.(Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn):rose:
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles] Nil Volentibus Arduum
Please use <PRE> tags for code snippets, they preserve indentation, and improve readability.
-
Here is one for you Win32 experts. When I as a Framework developer need to use a Win API function, I can declare the function with parameters I need rather than with strict type checking. For example, check out the underlined code in SendMessage()
[DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, StringBuilder lParam);
StringBuilder? Hello? What perplexes me is that the API is expecting a pointer but we pass in a .NET StringBuilder. Please explain to a semi-newbie what dark magic allows this to happen. Thank you. GordonThe .NET P/Invoke marshaller has special knowledge about some types. For example when passing a String, it passes the pointer to the string data, not the pointer to the managed string object. When passing a StringBuilder, the .NET marshaller passes a pointer to the StringBuilder's internal buffer. Another useful idiom is the SafeHandle: you can create your own SafeHandle-derived class and directly use that in P/Invoke signatures. The .NET marshaller will pass the underlying handle (IntPtr) instead. This is extremely useful because it avoids a number of problems: for example the finalizer could run when there are no active references to your object, but the underlying handle is still being used in a P/Invoke call. Using a SafeHandle directly in the P/Invoke signature makes the GC aware that the handle is used during the lifetime of the P/Invoke call, and it'll avoid finalizing the object until the call is complete.
-
The .NET P/Invoke marshaller has special knowledge about some types. For example when passing a String, it passes the pointer to the string data, not the pointer to the managed string object. When passing a StringBuilder, the .NET marshaller passes a pointer to the StringBuilder's internal buffer. Another useful idiom is the SafeHandle: you can create your own SafeHandle-derived class and directly use that in P/Invoke signatures. The .NET marshaller will pass the underlying handle (IntPtr) instead. This is extremely useful because it avoids a number of problems: for example the finalizer could run when there are no active references to your object, but the underlying handle is still being used in a P/Invoke call. Using a SafeHandle directly in the P/Invoke signature makes the GC aware that the handle is used during the lifetime of the P/Invoke call, and it'll avoid finalizing the object until the call is complete.
When passing a StringBuilder, the .NET marshaller passes a pointer to the StringBuilder's internal buffer. This is the voodoo that had me baffled the most. To the .NET developer, the StringBuilder is a sophisticated managed class, but somehow the API is able to treat is like a char* (I think). Thank you!
-
Luc's given you a brilliant answer! In my experience, anything that takes the same memory footprint can be used. This makes int/uint (4 bytes), or even long/Size (8 bytes) interchangable. In the case of pointers, any reference type (or value type if used with
ref
[orout
depending on the situation]) will work as it's the pointer to the object that's passed not the value pointed to. Whether the value stored at the address can be understood on the native side or managed side once returned is another matter of course!Dave
If this helped, please vote & accept answer!
Binging is like googling, it just feels dirtier. Please take your VB.NET out of our nice case sensitive forum.(Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn)Thank you, Dave.