Fixing up MFC Office 2007 Ribbon Applications, Part 3

Posted by Matt | Filed under , , , ,

In my continuing series, I am providing some workarounds/solutions to some issues in the Visual Studio 2008 SP1 projects created that use the new “MFC Feature Pack”.

Today’s issue is Print Preview.

In a new Office 2007 Ribbon application, if you tell the MFC Application Wizard to not include Print Preview support, it will still include it in the File/Application menu.  Once you create your new project, you’ll still see Print, Print Preview, etc. in the File menu (by clicking the “Pearl” on the top-left corner of the window).

To fix this, locate and remove the following code in MainFrm.cpp:

bNameValid = strTemp.LoadString(IDS_RIBBON_PRINT);
ASSERT(bNameValid);
CMFCRibbonButton* pBtnPrint = 
	new CMFCRibbonButton(ID_FILE_PRINT, 
		strTemp, 6, 6);
pBtnPrint->SetKeys(_T("p"), _T("w"));
bNameValid = strTemp.LoadString(IDS_RIBBON_PRINT_LABEL);
ASSERT(bNameValid);
pBtnPrint->AddSubItem(new CMFCRibbonLabel(strTemp));
bNameValid = strTemp.LoadString(IDS_RIBBON_PRINT_QUICK);
ASSERT(bNameValid);
pBtnPrint->AddSubItem(
	new CMFCRibbonButton(ID_FILE_PRINT_DIRECT, 
		strTemp, 7, 7, TRUE));
bNameValid = strTemp.LoadString(IDS_RIBBON_PRINT_PREVIEW);
ASSERT(bNameValid);
pBtnPrint->AddSubItem(
	new CMFCRibbonButton(ID_FILE_PRINT_PREVIEW, 
		strTemp, 8, 8, TRUE));
bNameValid = strTemp.LoadString(IDS_RIBBON_PRINT_SETUP);
ASSERT(bNameValid);
pBtnPrint->AddSubItem(
	new CMFCRibbonButton(ID_FILE_PRINT_SETUP, 
		strTemp, 11, 11, TRUE));
pMainPanel->Add(pBtnPrint);

Also, when you run the application, if you customize the Quick Access Toolbar, you’ll notice that Print Preview commands are still available.  To remove this, add the following code just after the ribbon is created, before the call to CMainFrame::InitializeRibbon():

m_wndRibbonBar.EnablePrintPreview(FALSE);

I have submitted the issue to Microsoft.  You can view the issue report here.

Fixing up MFC Office 2007 Ribbon Applications, Part 2

Posted by Matt | Filed under , , , ,

In my continuing series, I am providing some workarounds/solutions to some issues in the Visual Studio 2008 SP1 projects created that use the new “MFC Feature Pack”.

Today’s issue is the recent file list.

If you create a new project using the MFC Application Wizard, and you specify an Office 2007 Ribbon user interface, then your application will be created with the ability to have a recent file list.  This list is located in the File or “Application” menu (the “Pearl” in the top-left corner of the application).  The space is there, but if you run your application, you’ll notice that the recent file list is never populated with files you open or save.

The reason for this is because the project was created using a maximum of 0 files for the recent file list.  Take a look in your application’s main .cpp file in InitInstance() of your class.  You’ll notice a call to LoadStdProfileSettings().  It is passing as a parameter ‘0’.  This means to make the recent file list 0 elements long, or empty. 

To fix this issue, set the parameter to LoadStdProfileSettings() to something realistic, or remove the parameter completely since it has a default value of 4.

In summary: you need to change this:

LoadStdProfileSettings(0);  // Load standard INI file options

to:

LoadStdProfileSettings();  // Load standard INI file options

I have submitted the issue to Microsoft.  You can view the issue report here.

Fixing up MFC Office 2007 Ribbon Applications, Part 1

Posted by Matt | Filed under , , , ,

I am in process of converting some of my application’s sub-applications over to the new MFC feature pack.  They’re rather simple applications that I think will work nicely under the Office 2007 Ribbon framework.  As such, I’ve run into some headaches with the new applications and have submitted some bugs to microsoft via Microsoft Connect.

