Posts Tagged ‘MFC’

Unexpected behavior of Win32 when checking serial ports.

I recently noticed very interesting behavior in ClearCommError() function of Win32.
This code returns how many bytes there are in incoming buffer for a serial port.

DWORD CSerialPort::BytesWaiting()
{
  //Validate our parameters
  ASSERT(IsOpen());

  //Check to see how many characters are unread
  COMSTAT stat;
  GetStatus(stat);
  return stat.cbInQue;
}

void CSerialPort::GetStatus(COMSTAT& stat)
{
  //Validate our parameters
  ASSERT(IsOpen());

  DWORD dwErrors;
  if (!ClearCommError(m_hComm, &dwErrors, &stat))
  {
    DWORD dwLastError = GetLastError();
    TRACE(_T("CSerialPort::GetStatus, Failed in call to ClearCommError, Error:%d\n"), dwLastError);
    ThrowSerialException(dwLastError);
  }
}

However, after actually debugging it I found out that there are cases where

  • The number of bytes in its incoming buffer doesn’t increase while it is calling BytesWaiting() to see if it received enough bytes to read. Then, it should read its incoming buffer for the given COM port to have BytesWaiting() starts to return accumulated number of bytes after reading.
  • When BytesWaiting() returns 0, you still need to read the buffer not to fail in detecting any further data arrived from the other side of communication. reading 0 bytes in this case, will cause the BytesWaiting() keeps returning the number of bytes read after that point.

Normally, whether you read or not, callying BytesWaiting() correctly reports the number of bytes in an incoming buffer.
However, if any of those listed above happens, it stops reporting correct number of bytes.

So, this code fails.

int CLensControllerDlg::ReadIncomingPacket(BYTE incomingBuffer[], int numOfBytesToRead, bool *pShouldGiveUp)
{
     DWORD bytesInReceptionQueue;
     int readNumOfBytes, extraReadNumOfBytes;

     readNumOfBytes = extraReadNumOfBytes = 0;

     // First read
     readNumOfBytes = m_pSerialPort->Read( incomingBuffer, numOfBytesToRead );
     int lengthToReadFurther;
     int secondTryReadLength = 0;

     static int trialCount = 0;

     // Check if it needs to read once more
     if( (incomingBuffer[ 0] == (BYTE) 0x50) &&
           (incomingBuffer[ 1] == (BYTE) 0xAF ))
     {
           lengthToReadFurther = static_cast<int >(incomingBuffer[4]) + 1; // payload length + check sum
            // Read length + 1 more ( 1 is for checksum )

            //!!!!! [1] !!!!!
            // There are cases where bytesInReceptionQueue doesn't have accumulated number of
            // bytes in a reception bufffer.
            while( (bytesInReceptionQueue = m_pSerialPort->BytesWaiting()) < lengthToReadFurther )
           {
                Sleep( 3 ); // 10 msec
                trialCount++;

                //!!!!! [2] !!!!!
                // So, if the bytesInReceptionQueue doesn't contain the accumulated number of bytes
                // through the iteration, try to read some.
                // Then it will start to work again.
                 if( trialCount > 10 )
                {
                      if( bytesInReceptionQueue > 0 )
                     {
                            // Give another chance
                           secondTryReadLength = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], bytesInReceptionQueue );
                           readNumOfBytes += secondTryReadLength;

                           lengthToReadFurther -= secondTryReadLength;

                           trialCount = 0;

                     }
                      else
                     {
                            // let's see what it happens if try reading for 0 bytes and read further
                           BYTE temp[ 16];
                            int tempReadLength;

                            // bytesInReceptionQueue == 0
                           tempReadLength = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], bytesInReceptionQueue );
                           readNumOfBytes += tempReadLength;
                           lengthToReadFurther -= tempReadLength;

                            //// Once read, somehow, bytesInReceptionQueue starts to be accumulated again.
                            //bytesInReceptionQueue = m_pSerialPort->BytesWaiting();

                            //tempReadLength = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], bytesInReceptionQueue );
                            //readNumOfBytes += tempReadLength;
                            //lengthToReadFurther -= tempReadLength;

                           CString msgString;
                           msgString.Format( _T( ">> Will give up this incoming data : %x %x %x %x %x\r\n"),
                                                  incomingBuffer[0], incomingBuffer[1 ],
                                                  incomingBuffer[2], incomingBuffer[3 ],
                                                  incomingBuffer[4] );

                           WriteLogMessage( msgString );
                           TRACE( msgString );

                            if( pShouldGiveUp != NULL )
                           {
                                *pShouldGiveUp = true;
                           }

                           trialCount = 0;
                            return 0;
                     }
                }
           }

            // In the queue, now there are lengthToReadFurther or more to read.
           extraReadNumOfBytes = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], lengthToReadFurther );
     }

     return (readNumOfBytes + extraReadNumOfBytes);
}

