Click here to Skip to main content
Click here to Skip to main content

Win32 Simple Application AppWizard

, 22 Jun 2004
Rate this:
Please Sign up or sign in to vote.
An article on creating an AppWizard to create Win32 based applications.

Introduction

In order to extend the first article I wrote about a simple AppWizard for Screen Savers, I would like to contribute with this other article about a more complex AppWizard to generate the skeleton of a Win32 Application (without MFC) that lets the user choose between a Window based Win32 application and a Dialog based Win32 application, featuring the possibility of integrating the Common Controls Library (commctl32.lib) and/or the Winsock Library (ws2_32.lib).

Background

It will be useful reading the first article I wrote: Screen Saver AppWizard, to get the general idea of working with the Custom AppWizard that the Visual C++ 6.0 exports, and to get the hints for a correct compilation of the Wizard.

Using the code

As said before, this is a little more complex wizard than the Screen Saver one. In fact, this one features a step dialog needed by the wizard to determine the type of application the user requests and the types of libraries needed. Let's start creating the wizard, selecting the Custom AppWizard from the VC++ Projects tab:

Here you can notice that we specify that we'll need 1 step to configure our wizard.

Initial Environment

The initial environment after the creation of our Wizard project comes like the pane snapshots below:

The Class View Pane

The File View Pane

The Resource View Pane

As you can see in the figures, there's quite more files than the Screen Saver Wizard. This is because we indicated that we need one step configuration: this is obtained using a Dialog Resource (IDD_CUSTOM1) mapped to Dialog class CCustom1Dlg:

Customizing the Step 1 Dialog

In order to get to our goal, we need to customize the IDD_CUSTOM1 resource to feature our application's needs:

  • Choosing between two types of applications (Window Based or Dialog Based).
  • Letting specify the use of Common Controls Library.
  • Letting specify the use of the Winsock Library.

So, the final result of our customization will be something like:

Keep in mind that this approach is valid for any number of steps (dialogs) you specify. And you do not have to mind about the switch (to next or to previous) between step dialogs in your wizard because this behavior is handled by a specific class that the AppWizard framework supplies: the CDialogChooser class (see Class Pane figure).

Implementing the Step 1 Dialog

All the controls included in the dialog must be mapped to methods or to instance variables of the Dialog itself. So, we'll map the selection of the radio buttons (that specifies the type of application) to two relative class methods (using the ClassWizard) that will set a boolean value to indicate the application type, BOOL m_bWindowBased:

And the relative afx mapping:

// CCustom1Dlg.h
afx_msg void OnRadioDialogBased();
afx_msg void OnRadioWindowBased();

// Custom1Dlg.cpp
BEGIN_MESSAGE_MAP(CCustom1Dlg, CAppWizStepDlg)
    //{{AFX_MSG_MAP(CCustom1Dlg)
    ON_BN_CLICKED(IDC_RADIO_DIALOG_BASED, OnRadioDialogBased)
    ON_BN_CLICKED(IDC_RADIO_WINDOW_BASED, OnRadioWindowBased)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

...

void CCustom1Dlg::OnRadioDialogBased() 
{
    m_bWindowBased = FALSE;
}

void CCustom1Dlg::OnRadioWindowBased() 
{
    m_bWindowBased = TRUE;
}

In the same way, we have to map checkboxes that indicate the type of library we want to include and initialize for the application. So, we map every checkbox resource, still using the ClassWizard, to two boolean values:

These values will be mapped and handled through DDX Data exchange:

void CCustom1Dlg::DoDataExchange(CDataExchange* pDX)
{
    CAppWizStepDlg::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CCustom1Dlg)
    DDX_Check(pDX, IDC_CHECK_INIT_COMMON_CONTROLS, m_bInitCommonControls);
    DDX_Check(pDX, IDC_CHECK_USE_WINSOCK, m_bUseWinsock);
    //}}AFX_DATA_MAP
}

Done with the initial implementations, we can now concentrate in the logic part of creating the wizard. The CCustom1Dlg method needed to be implemented to let the wizard know the selections made by the user on what kind of application he wants, is OnDismiss():