Today’s issue is regarding the version of the MFC and MSVCR90 libraries that a new application will link to.

The VC9 (Visual Studio 2008) compiler will always use the first version of MSVCR90 and MFC (9.0.21022.8).  Please see here for more details.  In order to override this, the developer must #define _BIND_TO_CURRENT_VCLIBS_VERSION in some place like stdafx.h (near the top).  This will make the linker link to the current/latest version of MSVCR90 and MFC90 (9.0.30729.1).

The MFC feature pack pretty much requires the new libraries that are shipped with SP1 of Visual Studio 2008.  But, the MFC Application Wizard, when it creates a new project, does not include this #define anywhere.  So the manifest will bind to 9.0.21022.8 of the libraries.  At first, it may appear ok since the Windows SxS system will use the 9.0.30729.1 version if it’s found:  it will be found on the developer’s machine since Visual Studio was installed.

However, if you move the application to another computer where 9.0.30729.1 is not installed (nor should anyone think it should be since the application is bound to 9.0.21022.8), then it won’t work.  If the earlier version is found, it may start to run, but will run into issues when the later libraries are required.

The fix is to always, ALWSAYS #define _BIND_TO_CURRENT_VCLIBS_VERSION at the top of stdafx.h for new MFC projects using Visual Studio 2008 SP1.

I have submitted this issue to Microsoft.  You can view the issue here.

Migrating from VC6 to VC9

Posted by Matt | Filed under , ,

A year ago, my company started the migration path of moving from Visual C++ 6.0 to Visual Studio 2005 (VC8).  This was mainly done because after Vista was released, it was disclosed that VC6 had limited support on Vista.  If and when our development computers needed to be upgraded, XP may have limited availability.  So we decided then that moving to the newer compiler was pretty much required.

The problem is that, truthfully, migrating compilers is not something customers care about nor will pay for.  So when you have all your developers dedicated to new features and bug fixes, compiler migration always seems to take a back seat.

So throughout 2007, I put in cycles where I can to ensure our code base compiles using VC8.  However, in 2008, Visual Studio 2008 (VC9) was released.  Since we had not moved to 2005 yet, I decided to take the extra jump to 2008.

In February of 2008, I had all the developers start doing their development using VC9.  However, I continued to build our Alpha, Beta and Release builds using VC6.  This required some redundant work with projects and some fixes which worked with VC9 that would not work with VC6.

In April, I made the switch for our Alpha builds and later for the new Beta and Release builds to VC9.  This went as well as could be expected.

Below are some of the more common issues that we faced.

1.  For loop variable scope differences

In VC6, the following is valid:

for (int i = 0; i < 10; i++)
{
}

_tprintf(_T("%i\n"), i);

However, in VC9, by default, the above code is not valid.  Instead, the variable i is local for the for loop and cannot be referenced outside the loop.  To maintain similar functionality, we modified our code to be like the following:

int i = 0;
for (i = 0; i < 10; i++)
{
}

_tprintf(_T("%i\n"), i);

There is a project setting which we could have set to allow the old behaviour.  But I decided to keep the default and modify our code.  I think it's better and, IMO, makes more sense.

2.  STL iterators behave differently

In VC6, the following used to be valid:

std::list<int> mylist;
mylist.push_back(5);

std::list<int>::iterator it = mylist.begin();
// (*it) == 5
it++;
// it == mylist.end()
it++;
// it still == mylist.end()

Now, when debugging, the second ++ will produce an assertion failure and release will crash.  Some of our algorithms needed to be updated because they were taking advantage of the fact that you could iterate past end() and still have end().

Another change is the fact that you cannot -- again begin().  Example:

std::list<int>::iterator it = mylist.begin();
it--; // would go one before begin()

In hindsight, this is kind of silly, but if the libraries support it, why not use it?  We used this because std::list::erase() only takes an iterator, not a reverse_iterator, so to get around this, we could use an interator and work backwards through the list deleting as we went along until we got to the element "before" begin().

After migrating, this also needed to change.

As it turns out, I think the STL changes showed us exactly where many of the problems and crashes were in our code base.  For a long time, I felt that many crash reports we were getting were related to this code, but it would never crash here.  Instead, it invalidated some data somewhere and would crash later on making debugging, especially remotely, nearly impossible.  These changes ensured that improper use of STL would crash the software then and there.  At first, we faced many crashes in our alpha builds.  But after time, the code base became rock solid.

3.  Some 3rd-party libraries need to be recompiled

We were using some 3rd-party libraries such as libpng, zlib, libxml.  These DLLs needed to be recompiled since they were initially compiled using MSVCRT, but now must use MSVCR90.  Not a big deal.  Just recompile and re-link.

4.  Data type size changes

Some data types changed sizes.  For example, time_t moved from 4 bytes to 8 bytes.  We cared about this because we were writing some time_t values to file as a 4 byte integer.  We had to change our code to use __time32_t and time32() instead. It's possible to set some defines to force time_t to continue to be 4 bytes, but we decided to embrace the change.

5.  "Safe" C-library functions

Microsoft added some "safe" versions of many functions such as strcpy(), makepath(), etc.  They called them _strcpy_s() and _makepath_s() respectfully. When compiling the old code, many warnings were displayed about using the "unsafe" version and that the "safe" version should be used instead.  Again, there is a define that could be set to suppress this warning, but instead I decided to embrace the change.  This one I actually liked.  In order to accomplish this, I created my own set of these same functions.  When compiling against VC6, we linked to my custom versions.  When compiling against VC9, we linked to Microsoft's.  After it was done, it was painless to maintain.

After implementation, I feared that we'd get many assertions about improper use of the standard C-library functions.  However, we've not had one incidence of an issue because of this.  I think this is a fantastic change, but it did not help us in actuality.

6.  Previous posts related to our migration

Looking back, the biggest hit to us was the STL changes.  However, the way they were changed forced our code base to improve and become more solid.  For this reason, I accept the changes.

Migrating wasn't easy.  But now that I've migrated, I'll never go back to VC6 unless absolutely required.

Visual C++ 6.0 + XP Manifest == Problems

Posted by Matt | Filed under , , ,

When Windows XP was introduced in 2001, it came with the idea of "themes".  For your application to utilize these themes, you needed to add a manifest resource to your executable with the following contents:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
          manifestVersion="1.0"> 
  <assemblyIdentity version="1.0.0.0"
                    processorArchitecture="X86"
                    name="CompanyName.ProductName.YourApp"
                    type="win32" /> 
  <description>Your application description here.</description> 
  <dependency> 
    <dependentAssembly> 
      <assemblyIdentity type="win32"
                        name="Microsoft.Windows.Common-Controls"
                        version="6.0.0.0"
                        processorArchitecture="X86"
                        publicKeyToken="6595b64144ccf1df"
                        language="*" /> 
    </dependentAssembly> 
  </dependency> 
</assembly> 

This all seems to work well. 

About a year ago, after Vista came out, we wanted to introduce the manifest to our production executable to satisfy certification requirements.  We needed to include the following section in order to turn off Vista virtualization:

<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
  <security>
    <requestedPrivileges>
      <requestedExecutionLevel level="asInvoker" />
    </requestedPrivileges>
  </security>
</trustInfo>

At the time, we were using Microsoft Visual C++ version 6.0 for our production code.  When we added the trustInfo section, we decided to also include the dependency section as well.  We figured that we might as well take advantage of new common controls where possible.

All was good for a while.  We got a bug report for Japan that some of our edit controls were losing half of their data.  For example, if the user entered 4 Japanese characters into the edit control, we were only saving 2.

Debugging the code, we determined that the problem was with an EM_LINELENGTH message.  Sending this message to the edit box would return back "4" as the length of the text.  For 4 Japanese characters, this was incorrect because our application is compiled using Multi-byte character set (ie. not Unicode), and thus, 4 Japanese characters should have been 8 bytes.

