Mix and Match C with C++ and MFC

Nancy Winnick Cluts
Microsoft Developer Network Technology Group

Created: July 12, 1994

Click to open or copy the files in the MFCPROPS sample application for this technical article.

Abstract

The Microsoft® Foundation Class (MFC) library provides a set of C++ classes that encapsulate the functionality of applications written for the Microsoft Windows® operating system. The tools that accompany Microsoft Visual C++™ provide developers with a working application skeleton containing a main window, menus, toolbars, status bars, and other items. This functionality alone is tempting for developers who would like to let the tool do the grunt work for them. However, for various reasons, an application may contain areas that need to remain in C rather than being ported to C++ or MFC. This article discusses the steps required to port a simple application from C to C++, and explains how to mix and match C code with C++ and MFC code to take advantage of MFC’s built-in functionality.

Introduction

Just having finished a long series of articles covering the new common controls in the next version of the Microsoft® Windows® operating system (called Windows 95), I was looking for an interesting topic to cover in my next article. I decided to take a walk down the hall to Kyle Marsh’s office and ask his opinion. This was my first mistake. You see, Kyle is known for figuring out and suggesting exactly what you have been dreading to do. He reminded me that I had promised to learn and use C++ and the Microsoft Foundation Class (MFC) library in my samples. He suggested that I make good on that promise and start porting my samples from C to C++ and MFC. Because many developers will want to write MFC Windows 95–based applications, he advised me to learn how to use the new Windows 95 common controls, such as property sheets and toolbars, by mixing C, C++, and MFC.

Overview of MFC

This section gives a brief overview of MFC, for those of you who haven’t taken the time to read up on it. Those of you who are already familiar with MFC should feel free to skip this section.

The classes in MFC create an application framework. A developer uses this framework as the basis for building an application for Windows. This framework defines the skeleton of an application and implements standard user-interface components such as toolbars, status bars, and dialog boxes to be placed on the skeleton. The developer then only needs to fill in the rest of the skeleton with code that is specific to the application’s functionality. AppWizard, which is a major time-saver that is built into Visual C++™, creates the files for a fully functional starter application. You can use the resource editor, which is also built into Visual C++, to design dialog boxes, and the ClassWizard tool to create classes for those dialog boxes.

Version 3.0 of the MFC framework supports 32-bit programming for Win32® platforms, including Microsoft Windows NT™ version 3.5 and later. MFC’s Win32 support also includes multithreading, which can be used to create applications that run under Windows 95.

Aside from the fact that MFC 3.0 and Visual C++ offer major time savings in the creation of a Windows-based application, MFC also makes it much easier to write applications that specifically use OLE. You can make your application an OLE Visual Editing container, an OLE Visual Editing server, or both, and you can add OLE Automation so that other applications can use objects from your application or even drive it remotely.

MFC 3.0 also supplies a set of database classes that simplify writing data access applications. This allows an application developer to use the database classes to connect to databases through an open database connectivity (ODBC) driver, select records from tables, and display record information in an on-screen format.

Lastly, MFC 3.0 is fully enabled for writing applications that use Unicode™ and multibyte character sets (MBCS), specifically double-byte character sets (DBCS).

Porting a Property Sheet

I decided that the first sample I would port would be my new property sheet sample that accompanies the “Win32 Common Controls, Part 6: Tab Controls and Property Sheets” technical article in the Microsoft Development Library. A property sheet is a special tabbed dialog box that an application can implement to logically group dialog functions together. The Windows 95 shell uses property sheets to view and set the properties of objects within the system. OLE custom controls also use property sheets to set control properties, and Visual C++ version 2.0 uses a property sheet for project settings. Property sheets contain tabs that allow the user to move from one dialog box (page) within the property sheet to another without fully dismissing the dialogs. The figure below illustrates a simple property sheet that sets the properties of a slider control.

A Simple Property Sheet

My Property Sheet Has No Class

The first thing I discovered was that no special classes currently exist for the new Windows 95 controls. (A version of MFC to be released after Windows 95 will contain these classes, so do not despair.) MFC has several new classes for MFC property sheets, toolbars, and status bars, but I wanted to use the new Windows 95 property sheet and its new controls. This left me with two choices:

I decided to take the mix-and-match approach. The remainder of this article explains the steps I took to port, mix, and match code. You can, of course, change the order—this is just the way I decided to do it.

Step One: Generate a Project with AppWizard