// This is called whenever the user presses
// Next, Back, or Finish with this step
// present. Do all validation & data exchange
// from the dialog in this function.
BOOL CCustom1Dlg::OnDismiss()
{
    if (!UpdateData(TRUE))
      return FALSE;

    if(!m_bWindowBased){

      SimpleApplicationWizardaw.m_Dictionary.SetAt("DIALOG_BASED","Yes");
      SimpleApplicationWizardaw.m_Dictionary.RemoveKey("WINDOW_BASED");
    }else{
      SimpleApplicationWizardaw.m_Dictionary.SetAt("WINDOW_BASED","Yes");
      SimpleApplicationWizardaw.m_Dictionary.RemoveKey("DIALOG_BASED");
    }

    if(m_bInitCommonControls)
    {
      SimpleApplicationWizardaw.m_Dictionary.SetAt("INIT_COMMON_CONTROLS","Yes");
    }
    else
    {
      SimpleApplicationWizardaw.m_Dictionary.RemoveKey("INIT_COMMON_CONTROLS");
    }

    if(m_bUseWinsock)
    {
      SimpleApplicationWizardaw.m_Dictionary.SetAt("USE_WINSOCK","Yes");
    }
    else
    {
      SimpleApplicationWizardaw.m_Dictionary.RemoveKey("USE_WINSOCK");
    }

    return TRUE; // return FALSE if the dialog shouldn't be dismissed
}

This part needs a little attention, even if it is extremely easy to understand. Every wizard uses variables to understand what kind of decisions must be made, and it achieves this through a particular member of the main AppWizard (SimpleApplicationWizardAw) class that is a 'dictionary'. That keeps trace of the variables needed by the wizard.

These variables are set by this method:

SimpleApplicationWizardaw.m_Dictionary.SetAt("<SOME VARIABLE TO BE SET>","Yes");

and removed by this other method:

SimpleApplicationWizardaw.m_Dictionary.RemoveKey("<SOME VARIABLE TO BE REMOVED>");

These variables are then got by the framework during the creation of an application in order to do something or not to do.

The variables are useful even during the compiler/linker configuration in the main AppWizard class method void CSimpleAppWizardAppWiz::CustomizeProject(IBuildProject* pProject):