The code above was written while I was debugging. So, it’s not clean nor compact.
So, here is better version if you want to see.

int CLensControllerDlg::ReadIncomingPacket(BYTE incomingBuffer[], int numOfBytesToRead, bool *pShouldGiveUp)
{
     DWORD bytesInReceptionQueue;
     int readNumOfBytes, extraReadNumOfBytes;

     readNumOfBytes = extraReadNumOfBytes = 0;

     // First read
     readNumOfBytes = m_pSerialPort->Read( incomingBuffer, numOfBytesToRead );
     int lengthToReadFurther;
     int secondTryReadNumOfBytes = 0;

     // Check if it needs to read once more
     if( (incomingBuffer[0] == (BYTE)0x50) &&
          (incomingBuffer[1] == (BYTE)0xAF ))
     {
          lengthToReadFurther = static_cast<int>(incomingBuffer[4]) + 1; // payload length + check sum
          // Read length + 1 more ( 1 is for checksum )
          while( lengthToReadFurther > 0 )
          {
               Sleep( 1 ); // 10 msec

               bytesInReceptionQueue = m_pSerialPort->BytesWaiting();

               // If it's normal, just waiting until bytesInReceptionQueue >= lengthToReadFurther,
               // and read the whole "lengthToReadFurther" amount of data all at once.
               //
               // However, sometimes the inbound buffer should be read to have further data accumulated.
               // If not,
               secondTryReadNumOfBytes = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], lengthToReadFurther );
               readNumOfBytes += secondTryReadNumOfBytes;

               lengthToReadFurther -= secondTryReadNumOfBytes;
          }
     }

     return readNumOfBytes;
}

CCheckListbox and item height

Well, MFC shows its age.

