Copyright © Microsoft Corporation. This document is an archived reproduction of a version originally published by Microsoft. It may have slight formatting modifications for consistency and to improve readability.
Figure 1   MouseWheel.DLL

MouseWheel.mak

PROJ = MouseWheel
OBJS = $(PROJ).OBJ

CFLAGS = $(CFLAGS) /W3 /O1 /DWIN32_LEAN_AND_MEAN /D_WIN32_WINNT=0x400
LFLAGS =/DLL /MERGE:.idata=.data /MERGE:.rdata=.text /SECTION:.shared,RWS \
        /EXPORT:DoNothing /BASE:0x20000000
LIBS =  /OUT:$(PROJ).DLL kernel32.lib user32.lib advapi32.lib

!if "$(DEBUG)" == "1"
CFLAGS = $(CFLAGS) /YX /Fp"$(PROJ).PCH" /D_DEBUG /Zi  /Fd"$(PROJ).PDB"
LFLAGS = $(LFLAGS) /DEBUG /DEBUGTYPE:CV 
!else
CFLAGS = $(CFLAGS) /DNDEBUG
!endif

$(PROJ).DLL: $(OBJS)
    echo >NUL @<<$(PROJ).CRF
$(LFLAGS) $(OBJS) $(LIBS)
<<
    link @$(PROJ).CRF

.cpp.obj:
    CL $(CFLAGS) /c $<
MouseWheel.h
#define MW_SCROLL_INCREMENT_MASK     0x0000FFFF
#define MW_SCROLL_PAGE               0x00010000

// Dummy routine in MouseWheel.DLL to allow implicit importing
extern "C" void DoNothing(void);
MouseWheel.cpp
//==========================================
// Matt Pietrek
// Microsoft Systems Journal, June 1997
//==========================================
#include <windows.h>
#include "mousewheel.h"

//=============================================================================
// Data
//=============================================================================

// Shared Data
#pragma data_seg(".shared")     // Make a new section that we'll make shared
HHOOK g_hHook=0;                // HHOOK from SetWindowsHook
UINT  g_WM_MOUSEWHEEL = 0;      // Window message for mousewheel scrolling
                                // Back to regular, nonshared data
char g_szRegPath[] = "Software\\WheatyProductions\\MouseWheel";
#pragma data_seg()

// Per process data
BOOL g_IsHookingProcess = FALSE;
BOOL g_okToAct = FALSE;
BOOL g_pageIncrements = FALSE;
unsigned g_incrementAmount = 1;

//=============================================================================
// Start of code
//=============================================================================

LRESULT CALLBACK GetMsgProc(
    int code,        // hook code
    WPARAM wParam,   // removal flag
    LPARAM lParam)   // address of structure with message
{
    LRESULT retValue = 0;

    // Be a good citizen, and call the other hooks
    retValue = CallNextHookEx( g_hHook, code, wParam, lParam );

    if ( FALSE == g_okToAct )      // Bail out if this process isn't one that
        return retValue;           // we care about

    LPMSG pMsg = (LPMSG)lParam;    // Make a ptr to the MSG structure for exam

    // If it's not a MOUSEWHEEL message, or if the app is just PEEK'ing,
    // bail out now.

    if ( g_WM_MOUSEWHEEL != pMsg->message || (wParam == PM_NOREMOVE) )
        return retValue;

    // By this point, we know a WM_MOUSEWHEEL message will be delivered.
    // Synthesize the appropriate WM_VSCROLL message(s) and post them

    WPARAM upDown;

    if ( g_pageIncrements )
        upDown = (short)HIWORD(pMsg->wParam) > 0 ? SB_PAGEUP : SB_PAGEDOWN;
    else
        upDown = (short)HIWORD(pMsg->wParam) > 0 ? SB_LINEUP : SB_LINEDOWN;

    for ( unsigned i = 0; i < g_incrementAmount; i++ )
        PostMessage(pMsg->hwnd, WM_VSCROLL, upDown,    0 );

    return 1;
}

UINT GetMouseWheelMsg( void )
{
    OSVERSIONINFO osvi;

    osvi.dwOSVersionInfoSize = sizeof(osvi);

    if ( !GetVersionEx(&osvi) )
        return WM_MOUSEWHEEL;            // Got a better idea?

    // NT 4 and later supports WM_MOUSEWHEEL
    if ( VER_PLATFORM_WIN32_NT == osvi.dwPlatformId )
        if ( osvi.dwMajorVersion >= 4 )
            return WM_MOUSEWHEEL;

    // Future Win32 versions ( >= 5.0 ) should support WM_MOUSEWHEEL
    if ( osvi.dwMajorVersion >= 5 )
        return WM_MOUSEWHEEL;

    // Hmmm... an older version.  The mouse driver support app should
    // have registered a window message for it.  By registering the
    // same message, we should get back the same message number.
    // Note that "MSWHEEL_ROLLMSG" below is a #define taken from ZMOUSE.H,
    // which is from the "Intellimouse SDK".
    
    return RegisterWindowMessage( "MSWHEEL_ROLLMSG" );
}


BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,    // handle to DLL module 
    DWORD fdwReason,       // reason for calling function 
    LPVOID lpvReserved)    // reserved 
{
    //=========================================================================
    // DLL Process attach
    //=========================================================================

    if ( fdwReason == DLL_PROCESS_ATTACH )
    {
        // We don't need thread notifications for what we're doing.  Thus, get
        // rid of them, thereby eliminating some of the overhead of this DLL,
        // which will end up in nearly every GUI process anyhow.
        DisableThreadLibraryCalls( hinstDLL );

        if ( lpvReserved )   // Is this main process that sets the hook and
        {                    // loads this DLL initially ???

            if ( g_hHook )        // If we've already hooked, fail the DllMain
                return FALSE;

            g_IsHookingProcess = TRUE;

            // Set a global GetMessage hook 
            g_hHook = SetWindowsHookEx( WH_GETMESSAGE, (HOOKPROC)GetMsgProc,
                                        hinstDLL, 0 );

            g_WM_MOUSEWHEEL = GetMouseWheelMsg();
        }

        // Get the name of the parent process EXE, and uppercase it
        char szExeName[MAX_PATH];
        GetModuleFileName( 0, szExeName, sizeof(szExeName) );
        CharUpperBuff( szExeName, lstrlen(szExeName) );

        //
        // Determine if the parent process EXE's name is in the registry, under
        // our special key.  If not, we won't bother translating mousewheel
        // scroll messages into WM_VSCROLL messsages.
        //

        HKEY hKey;
        if (ERROR_SUCCESS==RegOpenKey( HKEY_CURRENT_USER, g_szRegPath, &hKey))
        {
            DWORD dwValue = 0;
            DWORD dType, cbValue = sizeof(dwValue);

            if ( ERROR_SUCCESS == RegQueryValueEx(  hKey, szExeName, 0, &dType,
                                                    (PBYTE)&dwValue, &cbValue))
            {
                g_incrementAmount = dwValue & MW_SCROLL_INCREMENT_MASK;
                g_pageIncrements = dwValue & MW_SCROLL_PAGE ? TRUE : FALSE;

                g_okToAct = TRUE;
            }

            RegCloseKey( hKey );
        }

        // else.... This process's EXE wasn't under our key.  Do nothing.
    }

    //=========================================================================
    // DLL Process detach
    //=========================================================================

    else if ( fdwReason == DLL_PROCESS_DETACH )
    {
        if ( g_IsHookingProcess && g_hHook )    // The main EXE that loaded 
        {                                       // this DLL is shutting down,
            UnhookWindowsHookEx( g_hHook );     // so remove the hook
            g_hHook = 0;
        }
    }

    return TRUE;
}

//-------------------------------------------------------------
// Dummy startup routine that does nothing except call DllMain
// This cuts out all of the standard startup code crud that
// bloats the DLL, and makes it take longer to load
//-------------------------------------------------------------
extern "C" BOOL __stdcall _DllMainCRTStartup( 
    HINSTANCE hinstDLL,     // handle to DLL module 
    DWORD fdwReason,        // reason for calling function 
    LPVOID lpvReserved)     // reserved 
{
    return DllMain( hinstDLL, fdwReason, lpvReserved );
}

//----------------------------------------------------------------------------
// Dummy routine that allows the main EXE to have an implicit import of
// this DLL.
//----------------------------------------------------------------------------
void DoNothing(void)
{
}

Figure 2   MW.EXE

