HOW TO Configure DCOM Timeouts

We will also look at how to dispense with the disturbing "Server Not Responding - Switch To" and "Server Busy" dialog boxes.

Introduction

Not long after my first attempts at ATL and DCOM, I developed two pet hates:

1) 'Prefab' dialog boxes that interrupt the application and launch the taskbar

START menu when the client cannot connect to the server.

2) Lack of support for timeouts.

A worst case scenario would be: A Windows 95 LAN client belonging to the domain "engineering.mycompany" attempts to instantiate an object on the server with the NETBIOS name "Einstein". The client can try to locate "Einstein" in a number of ways, including concatenating "Einstein" and "engineering.mycompany" to a single domain name and asking the local name server to resolve this name. Let us further assume that the LAN is connected to the internet via a dial-on-demand link and that the local DNS is not a root server. If "Einstein.engineering.mycompany" cannot be resolved, the DNS server will attempt to contact the next DNS in the hierarchy on the internet.

In the meantime, the user of the DCOM client application is either looking at an hour glass or gets a dialog box encouraging him/her to task-switch to the server application - as if it were possible to task-switch across a network.

My friend Bud Millwood and I, here at Weird Solutions Inc., have been working on a client / server application based on DCOM and devised a relatively straight forward solution to both problems described above. Bud suggested that I post our solution onto the DCOM mailing list to encourage further exploration of the issue and perhaps constructive feedback. So here it is folks....

Solutions

1) Addressing the "Server Busy" dialog problem.

Declare an object, say "MyFilter" of class "COleMessageFilter" in your application, ensure that you are including and call the following methods:


MyFilter.EnableNotRespondingDialog(FALSE);
MyFilter.EnableBusyDialog(FALSE);
MyFilter.Register();

This will have registered a new implementation of the IMessageFilter interface for your application. The IMessageFilter interface is used for concurrency management. Your new IMessageFilter differs from the default in that it does not display the notorious dialog boxes.

2) Contemplating a solution to the timeout problem. The suggested method to make timeouts configurable is to implement one's own "IMessageFilter" interface with the "MessagePending" method. >From within this method you can return PENDINGMSG_CANCELCALL when your patience has expired. This will unblock your application.

Canceling an ongoing DCOM call might well create temporary orphan resources on the server, but this seems a small price to pay compared to an non-responsive application that the user will shut down via task manager. In any event, one is well advised to exercise good judgement in establishing timeouts and perhaps even carry out some statistical analysis of typical response times. I have found, for instance, that starting a local COM service on my coding machine varies between one and three seconds. Clearly then, a client timeout of one second would be inappropriate, while anything in the excess of - say - 10 seconds would provide an ample safety margin.

The question that presents itself is whether one should implement an "IMessageFilter" interface from scratch or reuse what already ships with MFC. The MFC class "COleMessageFilter" almost meets the requirements; it even has a virtual function "COleMessageFilter::OnMessagePending" that you can override in a derived class of your own. It does, however, have one serious flaw. You might wish to examine the source code for this class in the file "....\vc\mfc\src\olemsgf.cpp".

Two methods are of interest here:

a) "MessagePending"

This maps to the COM interface IMessageFilter::MessagePending method and is called by DCOM to process pending messages during an otherwise blocking DCOM call. Its simplified structure is as follows:


MessagePending()  {

    //do something useful

	//call virtual function OnMessagePending

	return PENDINGMSG_WAITNOPROCESS
}

b) "OnMessagePending" This is the virtual function called by MessagePending(). You may override this function in a derived class to customize the behaviour of your application.

Now here is the problem: Microsoft has already decided for us what will be returned by MessagePending(). The return value of your implementation of OnMessagePending() is ignored completely in the parent function MessagePending(). Hence, whatever you return from OnMessagePending() is inconsequential.

What now?

We could of course modify the source of "COleMessageFilter" and rebuild MFC. For our purposes, this was inappropriate.

The MS Visual C++ 5.0 help files state:

"You will probably want to implement your own message filter.The default implementation provided by COM offers only minimal message filtering capability."

Rather than building the message filter from scratch, I have opted to take over the code base from "COleMessageFilter" to create my own base class with an identical interface and named it "COleMsgFilter". It exists parallel to and independent from the MFC class "COleMessageFilter". The minor - but crucial - change in the implementation of COleMsgFilter::MessagePending() is to actually return the value from the function call to COleMsgFilter::OnMessagePending(). This will allow returning PENDINGMSG_CANCELCALL from within OnMessagePending() and thus allow controlling the DCOM timeout. 3) Implementing the solution to the timeout problem. Prepare the class header file. ------------------------------

a) Open the file "../vc/mfc/include/afxole.h". "afxole.h" is a collection header file for a number of OLE classes. Look for the definition of the "COleMessageFilter" class. It is prefixed with the following header:


   ///////////////////////////////////////////////////////////////////
   ////////// // COleMessageFilter (implements IMessageFilter)
Mark and copy the COMPLETE class definition and paste it to a new header file, named "OleMsgFlt.h".

b) Search for every instance of "COleMessageFilter" in the file OleMsgFlt.h and replace it with "COleMsgFilter".

c) Change the return type of OnMessagePending() from BOOL to DWORD.

Prepare the class CPP file.

a) Open the file "../vc/mfc/src/olemsgf.cpp", copy its contents to a new file, and save it in your project directory under "OleMsgFlt.cpp".

b) Search for every instance of "COleMessageFilter" in the file OleMsgFlt.cpp and replace it with "COleMsgFilter".

c) Insert the statement shown below at the top of your CPP file.


     #include "OleMsgFlt.h" 

d) Insert the line shown below at the top of your CPP file


#include  

e) From the file "../vc/mfc/src/afximpl.h" copy the line shown below to the top of your CPP file


#define _countof(array) (sizeof(array)/sizeof(array[0]))   

f) From the file ""../vc/mfc/include/afxpriv.h" copy the line shown below to the top of your CPP file


#define WM_KICKIDLE         0x036A  

g) Find the method "COleMsgFilter::XMessageFilter::MessagePending" and within it, the line where OnMessagePending() is called. It should look like this:


                     pThis->OnMessagePending(&msg);
Prefix this line with "return", like so:

    return pThis->OnMessagePending(&msg);

h) Change the return type of the implementation of OnMessagePending() from BOOL to DWORD.

i) You will find that some of the initialization functions for the class' data members are absent from the class implementation. This is because they are in-lined in the file "AfxOle.inl". Their non in-lined counterparts are shown below. You can just copy and paste them directly into your CPP file.


void COleMsgFilter::SetRetryReply(DWORD nRetryReply) {
  m_nRetryReply = nRetryReply;
}
void COleMsgFilter::SetMessagePendingDelay(DWORD nTimeout) {
   m_nTimeout = nTimeout;
}
void COleMsgFilter::SetBusyReply(SERVERCALL nBusyReply) {
  m_nBusyReply = nBusyReply;
}
void COleMsgFilter::EnableBusyDialog(BOOL bEnableBusy) {
  m_bEnableBusy = bEnableBusy;
}
void COleMsgFilter::EnableNotRespondingDialog(BOOL
bEnableNotResponding ) {
  m_bEnableNotResponding = bEnableNotResponding;
}

Compilation and linking

Insert both files into your project, make sure as well as the standard DCOM / OLE headers are included in your project, and compile...

Usage

Derive from COleMsgFilter and implement your own OnMessagePending() function. Here you can check for timeout and return PENDINGMSG_CANCELCALL when your application has timed out.