Dual Interfaces in MFC

The wrapper classes created by the MFC wizard are very helpful, and for a while they were the best you could get. With Visual Studio 6, however, the compiler has built-in COM support, including a different way to create wrapper classes for COM objects through the new #import compiler directive. These "native" wrapper classes are faster and more flexible than the ones generated by MFC:

The MFC wrappers are based on the IDispatch interface, so every method or property you access needs to pack its parameters into VARIANTS and go through a call to the Invoke method. The native wrappers, by contrast, take advantage of dual interfaces to access properties and methods via direct calls, which is much faster.

The native wrappers are more complete and configurable. They include object-defined enumerations, default values for optional parameters, and an optional VB-like syntax for accessing properties. These new features allow you to write

  m_spGrid->MousePointer = flexHourglass;

  m_spGrid->AutoSize(1);

instead of

  m_Grid.SetMousePointer(11);

  COleVariant vtNone(0L, VT_ERROR);

  m_Grid.AutoSize(1, vtNone, vtNone, vtNone);

Taking advantage of dual interfaces in existing MFC projects is easy. All you have to do is include the appropriate #import statement in your StdAfx.h file, then create a pointer and assign it to the existing control. For example, assuming you have an MFC-based m_Grid control, all the extra code you would need would be this:

  // include this statement in the StdAfx.h file

  #import "c:\windows\system\vsflex8l.ocx" no_namespace

  // note: to use the OLEDB/ADO version of the control,

  // you need to #import "msdatsrc.tlb" as well.

  //#import "c:\windows\system\msdatsrc.tlb" no_namespace

  //#import "c:\windows\system\vsflex8l.ocx" no_namespace

Then, instead of using the control in the usual way, declare a variable of type IVSFlexGridPtr, initialize it by setting it to m_Grid.GetControlUnknown(), and use it instead of m_Grid. The two routines listed below illustrate the difference between the two approaches (both routines fill a grid 1000 times with a string and report how long it took them to do it):

  // Using the MFC-generated wrapper class

  void CMyDlg::BenchDispatchClick()

  {

    CString strText = "Hello.";]

    DWORD tStart = GetTickCount();

    for (long i = 0; i < 1000; i++)’

      for (long r = m_Grid.GetFixedRows(); r < m_Grid.GetRows(); r++)

        for (long c = m_Grid.GetFixedCols(); c < m_Grid.GetCols(); c++)

          m_Grid.SetTextMatrix(r, c, (LPCTSTR)strText);

    DWORD tElapsed = GetTickCount() - tStart;

    CString str;

    str.Format("Done in %d seconds using dispatch interface.",

              (int)(tElapsed / 1000));

    MessageBox(str);

  }

  // Using the native wrapper class (#import-based)

  void CMyDlg::BenchDualClick()

  {

    IVSFlexGridPtr spGrid = m_Grid.GetControlUnknown();

    _bstr_t strText = "Hello.";

    DWORD tStart = GetTickCount();

    for (long i = 0; i < 1000; i++)

      for (long r = spGrid->FixedRows; r < spGrid->Rows; r++)

        for (long c = spGrid->FixedCols; c < spGrid->Cols; c++)

          spGrid->PutTextMatrix(r, c, strText);

    DWORD tElapsed = GetTickCount() - tStart;

    CString str;

    str.Format("Done in %d seconds using dual interface.",

              (int)(tElapsed / 1000));

    MessageBox(str);

  }

The code looks very similar, except for the dot notation used with the m_Grid variable and the arrow used with the spGrid variable. The big difference is in execution speed. The MFC/Dispatch version takes 30 seconds to fill the grid 1000 times, while the native/dual version takes only 8 seconds. The dual version is over three times faster than the traditional MFC/Dispatch version.

In functions that only set a few properties, it probably doesn't matter much which type of wrapper class you choose. But in functions with lengthy for statements that access properties or methods several hundred times, you should definitely consider using the #import statement/ dual interface approach.