Although many fairly new programmers, who started to write code after the year 2000, think MFC is old, but in 90’s it was not.
It’s not as elegant as Cocoa or even new .NET ( with C#. .NET for C++ looks like too much overloaded truck with redundant stuffs on it. ).
However, it solves problems for its generation, and there was good reason for MS people to take such design.

However, they had to renovate it. There are many issues like control vs. view for similar components, inconsistent way to deal with widgets, etc.
Today I found a problem with item height for CCheckListBox. With VS resource editor, you can change “font size” of a dialog box or a window. What it actually does is not to change font size for widgets on them.

font size( I think it’s also a problem. What it actually does is to change “scale” of the coordinate system on that dialog box or window. Because the scale is changed, everything like button size is also changed as well as font size. So, it’s not right to name the property “Font(Size)”. )

Problem is that when an instance of CCheckListbox is instantiated and items are added to that, it checks whether each item height is bigger than its minimum size like this. ( Please focus on the last line. )

void CCheckListBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
  // You must override DrawItem and MeasureItem for LBS_OWNERDRAWVARIABLE
  ASSERT((GetStyle() & (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS)) ==
    (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS));

  CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
  ENSURE(pDC);

  if (((LONG)(lpDrawItemStruct->itemID) >= 0) &&
    (lpDrawItemStruct->itemAction & (ODA_DRAWENTIRE | ODA_SELECT)))
  {
    int cyItem = GetItemHeight(lpDrawItemStruct->itemID);
    BOOL fDisabled = !IsWindowEnabled() || !IsEnabled(lpDrawItemStruct->itemID);

    COLORREF newTextColor = fDisabled ?
      RGB(0x80, 0x80, 0x80) : GetSysColor(COLOR_WINDOWTEXT);  // light gray
    COLORREF oldTextColor = pDC->SetTextColor(newTextColor);

    COLORREF newBkColor = GetSysColor(COLOR_WINDOW);
    COLORREF oldBkColor = pDC->SetBkColor(newBkColor);

    if (newTextColor == newBkColor)
      newTextColor = RGB(0xC0, 0xC0, 0xC0);   // dark gray

    if (!fDisabled && ((lpDrawItemStruct->itemState & ODS_SELECTED) != 0))
    {
      pDC->SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
      pDC->SetBkColor(GetSysColor(COLOR_HIGHLIGHT));
    }

    if (m_cyText == 0)
      VERIFY(cyItem >= CalcMinimumItemHeight());

It turned out that cyItem, which is height of an interested item at that moment, is less than the minimum height if the font size is set to 12.
So, MFC is not updated to work with its visual resource editor or vice versa.
At least there should be some property to choose item height based on chosen “Font size” on the visual editor. If the code for MFC presents relevant error message when debugging, it can be even better.

Anyway, if you set the item height using SetItemHeight() sufficiently, its debugger will not complain again. This is not documented in the section for CCheckListBox at all.

We complain that Apple let Mac OS X and its Cocoa framework behind due to its focus on iOS. ( kernel is the same on the both and Cocoa is almost same to CocoaTouch, but still.. ) However, Apple people keep updating their tool set to work properly with their framework. MS doesn’t seem to be considerate on this long-lived-but-dependable MFC framework. Yes, model-wise, .NET is better. But I often notice that .NET with C++ is too bloated and .NET with C# is too weak. For native environment, MS doesn’t have new framework to replace MFC for C++.
Nowadays, it doesn’t seem to me that there are many S/W engineers working with MFC/C++. So, probably they don’t ask much to MS. Well, in the world of Web app, Java and C#, where people usually justify “computers are getting faster. So, bytecode/VM language like C#/Java are OK.”. I think it’s understandable argument. But still it’s not sufficient to me. It’s like to use 486 computers with the speed of 386 computers. For convenience of using and learning a language, MS gave up many things in C#/.NET, which can be done more easily with C++/MFC/Win32.

CreateEx() and Create() for CDockablePane

Historically CreateEx() has been thought as Create() with additional functionality like more styles.
So, if you take a look at many MFC classes which provide CreateEx() and Create(), you can see that CreateEx() calls Create() and do more chores to provide added functionality.

However, I found out that CDockablePane’s doesn’t.
Although MSDN document shows that there is CreateEx() for CDockablePane, it actually doesn’t exist.
So, you shouldn’t be fooled by the MSDN document and create CDockablePane with CreateEx() and wonder why it doesn’t work.

How to put CFormView on CDockableView

At StackOverflow, there is an inquiry,
How can I place a MFC CFormView inside a CDockablePane?

It is very popular question on the net. Try searching “CFormView on CDockablePane”, “How to put CFormView in CDockableView”, “CView on CDockableView” and so on. ( If CView can be on CDockablePane, CFormView can be too, because CFormView is a child class of CView. )
However I didn’t find anyone who was successful in doing so.

CDockablePane is part of MFC Feature Pack. Although MS distributes it as a part of Visual Studio Service Pack 1, but it was not made by MS. It was made by BCGsoft. I think the author did quite amazing job. However, as he said in the forum, it doesn’t support hosting of CView and its derivatives. One of the reason is said to be routing of window message. If you check all the CDockablePane related classes and all the panes on Visual Studio carefully, you will notice that all the widget on CDockablePane are controls not views.

(This is significant difference between Cocoa and MFC. With Cocoa NSView derivatives can be on any NSWindow/NSView. Actually NSView is derivative of NSWindow on Mac and vice versa on iPhone.

However on Windows, CView and its derivatives are not designed to be on CDialog and so on. So, that is why there are separate CView derivatives like CEditView, CListView, CRichEditView, CFormView, CHtmlEditView, CHtmlView, CTreeView and controls like CEdit, CListCtrl, CTreeCtrl. On dialog boxes, CView derivatives are not supposed to be put.

On Mac, on the other hand, any NSView derivatives can be put on any window and any NSView derivatives. Although NSOutlineView ( or whatever ) which contains a tree controller inside can be seen like the relationship between CListView and CListCtrl, but they are totally different. You use CListCtrl to put it on a dialog box, while you can make a new document window or some window as CListView with its own frame window. Also C*View utilizes the document template architecture, while *Ctrl don’t. However, on a Mac, to use tree control, COutlineView is used instead of its tree control. You can make your own view using the tree control, but if you want to use them as they are, you don’t need to handle the controls directly.

This makes GUI programming very easy on Mac. Very flexible, very powerful and very clean source codes are what you get.

In Cocoa Head LA, I was asked by others why I thought Mac was better than Windows. I would like to explain all this kind of things, but I couldn’t. First, I’m not good at casual talking in English. Second, they are subtle, so I couldn’t think of good example on MFC and Cocoa to compare. Once I run into this kind of issue, I can say “Ah.. this is it!”.

There is another good thing on Cocoa. With Interface Builder, you can see the containment of views visually. You will know how stressful to check someone else’s code which deals with GUI element connected as parent-child relationship a lot and to figure out what causes malfunctioning of the code. )

Anyway, lets go back to original topic.
However, it doesn’t mean that you CANNOT put CView derivatives on CDockablePane.

Here is how to.

For CMainFrame

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CMDIFrameWndEx::OnCreate(lpCreateStruct) == -1)
		return -1;

	// enable Visual Studio 2005 style docking window behavior
	CDockingManager::SetDockingMode(DT_SMART);
	// enable Visual Studio 2005 style docking window auto-hide behavior
	EnableAutoHidePanes(CBRS_ALIGN_ANY);

	// create docking windows
	if (!CreateDockingWindows())
	{
		TRACE0("Failed to create docking windows\n");
		return -1;
	}

	// JongAm
	m_myFormViewPane.EnableDocking( CBRS_ALIGN_ANY );
	DockPane( &m_myFormViewPane );
	////////

	return 0;
}

