使用“文件映射”技术实现单实例应用程序
另一种单实例实现
引言
创建“单实例应用程序”(一次只允许运行一个实例)的常用方法通常包含以下两个步骤:
- 应用程序启动时,创建一个命名的全局对象(例如 Mutex、Semaphore)。如果实例已启动,则会失败并显示错误代码
ERROR_ALREADY_EXISTS
。 - 如果应用程序实例正在运行,使用
FindWindow
或FindWindowEx
查找其主窗口,然后恢复并将窗口置于最前。
代码大致如下:
HANDLE hMutex = CreateMutex(NULL, FALSE, MUTEX_NAME);
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
HWND hWnd = FindWindowEx(NULL, NULL, CLASSNAME, NULL);
if (IsWindow(hWnd))
{
ShowWindow(hWnd, SW_RESTORE);
BringWindowToTop(hWnd);
SetForegroundWindow(hWnd);
}
}
我正在寻找一种新的实现,因为我不太喜欢以前常用的方法。要找到正在运行实例的主窗口,必须使用已知的类名和(或)窗口名称创建它,但在 MFC 中这样做非常尴尬(我们必须重写主窗口的 PreCreateWindow
函数并设置 CREATESTRUCT
的 lpszClass
和 lpszName
)。另一个问题是不建议使用 FindWindow
。
背景
我们可以将这两个步骤与“文件映射”技术结合使用。文件映射可用于在进程之间共享数据。当第一个应用程序实例启动时,它会创建一个基于分页文件(用作系统虚拟内存的磁盘文件)的命名文件映射对象,并将它的主窗口句柄存储在 4 字节的映射共享内存中。后面的实例找到并打开该命名文件映射对象,并检索映射共享内存中正在运行的主窗口句柄。
文件映射相关函数在此列出:
-
HANDLE CreateFileMapping( HANDLE hFile, // handle to file to map LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // optional security attributes DWORD flProtect, // protection for mapping object DWORD dwMaximumSizeHigh, // high-order 32 bits of object size DWORD dwMaximumSizeLow, // low-order 32 bits of object size LPCTSTR lpName // name of file-mapping object );
创建一个具有系统全局名称的文件映射对象。
hFile
参数可以是已打开的磁盘文件句柄,也可以是(HANDLE)-1
以引用分页文件。lpName
参数是对象的全局名称,用于查找由另一个进程创建的文件映射对象。 -
HANDLE OpenFileMapping( DWORD dwDesiredAccess, // access mode BOOL bInheritHandle, // inherit flag LPCTSTR lpName // pointer to name of file-mapping object ); Open the File Mapping object with name referred by “lpName” parameter.
-
LPVOID MapViewOfFile( HANDLE hFileMappingObject, // file-mapping object to map into address space DWORD dwDesiredAccess, // access mode DWORD dwFileOffsetHigh, // high-order 32 bits of file offset DWORD dwFileOffsetLow, // low-order 32 bits of file offset DWORD dwNumberOfBytesToMap // number of bytes to map );
将文件视图映射到调用进程的地址空间。 返回值是一个
VOID
指针,它指向磁盘文件的偏移地址。 磁盘文件可以是真实文件或分页文件。
参考
我编写了一个类 CSingleInstance
来包装这些函数并简化其使用。 整个实现只包含一个源文件“SingleInstance.h”。 只需两行源代码即可使用它。
函数参考
1. CCSingleInstance::CCSingleInstance(LPCTSTR pszUniqueName=NULL,
int nCmdShow=SW_RESTORE)
构造函数接受两个参数。
pszUniqueName
是文件映射对象的全局唯一名称,如果它是NULL
,CSingleInstance
将使用默认名称__akui__
。nCmdShow
告诉激活时如何显示正在运行的实例的主窗口。
2. void CCSingleInstance::SetWindow(HWND hWnd)
// Store main window handle for later ones to active me.
Using the Code
- 包含“SingleInstance.h”
- 声明一个全局
CSingleInstance
对象 - 在主窗口句柄有效后,调用
SetWindow
存储它
在 MFC 应用程序中,只需在 AppName.cpp 中声明 CSingleInstance
对象,在 theApp
声明之前。 并在主窗口句柄有效时,在 CAppName::InitInstance()
中的返回之前调用 SetWindow
。
如果要对源代码进行一些更改,请注意,全局对象是在应用程序实际开始运行(进入 WinMain
)之前创建的。 所以不要调用 MFC 函数,因为 MFC 可能尚未正确初始化。
历史
- 2009 年 4 月 29 日:初始发布