When I started this project, I knew I wanted to use the tools that are included with Visual C++ 2.0 as much as possible. Why? Because I knew how much code the tools would generate automatically, and I wanted to use the same tools as other developers. So, the first step was to create a new project by selecting File New and choosing Project from the list box. I was prompted for the type and name of the project that I wanted to create. I also decided not to add any new features at the current time. I figured that there was plenty of time for this later on, once I was comfortable with C++ and MFC. I entered the project name MFCPROPS and selected the following options (dubbed the “Just Say No” options):

  1. Single-document interface (SDI)

  2. No database support

  3. No OLE Compound Document support

  4. No OLE Automation

  5. No toolbar, no status bar, no printing, no context-sensitive help, and no 3-D controls

  6. Yes for comments

  7. Use a Visual C++ make file

  8. Use MFC in a static library

When I clicked Finish, AppWizard displayed a list of the files it would create. A brief explanation of the files that AppWizard creates is also provided in the README.TXT file, which you will find in the same directory as the project files. Here’s a list of the files that AppWizard created for me:

If you look at the files AppWizard creates for you, you’ll see lots of areas with the "TODO" tag. These tags indicate the parts of the source code that you should customize for your own use. At this point, you have a complete application skeleton that you can build and run. The application does not do much, but you will notice that it does contain a main window with standard menus. Just think of all the time you can save by having this code generated for you, rather than having to plow through the dependencies and name changes yourself.

Step Two: Make Resource Changes

AppWizard automatically generates several menu items that I did not use in my sample, so the first thing I did was to remove all the items I did not support. I removed all the File menu items, with the exception of the Exit command, and I removed the Edit and View menus entirely. I then changed the name of the File menu to Options and added in the one item I wanted to support: Slider properties with the identifier equal to IDM_PROPERTIES. This gave me the menu configuration I wanted.

Next, I removed the accelerator table entirely. I could have removed only the accelerators for the commands that I eliminated, and added accelerators for my new menu items, but I wasn’t going to add any functionality yet, so I didn’t.

Dialog Changes

I had to decide what to do about the dialog boxes I needed. Because I was porting a property sheet sample that I had already built, I had a choice between simply including the old dialog boxes in the MFCPROPS.RC2 file and the definitions in RESOURCE.H, or using the dialog editor that comes with Visual C++ to generate the property-sheet pages. I wanted to become as familiar as possible with the new tools, so I went ahead and regenerated the pages using the dialog editor.

Regenerating the pages was a fairly simple task, but I had to undo a few things that the dialog editor did automatically. Because I was creating pages, I deleted the OK and Cancel buttons from the default dialog box, then created my dialog box in the standard way. The Visual C++ 2.0 tools do not support Windows 95 fully yet, so I made one manual change to my dialog boxes: I added the DS_3DLOOK style to each page definition to get that neat new 3-D look that all the new Windows 95 controls have.

Gotcha! Check Your Link Options

The last thing I did before compiling my code was to change the Link options for my project. I chose the Settings from the Project menu, clicked the Link tab, then selected Win32 Debug in the Settings For box. The Project Options box contains the following line:

/SUBSYSTEM:Windows

I changed this line as follows, to mark my executable as a Windows 95 executable:

/SUBSYSTEM:Windows,4.0

I made the same change for the Win32 release version of my project. Why? In the current version of Visual C++ 2.0, the default executable is marked as a Windows 3.x executable. This is simply marvelous for an executable that will run under Windows 3.x or Windows NT, but if you try to run this executable under Windows 95, the operating system will assume that your executable does not know how to handle some of the new Windows 95 messages such as WM_NOTIFY (a key new message used in the Windows 95 shell). As a result, although your application will build, it will not receive WM_NOTIFY messages, and you’ll think you did something wrong. This problem made me bang my head against the wall until I figured out what was going on and marked my executable properly.

Recompile and Run

That just about says it all. At this point, I built the project and ran it. All of my changes were now in place. Of course, I still hadn’t added my property-sheet page manipulation, but I did have something that was incredibly fast and easy to put together.

Step Three: Add My C Code

I took my original sample files and copied them over to my working directory and renamed the .C file to .CPP. Then I ripped out the initialization code (the code AppWizard created for me already took care of initialization) and removed my window procedure. This left me with the functions that support the property-sheet creation and manage the property-sheet pages. I also had to write a function that would create my slider because this code was originally in my main window’s window procedure. I wrapped the code within a declaration that indicated this was C code, and I was nearly done. Here is the C code I used:

#include <windows.h>
#include <commctrl.h>
#include <prsht.h>
#include "resource.h"