BOOL CMainFrame::CreateDockingWindows()
{
	BOOL bNameValid;

	// JongAm
	// Create MyFormViewPane

	CString strMyFormViewPane;
	bNameValid = strMyFormViewPane.LoadStringW( IDS_MYFORMVIEW_PANE );
	ASSERT( strMyFormViewPane );
	if( !m_myFormViewPane.Create( strMyFormViewPane, this, CRect( 0, 0, 205, 157 ), TRUE,
								  ID_FILE_NEWFORMVIEW,
								  WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN |
								  CBRS_BOTTOM | CBRS_FLOAT_MULTI ) )
	{
		TRACE0( "Failed to create My Form View Pane" );
		return -1;
	}

	////////////

	SetDockingWindowIcons(theApp.m_bHiColorIcons);
	return TRUE;
}

Now for the Dockable pane

CDockableFormViewPane::CDockableFormViewPane()
: m_pMyFormView(NULL)
{
}

int CDockableFormViewPane::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CDockablePane::OnCreate(lpCreateStruct) == -1)
		return -1;

	// TODO:  Add your specialized creation code here

	m_pMyFormView = CMyFormView::CreateOne(this);

	return 0;
}

void CDockableFormViewPane::OnSize(UINT nType, int cx, int cy)
{
	CDockablePane::OnSize(nType, cx, cy);

	// TODO: Add your message handler code here

	if( m_pMyFormView )
		m_pMyFormView-&gt;SetWindowPos( NULL, 0, 0, cx, cy, SWP_NOZORDER );
}