void CSimpleApplicationWizardAppWiz::CustomizeProject(IBuildProject* pProject)
{
    CComPtr<IConfigurations> pConfigs;
    HRESULT hr=pProject->get_Configurations(&pConfigs);
    if(FAILED(hr))
    {
        AfxMessageBox("An error occurred while 
           obtaining the IConfigurations interface pointer");
        return;
    }
    CComPtr<IConfiguration> pConfig;
    CComVariant index;
    VARIANT dummy = {0};
    CComBSTR Name;
    CString text;
    CString output;

    long Count=0;
    pConfigs->get_Count(&Count);

    // Iterate through all the configurations of the project
    for(int i=1; i <= Count; i++)
    {
        index=i;
        hr=pConfigs->Item(index, &pConfig);
        if(FAILED(hr))
        {
            AfxMessageBox("An error occurred 
               while obtaining the IConfiguration pointer");
            return;
        }
        pConfig->get_Name(&Name);
        text = Name;

        if (text.Find("Debug") == -1)
            output = "Release";
        else
            output = "Debug";

        text.Format("/out:\"%s/%s.exe\"",output,m_Dictionary["Root"]);
        pConfig->AddToolSettings(L"link.exe", text.AllocSysString(), dummy);

        pConfig->AddToolSettings(L"mfc", L"0", dummy);
        pConfig->AddToolSettings(L"link.exe", L"/subsystem:windows", dummy);
        pConfig->AddToolSettings(L"link.exe", L"/incremental:yes", dummy);
        pConfig->AddToolSettings(L"link.exe", L"/machine:I386", dummy);
        pConfig->AddToolSettings(L"link.exe", 
                                  L"/nodefaultlib:\"MSVCRTD\"", dummy);

        // change the preprocessor definitions
        pConfig->AddToolSettings(L"cl.exe", L"/D \"_WINDOWS\"", dummy);
        pConfig->AddToolSettings(L"cl.exe", L"/nologo", dummy);
        pConfig->AddToolSettings(L"cl.exe", L"/D \"_MBCS\"", dummy);
        pConfig->AddToolSettings(L"cl.exe", L"/D \"WIN32\"", dummy);
        pConfig->AddToolSettings(L"cl.exe", L"/Od", dummy);
        pConfig->AddToolSettings(L"cl.exe", L"/MD", dummy);
        pConfig->AddToolSettings(L"cl.exe", L"/W3", dummy);
        // Program Database for "Edit & Continue"
        // can not be defined when /driver option is defined
        pConfig->AddToolSettings(L"cl.exe", L"/ZI", dummy);
        //GZ initializes all local variables not explicitly 
        //initialized by the program. It fills all memory 
        //used by these variables with 0xCC
        pConfig->AddToolSettings(L"cl.exe", L"/GZ", dummy);
        // Program Database
        pConfig->AddToolSettings(L"cl.exe", L"/Zi", dummy);
        pConfig->AddToolSettings(L"cl.exe", L"/Oi", dummy);
        // __stdcall calling convention
        pConfig->AddToolSettings(L"cl.exe", L"/Gz", dummy);
        pConfig->AddToolSettings(L"cl.exe", L"/Gz", dummy);


        // Change the libraries
        pConfig->AddToolSettings(L"link.exe", L"kernel32.lib", dummy);
        pConfig->AddToolSettings(L"link.exe", L"user32.lib", dummy);
        pConfig->AddToolSettings(L"link.exe", L"gdi32.lib", dummy);
        pConfig->AddToolSettings(L"link.exe", L"winspool.lib", dummy);
        pConfig->AddToolSettings(L"link.exe", L"comdlg32.lib", dummy);
        pConfig->AddToolSettings(L"link.exe", L"advapi32.lib", dummy);
        pConfig->AddToolSettings(L"link.exe", L"shell32.lib", dummy);
        pConfig->AddToolSettings(L"link.exe", L"ole32.lib", dummy);
        pConfig->AddToolSettings(L"link.exe", L"oleaut32.lib", dummy);
        pConfig->AddToolSettings(L"link.exe", L"uuid.lib", dummy);
        pConfig->AddToolSettings(L"link.exe", L"odbc32.lib", dummy);
        pConfig->AddToolSettings(L"link.exe", L"odbccp32.lib", dummy);

        CString cstr;

        if(m_Dictionary.Lookup(TEXT("INIT_COMMON_CONTROLS"),cstr))
        {
            pConfig->AddToolSettings(L"link.exe", L"comctl32.lib", dummy);
        }

        if(m_Dictionary.Lookup(TEXT("USE_WINSOCK"),cstr))
        {
            pConfig->AddToolSettings(L"link.exe", L"ws2_32.lib", dummy);
        }

        pConfig=NULL;
    }
    pConfigs=NULL;
}

Template Files

As seen for the Screen Saver AppWizard, the Wizard needs files as templates to create the appropriate application. There are two files that are always present: confirm.inf and newproj.inf; and some others that must be added and that describe and implement the application the Wizard will create:

The files:

  • root.cpp
  • root.rc
  • resource.h
  • Safx.h
  • Safx.cpp

are the template files that implement the Win32 application the Wizard will create, and must be registered in the file newproj.inf.

Let's analyze the root.cpp file that is the main program of the target application:

// $$Root$$.cpp : Defines the entry point for the application.
//

#include "stdafx.h"

LRESULT CALLBACK WindProc(HWND hwnd, 
    UINT message, WPARAM wParam, LPARAM lParam);
void Init(HWND);

$$IF(USE_WINSOCK)
WSADATA wsaData;
$$ENDIF
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    MSG msg;
    WNDCLASS wnd;
    HWND hWnd;

    static TCHAR szAppName[] = TEXT ("$$Root$$") ;

    wnd.style            = CS_HREDRAW | CS_VREDRAW;
$$IF(WINDOW_BASED)
    wnd.cbWndExtra        = 0;
$$ELSE
    wnd.cbWndExtra        = DLGWINDOWEXTRA;
$$ENDIF // WINDOW_BASED
    wnd.cbClsExtra        = 0;
    wnd.hCursor            = LoadCursor(NULL,MAKEINTRESOURCE(IDC_ARROW));
    wnd.hIcon            = LoadIcon(NULL,MAKEINTRESOURCE(IDI_WINLOGO));
    wnd.hInstance        = hInstance;
    wnd.lpfnWndProc        = WindProc;
    wnd.lpszClassName    = szAppName;
    wnd.lpszMenuName    = NULL;
    wnd.hbrBackground    = (HBRUSH)(COLOR_WINDOW);

    if(!RegisterClass(&wnd))
    {
        return 0;
    }

$$IF(WINDOW_BASED)
    hWnd = CreateWindow(wnd.lpszClassName,
                        "$$Root$$",
                        WS_BORDER |
                        WS_OVERLAPPED|
                        WS_VISIBLE|
                        WS_SYSMENU,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        300,            // change to desired width
                        300,            // change to desired height
                        NULL,
                        NULL,
                        hInstance,
                        0
                        );
$$ELSE
    hWnd = CreateDialog(hInstance,szAppName,NULL,NULL);
$$ENDIF // WINDOW_BASED

$$IF(INIT_COMMON_CONTROLS)
    InitCommonControls();
$$ENDIF // INIT_COMMON_CONTROLS

    PostMessage(hWnd,WM_COMMAND,WM_INITAPPLICATION,0);

    ShowWindow(hWnd,nCmdShow);
    UpdateWindow(hWnd);

    while(GetMessage(&msg,NULL,0,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WindProc(HWND hwnd, 
       UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message) {

        case WM_CREATE:
            {

                break;
            }

        case WM_DESTROY:
            {
$$IF(USE_WINSOCK)
                WSACleanup();
$$ENDIF
                PostQuitMessage(0);
                break;
            }

        case WM_COMMAND:
            {
                switch(LOWORD(wParam)) {

                    case WM_INITAPPLICATION:   
                        {
                            Init(hwnd);
                            break;
                        }

                    default:
                        break;
                }

                break;
            }

        case WM_PAINT:
            {
                break;
            }

        default:
            return DefWindowProc(hwnd,message,wParam, lParam);
    }

    return DefWindowProc(hwnd,message,wParam, lParam);
}

void Init(HWND hWnd)
{
$$IF(USE_WINSOCK)
    WORD wVersionRequested;
    int err;
 
    wVersionRequested = MAKEWORD( 2, 2 );
 
    err = WSAStartup( wVersionRequested, &wsaData );
    if ( err != 0 ) {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.       
        */
        MessageBox(hWnd, 
          "Couldn't find a usable Winsock DLL",
          "Winsock Error",MB_OK);
        return;
    }
 
    /* Confirm that the WinSock DLL supports 2.2.*/
    /* Note that if the DLL supports versions greater    */
    /* than 2.2 in addition to 2.2, it will still return */
    /* 2.2 in wVersion since that is the version we      */
    /* requested.                                        */

    if ( LOBYTE( wsaData.wVersion ) != 2 ||
            HIBYTE( wsaData.wVersion ) != 2 ) {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */
        WSACleanup( );
        MessageBox(hWnd, 
          "Couldn't find a usable Winsock DLL",
          "Winsock Error",MB_OK);
        return; 
    }
 
    /* The WinSock DLL is acceptable. Proceed. */
$$ENDIF

    // initialize data here
}

There are some points of interest here: first, for sure you'll have noticed those strange macros $$IF ... $$ELSE ... $$ENDIF; these ones are needed by the AppWizard framework to check if some variables are present in the dictionary, and the Wizard will decide to include or not blocks of code relatively to these variables. In fact, the wizard will specify DLGWINDOWEXTRA only in the case the user selected the radio button for the Dialog Based application (and the CCustom1Dlg::OnDismiss() will create the variable in the dictionary); or again the initialization of the common controls will be included only if the checkbox in the Wizard Step Dialog will be checked. Same for the initialization code for the Winsock Library. This approach is done even in the Safx.h:

#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

// Exclude rarely-used stuff from Windows headers
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
$$IF(INIT_COMMON_CONTROLS)
#include <commctrl.h>
$$ENDIF
$$IF(USE_WINSOCK)
#include <winsock2.h>
$$ENDIF
// TODO: reference additional headers your program requires here

#include "resource.h"

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional
// declarations immediately before the previous line.

#endif 
// !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)

Only the header files of the appropriate kind of application will be included.

Macros are even in the newproj.inf to decide the inclusion of the .rc file (only in the case of a Dialog App):

$$// newproj.inf = template for list of template files
$$//  format is 'sourceResName' \t 'destFileName'
$$//    The source res name may be preceded by any 
$$//       combination of '=', '-',  '!', '?', ':', '#', and/or '*'
$$//       '=' => the resource is binary
$$//       '-' => the file should not be added to the project
$$//              (all files are added to the project by default)
$$//       '!' => the file should be marked exclude from build
$$//       '?' => the file should be treated as a help file
$$//       ':' => the file should be treated as a resource
$$//       '#' => the file should be treated as a template (implies '!')
$$//       '*' => bypass the custom AppWizard's resources when loading
$$//    if name starts with / => create new subdir

ROOT.CPP    $$Root$$.cpp
RESOURCE.H    resource.h

$$IF(DIALOG_BASED)
ROOT.RC    $$Root$$.rc
$$ENDIF // DIALOG_BASED

SAFX.CPP    StdAfx.cpp
SAFX.H    StdAfx.h

Notice that the SAfx.h and SAfx.cpp files will be remapped to their normal names here.

And even in confirm.inf to describe the files that will be created by the wizard:

These files will be created:

$$Root$$.cpp
StdAfx.h
Stdafx.cpp
resource.h
$$IF(DIALOG_BASED)
$$Root$$.rc
$$ENDIF

$$IF(INIT_COMMON_CONTROLS)
This application will use the Common Controls Library
$$ENDIF
$$IF(USE_WINSOCK)
This application will use the Winsock 2 Library
$$ENDIF

Building and Running the Wizard

Building the Wizard

During compilation, the compiler could complain about not recognizing the IBuildProject interface. Three header files are then needed to be included in your Stdafx.h Custom AppWizard Project:

#include <atlbase.h>
#include <ObjModel/bldauto.h>
#include <ObjModel/bldguid.h>

Running the Wizard

After compilation, the Wizard files produced are automatically copied into the Visual Studio Template directory. So, the only thing you have to do is open a new instance of Visual Studio (even if it is not needed to open a new one) and see if the 'SimpleApplicationWizard AppWizard' is present. If yes, choose it and try it:

Conclusion

Hope this article could be useful for those who want to get more from the VC++ IDE. Comments and hints are welcome, thanks.

History

22 June 2004 v1.0.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Fabio Fornaro
Software Developer (Senior)
Italy Italy
Worked as a Java developer targeting J2EE and J2SE for half a decade.
 
Now he's employed for Avanade as a Senior Associate Consultant on Microsoft technologies.
 
His hobbies are Win32 programming using SDK, MFC, ATL and COM; playing guitar and listening to music.

Comments and Discussions

 
Generalcompiling errors PinmemberRoj18-Jun-06 12:30 
When I try to compile your sample project I get these warnings :
 
--------------------Configuration: SimpleApplicationWizard - Win32 Pseudo-Debug--------------------
Build : warning : failed to (or don't know how to) build 'C:\Documents and Settings\PC\Desktop\Win32 programming\SimpleApplicationWizard\hlp\AfxPrint.rtf'
 
Build : warning : failed to (or don't know how to) build 'C:\Documents and Settings\PC\Desktop\Win32 programming\SimpleApplicationWizard\hlp\AfxCore.rtf'
 
Making help file...
1 file(s) copied.
1 file(s) copied.
 
SimpleApplicationWizard.awx - 0 error(s), 2 warning(s)
 
I can't find these files in my Visual Studio 6 directories, where would I find them. I did a search, but was unable to find them. So, I used the
replacement AfxCore.rtf and AfxPrint.rtf files on this website. Now, I get a dialog box asking for the name of an executable file name for debug session. What does this mean?
 
Z.K.
GeneralRe: compiling errors PinmemberFabio Fornaro19-Jun-06 1:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141216.1 | Last Updated 23 Jun 2004
Article Copyright 2004 by Fabio Fornaro
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid