If you are familiar with Internet Explorer 4.0 you would
find that in the Advanced Internet Settings there is an option "Browse in a
New Process". If you uncheck this check box and run IExplorer.exe and then
start task manager (or Process Viewer) you would find that there is no IExplorer
in the processes yet the Internet Explorer appears. Checking "Browse in a
new process" and starting Internet Explorer runs it as IExplorer.exe again.
In the first case instead of a separate process being
created for Internet Explorer a thread is created in explorer.exe and instances
of Internet Explorer run in different threads within the process space of
explorer.exe. Recently, I also found the need to do the same in my application.
This article demonstrates how this could be achieved.
This is particularly useful when lots of data need to be
shared among instances of applications. Typically the first instance of the
application loads data from the disk file or initializes through other methods
and this data needs to be more or less constant throughout the application.
Memory mapped file is obviously an alternative but it requires quite a bit of
pointer arithmetic and to a lot of extent convenience is lost.This alternative
is convenient but has certain risks associated like if a single instance of the
application crashes then all the instances are gone and also lot of defensive
coding is required for ensuring thread safety.
Implementation
Coming to the implementation, it’s quite simple. Any
instance of the application launched either through Explorer or through an API
call such as CreateProcess, first detects if a window of a particular class
exists. If it does not, a new window is created. If the window does exists, the newly
created process simple sends a message to the window and transfers the control
to the already running instance of the application. The already running instance
of the application launches a separate thread but the end user feels that
different instances of application are running.
In my demonstration I take an example of a simple MFC MDI
application. Even if multiple instances of the application are launched, Task
Manager or Process Viewer will show only one process. When observed with SPY++, it
would be seen that the process has many threads and different set of MDI windows
belong to the different threads as shown in the following screen shot.
Let me now explain stepwise how this has been
achieved.
1. In InitInstance function of the main CWinApp derived class, following
code has been added.
CWnd* pWnd = FindOrCreateAppWnd();if (!pWnd) //Creation failed
return FALSE; //Terminate safely// m_pMainWnd is NULL if either we are not the first instance
// or if window creation failed somehow
if (m_pMainWnd == NULL)
{
SendCopyDataMsg(pWnd);
return FALSE;
}// Register the application’s document templates. Document templates
// serve as the connection between documents, frame windows and views.
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_SAMEPRTYPE,
RUNTIME_CLASS(CSameprocDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CSameprocView));
AddDocTemplate(pDocTemplate);//Finally send a message to create a thread
SendCopyDataMsg(pWnd);
The FindOrCreateAppWnd
function first finds whether a window of a class "SomeProcClass"
exists if it does it returns the pointer to that window else a new window of
that class is created and the pointer is returned (m_pMainWnd is also set to the
pointer to newly created window).
If a window already exists then the command line
information is conveyed to the window through WM_COPYDATA message so that the
already running process to which the execution would now switch make use of
them. In the sample application I have not used them but it’s not a big deal.
Here is the listing of function SendCopyDataMsg.
void CSameprocApp::SendCopyDataMsg(CWnd* pWnd)
{
//Send the command line info to the other application
COPYDATASTRUCT cds;cds.dwData = m_nCmdShow ;
cds.cbData = _tcslen(m_lpCmdLine ) + 1;
cds.lpData = reinterpret_cast<LPVOID>(m_lpCmdLine);pWnd->SendMessage(WM_COPYDATA, 0, (LPARAM)(&cds));
}
2. If
FindWindow fails to locate the window
the special window of "SameProcClass" encapsulated in C++ class CAppWnd is created.
bool CAppWnd::CreateAppWnd()
{
//Register SameProcClass
WNDCLASS wndclass;::ZeroMemory(&wndclass, sizeof(WNDCLASS));
wndclass.lpfnWndProc = AfxWndProc;
wndclass.lpszClassName = szClassName;// "SameProcClass"
wndclass.hInstance = AfxGetInstanceHandle();if (!AfxRegisterClass(&wndclass))
{
AfxMessageBox(SP_IDP_REGISTER_FAILED);
return false;
}// Now create the window
if (!CFrameWnd::Create(szClassName, _T("SameProcMainWnd"), 0))
{
AfxMessageBox(SP_IDP_CREATE_FAILED);
return false;
}return true;
}
Everything is self explanatory. The important point to be
observed is that the lpfnWndProc member of WNDCLASS should be AfxWndProc as MFC message mapping architecture is being used.
3. CAppWnd
handles WM_COPYDATA as following :-
BOOL CAppWnd::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
// See that we don’t close
m_bCanClose = false;// Command line info from another instance is communicated here
// If you are interested in having the command line processing you have to add
// a bit of code to pass it to CAppThread class
DWORD nCmdShow = pCopyDataStruct->dwData ;
LPTSTR szCmdLine = reinterpret_cast<LPTSTR>(pCopyDataStruct->lpData);AfxBeginThread(RUNTIME_CLASS(CAppThread));
return CFrameWnd::OnCopyData(pWnd, pCopyDataStruct);
}
It gets back the nCmdShow
and the command line passed from other application and it creates a thread CAppThread.
4. Finally the class CAppThread which represents each individual thread creates the MDI
windows (which is traditionally done in the application class).
BOOL CAppThread::InitInstance()
{
InterlockedIncrement(&s_nInstances);// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;CCommandLineInfo cmdInfo;
// Dispatch commands specified on the command line
if (!AfxGetApp()->ProcessShellCommand(cmdInfo))
return FALSE;// The main window has been initialized, so show and update it.
pMainFrame->ShowWindow( SW_SHOWNORMAL/* m_nCmdShow */);
pMainFrame->UpdateWindow();return TRUE;
}
The point to mention here is that a static counter
variable s_nInstances is being maintained in the CAppThread class which maintains a count of number of
"virtual instances" (i.e. the number of CAppThreads running in the process). This is needed so that the main
application knows when to shutdown.
5. When each thread exits it decrements the
count
s_nInstances and when the count reaches
zero the application main window (CAppWnd)
should be notified that it is time to close.
int CAppThread::ExitInstance()
{
InterlockedDecrement(&s_nInstances);if (s_nInstances == 0) // No more Instances left
{
AfxGetApp()->m_pMainWnd->PostMessage(WM_CLOSE);
}return CWinThread::ExitInstance();
}
6. Coming to the OnClose member function of
CAppWnd
some strange jugglery is being done.
void CAppWnd::OnClose()
{
static bool bTimerSet = false;
if (!m_bCanClose)
{
if (bTimerSet)
KillTimer(1);m_bCanClose = true;
//Set a timer after which we can close
SetTimer(1, 1000, NULL);bTimerSet = true;
}
else
{
KillTimer(1);
CFrameWnd::OnClose();
}
}void CAppWnd::OnTimer(UINT nIDEvent)
{if (nIDEvent == 1)
PostMessage(WM_CLOSE);
}
First time when the function reaches OnClose m_bCanClose is false. This makes the
application set a timer for 1 second. Next in the WM_TIMER handler
WM_CLOSE message is being
posted again. This makes the application close. In the mean time if WM_COPYDATA message arrives m_bCanClose is set to false in the WM_COPYDATA handler so that
application doesnot close down.
7. One final point remaining to be mentioned
is that the command routing must be done to the CAppThreads so that they also get their share of WM_COMMAND messages.
BOOL CSameprocApp::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{// The current thread should have the first preference
CWinThread* pThread = AfxGetThread();
if (pThread != this) // Not the main thread
{
if (pThread->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
}return CWinApp::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
The advantage of running multiple instances in different
threads in the same application rather than in separate processes are :-
- Quick initialization of Application esp. in those cases when application
has one time initialization work which is quite costly. - Easy sharing of data.
Finally, a word of caution. Multithreaded applications are very hard to debug
and test. A great deal of defensive coding is required to protect the global
data.
Date Last Updated: March 3, 1999