extern "C"{

BOOL APIENTRY Range(HWND, UINT, UINT, LONG);
BOOL APIENTRY PageSize(HWND, UINT, UINT, LONG);  
VOID CreateSlider(HWND, HINSTANCE);
int CreatePropertySheet(HWND, HINSTANCE);

HWND hWndSlider;

// Create the slider control.
VOID CreateSlider(HWND hWnd, HINSTANCE hInst)
{
  // Ensure that the common control library has been loaded.
  InitCommonControls();

  // Create the slider control.
  hWndSlider  = CreateWindow(
        TRACKBAR_CLASS,
        "",
        WS_CHILD | WS_VISIBLE | TBS_AUTOTICKS | TBS_TOP | TBS_ENABLESELRANGE,
        10,100,200,20,
        hWnd,         
        (HMENU)ID_SLIDER,         
        hInst,
        NULL);
  if (hWndSlider == NULL)
    MessageBox (NULL, "Slider not created!", NULL, MB_OK );

  // Set the default range.
  SendMessage( hWndSlider, TBM_SETRANGE, TRUE, MAKELONG(1,10));

  // Set the selection. 
  SendMessage( hWndSlider, TBM_SETSEL, TRUE, MAKELONG(3,5));

  return;
}
// Function that handles the Range tab.
BOOL APIENTRY Range(HWND hDlg, UINT message, UINT wParam,
  LONG lParam)
{
  BOOL bErr;
  static UINT uMin, uMax, uMinSave, uMaxSave;

  switch (message)
  {
     case WM_NOTIFY:
        switch (((NMHDR FAR *) lParam)->code) 
        {
            case PSN_HASHELP:
                 // Indicate that the Help button is not supported.
                 SetWindowLong(hDlg, DWL_MSGRESULT, FALSE);
                 break;

            case PSN_SETACTIVE:
                 // Initialize the controls.
                 uMinSave = SendMessage( hWndSlider, TBM_GETRANGEMIN, 0L, 0L);
                 uMaxSave = SendMessage( hWndSlider, TBM_GETRANGEMAX, 0L, 0L);
                 SetDlgItemInt(hDlg, IDE_MIN, uMinSave, TRUE);
                 SetDlgItemInt(hDlg, IDE_MAX, uMaxSave, TRUE);
                 break;

            case PSN_APPLY:
                 uMin = GetDlgItemInt(hDlg, IDE_MIN, &bErr, TRUE);
                 uMax = GetDlgItemInt(hDlg, IDE_MAX, &bErr, TRUE);
                 SendMessage( hWndSlider, TBM_SETRANGE, TRUE, 
                    MAKELONG(uMin, uMax));
                 SetWindowLong(hDlg, DWL_MSGRESULT, TRUE);
                 break;

            case PSN_KILLACTIVE:
                 SetWindowLong(hDlg,  DWL_MSGRESULT, FALSE);
                 return 1;
                 break;

            case PSN_RESET:
                 // Reset to the original values.
                 SendMessage( hWndSlider, TBM_SETRANGE, TRUE, 
                      MAKELONG(uMinSave, uMaxSave));
                 SetWindowLong(hDlg, DWL_MSGRESULT, FALSE);
                 break;
      }
  }
  return (FALSE);   

}
// Function for handling Page Size tab.
BOOL APIENTRY PageSize(HWND hDlg, UINT message, UINT wParam,
  LONG lParam)
{
  BOOL bErr;
  static UINT uPage, uPageSave;
  static UINT uLine, uLineSave;
  static PROPSHEETPAGE * ps;

  switch (message)
  {
    case WM_NOTIFY:
        switch (((NMHDR FAR *) lParam)->code) 
        {
            case PSN_HASHELP:
                 // Indicate that the Help button is not supported.
                 SetWindowLong(hDlg, DWL_MSGRESULT, FALSE);
                 break;

            case PSN_SETACTIVE:
                // Initialize the controls.
                uPageSave = SendMessage( hWndSlider, TBM_GETPAGESIZE, 0L, 0L);
                uLineSave = SendMessage( hWndSlider, TBM_GETLINESIZE, 0L, 0L);
                SetDlgItemInt(hDlg, IDE_LINE, uLineSave, TRUE);
                SetDlgItemInt(hDlg, IDE_PAGE, uPageSave, TRUE);
                break;

            case PSN_APPLY:
                // Reset the items to the desired values.
                uPage = GetDlgItemInt(hDlg, IDE_PAGE, &bErr, TRUE);
                PostMessage( hWndSlider, TBM_SETPAGESIZE, 0L, uPage);
                uLine = GetDlgItemInt(hDlg, IDE_LINE, &bErr, TRUE);
                PostMessage( hWndSlider, TBM_SETLINESIZE, 0L, uLine);
                SetWindowLong(hDlg,  DWL_MSGRESULT, TRUE);
                break;

            case PSN_KILLACTIVE:
                SetWindowLong(hDlg, DWL_MSGRESULT, FALSE);
                return 1;
                break;

            case PSN_RESET:
               // Reset to the original values.
               PostMessage(hWndSlider, TBM_SETPAGESIZE, 0L, uPageSave);
               PostMessage( hWndSlider, TBM_SETLINESIZE, 0L, uLineSave);
               SetWindowLong(hDlg, DWL_MSGRESULT, FALSE);
               break;
      }
  }
  return (FALSE);   
}

/****************************************************************************
*    FUNCTION: CreatePropertySheet(HWND)
*
*    PURPOSE:  Creates a property sheet
*
****************************************************************************/
int CreatePropertySheet(HWND hwndOwner, HINSTANCE hInst)
{
    PROPSHEETPAGE psp[2];
    PROPSHEETHEADER psh;

    psp[0].dwSize = sizeof(PROPSHEETPAGE);
    psp[0].dwFlags = PSP_USETITLE;
    psp[0].hInstance = hInst;
    psp[0].pszTemplate = MAKEINTRESOURCE(IDD_RANGE);
    psp[0].pszIcon = NULL;
    psp[0].pfnDlgProc = (DLGPROC)Range;
    psp[0].pszTitle = "Slider Range";
    psp[0].lParam = 0;

    psp[1].dwSize = sizeof(PROPSHEETPAGE);
    psp[1].dwFlags = PSP_USETITLE;
    psp[1].hInstance = hInst;
    psp[1].pszTemplate = MAKEINTRESOURCE(IDD_PROPS);
    psp[1].pszIcon = NULL;
    psp[1].pfnDlgProc = (DLGPROC)PageSize;
    psp[1].pszTitle = "Slider Page and Line  Size";
    psp[1].lParam = 0;
    
    psh.dwSize = sizeof(PROPSHEETHEADER);
    psh.dwFlags = PSH_PROPSHEETPAGE;
    psh.hwndParent = hwndOwner;
    psh.hInstance = hInst;
    psh.pszIcon = NULL;
    psh.pszCaption = (LPSTR) "Slider Properties";
    psh.nPages = sizeof(psp) / sizeof(PROPSHEETPAGE);
    psh.ppsp = (LPCPROPSHEETPAGE) &psp;

    return (PropertySheet(&psh));
}
} // End of extern "C" bracket.

