Why can’t I GetProcAddress for CreateWindow?


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, 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 CreateWindow is doubly a macro. First, it's a redirecting macro that expands to either CreateWindowA or CreateWindowW, depending on whether or not you are compiling UNICODE. Those are in turn function-like macros that call the real function CreateWindowExA or CreateWindowExW. All this is handled by the compiler if you include the winuser.h header file, but if for some reason you want to GetProcAddress for a function-like macro like CreateWindow, you'll have to manually expand the macro to see what the real function is and pass that function name to GetProcAddress.

Similar remarks apply to inline functions. These functions can't be obtained via GetProcAddress because they aren't exported at all; they are provided to you as source code in the header file.

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. As another example, the Interlocked family of functions are exported functions on the x86 version of Windows but are inlined functions on all other Windows architectures.

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)
  1. … or "dumpbin /exports target.dll" to see whether the DLL exports it.

  2. Tom Parker says:

    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.

    [If it’s compiled into a binary (like a macro)
    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
    ]
  3. Dave says:

    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.

  4. Marvin says:

    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 can’t take the address of an inline function. Besides, support for inline functions was weak back in 1989. -Raymond]
  5. Doug says:

    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.

  6. Legolas says:

    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.

  7. Ben Cooke says:

    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 /?)

  8. Eric Wilson says:

    <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?

    [And that smarter macro would be…? (Not sure what you mean by “all compiler warnings” since each compiler has a different set of warnings, plus a compiler is permitted to generate new warnings at any time.) -Raymond]
  9. Erzengel says:

    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.

  10. Marvin says:

    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

       }

    }

  11. Sven Groot says:

    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.

    [Yeah that bugs me too. But I have no control over it either. -Raymond]
  12. Sven Groot says:

    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.

  13. Norman Diamond says:

    > 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?

    [It bugs me but not enough to file a bug. Life is too short to get bent out of shape over everything that doesn’t go your way. -Raymond]
  14. Ivo says:

    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

  15. Sven Groot says:

    Ivo, very nice (I should’ve thought of that!). I’m going to use that whenever I next run into this problem. :)

  16. Jon Grant says:

    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.

    [Define “proper”. The most general p/invoke signature for SendMessage (for example) is also the most useless one. -Raymond]
  17. Marc K says:

    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>

Comments are closed.