Date: | January 2, 2007 / year-entry #2 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20070102-04/?p=28533 |
Comments: | 18 |
Summary: | Occasionally, I'll see people having trouble trying to GetProcAddress for functions like CreateWindow or ExitWindows. Usually, it's coming from people who are trying to write p/invoke signatures, for p/invoke does a GetProcAddress under the covers. Why can't you GetProcAddress for these functions? Because they're not really functions. They're function-like macros: #define CreateWindowA(lpClassName, lpWindowName, dwStyle, x,... |
Occasionally, I'll see people having trouble trying to
Because they're not really functions. They're function-like macros: #define CreateWindowA(lpClassName, lpWindowName, dwStyle, x, y,\ nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)\ CreateWindowExA(0L, lpClassName, lpWindowName, dwStyle, x, y,\ nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) #define CreateWindowW(lpClassName, lpWindowName, dwStyle, x, y,\ nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)\ CreateWindowExW(0L, lpClassName, lpWindowName, dwStyle, x, y,\ nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) #ifdef UNICODE #define CreateWindow CreateWindowW #else #define CreateWindow CreateWindowA #endif // !UNICODE #define ExitWindows(dwReserved, Code) ExitWindowsEx(EWX_LOGOFF, 0xFFFFFFFF)
In fact, as you can see above
Similar remarks apply to inline functions.
These functions can't be obtained via
Note that whether something is a true function or a function-like
macro (or an inline function) can depend on your target platform.
For example, How can you figure all this out? Read the header files. That'll show you whether the function you want is a redirecting macro, a function-like macro, an inline function, an intrinsic function, or a proper exported function. If you can't figure it out from the header files, you can always just write a program that calls the function you're interested in and then look at the disassembly to see what actually got generated. |
Comments (18)
Comments are closed. |
… or "dumpbin /exports target.dll" to see whether the DLL exports it.
Surely this discussion should come with a caveat that all this is
subject to change at Microsoft’s whim and that tying your code to a
particular implementation of a header file is a bad idea?
Writing code like this is the kind of thing that gives maintenance teams headaches.
then it must continue to be supported at the binary level. Sure, the
macro might change in the future, but the old method must still work
because nobody is going to go back and recompile those older programs
that used the older macro. -Raymond]
Tom, as far as the ANSI/Unicode support goes, even Microsoft’s whims are not powerful enough to change that. If you are going to dynaload a DLL and call an entry point you have to add the A or W if it accepts or returns characters, even if indirectly in a struct. All of this is pretty clear when you’re doing it in C++ but not so much when you’re dealing with layers of .NET abstraction.
Which reminds me to ask. Why on earth do you still make them macros and hurt people all over who might want to have a completely unrelated function called CreateWindow in their code? MS compiler supported __inline keyword for a long time so why not make them simple __inline forwarders?
Laziness or some deep reason?
You are forced into GetProcAddress when you are trying to write software that will run on multiple versions of the OS. Specifically when you want to support newer features that are not available on earlier systems.
I was going to say that the end functions that the macros point to never change, but then I’m sure that Win311 code had a CreateWindow function, which got converted to a macro for unicode support.
Funny that you say:
"If you can’t figure it out from the header files, you can always just write a program that calls the function you’re interested in and then look at the disassembly to see what actually got generated."
I wonder if there’s anyone that can’t figure it out from the headers but can figure it out from the assembly generated… certainly not me!
By the way: isn’t there some way to see the precompiled result of your source file? (That would have all the macro’s resolved, far easier that way!) I’ve seen some IDEs that have support for this but can’t remember right now if visual studio is one of them.
Legolas,
There are flags you can pass to CL.EXE to either dump the preprocessor output to a file or to stdout. (See /E and /P)
However, you’ll soon discover that one simple #include <windows.h> expands out to literally thousands of lines of code. This often causes your own program to be lost in the noise. I wish there was a way to run the preprocessor but just process rather than expand the #include directives, so the macros all still work out but you don’t get all of the included code dumped out. (maybe there is, but I can’t see it in cl /?)
<i>Note that whether something is a true function or a function-like macro (or an inline function) can depend on your target platform. For example, GetWindowLongPtrA is a true exported function on 64-bit Windows, but on 32-bit Windows, it’s just a macro that resolves to GetWindowLongA.</i>
Forever requiring every developer to use #ifdef around calls to GetWindowLongPtrA if they want to get rid if all compiler warnings under both win64 and win32. Couldn’t Microsoft have included a smarter macro for this?
Intellisense and "Go to definition" are amazingly useful for discovering what the macros and functions actually do.
A Raymond Chen blog entry where I actually already knew everything in the post; that’s rare.
Marvin: Macros are well supported. Windows API must be able to be compiled by practically anything. __inline would be compiler specific, so they can’t do that without introducing much more gunk to the header files to support various compilers, and they’d have to provide the macros anyway for unknown compilers. Imagine the legal ramifications if some new compiler was made and the Microsoft APIs didn’t support it.
Raymond: It is the other way around. You can take an address of inline function (the compiler will just put an out-of-line version for you somewhere). But you cannot call an address of whatever CreateWindow expands to because you don’t know its signature. About the only thing you can do is to store it somewhere.
Erzengel: It might have been true in the past but SDK headers already include lots of MS specific stuff. From winnt.h:
__inline ULONGLONG
NTAPI
Int64ShllMod32 (
ULONGLONG Value,
DWORD ShiftCount
)
{
__asm {
mov ecx, ShiftCount
mov eax, dword ptr [Value]
mov edx, dword ptr [Value+4]
shld edx, eax, cl
shl eax, cl
}
}
Raymond, I know precisely what compiler warnings Eric is talking about, and they bug me as well.
The problem occurs when doing something like this:
WNDPROC w = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC));
MS’s 64 bit compilers have no problem with this because they see a conversion from LONG_PTR to a pointer, which is perfectly fine (because the LONG_PTR typedef is marked __w64 which tells the compiler its safe to cast to a pointer no matter the architecture). However, MS’s more recent 32 bit compilers will emit warnings when they see something that wouldn’t be safe on a 64 bit architecture. Because GetWindowLongPtr is a macro on 32 bit architectures, the 32 bit compiler ends up seeing a call to GetWindowLong, which returns a LONG, so it’s a conversion from LONG to a pointer. Safe on 32 bit, but potentially unsafe on 64 bit. So the compiler will emit a warning that the conversion is unsafe, while in fact the code is safe because you’re already using GetWindowLongPtr. The only way to deal with that is some preprocessor magic; I usually use #pragma warning to disable the warning and re-enable it after the call. Very messy.
Actually I just thought of a better way to get rid of the warning on both x86 and x64:
LONG_PTR l = GetWindowLongPtr(hWnd, GWLP_WNDPROC);
WNDPROC w = reinterpret_cast<WNDPROC>(l);
Seems to do the trick in eliminating the warning (and no, I don’t usually name my variables like that).
The same won’t work for SetWindowLongPtr though, since even if you introduce temporaries it’ll always look like a potential narrowing conversion to the x86 compiler.
> How can you figure all this out? Read the
> header files.
In drafts and I’m sure the approved 1989 version of the C standard, the library section stated that where it used the word “function” it was guaranteed that the thing would be a function, and where it used the word “macro” the thing could be either a function or a macro. If MSDN would do the same then readers could figure all this out from MSDN, instead of depending on undocumented behaviour.
About the particular warnings in a sub-thread:
> Yeah that bugs me too. But I have no control
> over it either.
Even though you have no control over it, aren’t you allowed to submit a bug report on the Connect site?
Vestigial organs.
Here’s how you can do it – right after #include <windows.h>:
#ifndef _WIN64
#undef GetWindowLongPtr
#undef SetWindowLongPtr
inline LONG_PTR GetWindowLongPtr( HWND hWnd, int nIndex )
{
return (LONG_PTR)GetWindowLong(hWnd,nIndex);
}
inline LONG_PTR SetWindowLongPtr( HWND hWnd, int nIndex, LONG_PTR dwNewLong )
{
return (LONG_PTR)SetWindowLong(hWnd,nIndex,(LONG)dwNewLong);
}
#endif
Ivo, very nice (I should’ve thought of that!). I’m going to use that whenever I next run into this problem. :)
I so dearly wish that Microsoft will one day produce a list of the proper P/Invoke signatures for all the APIs…
I think I am probably living in a dream world though.
The "smarter macro" that the SDK should have is one that properly replicates the documented behavior.
#define GetWindowLongPtrW(hwnd, nIndex)((LONG_PTR)(GetWindowLongW(hwnd, nIndex)))
<similar for GetWindowLongPtrA>
#define SetWindowLongPtrW(hwnd, nIndex, NewLong)((LONG_PTR)(SetWindowLongW(hwnd, nIndex, (LONG)(LONG_PTR)(NewLong))))
<similar for SetWindowLongPtrA>