How to Pinvoke to Getwindowlongptr and Setwindowlongptr on 32-Bit Platforms

How do I pinvoke to GetWindowLongPtr and SetWindowLongPtr on 32-bit platforms?

I'd recommend you deal with this the way Windows Forms does it internally:

public static IntPtr GetWindowLong(HandleRef hWnd, int nIndex)
{
if (IntPtr.Size == 4)
{
return GetWindowLong32(hWnd, nIndex);
}
return GetWindowLongPtr64(hWnd, nIndex);
}

[DllImport("user32.dll", EntryPoint="GetWindowLong", CharSet=CharSet.Auto)]
private static extern IntPtr GetWindowLong32(HandleRef hWnd, int nIndex);

[DllImport("user32.dll", EntryPoint="GetWindowLongPtr", CharSet=CharSet.Auto)]
private static extern IntPtr GetWindowLongPtr64(HandleRef hWnd, int nIndex);

SetWindowLong/GetWindowLong and 32-bit/64-bit CPUs

I guess you are wondering if you chose the type UInt32 correctly. The answer is yes. The docs explicitly say it is always 32 bit value: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx

Your code is correct.

P/Invoke entry points should exist with what should be correct entry points stated

(This answer also posted in an edit at the bottom of the original question, to help people find it quick and easy)

Turns out the problem is that Visual Studio itself (and therefore the Code Analysis tool) are 32bit. When the code analysis tool checks user32.dll to see if those functions are there, it checks the 32bit version of user32.dll (in C:/Windows/SysWOW64/) instead of the one that the program will actually use (the 64bit version in C:/Windows/System32), and these functions only exist in the 64bit version (32bit version uses GetWindowLong/SetWindowLong instead of GetWindowLongPtr/SetWindowLongPtr (notice the PTR part)).

I keep getting Unable to find an entry point named 'GetWindowLongPtrA' in DLL 'user32.dll'

There is no function named GetWindowLongPtr, GetWindowLongPtrA or GetWindowLongPtrW in the 32-bit version of user32.dll:

32-bit user32.dll

The reason that using GetWindowLongPtr regardless of target bitness works C and C++ WinAPI code is that in 32-bit code it's a macro that calls GetWindowLong(A|W). It only exists in the 64-bit version of user32.dll:

64-bit user32.dll

The documentation for importing GetWindowLongPtr on pinvoke.net includes a code sample for how to make this importation transparent to target bitness (remember, the error is thrown when you actually try to call the imported function that doesn't exist, not on the DllImport line):

[DllImport("user32.dll", EntryPoint="GetWindowLong")]
private static extern IntPtr GetWindowLongPtr32(IntPtr hWnd, int nIndex);

[DllImport("user32.dll", EntryPoint="GetWindowLongPtr")]
private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex);

// This static method is required because Win32 does not support
// GetWindowLongPtr directly
public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
{
if (IntPtr.Size == 8)
return GetWindowLongPtr64(hWnd, nIndex);
else
return GetWindowLongPtr32(hWnd, nIndex);
}

GetWindowLong vs GetWindowLongPtr in C#

Unfortunately it's not that easy, because GetWindowLongPtr doesn't exist in 32bit Windows. On 32bit systems GetWindowLongPtr is just a C macro that points to GetWindowLong. If you really need to use GetWindowLongPtr on both 32 and 64 bit systems you'll have to determine the correct one to call at run time. See the description at pinvoke.net

Using pinvoke for both 32 and 64 bit dlls from the same managed dll

.NET does have the equivalent of a DllMain() function, it is called a module initializer. It is however out of reach from C# and VB.NET code, you can only create one in IL or in C++/CLI. C++/CLI itself has a bitness dependency so that only leaves IL. You'll find sample code for one in this answer. Getting it linked into your assembly is pretty awkward, the build system doesn't directly support running the assembly linker.

Next best thing is a "type initializer" as mentioned in the same article, called a static constructor in C#. You do need some semblance of organization in your code to make these pay off, a class that's guaranteed to get used before one of your 'thousands of methods' get called. That ought to be difficult with that many methods.

That doesn't leave much beyond an initialization method that has to be called by the app in its Main() method. And of course the standard solution, two installers, one for 32-bit machines, another for 64-bit machines. Which also ensures your app ends up in the 'right' directory, c:\program files vs c:\program files (x86).

UPDATE: .NET 5 now supports module initializers in C# v9 with the [ModuleInitializer] attribute

Custom windows form controls that could run on both 32bit (x86) and 64bit (x64) platform

You don't have to do anything special. The underlying Windows API is the same irrespective of whether you are on 32 or 64 bit.

Data types that hold pointer sized things, e.g. window handles, float between 32 and 64 bit depending on which platform is targeted. For that reason they are declared as IntPtr for P/Invoke. So long as you get that right, your code will work on both platforms.

Bit masking an IntPtr

Just convert IntPtr to an int (it has a conversion operator) and use logical bit operators to test bits.

const int WS_VISIBLE = 0x10000000;
int n = (int)myIntPtr;
if((n & WS_VISIBLE) == WS_VISIBLE)
DoSomethingWhenVisible()`


Related Topics



Leave a reply



Submit