Search code examples
winapisubclassing

Subclassing an Edit control with SHAutoComplete applied


I'm new, so sorry if the question isn't posed exactly as you're get used to.

I inevitably need to subclass an Edit control for which SHAutoComplete() has been called like this:

// initialization
if FAILED( CoInitialize(NULL) ) exit(1);
// creation of an Edit control
HWND hEdit=CreateWindow( WC_EDIT, ... );
// calling SHAutoComplete - I essentially understand this as a subclassing behind the scenes
SHAutoComplete( hEdit , SHACF_AUTOSUGGEST_FORCE_ON|SHACF_FILESYSTEM );
// subclassing an Edit box for which SHAutoComplete has been called
WNDPROC autoCompleteWndProc=SubclassWindow( hEdit , __myNewWndProc__ ); // using macro

Suppose the __myNewWndProc__ function looks like a classic window procedure:

LRESULT CALLBACK __myNewWndProc__(HWND hEdit, UINT msg, WPARAM wParam, LPARAM lParam){
    // handle subclass-specific messages
    switch (msg){ ... }
    // handle SHAutoComplete-specific messages
    return autoCompleteWndProc(hEdit,msg,wParam,lParam); // <-- here's the problem (read further)
}

The problem is, it doesn't work. The application crashes with error:

Process returned -1073741819 (0xC0000005)" (access violation)

pointing the problem at line marked in the above listing.

The question is what am I doing wrong?

(I've experienced the same problem when subclassing from ComboBoxEx, but I managed to work around it, but can't find any trick with the SHAutoComplete() problem.)


Solution

  • SubclassWindow() is a wrapper for SetWindowLongPtr():

    #define     SubclassWindow(hwnd, lpfn)       \
                  ((WNDPROC)SetWindowLongPtr((hwnd), GWLP_WNDPROC, (LPARAM)(WNDPROC)(lpfn)))
    

    When you use that type of subclassing, you MUST use CallWindowProc() to call the previous window procedure when needed, DO NOT call the procedure directly:

    LRESULT CALLBACK __myNewWndProc__(HWND hEdit, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        //...
        // handle SHAutoComplete-specific messages
        return CallWindowProc(autoCompleteWndProc,hEdit,msg,wParam,lParam);
    }
    

    The reason for that is stated in the documentation:

    SetWindowLongPtr function

    Calling SetWindowLongPtr with the GWLP_WNDPROC index creates a subclass of the window class used to create the window. An application can subclass a system class, but should not subclass a window class created by another process. The SetWindowLongPtr function creates the window subclass by changing the window procedure associated with a particular window class, causing the system to call the new window procedure instead of the previous one. An application must pass any messages not processed by the new window procedure to the previous window procedure by calling CallWindowProc. This allows the application to create a chain of window procedures.

    CallWindowProc function:

    lpPrevWndFunc [in]
    Type: WNDPROC

    The previous window procedure. If this value is obtained by calling the GetWindowLong function with the nIndex parameter set to GWL_WNDPROC or DWL_DLGPROC, it is actually either the address of a window or dialog box procedure, or a special internal value meaningful only to CallWindowProc.

    You are probably running into the latter case, which will crash trying to call something that is not directly callable.

    With that said, you should not be using SetWindowLongPtr() to subclass. Use SetWindowSubClass() instead. Read the following articles for more details:

    Subclassing Controls

    Safer subclassing