Using CallWindowProc To Call 'Non-WndProc' Functions

M

Mike S

I've seen examples of using the CallWindowProc Windows API function to
call functions through their addresses in VB6 -- a clever workaround to
emulate C-like function pointer semantics. A well-known example is the
use of CallWindowProc to call a function gotten via
LoadLibrary/GetProcAddress. For example, I've seen code similar to the
following which can register/unregister a COM DLL where the path to the
DLL is known only at runtime -- in this case, you can't declare the
register/unregister functions in the normal fashion since you can't use
a variable in the Lib clause:

Option Explicit

Public Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA"
_
(ByVal lpszLibraryPath As String) As Long

Public Declare Function FreeLibrary Lib "kernel32" _
(ByVal hLib As Long) As Long

Public Declare Function GetProcAddress Lib "kernel32" _
(ByVal hLib As Long, ByVal lpszProcName As String) As Long

Public Declare Function CallWindowProc Lib "user32" Alias
"CallWindowProcA" _
(ByVal lpWndProc As Long, ByVal hWnd As Long, _
ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As
Long


Public Function RegisterComDll(ByVal sDllPath As String) As Boolean

Dim hLib As Long
Dim pRegFunc As Long

' Dynamically load the requested DLL
hLib = LoadLibrary(sDllPath)

If hLib = ERROR_FAILURE Then
' Indicate failure
RegisterComDll = False
Else
' Get the address of this DLL's DllRegisterServer entry point
pRegFunc = GetProcAddress(hLib, "DllRegisterServer")
' Use CallWindowProc as a proxy. We need it to call function
' for us since VB6 doesn't support calling through a function
' pointer directly.
CallWindowProc pRegFunc, 0&, 0&, 0&, 0&
' Done with the DLL, free it
FreeLibrary hLib
' Indicate success
RegisterComDll = True
End If

End Function

I have seen other posters claim that you can only do this if the called
function has the same signature as a window procedure, since
CallWindowProc naturally expects to be given a pointer to a window
procedure. A window procedure takes 4 Longs and is returns a Long. In
the above example, the DllRegisterServer function takes no parameters
and returns a Long.

I can understand the reason why people think the signatures need to
match. The Windows API and VB6 both using the STDCALL calling
convention, where the called function is responsible for cleaning
parameters off the stack (as opposed to the standard C calling
convention, where the caller cleans the parameters off the stack). The
function that CallWindowProc calls is also expected to be STDCALL.
Since a STDCALL function is responsible for cleaning up its own
parameters, it can only clean up the the number of parameters it
expects, i.e. if you pass 5 parameters to a STDCALL function which only
expects 1 parameter, the called function will clean up only 1 parameter
(the number it was expecting), which leaves 4 parameters on the stack.
VB6 can detect when the stack has become unbalanced in this manner by
comparing where the stack pointer was before calling the function with
where it is after the function returns. If the values don't match, then
too few or too many parameters must have been passed to the called
function, and VB6 will generate a 'Run-time error 49: Bad DLL Calling
Convention.' However, the above code works fine. To verify that the
stack is in fact correctly maintained, I repeatedly called the
RegisterComDll in an infinite while loop and let it run for a while.
After 60 million calls, it was safe to assume that the stack was never
going to overflow (i.e. the stack was still balanced after each call to
CallWindowProc, even it invoked DllRegisterServer with the wrong number
of arguments).

This behavior is counterintuitive, because with STDCALL you would
actually expect the code produce a stack overflow after a number of
iterations because the extraneous parameters passed to
DllRegisterServer will be orphaned on the stack. Actually, I would
expect the program to crash and burn on the first call, since an
unbalanced stack can screw up the return addresses.

Does anyone know why this *doesn't* happen? My guess is that
CallWindowProc is storing away the current stack pointer before it
calls the function passed to it, and then restores the stack pointer to
the previosly-stored value, to guarantee that the stack is always in
the right place and that CallWindowProc can always return correctly,
regardless of whether or not the function it calls was given the right
number of params. That's the only logical way I can figure that the
above code would work -- in any event, if it is true that
CallWindowProc is ensuring that the stack is balanced (and it certainly
seems to be the case, at least as far as observable behavior is
concerned), it should then be perfectly safe to use CallWindowProc to
call a function with a different parameter count/parameter types than a
proper window procedure.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top