前言
这次的实验代码都给了,需要做的事情是实现简单的生产者-消费者模型,这部分的原理也很简单,所以也就不重新造轮子了。本来这次实验的目的也是想让我们了解一下Windows内核对象的一些背景知识、WINAPI的相关调用过程、DLL的创建过程及如何在主函数中调用DLL。
项目拓扑
这里说一句题外话,原来Visual Studio是可以直接打开VC的项目的…之前还傻傻的复制粘贴。接下来就是项目的拓扑。
可以看到,整个项目分为三个模块,分别对应着Producer、Customer、SharedDll这三个文件。
Producer和Customer模块
这两个模块相似度非常高,主函数的代码:
int _tmain(int argc, _TCHAR* argv[])
{
int aData = 0;
while( 1 )
{
++aData;
WriteBuffer(aData);
printf("write %d to the buffer.....\n", aData);
}
return 0;
}
两个模块的不同在于WriteBuffer函数和ReadBuffer函数,其他的细节完全相同。另外这两个模块的dll库的导入采用导入表方式导入,而不是采用LoadLibrary
动态加载,对应的IDE的设置应该是项目下的引用。
ShareDll模块
这个模块才是重点,我们首先看一下由三个模块共同引用的ShallDllLib.h
这个头文件
ShareDllLib.h
该头文件的主要作用是定义一个用于导入、导出的符号__SHAREDLLLIB
,具体的实现代码为这一小段
#ifdef __SHAREDLLLIB
#else
#define __SHAREDLLLIB extern "C" __declspec(dllimport)
#endif
这段通过判断是否define了__SHAREDLLLIB
来定义不同的__SHAREDLLLIB
符号,具体来说,当Customer模块或者Producer模块调用该头文件时,__SHAREDLLLIB
被定义为extern "C" __declspec(dllimport)
。而当ShareDll模块调用该头文件时,__SHAREDLLLIB
被定义为extern "C" __declspec(dllexport)
。两者的主要区别在于一个是告诉编译器这个函数(变量)是导入的,另一个告诉编译器是导出的。
使用extern "C"
的原因是在于C++会允许源码中的函数重载,但是实际上函数重载的实现机理是编译器对函数的重命名,如将int a(int,int)
这样的函数声明修改为int_a_int_int
,不同的编译器实现细节不同,这会导致按名索引时出现麻烦,而使用了extern "C"
实际上强制编译器使用统一的C规范,同时这也会导致函数无法重载。
接下来头文件中通过调用这个符号来定义了三个需要导入、导出的符号。
__SHAREDLLLIB LONG glInstanceCounter;
__SHAREDLLLIB int WriteBuffer( int & aData);
__SHAREDLLLIB int ReadBuffer( int & aData);
ShareDll.cpp
这个cpp文件中,主要是做了建立一个共享段,在共享段里面分配了一些数据结构,实现了ReadBuffer
和WriteBuffer
的接口、实现了DllMain
函数来告诉使用者Dll库已经加载。
建立共享段的例程为:
#pragma data_seg("DvShareData")//定义一个自定义的内存段
//for count the instance
LONG glInstanceCounter = 0;
#pragma data_seg()
//to allocate the glob buffer in the DvShareData section
__declspec(allocate("DvShareData")) class CRbuffer<int, 7, 1> gBuffer;
//to share the section DvShareData
#pragma comment(linker, "/SECTION:DvShareData,RWS")
其中的#pragma
的作用分别为:
#pragma data_seg("xxx")
表示建立一个自定义的数据段名称(一般情况下数据段名称为DATA),这个编译选项需要和#pragma data_seg()
成对使用,夹在中间的是这个内存段中的数据结构。__declspec(allocate("xxx")) class CRbuffer<int, 7, 1> gBuffer;
则表示在xxx内存段中分配一个数据结构,这里是CRbuffer的一个模板类的对象。#pragma comment(linker, "/SECTION:DvShareData,RWS")
则是添加了一个链接器参数/SECTION:DvShareData,RWS
,表示将该内存段设置为RWS
,即可读写、共享;
而在ShareDll这个动态库中的DllMain
函数主要作用是显示有几个进程加载了ShareDll。
而DllMain
函数是动态库中在动态库初始化、写在动态库时由Windows调用的回调函数,一般用于初始化、回收一些资源。该函数有四个调用原因,分别是进程初始化(DLL_PROCESS_ATTACH)、线程初始化(DLL_THREAD_ATTACH)、线程卸载(DLL_THREAD_DETACH)、进程卸载(DLL_PROCESS_DETACH)1。另外需要注意的是,在DllMain中有一些函数是不可以被调用的,否则会造成死锁。
Rbuff.h
最重点的是这个头文件,该头文件定义了一个CBuffer
类模板,该类模板实现了生产者-消费者模型,在这个类的构造函数中,通过调用CreateMutex
和CreateSemaphore
函数来定义了两个互斥体对象和两个信号量对象,另外一提点,在VC中CreateMutex
是CreateMutexA
的别名。这两个函数的API接口分别为:
HANDLE CreateMutexA(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCSTR lpName
);
HANDLE CreateSemaphoreA(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCSTR lpName
);
其中的参数意义是:
- 两者共有的第一个参数是一个安全令牌的指针,当为NULL时,该内核对象使用创建者的令牌或者是模拟令牌,并且不可以被子进程继承。
- Mutex的第二个参数为
ture
时,创建者线程获取刚刚创建好的内核对象的所有权。2 - CreateSemaphoreA的第二和第三参数分别表示信号量模型的初始化数量和最大数量。
- 而最后一个参数则是表示内核对象的命名。如果在内核的名字空间中已经存在的话,创建函数失败,可以调用
GetLastError
函数获取错误的原因,另外,该函数可以为空。2
另外在Windows中信号的信号发送机制是当资源拥有量大于0时,即向队列的第一个进程发送信号。3
而ReadBuffer
函数和WriteBuffer
函数的结构基本上是一致的,他们的大致逻辑为:
- 调用
WaitForSingleObject
函数等待进入临界区; - 在临界区中向缓冲区写入/取出一个Buffer;
- 调用
ReleaseMutex
或ReleaseSemaphore
来释放自己的互斥体/信号量;
WaitForSingleObject
的函数声明为:
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
第一个为互斥体的句柄,第二个参数为超时时间,如果为0的话,该函数总是不会阻塞,如果是INFINITE
的话(INFINITE的值为0xffffffff),该函数总是等待直到句柄的对象被信号通知。4
而ReleaseMutex
的函数接口为:
BOOL ReleaseMutex(
HANDLE hMutex
);
该函数有以下几点注意事项5:
- 该函数必须被拥有互斥体的线程所使用,如果该线程没有互斥体,则返回错误;
- 一个线程可以通过Wait functions来判断自己是否拥有互斥体;
- 一旦一个线程从Wait functions的调用中返回时,该线程已经默认获取了对应互斥体的所有权限;
ReleaseSemaphore
函数和上述大致一致。
MSDN:https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain ↩︎
MSDN:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexa ↩︎ ↩︎
MSDN:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createsemaphorew ↩︎
MSDN:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject ↩︎
MSDN:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-releasemutex ↩︎