Viewer的文章

  • 录屏程序之添加实时音频流

    背景之前自己在视频教程的时候,就从网上找了个破解版的录屏软件来录制视频。虽然是破解版的,但是破解不完全,因为录制的视频播放到十几分钟之后,就开始显示未注册版本的字样在屏幕中,所以很是碍眼啊。
    后来,自己细想之后,觉得自己完全可以写一个专属的录屏软件,功能不用太复杂,就支持画面录制和声音录制,存储为视频文件就好。是的,后来我开发出来了。所以,才有了这篇分享文章给大家,来总结下开发心得,同时也向大家剖析下这个程序的实现过程和原理。
    这篇文档分成上、下两个部分,上部分主要讲解实现实时录制屏幕画面,保存为视频文件。下部分主要讲解实现实时录制声音,并将声音添加到视频文件中。现在,本文是下部分,讲解实时录音,添加音频流过程。
    实现原理有了上半部分创建视频流并为视频流添加画面帧的基础,向添加音频流就不难理解了。上半部分的关键就是在于将一张张屏幕截图添加在视频流里,形成视频。那么,音频也是同样的原理,我们将一段段音频数据添加到音频流中。其中,实时获取音频流是关键。还好,本站上之前有人写的 “编程实现录音及保存为WAV音频文件” 这篇文章,详细介绍了获取音频数据的过程和原理,大家可以参考,在此就不进行介绍了。现在,就讲讲获取到音频数据之后,怎么添加到音频流中。实现的原理是:

    首先,初始化 AVIFile 库,因为下面要使用到该库的函数
    然后,判断是否创建音频流,若没有,则开始创建音频流

    首先,设置音频流信息 AVISTREAMINFO,设置流类型为音频流、声道数、音频样本频率、缓存大小、样本数等。再按照设置的音频流格式调用 AVIFileCreateStream 函数创建音频流,并获取音频流接口
    成功获取音频流接口之后,开始按照波形声音格式 WAVEFORMATEX 设置音频流格式

    调用 AVIStreamWrite 函数将音频数据写入到音频流中,并更新 HAVI 结构体中记录音频下一帧的成员
    最后,关闭 AVIFile 库

    编码实现// *************** 添加音频帧 ****************// hAvi句柄// lpWaveData 音频数据// dwWaveDataSize 音频数据大小HRESULT AddWaveFrame(PHAVI pAvi, PVOID lpWaveData, DWORD dwWaveDataSize){ // AVI文件环境初始化 ::AVIFileInit(); if (NULL == lpWaveData) { return S_OK; } // 创建音频流 if (NULL == pAvi->aStream) { // 设置音频流信息 AVISTREAMINFO aviStreamInfo = { 0 }; ::RtlZeroMemory(&aviStreamInfo, sizeof(AVISTREAMINFO)); // 流类型 aviStreamInfo.fccType = streamtypeAUDIO; // 编码器 aviStreamInfo.fccHandler = NULL; // 声道数 aviStreamInfo.dwScale = pAvi->waveFormatEx.nChannels; // Hz aviStreamInfo.dwRate = pAvi->waveFormatEx.nSamplesPerSec; // 缓存大小 aviStreamInfo.dwSuggestedBufferSize = BUFFERLENGTH; aviStreamInfo.dwSampleSize = 1; // 创建音频流 HRESULT hr = ::AVIFileCreateStream(pAvi->pAviFile, &pAvi->aStream, &aviStreamInfo); if (AVIERR_OK != hr) { ShowError("AVIFileCreateStream"); return hr; } // 设置音频流格式 hr = ::AVIStreamSetFormat(pAvi->aStream, 0, &pAvi->waveFormatEx, sizeof(WAVEFORMATEX)); if (AVIERR_OK != hr) { ShowError("AVIStreamSetFormat"); return hr; } } // 添加到音频流 HRESULT hr = ::AVIStreamWrite(pAvi->aStream, pAvi->dwNextWaveFrame, 1, lpWaveData, dwWaveDataSize, AVIIF_KEYFRAME, NULL, NULL); if (AVIERR_OK != hr) { ShowError("AVIStreamWrite"); return hr; } pAvi->dwNextWaveFrame++; return S_OK;}
    程序测试我们直接运行程序,开始录屏,等待一段时间,结束录屏。这是,便生成了一个视频文件。我们打开视频文件,成功播放刚才的录制画面和声音。所以,录屏程序录屏和录音功能成功实现。

    总结这个程序总体上来说,不算复杂。如果你仍觉得比较难理解的话,建议你把这个程序的功能先拆分实现。先实现实时录音功能,再实现实时录屏功能,最后将录音和录屏结合在一起,这样理解起来会比较深刻。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2018-12-18 09:28:00
  • 录屏程序之屏幕实时录制保存成AVI视频文件

    背景之前自己在视频教程的时候,就从网上找了个破解版的录屏软件来录制视频。虽然是破解版的,但是破解不完全,因为录制的视频播放到十几分钟之后,就开始显示未注册版本的字样在屏幕中,所以很是碍眼啊。
    后来,自己细想之后,觉得自己完全可以写一个专属的录屏软件,功能不用太复杂,就支持画面录制和声音录制,存储为视频文件就好。是的,后来我开发出来了。所以,才有了这篇分享文章给大家,来总结下开发心得,同时也向大家剖析下这个程序的实现过程和原理。
    这篇文档分成上、下两个部分,上部分主要讲解实现实时录制屏幕画面,保存为视频文件。下部分主要讲解实现实时录制声音,并将声音添加到视频文件中。现在,本文是上部分,讲解实时录制画面的过程。
    函数介绍AVIFileInit 函数
    初始化AVIFile库。AVIFile库维护初始化次数的计数,而不是释放次数。 使用AVIFileExit函数释放AVIFile库并减少引用计数。 在使用任何其他AVIFile功能之前调用AVIFileInit。此功能取代过时的AVIStreamInit函数。
    函数声明
    STDAPI_(VOID) AVIFileInit(void);
    参数

    无参数。
    返回值

    无返回值。

    AVIFileOpen 函数
    打开AVI文件,并返回用于访问它的文件接口的地址。 AVIFile库维护文件打开次数的计数,而不是释放文件的次数。 使用AVIFileRelease函数释放文件并减少计数。
    STDAPI AVIFileOpen( PAVIFILE *ppfile, LPCTSTR szFile, UINT mode, CLSID pclsidHandler);
    参数

    ppfile指向接收新的IAVIFile接口指针的缓冲区。
    szFile以Null结尾的字符串,其中包含要打开的文件的名称。
    mode打开文件时使用的访问模式。 默认访问模式为OF_READ。 可以使用AVIFileOpen指定以下访问模式:




    VALUE
    MEANING




    OF_CREATE
    创建一个新文件。 如果文件已经存在,它将被截断为零长度


    OF_PARSE
    跳过耗时的操作,如建立索引。 如果要使函数尽可能快地返回,请设置此标志,例如,如果要查询文件属性但未读取该文件


    OF_READ
    打开文件读取数据


    OF_READWRITE
    打开文件读取和写入数据


    OF_SHARE_DENY_NONE
    打开文件非排他性。 其他进程可以通过读或写访问来打开文件。 如果另一个进程以兼容模式打开文件,AVIFileOpen将失败


    OF_SHARE_DENY_READ
    打开文件非排他性。 其他进程可以用写访问来打开文件。 如果另一个进程以兼容模式打开文件或对其进行了读取访问,则AVIFileOpen将失败


    OF_SHARE_DENY_WRITE
    打开文件非排他性。 其他进程可以打开具有读访问权限的文件。 如果另一个进程以兼容性模式打开文件或对其进行写入访问,AVIFileOpen将失败


    OF_SHARE_EXCLUSIVE
    打开文件并拒绝其他进程的访问。 如果任何其他进程已打开文件,AVIFileOpen将失败


    OF_WRITE
    打开文件写入数据




    pclsidHandler指向要使用的标准或自定义处理程序的类标识符。 如果值为NULL,系统将根据文件扩展名或文件中指定的RIFF类型从注册表中选择一个处理程序。
    返回值

    如果成功返回零,否则返回错误。

    AVIFileCreateStream 函数
    在现有文件中创建一个新流,并创建一个新流的接口。
    函数声明
    STDAPI AVIFileCreateStream( PAVIFILE pfile, PAVISTREAM *ppavi, AVISTREAMINFO *psi);
    参数

    pfile处理一个打开的AVI文件。ppavi指向新流接口的指针。psi指向包含有关新流的信息的结构,包括流类型和采样率。
    返回值

    如果成功返回零,否则返回错误。 除非文件已经被写入权限打开,否则此函数返回AVIERR_READONLY。

    AVIMakeCompressedStream 函数
    从未压缩的流和压缩过滤器创建压缩流,并将指针的地址返回到压缩流。 此功能支持音频和视频压缩。
    函数声明
    STDAPI AVIMakeCompressedStream( PAVISTREAM *ppsCompressed, PAVISTREAM psSource, AVICOMPRESSOPTIONS *lpOptions, CLSID *pclsidHandler);
    参数

    ppsCompressed指向接收压缩流指针的缓冲区。psSource指向要压缩的流的指针。lpOptions指向要使用的压缩类型的结构的指针以及要应用的选项。 您可以通过在AVICOMPRESSOPTIONS结构中标识适当的处理程序来指定视频压缩。 对于音频压缩,请指定压缩数据格式。pclsidHandler指向用于创建流的类标识符。
    返回值

    如果成功则返回AVIERR_OK,否则返回错误。

    AVIStreamSetFormat 函数
    设置指定位置的流的格式。
    函数声明
    STDAPI AVIStreamSetFormat( PAVISTREAM pavi, LONG lPos, LPVOID lpFormat, LONG cbFormat);
    参数

    pavi处理开放的流。lPos在流中接收格式的位置。lpFormat指向包含新格式的结构的指针。cbFormatlpFormat引用的内存块的大小(以字节为单位)。
    返回值

    如果成功返回 0,否则返回错误。

    AVIStreamWrite 函数
    将数据写入流。
    函数声明
    STDAPI AVIStreamWrite( PAVISTREAM pavi, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, DWORD dwFlags, LONG *plSampWritten, LONG *plBytesWritten);
    参数

    pavi处理开放的流。lStart第一个样本要写。lSamples要写的样本数。lpBuffer指向包含要写入数据的缓冲区。cbBufferlpBuffer引用的缓冲区大小。dwFlags与此数据相关的标记。AVIIF_KEYFRAME,表示此数据不依赖于文件中的前面的数据。plSampWritten指向缓冲区的指针,该缓冲区接收写入的样本数。 这可以设置为NULL。plBytesWritten指向缓冲区的指针,用于接收写入的字节数。 这可以设置为NULL。
    返回值

    如果成功返回 0,否则返回错误。

    实现原理这个录屏程序的实时屏幕画面录制部分,可以大致分成 4 个部分,分别是:视频文件 AVI 文件的创建级初始化、屏幕画面的截屏并获取截屏数据、对视频流添加画面帧、结束录制。现在,我们就分别针对每一部分的实现原理进行解析:
    创建 AVI 视频文件及初始化
    首先,调用 AVIFileInit 函数初始化 AVIFile 库,因为下面要调用该库中的函数
    然后,调用 AVIFileOpen 函数根据文件路径以及创建标志,创建一个可写入的 AVI 视频文件
    接着,对 HAVI 结构体中的数据进行初始化,设置文件接口以及帧距
    最后,关闭 AVIFile 库

    获取屏幕截屏数据
    首先,我们先使用 GetDesktopWindow 获取桌面窗口的句柄,再使用 GetDC 函数获取桌面窗口的设备上下文
    然后,创建一个与桌面窗口设备上下文兼容的内存设备上下文,并获取计算机屏幕的宽和高,以此来创建一个兼容的位图,并选中到兼容设备上下文中
    接着,我们就可以把桌面的画面内容复制到兼容的设备上下文档中,保存在兼容的内存位图里。再继续把光标绘制到画面上。这样,就可以成功获取屏幕和鼠标的画面了
    然后,调用 GetDIBits 函数,根据位图句柄获取位图中的数据

    添加画面帧在获取截图画面数据后,再初始化位图信息头结构 BITMAPINFOHEADER,将数据和信息头传递给添加画面帧函数,将画面添加到视频流当中。那么,为视频添加画面帧的流程如下:

    首先,调用 AVIFileInit 函数初始化 AVIFile 库,因为下面要调用该库中的函数
    然后,先判断视频流接口有没有创建,若没有创建,则接下来就开始创建视频流接口。

    首先设置视频流信息 AVISTREAMINFO,设置流类型为视频流、帧距、样本帧率、缓存大小、视频尺寸等
    然后,调用 AVIFileCreateStream 按照设置的流信息创建视频流,获取视频流接口

    接着,判断视频压缩流接口有没有创建,若没有创建,则接下来就开始创建压缩视频流接口

    首先,设置视频流压缩信息 AVICOMPRESSOPTIONS,指定使用的压缩编码器 xvid
    然后,调用 AVIMakeCompressedStream 按照压缩流格式创建视频压缩流,并获取压缩视频流接口
    若能成功获取视频压缩流接口,便调用 AVIStreamSetFormat 函数,设置压缩视频流的格式;若不能获取压缩视频流,则对原始的视频流,设置视频流格式

    若有压缩视频流,则调用 AVIStreamWrite 函数,将位图画面数据写入到压缩视频流中。若没有压缩视频流,则写入到原始视频流中。写入帧数是 1 帧,并更新 HAVI 结构中的记录下一帧的成员
    最后,关闭 AVIFile 库

    结束录制
    首先,调用 AVIFileInit 函数初始化 AVIFile 库,因为下面要调用该库中的函数
    然后,根据 HAVI 机构中的数据,判断视频流接口、压缩视频流接口、音频流接口、文件接口等是否存在,若存在,则调用 AVIStreamRelease 进行释放
    最后,关闭 AVIFile 库

    编码实现初始化工作及创建 AVI 视频文件// *************** 创建avi文件 ****************// pszFileName avi文件完整路径// iFramePeriod 相邻帧时间间隔// pwaveFormatEx 如果不添加音频,pwaveFormatEx为NULLPHAVI CreateAvi(char* pszFileName, DWORD dwFramePeriod, WAVEFORMATEX *pwaveFormatEx){ // AVI文件环境初始化 ::AVIFileInit(); PHAVI pAvi = new HAVI; ::RtlZeroMemory(pAvi, sizeof(HAVI)); // 创建AVI IAVIFile *pFile = NULL; HRESULT hr = ::AVIFileOpen(&pFile, pszFileName, OF_WRITE | OF_CREATE, NULL); if (AVIERR_OK != hr) { ShowError("AVIFileOpen"); return pAvi; } // 初始化 HAVI 结构体 // avi接口 pAvi->pAviFile = pFile; if (NULL != pwaveFormatEx) { // 音频流创建时使用 ::RtlCopyMemory(&pAvi->waveFormatEx, pwaveFormatEx, sizeof(WAVEFORMATEX)); } // 视频流创建时使用 pAvi->dwPeriod = dwFramePeriod; // 退出AVI文件 ::AVIFileExit(); return pAvi;}
    向视频文件添加视频帧// *************** 添加视频帧 ****************// hAvi句柄// bmpInfoHeader 位图信息头// lpBmpData 指向位图数据的指针,必须指向 DIBSection.HRESULT AddAviFrame(PHAVI pAvi, BITMAPINFOHEADER bmpInfoHeader, PVOID lpBmpData){ // AVI文件环境初始化 ::AVIFileInit(); if (NULL == lpBmpData) { return AVIERR_BADPARAM; } // 若不存在原始视频流,则创建 if (NULL == pAvi->pStream) { // 设置视频流信息 AVISTREAMINFO aviStreamInfo = { 0 }; ::RtlZeroMemory(&aviStreamInfo, sizeof(AVISTREAMINFO)); // 流类型 aviStreamInfo.fccType = streamtypeVIDEO; // 编码器 aviStreamInfo.fccHandler = NULL; // 帧距 aviStreamInfo.dwScale = pAvi->dwPeriod; // 样本帧数 aviStreamInfo.dwRate = 1000; // 缓存大小 aviStreamInfo.dwSuggestedBufferSize = bmpInfoHeader.biSizeImage; // 视频尺寸 ::SetRect(&aviStreamInfo.rcFrame, 0, 0, bmpInfoHeader.biWidth, bmpInfoHeader.biHeight); // 创建视频流 HRESULT hr = ::AVIFileCreateStream(pAvi->pAviFile, &pAvi->pStream, &aviStreamInfo); if (AVIERR_OK != hr) { ShowError("AVIFileCreateStream"); return hr; }/* // 如果压缩视频流成功, 那么这里不能设置格式 // 设置视频流格式 hr = ::AVIStreamSetFormat(pAvi->pStream, 0, &bmpInfoHeader, sizeof(bmpInfoHeader)); if (AVIERR_OK != hr) { ShowError("AVIStreamSetFormat"); return hr; }*/ } // 若不存在压缩视频流,则创建 if (NULL == pAvi->pStreamCompressed) { // 设置压缩选项 AVICOMPRESSOPTIONS aviCompressOptions = { 0 }; ::RtlZeroMemory(&aviCompressOptions, sizeof(AVICOMPRESSOPTIONS)); aviCompressOptions.fccHandler = mmioFOURCC('x', 'v', 'i', 'd'); // 创建压缩视频流 HRESULT hr = ::AVIMakeCompressedStream(&pAvi->pStreamCompressed, pAvi->pStream, &aviCompressOptions, NULL); if (AVIERR_OK == hr) { // 设置压缩视频流格式 ::AVIStreamSetFormat(pAvi->pStreamCompressed, 0, &bmpInfoHeader, sizeof(BITMAPINFOHEADER)); } else { // 设置视频流格式 ::AVIStreamSetFormat(pAvi->pStream, 0, &bmpInfoHeader, sizeof(BITMAPINFOHEADER)); } } // 当没有压缩视频流时, 使用视频流 IAVIStream *pStream = NULL; if (pAvi->pStreamCompressed) { pStream = pAvi->pStreamCompressed; } else if (pAvi->pStream) { pStream = pAvi->pStream; } else { return S_OK; } // 添加帧到视频流 HRESULT hr = ::AVIStreamWrite(pStream, pAvi->dwNextFrame, 1, lpBmpData, bmpInfoHeader.biSizeImage, AVIIF_KEYFRAME, NULL, NULL); if (AVIERR_OK != hr) { ShowError("AVIStreamWrite"); return hr; } pAvi->dwNextFrame++; // 退出AVI文件 ::AVIFileExit(); return S_OK;}
    录制结束// *************** 关闭avi文件 ****************// hAvi 要关闭的avi句柄HRESULT CloseAvi(PHAVI pAvi){ // AVI文件环境初始化 ::AVIFileInit(); if (NULL == pAvi) { return S_OK; } // 删除接口引用并关闭avi if (NULL != pAvi->pAviFile) { ::AVIFileRelease(pAvi->pAviFile); pAvi->pAviFile = NULL; } // 释放音频流 if (NULL != pAvi->aStream) { ::AVIStreamRelease(pAvi->aStream); pAvi->aStream = NULL; } // 释放压缩视频流 if (NULL != pAvi->pStreamCompressed) { ::AVIStreamRelease(pAvi->pStreamCompressed); pAvi->pStreamCompressed = NULL; } // 释放原始视频流 if (NULL != pAvi->pStream) { ::AVIStreamRelease(pAvi->pStream); pAvi->pStream = NULL; } // 释放内存 if (pAvi) { delete pAvi; pAvi = NULL; } // 退出AVI文件 ::AVIFileExit(); return S_OK;}
    屏幕截屏void Recording(PHAVI pAvi){ int iRet = 0; HRESULT hr = S_OK; // 获取桌面屏幕上下文,并创建一个兼容内存上下文 HDC hDCSource = ::GetDC(::GetDesktopWindow()); HDC hDCMem = ::CreateCompatibleDC(hDCSource); // 获取屏幕分辨率 int iScreenWidth = ::GetSystemMetrics(SM_CXSCREEN); int iScreenHeight = ::GetSystemMetrics(SM_CYSCREEN); // 创建位图共内存上下文使用 HBITMAP hDesktopBmp = ::CreateCompatibleBitmap(hDCSource, iScreenWidth, iScreenHeight); HBITMAP oldBmp = (HBITMAP)::SelectObject(hDCMem, hDesktopBmp); // 将桌面屏幕上下文绘制到兼容内存上下文 ::BitBlt(hDCMem, 0, 0, iScreenWidth, iScreenHeight, hDCSource, 0, 0, SRCCOPY); // 加载光标 HCURSOR hCursor = ::LoadCursor(NULL, IDC_ARROW); // 绘制光标 POINT ptPoint = { 0 }; ::GetCursorPos(&ptPoint); ::DrawIconEx(hDCMem, ptPoint.x, ptPoint.y, hCursor, 0, 0, 0, NULL, DI_NORMAL | DI_COMPAT | DI_DEFAULTSIZE); // 定义位图信息头 BITMAP stBmp = { 0 }; ::GetObject(hDesktopBmp, sizeof(BITMAP), &stBmp); BITMAPINFOHEADER bmpInfoHeader = { 0 }; ::RtlZeroMemory(&bmpInfoHeader, sizeof(BITMAPINFOHEADER)); bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER); bmpInfoHeader.biWidth = iScreenWidth; bmpInfoHeader.biHeight = iScreenHeight; bmpInfoHeader.biSizeImage = stBmp.bmWidthBytes * iScreenHeight; bmpInfoHeader.biPlanes = stBmp.bmPlanes; bmpInfoHeader.biBitCount = stBmp.bmBitsPixel; bmpInfoHeader.biCompression = BI_RGB; // 获取桌面屏幕位图的DIB数据 BYTE *lpBits = new BYTE[bmpInfoHeader.biSizeImage]; BITMAPINFO bmpInfo = { 0 }; bmpInfo.bmiHeader = bmpInfoHeader; iRet = ::GetDIBits(hDCSource, hDesktopBmp, 0, iScreenHeight, lpBits, &bmpInfo, DIB_RGB_COLORS); if (0 == iRet || NULL == lpBits) { ::MessageBox(NULL, "GetDIBits Error!", "ERROR", MB_OK | MB_ICONERROR); } // 添加视频帧到已创建的avi视频文件 hr = AddAviFrame(pAvi, bmpInfoHeader, lpBits); if (S_OK != hr) { ::MessageBox(NULL, "AddAviFrame Error!", "ERROR", MB_OK | MB_ICONERROR); } // 释放 ::SelectObject(hDCMem, oldBmp); delete[]lpBits; lpBits = NULL; ::DeleteObject(hDesktopBmp); ::DeleteObject(hCursor); ::DeleteDC(hDCMem); ::ReleaseDC(::GetDesktopWindow(), hDCSource);}
    程序测试我们运行程序,便开始录屏,等待一会儿后,停止录屏。这时,成功生成视频文件。打开视频,成功播放刚才的录制画面。

    总结注意,如果电脑上没有安装 XVID 解码器的话,录制视频的时候,就不能将画面进行压缩,因为我们程序里面指定使用 XVID 来压缩视频,获取压缩视频流的。所以,就会得到未压缩的视频文件,这时就会发现生成的视频文件比较大。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2018-12-15 17:04:24
  • 运行单一实例

    背景病毒木马在使用各种手段植入到用户计算机后,也会使用浑身解数使自己被用户执行激活。但是,如果病毒木马自己被多次重复运行,系统中存在多分病毒木马的进程,那么,这就有可能增加被暴露的风险。所以,要想解决上述问题,就要确保系统上只运行一个病毒木马的进程实例。
    确保运行一个进程实例的实现方法有很多,可以是通过扫描进程列表来实现,也可以通过枚举程序窗口的方式来实现或者可以通过共享全局变量来实现。接下来,本文将介绍一种被病毒木马广泛使用而且使用简单的方法,即通过创建系统命名互斥对象的方式实现。
    函数介绍1. CreateMutex函数
    创建或打开一个已命名或未命名的互斥对象。
    HANDLE WINAPI CreateMutex( _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, _In_ BOOL bInitialOwner, _In_opt_ LPCTSTR lpName);
    参数
    lpMutexAttributes [in, optional]指向SECURITY_ATTRIBUTES结构的指针。如果此参数为NULL,则该句柄不能由子进程继承。bInitialOwner [in]如果此值为TRUE并且调用者创建了互斥锁,则调用线程将获得互斥锁对象的初始所有权。否则,调用线程不会获得互斥锁的所有权。lpName [in, optional]互斥对象的名称。该名称仅限于MAX_PATH字符。名称比较区分大小写。如果lpName为NULL,则会创建不带名称的互斥体对象。如果lpName与现有事件,信号量,等待定时器,作业或文件映射对象的名称匹配,则该函数将失败,并且GetLastError函数返回ERROR_INVALID_HANDLE。这是因为这些对象共享相同的名称空间。该名称可以具有“Global”或“Local”前缀以在全局或会话名称空间中显式创建对象。名称的其余部分可以包含除反斜杠字符(\)以外的任何字符。
    返回值
    如果函数成功,则返回值是新创建的互斥对象的句柄。如果函数失败,返回值为NULL。 要获得扩展的错误信息,请调用GetLastError。如果互斥锁是一个已命名的互斥锁,并且该对象在此函数调用之前就存在,则返回值是现有对象的句柄,GetLastError返回ERROR_ALREADY_EXISTS。

    实现原理通常情况下,系统上的进程是相互独立的,每个进程都拥有自己独立资源和地址空间,进程间互不影响。所以,同一个程序可以重复运行,在系统上的进程互不影响。但是,在一些特殊的情况,需要程序在系统上只存在一份进程实例,这就引出了进程互斥的问题。
    微软提供了CreateMutex函数来实现创建或者打开一个已命名或未命名的互斥对象,程序每次运行的时候,通过判断系统上是否存在相同的命名互斥对象来确定程序是否重复运行。
    CreateMutex函数一共有三个参数,第一个参数表示互斥对象的安全设置,是一个指向SECURITY_ATTRIBUTES结构的指针,在该程序中直接设置为NULL即可。第二个参数表示线程是否获得互斥锁对象的初始所有权,在该程序中,无论该参数为TRUE或者FALSE,均不影响程序的正常执行。第三个参数表示互斥对象的名称,对于通过过互斥对象来判断进程实例是否重复运行的程序来说,该参数一定要设置,而且设置的名称要保证唯一性。
    程序的判断原理是通过CreateMutex函数创建一个命名互斥对象,如果对象创建成功,而且通过调用GetLastError函数获取的返回码的值为ERROR_ALREADY_EXISTS,则表示该命名互斥对象存在,即程序重复运行。否则,认为是首次运行程序。
    编码实现// 判断是否重复运行BOOL IsAlreadyRun(){ HANDLE hMutex = NULL; hMutex = ::CreateMutex(NULL, FALSE, "TEST"); if (hMutex) { if (ERROR_ALREADY_EXISTS == ::GetLastError()) { return TRUE; } } return FALSE;}
    测试直接运行上面的程序,第一次运行的时候,程序提示“NOT Already Run!“,如图2-1所示,意思是系统上没有该实例运行。接着继续双击执行程序,这次程序提示”Already Run!!!!“,如图2-2所示,意思是系统上已经存在该实例正在运行着。所以,程序成功判断出程序是否重复运行。


    小结这个程序实现起来不难,关键是熟悉对CreateMutex函数的调用。在调用CreateMutex函数来创建命名的互斥对象的时候,注意互斥对象的名称不要与现有事件、信号量或者文件映射对象等的名称相同,否则创建互斥对象失败。
    在实现的过程中,特别要注意,程序一定不要调用CloseHandle函数来关闭CreateMutex函数创建出来的互斥对象的句柄,否则会导致互斥对象判断失败。因为CloseHandle函数会关闭互斥对象的句柄,释放资源。这样,系统上便不会存在对应的命名互斥对象了。这样,通过CreateMutex来创建命名互斥对象都不会重复的。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2018-12-20 12:31:10

发送私信

如果没有为想要的东西努力,就别为自己失去的东西哭泣

16
文章数
16
评论数
eject