And finally for CFormView

void CMyFormView::OnInitialUpdate()
{
	CFormView::OnInitialUpdate();

	// TODO: Add your specialized code here and/or call the base class
	GetParentFrame()->RecalcLayout();
	SetScrollSizes( MM_TEXT, CSize( 205, 157 ) );

	ResizeParentToFit(FALSE);
}

CMyFormView *CMyFormView::CreateOne( CWnd *pParent )
{
	CMyFormView *pFormView = new CMyFormView;
	//CMyFormView *pFormView = NULL;
	//CRuntimeClass *pRuntimeClass = RUNTIME_CLASS(CMyFormView);
	//pFormView = (CMyFormView *)pRuntimeClass->CreateObject();

	CDockableFormViewAppDoc *pDoc = CDockableFormViewAppDoc::CreateOne();
	pFormView->m_pDocument = pDoc;

	CCreateContext *pContext = NULL;

#if 0
	if( !pFormView->CreateEx(0, NULL, NULL, WS_CHILD | WS_VISIBLE, CRect(0,0,205,157),
		pParent, -1, pContext ) )
#else
	if (!pFormView->Create(NULL, NULL, WS_CHILD | WS_VISIBLE, CRect(0, 0, 205, 157), pParent, 0, pContext))
#endif
	//if( !pFormView->CreateEx( 0, AfxRegisterWndClass(0, 0, 0, 0), NULL,
	//	WS_CHILD | WS_VISIBLE, CRect( 0, 0, 205, 157), pParent, -1, pContext) )
	{
		AfxMessageBox( _T("Failed in creating CMyFormView") );
	}

	pFormView->OnInitialUpdate();

	return pFormView;
}

int CMyFormView::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message)
{
	// TODO: Add your message handler code here and/or call default

	int nResult = 0;

	CFrameWnd* pParentFrame = GetParentFrame();

	if( pParentFrame == pDesktopWnd )
	{
		// When this is docked
		nResult = CFormView::OnMouseActivate(pDesktopWnd, nHitTest, message);
	}
	else
	{
		// When this is not docked

		BOOL isMiniFrameWnd = pDesktopWnd->IsKindOf( RUNTIME_CLASS( CMiniFrameWnd ) );
		BOOL isPaneFrameWnd = pDesktopWnd->IsKindOf( RUNTIME_CLASS( CPaneFrameWnd ) );
		BOOL isMultiPaneFrameWnd = pDesktopWnd->IsKindOf( RUNTIME_CLASS( CMultiPaneFrameWnd ) );

		// pDesktopWnd is the frame window for CDockablePane

		nResult = CWnd::OnMouseActivate( pDesktopWnd, nHitTest, message );

		//nResult = CWnd::OnMouseActivate( pDesktopWnd, nHitTest, message );
		//if( nResult == MA_NOACTIVATE || nResult == MA_NOACTIVATEANDEAT )
		//	return nResult;

		//if (pDesktopWnd != NULL)
		//{
		//	// either re-activate the current view, or set this view to be active
		//	//CView* pView = pDesktopWnd->GetActiveView();
		//	//HWND hWndFocus = ::GetFocus();
		//	//if (pView == this &&
		//	//	m_hWnd != hWndFocus && !::IsChild(m_hWnd, hWndFocus))
		//	//{
		//	//	// re-activate this view
		//	//	OnActivateView(TRUE, this, this);
		//	//}
		//	//else
		//	//{
		//	//	// activate this view
		//	//	pDesktopWnd->SetActiveView(this);
		//	//}
		//}
	}

	return nResult;
}

However, as the author of MFC Feature Pack mentioned, there are many issues about windows message routing. For example, OnMouseActivate() should be customized to prevent crash when you click a mouse button when the dockable pane on which CFormView resides is floating. Also, if the View on the dockable pane has more complicated structure like CSplitView, which contains multiple GUI widget which are needed to be updated to refresh their content, it becomes very complicated.