What was happening was EM_LINELENGTH was being sent to the Unicode handler for the message instead of the MBCS handler.  English characters were fine.  It appeared that all 2 byte characters were being returned as 1 byte (or 1 character).

Trying to figure out why this was happening was not an easy feat.  Basically, it was someone's suggestion to just remove the manifest completely just to see if it had any effect. 

It did.

As it turns out, including the above XP theme section in our manifest, while using Visual C++ 6.0, causes the issue.  There appears to be some compatibility issues between that compiler and the XP themed common controls.  There is no issue if our manifest only includes the execution level section.

Since then, we have migrated our application to the Visual Studio 2008 compiler, but we have not tried restoring the manifest section.  I'll update you when we give it a try.

Debugging issues with MSVCR90.DLL

Posted by Matt | Filed under , , ,

When we first migrated our applications from Microsoft Visual C++ 6 to Microsoft Visual Studio 2008, we received the following messages when trying to run our debug builds in the debugger:

This application has failed to start because MSVCR90.dll was not found.  Re-installing the application may fix this problem.

At first, it didn't make sense.  We looked for MSVCR90.dll and it was properly installed in the Side-by-side (SxS) system folders.  Our release version would also run fine and that used the same DLL.

Then it hit me:  why is it looking for MSVCR90.DLL while running debug?  Shouldn't it be looking for MSVCR90D.DLL?