Step Four: Integrate My C Code with MFC

Just because I added my C code doesn’t mean that MFC knows to call it. I mean, MFC is definitely very smart, but it can't read minds yet.

To call my C function, I had to alter the code that AppWizard created for me. I opened the MFCPRVW.CPP file that handles the view, and ran ClassWizard from the Project menu. I selected the view and the OnInitialUpdate message, clicked the Add Function button, then clicked the Edit Code button to edit the code. I used the following code to create the slider:

void CMfcpropsView::OnInitialUpdate() 
{
   // TODO: Add your specialized code here and/or call the base class.
   CreateSlider(GetSafeHwnd() , AfxGetInstanceHandle());
   CView::OnInitialUpdate();
}

Because my C function needed a handle to a window and the handle to the current instance, I had to call two MFC functions: GetSafeHwnd and AfxGetInstanceHandle. Adding this code was all I needed to do to have my slider function correctly. Now I had to get that property sheet working.

In my original design, the property sheet is displayed when the window procedure receives an IDM_PROPERTIES command. In MFC, we no longer have a window procedure; instead, the system has a message map that dispatches the appropriate messages to the appropriate windows. To add a handler for a new command, I once again used ClassWizard on my view class, but this time I selected IDM_PROPERTIES and COMMAND in the message box, then clicked Add Function. This action displayed a prompt for the function name. I accepted the default (which was "OnProperties") and clicked the Edit Code button. These actions added a line to my message map that directed MFC to call the OnProperties function when the view received the IDM_PROPERTIES command. The following code demonstrates the final changes I made to my view code. Note that I added prototypes for the C functions I called and bracketed the prototypes with the extern “C” directive.

extern "C" {
extern VOID CreateSlider(HWND, HINSTANCE);
extern int CreatePropertySheet(HWND, HINSTANCE);
}