Currently I try to solve such problems.
However, here I at least showed how to put CFormView/View on CDockablePane. I hope this can save many people’s time to investigate how to solve this issue.

ADDED : Because one guy asked me how to implement the CreateOne() member function, I added it above and its header file here.

#pragma once

// CMyFormView form view

class CDockableFormViewAppDoc;

class CMyFormView : public CFormView
{
	DECLARE_DYNCREATE(CMyFormView)

public:
	CMyFormView();           // protected constructor used by dynamic creation
	virtual ~CMyFormView();

	static CMyFormView *CreateOne( CWnd *pParent );

	// return associated document
	CDockableFormViewAppDoc * GetDocument(void);

public:
	enum { IDD = IDD_CONTENT_PANE };
#ifdef _DEBUG
	virtual void AssertValid() const;
#ifndef _WIN32_WCE
	virtual void Dump(CDumpContext& dc) const;
#endif
#endif

protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

	DECLARE_MESSAGE_MAP()
	// points to linked document
	CDockableFormViewAppDoc *m_pDocument;

public:
	virtual void OnInitialUpdate();
	afx_msg int OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message);
	afx_msg void OnSizing(UINT fwSide, LPRECT pRect);
};

Sources : DockableFormViewApp-Modified.zip

Storing a pointer value in a non-pointer variable vs. Storing general value in a pointer variable : INT_PTR, LONG_PTR etc vs. MAKEINTRESOURCE

If you are a programmer who studied C/C++ after Windows 95, you may not know the existence of windows data types like INT_PTR, LONG_PTR, because you don’t really need to use it.

So, when you have a chance to port legacy codes to x64 platform, you may  fail in considering proper porting of variables declared with those or variables which don’t use them but semantically means the same.

At StackOverFlow, there is good explanation on this subject.

DWORD_PTR, INT_PTR, LONG_PTR, UINT_PTR, ULONG_PTR When, How and Why?

However, I would like to add more comment.

During MS DOS era, many programmers wrote codes in a way that the longest data type available on his/her machine to store addresses or pointer values. There were a little reason for that. For example, you want to do somewhat mathematical calculation with addresses without bordering with the width of the data type.
To me, it has been always better to calculate on addresses with the width of data type in mind, but there are some other cases you should have disregard the width of data type and just calculate something. It was kind of high technique for doing something weird. ( Some can be, while others are not. )

Some Windows API functions and member functions still require to use such convention. They ask you to pass address value to a variable which is not pointer variables. For that purpose, they use *_PTR.

So, I noticed that not a few programmers, who don’t know history, tended to fail in porting legacy code to x64.

Also, there is opposite example.
This time, it is to store non-pointer value to a pointer variable. Such an example is MAKEINTRESOURCE() macro.

#define MAKEINTRESOURCE(i)(LPTSTR)((DWORD)((WORD)(i)))

So, it takes an integer number for a resource like resource ID for a bitmap or an icon, and converts it to pointer to string (LPTSTR).

So, if you are one of new guys who don’t know its history, just get familiar with this and don’t be puzzled!

LoadLibrary() can’t load a DLL, and have no clue why?

How do you link or load a DLL file?
My approach is to link a stub *.lib file to a host project. This approach supports best of dynamic load and static load. In other words, each DLL files can be maintained independently, while it is very easy to figure out what functions are not found properly and what functions are in conflict, etc.

However, if you really want to write a host program which can support plug-ins such that each plug-ins can be dropped in a given folder and the host program recognize the plug-in on the fly and starts to provide functionality defined in the plug-ins, you will want more dynamic approach.

Yes. MFC/Win32 provides such approach. With LoadLibrary(), anywhere in your source codes, DLL files can be loaded. Then you can define some common interface/parent class for pluggable architecture.
Neat, isn’t it?
However, there is one problem with LoadLibrary(). It can be difficult to troubleshoot. For example, if your DLL file can’t be loaded, you will check if the search path is correct, if the DLL file is really there, and so on. However, what if all those which affect visibility of the DLL are satisfied but the LoadLibrary() still fails?