AddProgram.cpp


 // AddProgram.cpp : implementation file
 //
 
 �
 �
 �

 /////////////////////////////////////////////////////////////////////////////
 // CAddProgram message handlers
 
 void CAddProgram::OnBrowseButton() 
 {
     // TODO: Add your control notification handler code here
     CFileDialog dlg( TRUE, "EXE", "*.EXE", OFN_FILEMUSTEXIST, 0, this );
     
     if ( IDOK != dlg.DoModal() )
         return;
 
     ((CWnd*)GetDlgItem(IDC_NEW_PROGRAM_NAME))->SetWindowText(dlg.GetPathName());
 }
 
 
 BOOL CAddProgram::OnInitDialog() 
 {
     CDialog::OnInitDialog();
     
     // TODO: Add extra initialization here
     
     CSpinButtonCtrl * pSpinButton = (CSpinButtonCtrl *)GetDlgItem( IDC_SPIN1 );
     pSpinButton->SetBuddy( GetDlgItem(IDC_EDIT_INCREMENT_AMOUNT) );
     pSpinButton->SetRange( 1, 100 );
 
     // Set the line scrolling BOOL to TRUE (by default), and check
     // the "line scrolling" checkbox to reflect this
     m_fLineScrolling = TRUE;
     ((CButton *)GetDlgItem(IDC_RADIO_LINE))->SetCheck( 1 );
 
     return TRUE;  // return TRUE unless you set the focus to a control
                   // EXCEPTION: OCX Property Pages should return FALSE
 }
 
 void CAddProgram::OnRadioLine() 
 {
     // TODO: Add your control notification handler code here
     m_fLineScrolling = TRUE;
 }
 
 void CAddProgram::OnRadioPage() 
 {
     // TODO: Add your control notification handler code here
     m_fLineScrolling = FALSE;    
 }
 
 void CAddProgram::OnOK() 
 {
     // TODO: Add extra validation here
     CEdit * pEdit = (CEdit *)GetDlgItem( IDC_NEW_PROGRAM_NAME );
 
     if ( 0 == pEdit->GetWindowTextLength() )
     {
         MessageBox( "Must specify a program name" );
         return;
     }
 
     CDialog::OnOK();
 }