BEGIN_MESSAGE_MAP(CMfcpropsView, CView)
   //{{AFX_MSG_MAP(CMfcpropsView)
   ON_COMMAND(IDM_PROPERTIES, OnProperties)
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CMfcpropsView::OnProperties() 
{
   // TODO: Add your command handler code here.
   CreatePropertySheet(GetSafeHwnd(), AfxGetInstanceHandle());
}

Next, I did a quick scan for all dependencies, and compiled and linked the code. I ran into one small problem, and had to remove the precompiled header option from PROPS.CPP (the file containing all of my C source code) as a workaround. In PROPS.CPP, I did not include the stdafx.h file at the beginning of my source listing, so I had to ensure that the project was built without presuming that I would be using precompiled headers for this file. I did this by using the Project Settings property sheet. I chose PROPS.CPP from the list of source files, and clicked the C/C++ tab. From the Category drop-down combo box, I selected Precompiled Headers. To disable the use of stdafx.h, I clicked the check box labeled "Use .PCH File". Choosing this check box adds the string

     Project settings, and not /Yu"stdafx.h"

to the Source File Options edit box at the bottom of the page.

I attempted another build, only to find another small problem—I needed to include COMCTL32.LIB in my Object/Library Link Modules list by clicking the Link tab in the Project Settings property sheet. At this point, my application ran just like the original application. I had successfully used MFC for items that MFC supports, and C for the common controls that MFC does not yet support.

Can I Use MFC Controls with Windows 95 Controls?

You may be wondering if you can use MFC’s built-in controls (such as the toolbar, status bar, and property sheet) in conjunction with the Windows 95 common controls. Well, you can. Use the built-in controls as usual, and keep the Windows 95–specific, non-MFC code in a separate C file. The separate file is a personal choice. I like to separate the C code from the C++ code so I will quickly know what needs to be done when I port my application in its entirety to MFC.

Just to prove to you that anybody can do this (and to keep Nigel Thompson from telling me that I really didn’t use enough MFC to consider myself a convert), I decided to modify my sample to use the property sheet included in MFC.

Here’s what you do to create an MFC property sheet:

  1. Create a dialog template resource for each property page, using the dialog editor. The first page you add to the property sheet determines how much space to allocate for the property pages in the property sheet. Set the following dialog properties for each page:
  2. Use ClassWizard to create a CPropertyPage-derived class for each page. I had to do this for two pages: the Line And Size page and the Range page. To create the class, select the dialog resource, choose ClassWizard from the Project menu, then choose CPropertyPage as the base class in ClassWizard.

  3. Using ClassWizard, create member variables to hold the values for each property page. In the sample I wrote, I created member variables to hold the minimum and maximum range values, and the line size and page size for the slider.

  4. Create a CPropertySheet object in the view source code. I created this object in response to the IDM_PROPERTIES command, OnProperties.

  5. Call ::AddPage for each page to be added to the property sheet.

  6. Call ::DoModal to display the property sheet.

Remember the code in the "Step Three: Add My C Code" section that implemented the Windows 95 property sheet? A lot of code, wasn't it? Well, take a look at the code below that I wrote to use the MFC-supported property sheet:

void CMfcpropsView::OnProperties() 
{
   // Create a property sheet object.
    CPropertySheet dlgPropertySheet(AFX_IDS_APP_TITLE,this);
   CLineAndPageSize sizePage;
   CRange rangePage;

   dlgPropertySheet.AddPage(&sizePage);
   dlgPropertySheet.AddPage(&rangePage);

   if (dlgPropertySheet.DoModal() == IDOK)
   {
      UpdateSlider(rangePage.m_MinRange, rangePage.m_MaxRange, 
          sizePage.m_PageSize, sizePage.m_LineSize);
        GetDocument()->SetModifiedFlag();
        GetDocument()->UpdateAllViews(NULL);
   }
}

If the simplicity of the code above doesn’t inspire you to learn C++ and MFC, I don’t know what will. After seeing the time I could have saved with MFC, I could kick myself for not migrating to MFC earlier.

Summary

I migrated to C++ and MFC kicking and screaming, but once I finally sat down and got to work, I spent less time porting my code than I spent complaining about how difficult it was going to be. To prepare myself for this migration, I took classes in C++ and MFC and read everything I could get my hands on. The Library contains useful information—for example, be sure to read Herman Rodent's article "Using MFC to Create Simple Windows-Based Applications” (MSDN Library Archive, Technical Articles, C++ 1.0 [16-bit] Articles). Of course, having a Visual C++/MFC guru down your hall doesn't hurt either. I hope the information in this article will inspire you to take the leap.

Come on in—the water’s fine!