A quick look using Dependency Walker (one of the best developer's utilities ever created) showed me that we were linking to both MSVCR90D.DLL and MSVCR90.DLL.

The solution is to ignore MSVCR90 (and if necessary, MSVCRT) when linking.  This can be done from the project properties:

image

But only do this for the debug configurations.  Once you do this, re-link, and your application should start.  If you continue to get this same error message, then using Dependency Walker again, check that some of your DLLs don't need the same modification.  In addition, if you attempt to do a LoadLibrary() on one of your DLLs and you're getting a cryptic message about the DLL not being found, it may be the same thing.  Use Dependency Walker on that DLL and modify those projects if necessary.

Thinking back, when linking using Visual C++ 6, I remember some linker messages like the following which (at the time) I thought was safe to ignore.

LINK : warning LNK4098: defaultlib 'MSVCRT' conflicts with use of other libs; use /NODEFAULTLIB:library

Maybe in the good 'ole days, we could ignore such a message.  Today, we're not so lucky.

BTW, Microsoft, please make a better error message for such a situation.

Visual Studio Start Page

Posted by Matt | Filed under , , , ,

Something that I really like about Visual Studio 2005 and 2008 is it's "Start Page".  Rather than starting with a blank project or an otherwise blank screen, it presents to the user:

  1. Recent projects to open
  2. Information useful to the user (RSS feeds, links to help documents, etc.)

I've wanted to try to mimic the start page in my own applications.  At first, based on it's visual attractiveness, I thought it was a CHtmlView-based window.  However, after further examination, it appears to be a custom-drawn window.

To start your own "Start Page" in your own MDI applications, you need to create a new CView-derived class (CHtmlView, CScrollView, etc.), a CDocument-derived class, and a CMDIChildWnd- or CMdiChildWndEx-derived class to work with your view.  In your application's InitInstance() function, create a second doc template, but don't register it by calling AddDocTemplate().

pDocTemplate = new CMultiDocTemplate(IDR_STARTPAGE,
    RUNTIME_CLASS(CStartPageDoc),
    RUNTIME_CLASS(CStartPageFrame),
    RUNTIME_CLASS(CStartPageView));
if (!pDocTemplate)
    return FALSE;
m_pStartPageTemplate = pDocTemplate;

Here, we saved the template into our own member variable in the application.  If you call AddDocTemplate(), then when you attempt a File New, you'll be prompted with a dialog of what type of document to create. We don't want that, so we just won't register the template with the MFC sub-system.

Create a function in your application that will show the existing start page, or create a new one if one does not already exist.

void CStartPageApp::OnStartPage()
{
    // See if we already have an existing doc open
    POSITION pos = m_pStartPageTemplate->GetFirstDocPosition();
    if (pos == NULL)
    {
        // We don't, so just create a new one
        m_pStartPageTemplate->OpenDocumentFile(NULL);
    }
    else
    {
        // Get the existing doc's first view's frame
        // and activate that frame.
        CDocument *pDoc = m_pStartPageTemplate->GetNextDoc(pos);
        ASSERT(pDoc != NULL);
        if (pDoc == NULL)
            return;

        pos = pDoc->GetFirstViewPosition();
        ASSERT(pos != NULL);
        if (pos == NULL)
            return;

        CView *pView = pDoc->GetNextView(pos);
        ASSERT(pView != NULL);
        if (pView == NULL)
            return;

        CFrameWnd *pFrame = pView->GetParentFrame();
        ASSERT(pFrame != NULL);
        if (pFrame == NULL)
            return;

        pFrame->ActivateFrame();
    }
}

Once you have this function, you can call it at any point to show your start page.  As long as this is the only place that you create your view, then you'll enforce having only one Start Page visible at any time.

How you show your Start Page is up to you.  I had it shown on startup at the end of the application's InitInstance() function.

// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

if (cmdInfo.m_nShellCommand == CCommandLineInfo::FileNew)
{
    OnStartPage();
}
else
{
    // Dispatch commands specified on the command line.
    // Will return FALSE if app was launched with /RegServer,
    // /Register, /Unregserver or /Unregister.
    if (!ProcessShellCommand(cmdInfo))
        return FALSE;
}

I also had a menu item that showed the page.

ON_COMMAND(ID_START_PAGE, &CStartPageApp::OnStartPage)

In your document, you can force it's title to be "Start Page".

BOOL CStartPageDoc::OnNewDocument()
{
    SetPathName(_T("Start Page"), FALSE);

    if (!CDocument::OnNewDocument())
        return FALSE;
    return TRUE;
}

What you show on your Start Page is also up to you.  In a future post, I'll go into some details on how I attempted to mimic Microsoft's Start Page's look and feel.

Allocating memory in a DLL and freeing in an EXE

Posted by Matt | Filed under , , ,

I want to clarify something that I alluded to in a previous post.

There are some issues when you try to allocate memory (either using new or malloc, etc.) in one binary (DLL, EXE, etc.) and freeing that memory (using delete, free, etc.) in another binary (DLL, EXE, etc.). 

The main problem comes from the C runtime library.  This is where the selection of the runtime linking is critical.

If you link to the C runtime as a static library (eg. Multithread or Multithread Debug), then you have 2 different C runtimes in the 2 different binaries.

A solution to this problem is to link to the C runtime as a dynamic library (eg. Multithread DLL or Multithread DLL Debug).  This allows the different binaries to share the C runtime that's in the DLL.

Of course, this requires your application to ship and install the MSVCR90.DLL (if you're using Visual Studio 2008).  But it gives you the freedom to allocate and deallocate memory anywhere inside your application logic.

Static vs. Shared DLL

Posted by Matt | Filed under , , ,

The Visual Studio 2008 C++ compiler has 2 important options which need to be set properly, especially when working with multiple projects and DLLs:

General\Use of MFC

image

 and C/C++\Code Generation\Runtime Library

image

When you're working with a single project, the values of these 2 settings really don't matter.  However, to be safe, best to set them to similar values.

When you're working with multiple projects, these values must be set appropriately.  The default is "Use MFC in a Shared DLL" and "Multi-threaded DLL".   What this means is that the implementation of the C-runtime and MFC are linked to MFC90.DLL and MSVCR90.DLL.  These DLLs are normally installed in the Windows side-by-side (SxS) DLLs folder.  Note the keyword "installed".  Doing an xcopy installation without these DLLs being properly installed won't work (note the exception below).

If your application is meant to run without a full installation program (for example, an Autorun program), you should set these values to "Use MFC in a Static Library" and "Multi-threaded".  What this means is that the implementation of the C-runtime and MFC are linked into your EXEs and DLLs.  This will make your binaries larger, but you can run your binaries without doing a full install process.

There are some dangers to doing this.

New/Delete

You cannot allocate memory in one binary (DLL/EXE) and free the memory in another binary.  Running the following application in the debugger will produce failures.

DLL.vcproj/ClassDLL.h:

#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

class DLL_API CData
{
public:
	int m_nData;
};

void DLL_API MyFreeFunction(CData *pPtr);

DLL.vcproj/ClassDLL.cpp:

#include "stdafx.h"
#include "ClassDll.h"

void MyFreeFunction(CData *pPtr)
{
	delete pPtr;
}

EXE.vcproj/main.cpp:

#include "stdafx.h"
#include "../ClassDll/ClassDll.h"

void main()
{
	CData *pPtr = new CData;
	MyFreeFunction(pPtr);
}

If you execute this, you'll get an error in the delete call in MyFreeFunction().  The reason for the problem is that each binary has it's own implementation of new/delete/malloc/free/etc.  Unfortunately, I don't know the nitty-gritty details of why this is an issue, so if there are any people who can give me more details, please let me know.

If you need to product this type of code, then you pretty much need to use shared DLLs.

Templates

Templates are strange creatures.  Generally speaking, they are implemented in the modules where they are instantiated.  So, if you have a template in a DLL, it's real binary code won't be produced until it's used in the EXE.  If 2 binaries use the same template, each will have it's own binary code for the class.

Let's look at the following example:

DLL.vcproj/ClassDLL.h:

#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

class DLL_API CData
{
public:
	int m_nData;
};

void DLL_API MyFunction(CList<CData> &list);

DLL.vcproj/ClassDLL.cpp:

#include "stdafx.h"
#include "ClassDll.h"

void MyFunction(CList<CData> &list)
{
	CData data;
	data.m_nData = 5;
	list.AddTail(data);
}

EXE.vcproj:main.cpp:

#include "stdafx.h"
#include "../ClassDll/ClassDll.h"

void main()
{
	CList<CData> list;
	MyFunction(list);
}

In this example, the list is being allocated and deallocated in the EXE, but an element is being added to the list in the DLL.  If you run this, it will crash in the destructor for the list.  Because the DLL has it's own implementation of the template, the allocation for the new list node occurred in the DLL, but it's trying to be freed in the EXE.

There is a fix for this.

If you add a destructor to CData and implemented it in the DLL.  So files will become:

DLL.vcproj/ClassDLL.h:

#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#define EXPIMP_TEMPLATE
#else
#define DLL_API __declspec(dllimport)
#define EXPIMP_TEMPLATE extern
#endif

class DLL_API CData
{
public:
	int m_nData;

	~CData();

	// needed by CList now
	BOOL operator == (const CData &other) const;
};

void DLL_API MyFunction(CList<CData> &list);

EXPIMP_TEMPLATE template class DLL_API CList<CData>;

DLL.vcproj/ClassDLL.cpp:

#include "stdafx.h"
#include "ClassDll.h"

CData::~CData()
{
}

BOOL CData::operator == (const CData &other) const
{
	return (m_nData == other.m_nData);
}

void MyFunction(CList<CData> &list)
{
	CData data;
	data.m_nData = 5;
	list.AddTail(data);
}

This will execute properly.  The reason is because the EXPIMP_TEMPLATE ... line is defining a local implementation of the template in the DLL and exporting that implementation.  The EXE then uses that implementation instead of compiling it's own.

Conclusion

When all is said and done, it's much easier to use the shared DLL settings.  It does require an installation process, but it will avoid other development headaches.

Exception

I mentioned above that the MFC90.DLL and MSVCR90.DLL files must be installed into the Windows SxS subsystem.  This is not 100% true.  In the Visual Studio program directory is a redistribution folder which if copied to your applications folder, will allow it to run without installing the Microsoft binaries.  For me, they are located at C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\...   Just copy the x86 DLL as-is to your application folder.