Let’s take a look at such codes here.

// JongAm
// Let's try loading "Mediabase.dll". Why the hell WBMediabase started failing in finding the dll?

// Enable the line below to see what is wrong with LoadLibrary()
//SetErrorMode( 0 );

HMODULE dynamicLibrary_Handle = LoadLibrary( _T("MediaBase.dll") );

if( dynamicLibrary_Handle )
{
	AfxMessageBox( _T("Successful in finding Mediabase.dll") );
	TRACE( "Successful\n" );
}
else
{
	int lastError = GetLastError();

	CString message;
	message.Format( _T("Failed in finding Mediabase.dll (%d)"), lastError );
	AfxMessageBox( message );

	TRACE( "Can't find the mediabase.dll\n" );
}

Nothing special, right? However, it failed on me. Why? Later I found out that MediaBase.dll also required other DLL files, and those DLL files also require other DLLs. They are all loaded using LoadLibrary() functions. So, they were not detected with Dependency Walker. Hmmmm….
So, whenever you write LoadLibrary(), it is better to provide some feedback message when it fails loading DLL files like “Failed in loading Blah Blah.dll”.

However, what if you already have a few 3rd parth DLL files which you can’t control?
In such case, there is a really handy diagnosis function. It is the SetErrorMode(). If 0 is passed as its parameter, it will display a message box which tells what is wrong, like “X.dll is missing”.
So, for example, when you load a A.dll using LoadLibrary() but the A.dll also load A-1.dll and the A-1.dll loads A-2.dll and the A-2.dll is missing, the SetErrorMode(0) will make a message box which says “A-2.dll is missing” displayed.

Here are a few screenshots.

When a DLL file required by the designated Mediabase.dll is missing

When the designated DLL file, Mediabase.dll, itself can't be found.

When all DLL file dependency are resolved

Wow.. quite helpful, isn’t it?

Why MFC is bad

Well, MFC has lots of weird aspects. Basically MFC is a framework. What is a framework? Framework is a wrapper of API to make the API easier to use.
However, when MFC is compared to ToolBox which was most popular during similar period of time, MFC is not as easy to use as ToolBox. ToolBox is, however, API not framework.

Here I would like to show one example why MFC is bad and why its design is bad. Let’s think about CListCtrl. It is a list control or table control if you think of it in the context of Mac OS X. What do you want to do with it?
Let’s summarize some of them.

  • Populate ClistCtrl
  • Retrieve row item or column data
  • Change properties like text color, background color, font and so on for row items.
  • Customizing the UI
  • and so on

First, to manipulate the data or to populate the CListCtrl, this class itself doesn’t provide consistency. Why am I talking about consistency? Let’s take a look at CStatic or CEdit. On Windows, you can set a control variable for them or data variable for them. A control variable is like :

CEdit *editBox

, while data variable is like :

CString editBoxString

To manipulate the value itself, data variable is easier. However to control the widget itself, you should use the control value.
With Cocoa, you don’t need to have this separate mechanism. In Windows point of view, it is like to declare CEdit variable and the CEdit class provides all the methods to manipulate the data value. Or with Cocoa binding, things can be done much more easily.

It does not mean that it is impossible to manipulate data with the control variable. With some widget, or control, it is not easy or is not possible at all.

Here, with CListCtrl, there is no “data” variable unlike that of CEdit or CStatic. So, you should manipulate the data with the CListCtrl itself. So, there is no consistency.

Second, it is very difficult to retrieve row item or data.
GetItem() is for that, but if you look up MSDN document for it, it is not clear how to retrieve an item you want.
Here is explanation on what it does.

Retrieves some or all of a list view item’s attributes.

The LVITEM structure specifies or receives the attributes of a list view item.

