Making Multiple Instances of an Application run in the Same Process Space as different Threads

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.

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.

Download demo project - 55 KB

Date Last Updated: March 3, 1999



Comments

  • There are some hazards to be aware of

    Posted by Legacy on 01/10/2000 12:00am

    Originally posted by: Phil

    The primary problem with this implementation is that the document template lives in the main windows application thread. The document pointers in the multi doc template are only valid in the context of the threads they are running in. If you attempt to use any of those doc pointers in the wrong context (thread), you'll get an immediate assertion.

    One approach around this hazard would be to create multi document tempates in each of the UI threads created. This, unfortunately, may lead to other issues. For example, the CWinApp::OnFileNew, CWinApp::OnFileOpen, etc. handlers will probably have to be overridden in the threads, etc.

    As for the SDI case, you cannot mimic the technique used in this article simply because the CSingleDocemplate can only manage one doc at a time. The technique described in the article calls for the app to manage all the documents in all the threads using one doc template.

    SDI can be done, however, using the approach I describe above: putting the CSingleDocTemplate in the visible frame's thread, not the main app's thread, and writing the document appropriate handlers (File New, Open, etc) in the UI thread.

    Phil

    Reply
  • Good job...!!! but, in case of SDI...???

    Posted by Legacy on 12/02/1999 12:00am

    Originally posted by: Bruce Lee

    Thanks for good job.

    I tried to make multi instance program using SDI.
    But, SDI program couldn't launch new instance.
    Look at below codes...

    --- extracted from application thread class ---
    BOOL CAppThread::InitInstance()
    {
    ...
    CMainFrame* pMainFrame = new CMainFrame;
    if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
    return FALSE;
    m_pMainWnd = pMainFrame;
    ...
    }

    In case SDI, I couldn't use LoadFrame() function.
    What should I do for create multiple instance SDI?

    Thanks...

    Reply
  • Excellent article

    Posted by Legacy on 03/16/1999 12:00am

    Originally posted by: Kamen Lilov

    This is a great implementation. Couple of years ago, I did much the same thing with the direct Win32 API, but this is the first good reference on how to do it with MFC.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Learn How A Global Entertainment Company Saw a 448% ROI Every business today uses software to manage systems, deliver products, and empower employees to do their jobs. But software inevitably breaks, and when it does, businesses lose money -- in the form of dissatisfied customers, missed SLAs or lost productivity. PagerDuty, an operations performance platform, solves this problem by helping operations engineers and developers more effectively manage and resolve incidents across a company's global operations. …

  • Live Event Date: December 18, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this upcoming webcast …

Most Popular Programming Stories

More for Developers

RSS Feeds