MWDlg.cpp

 // MWDlg.cpp : implementation file
 //
 �
 �
 �

 /////////////////////////////////////////////////////////////////////////////
 // CMWDlg message handlers
 
 BOOL CMWDlg::OnInitDialog()
 {
     CDialog::OnInitDialog();
 
     CMenu* pSysMenu = GetSystemMenu(FALSE);
 
     // Set the icon for this dialog.  The framework does this automatically
     // when the application's main window is not a dialog
     SetIcon(m_hIcon, TRUE);         // Set big icon
     SetIcon(m_hIcon, FALSE);        // Set small icon
 
     m_notifyIconData.cbSize = sizeof(m_notifyIconData);
     m_notifyIconData.hWnd = this->m_hWnd;
     m_notifyIconData.uID = WM_MY_TRAY_NOTIFICATION;
     m_notifyIconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
     m_notifyIconData.uCallbackMessage = WM_MY_TRAY_NOTIFICATION;
     m_notifyIconData.hIcon = m_hIcon;
     lstrcpy( m_notifyIconData.szTip, "MouseWheel" );
     Shell_NotifyIcon( NIM_ADD, &m_notifyIconData );
 
     RefreshProgramListbox();
 
     return TRUE;  // return TRUE  unless you set the focus to a control
 }
 
 //  If you add a minimize button to your dialog, you will need the code below
 //  to draw the icon.  For MFC applications using the document/view model,
 //  this is automatically done for you by the framework.
 
 void CMWDlg::OnPaint() 
 {
     // If the user hasn't asked to see the dialog yet, hide it.  It would be
     // better to not show the dialog in the first place, but MFC seems bound
     // and determined to get that dialog on the screen!
     if ( !m_okToShow )
     {
         ShowWindow( SW_HIDE );
     }
 
     if (IsIconic())
     {
         CPaintDC dc(this); // device context for painting
 
         SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
 
         // Center icon in client rectangle
         int cxIcon = GetSystemMetrics(SM_CXICON);
         int cyIcon = GetSystemMetrics(SM_CYICON);
         CRect rect;
         GetClientRect(&rect);
         int x = (rect.Width() - cxIcon + 1) / 2;
         int y = (rect.Height() - cyIcon + 1) / 2;
 
         // Draw the icon
         dc.DrawIcon(x, y, m_hIcon);
     }
     else
     {
         CDialog::OnPaint();
     }
 }
 
 // The system calls this to obtain the cursor to display while the user drags
 // the minimized window.
 HCURSOR CMWDlg::OnQueryDragIcon()
 {
     return (HCURSOR) m_hIcon;
 }
 
 void CMWDlg::OnAddProgram() 
 {
     // TODO: Add your control notification handler code here
     CAddProgram addDialog( this );
     
     if ( IDOK != addDialog.DoModal() )
         return;
 
     // Low WORD is the number of units to scroll
     DWORD dwRegValue = addDialog.m_incrementAmount;
 
     // High word is flags. Right now, the only flag is the "scroll by pages" flag
 
     if ( FALSE == addDialog.m_fLineScrolling )
         dwRegValue |= MW_SCROLL_PAGE;
 
     HKEY hKey = ((CMWApp *)AfxGetApp())->GetRegKey();
     if ( !hKey )
         return;
 
     if ( 0 == RegSetValueEx(hKey,
                             addDialog.m_newProgramName,
                             0,
                             REG_DWORD,
                             (PBYTE)&dwRegValue,
                             sizeof(dwRegValue) ) )
     {
         RefreshProgramListbox();
         MessageBox("You must shut down and restart MW.EXE for this change to "
                    "take affect");
     }
     else
         MessageBox( "Error adding program to registry" );
 
     RegCloseKey( hKey );
 }
 
 
 BOOL CMWDlg::RefreshProgramListbox( void )
 {
     CListBox * pListBox = (CListBox *)GetDlgItem(IDC_PROGRAM_LIST);
     
     pListBox->ResetContent();    // Clear anything that's in there now
 
     HKEY hKey = ((CMWApp *)AfxGetApp())->GetRegKey();
     if ( !hKey )
         return FALSE;
 
     for ( unsigned i =0; ; i++ )
     {
         LONG hResult;
         char szValueName[MAX_PATH];
         DWORD dwValue;
         DWORD type, cbValueName = sizeof(szValueName), cbValue = sizeof(dwValue);
 
         hResult = RegEnumValue(    hKey, i, szValueName, &cbValueName, 0,
                                 &type, (PBYTE)&dwValue, &cbValue );
 
         if ( ERROR_NO_MORE_ITEMS == hResult )
             break;
 
         char szLB_Line[ MAX_PATH + 32 ];
         wsprintf( szLB_Line, "%s\t%s %u",
                     szValueName,
                     dwValue & MW_SCROLL_PAGE ? "PAGE" : "LINE",
                     dwValue & MW_SCROLL_INCREMENT_MASK );
 
         pListBox->AddString( szLB_Line );
     }
 
     RegCloseKey( hKey );
 
     return TRUE;
 }
 
 
 void CMWDlg::OnDeleteProgram() 
 {
     // TODO: Add your control notification handler code here
     CListBox * pListBox = (CListBox *)GetDlgItem(IDC_PROGRAM_LIST);
 
     int curSel = pListBox->GetCurSel();
     if ( LB_ERR == curSel )
     {
         MessageBox( "Must select an program to delete" );
         return;
     }
 
     CString cLBText;
     
     pListBox->GetText( curSel, cLBText );
 
     CString cProgramName = cLBText.SpanExcluding( "\t" );
 
     HKEY hKey = ((CMWApp *)AfxGetApp())->GetRegKey();
     if ( !hKey )
         return;
 
     RegDeleteValue( hKey, cProgramName );
 
     RegCloseKey( hKey );
 
     RefreshProgramListbox();
 }
 
 void CMWDlg::OnOK() 
 {
     // Default OK behavior should hide the dialog, not terminate the app
     ShowWindow( SW_HIDE );
 }
 
 
 void CMWDlg::OnClose() 
 {
     // TODO: Add your message handler code here and/or call default
     
     m_notifyIconData.uFlags = NIF_ICON;
     Shell_NotifyIcon( NIM_DELETE, &m_notifyIconData );
 
     CDialog::OnClose();
 }
 
 
 //////////////////
 // Handle notification from tray icon: display a message.
 //
 LRESULT CMWDlg::OnTrayNotification(WPARAM uID, LPARAM lEvent)
 {
     if ( lEvent == WM_LBUTTONDBLCLK )
     {
         m_okToShow = TRUE;    // Allow the dialog to be shown
 
         if ( !IsWindowVisible() )
             ShowWindow( SW_SHOW );
 
         if ( IsIconic() )
             ShowWindow( SW_RESTORE );
     }
 
     #if 1
     else if ( lEvent == WM_RBUTTONUP )
     {
         CMenu * pSysMenu = GetSystemMenu( FALSE );
         if ( pSysMenu )
         {
             CPoint mouse;
             GetCursorPos(&mouse);
             pSysMenu->TrackPopupMenu( TPM_CENTERALIGN, mouse.x, mouse.y, this );
             // delete pSysMenu;
         }
     }
     #endif
 
     return 0;
 }
 
 
 
 BOOL CMWDlg::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) 
 {
     // If it's the WM_SYSCOMMAND SC_CLOSE message, repost it as a WM_CLOSE message
     // so that our normal WM_CLOSE handler will handle it.
     if ( SC_CLOSE == nID )
         PostMessage( WM_CLOSE );
 
     return CDialog::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
 }