However, it doesn’t say about how to do so.

There is something which is even worse.
Let’s take a look at the LVITEM parameter.
What is lParam? What does it supposed to contain? There is no explanation about it in MSDN document.

Let’s assume that you want to customize the CListCtrl. You want to set a bold font for a specific row item or column. Does CListCtrl provide such method? No. Also, there is no each way to retrieve ith item on a CListCtrl. Among them, one easy thing is to set a text color or a background color of a item. Well.. how to to so? There is no explanation about it.
Well.. you can implement a message handler for WM_CUSTOMDRAW message. However the MFC document for CListCtrl doesn’t even contain link for the message. You should look up Win32 document. Isn’t it strange? Framework is a wrapper for API to make API easy to use. However, For MFC, if you want to do some common thing like this, the API, or Win32, should be revealed.

Third. Let’s think about a situation in which you want to set a bold font for some items. There seems to be no way to do so.
Creating a CListCtrl Class with Item Style Features (CListCtrlStyled Class) from CodeGuru introduces a customized CListCtrl clas to do that.

Let’s take a look at some source codes from the class.

void CListCtrlStyled::SetItemStyle(int nItem,int nSubItem,DWORD Style,bool redraw)
{
	// We must retrieve the Style info structure of this item
	//
	LVITEM pItem;
	InitLVITEM(nItem,0,&pItem);

	LS_item * lpLS_item = NULL;
	lpLS_item = (LS_item*) pItem.lParam;

	if(nSubItem > 0)
		lpLS_item = lpLS_item->subitems[ nSubItem - 1];

	// no we can update the style
	//
	lpLS_item->StyleFlag = Style;

	DWORD mask = LIS_BOLD | LIS_ITALIC | LIS_UNDERLINE| LIS_STROKE ;
	lpLS_item->in_use = (Style & mask) > 0;

	// if any font exist for this item then delete it
	//
	this->Free_LS_font(lpLS_item);

	// Redraw it
	if(redraw)	CListCtrl::Update(nItem);
}

void CListCtrlStyled::InitLVITEM(int nItem,int nSubItem,LVITEM * pItem)
{ /*
  typedef struct _LV_ITEM {
    UINT   mask;         // see below
    int    iItem;        // see below
    int    iSubItem;     // see below
    UINT   state;        // see below
    UINT   stateMask;    // see below
    LPSTR  pszText;      // see below
    int    cchTextMax;   // see below
    int    iImage;       // see below
    LPARAM lParam;       // 32-bit value to associate with item
   } LV_ITEM;
  */
	pItem->mask = LVIF_PARAM;
	pItem->iItem = nItem;
	pItem->iSubItem = nSubItem;
	pItem->state = NULL;
	pItem->stateMask = NULL;
	pItem->pszText = NULL;
	pItem->cchTextMax = NULL;
	pItem->iImage = NULL;
	pItem->lParam = NULL;
	CListCtrl::GetItem(pItem);
}

In InitLVITEM() method, it calls GetItem(). What it does is to retrieve lParam value in LVITEM. Then in SetItemStyle(), it calls :

	LS_item * lpLS_item = NULL;
	lpLS_item = (LS_item*) pItem.lParam;

	if(nSubItem > 0)
		lpLS_item = lpLS_item->subitems[ nSubItem - 1];

Hmm.. lpLS_item is once set to lParam. And it is set to lParam’s subitems[].
Then based on the value, it tries modify the variable to set a font face.
To do this, you should know that what the lParam means and the data structure lParam contains. Also, you need to know howdata structure its subitems[] looks like.

But where is explanation on them is? I can’t find anything on MSDN.

There are a lot of things in MFC like this.
Classes are not prepared to handle things easily, and its MFC documentation doesn’t have any link or explanation.

I don’t understand why MS doesn’t provide good documentation about its classes. The bad design of MFC can be understood if we consider when it was designed. However, if MFC designer at MS still want to keep the MFC for native environment, they should do something about it.

%d bloggers like this: