基于MFC的操作系统打印进程模拟

Feelme

发布日期: 2019-06-09 13:05:48 浏览量: 447
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

一.设计描述

本次分配的题目是进程同步打印问题,两个缓冲区都用动态链接库来操作,P1、P2、P3三个进程分别创建了三个工程。本次设计用了信号量和事件来对进程实现同步。P1的作用是将磁盘文件拷贝到缓冲区一,所以设计了个定时器,用作定时改变数据i(模拟不同时刻,不同的文件)发送到缓存区一,并且发出触发进程P2的事件。P2等待到事件后,便将数据从缓冲区一里拷贝到缓冲区二,并且同时在窗口的编辑框中显示拷贝完成,拷贝完成后又会发出触发打印进程P3的的事件。P3等到事件传过来后,便进行打印操作,完成后在进程三的窗口显示出打印完成,完成人机交互。

显然设计存在一个问题,当打印进程P3未完成打印的时候,P1的定时器时间到了,就会造成还未打印完成,便进行了第二个文件的打印,会覆盖缓冲区里的正在打印的文件。因此为了避免这个情况,设置了信号量,让P3完成打印前不释放资源,只有完成打印,释放掉资源,P1才能进行下个文件的拷贝。用这种方法对该意外情况做出限制。

同时,考虑到在实际应用中,点击打印系统就能自动完成拷贝和打印全过程,所以,在P1中设置了打开P2、P3进程的语句,打开P1.exe就会自动打开P2和P3进程,完成自动打印。

二.程序流程

程序从P1进程开始,进程内设置了定时器,用来定时执行打印操作,首先p1进程连接动态链接库1(用作缓存区一),然后将数据i写入到链接库申请的内存段内,之后对P1进程进行信号量上锁,并且创建事件触发P2进程。

P2进程等到事件后,进行将数据i从缓冲区一里面复制到缓冲区二里面,并且同时创建事件触发P3进程。

然后P3等待到进程后,开始打印工作。在打印完成后,便释放P1进程的信号量,使P1不再堵塞,从而进行下一个数据的打印全过程。

程序的流程图,如下:

三.编写代码

3.1 P1相关代码

P1进程中先定义一个定时器,每隔三秒便执行一次OnTimer()函数

  1. SetTimer(1, 3000, NULL);

OnTimer函数如下,创建了一个事件THREAD用于让线程进行传送数据操作

  1. void CP1Dlg::OnTimer(UINT_PTR nIDEvent)
  2. {
  3. // TODO: 在此添加消息处理程序代码和/或调用默认值
  4. CEvent eventhread(FALSE, FALSE, L"THREAD");//然后在这里创建了一个事件
  5. eventhread.PulseEvent();
  6. }

线程Thread.cpp文件代码如下,响应了事件THREAD,上锁mutex发送消息WM_RESULT0,根据写在消息队列里的代码图3.1.3,执行OnResultsend函数。

P1进程中的线程代码

  1. //数据线程定义
  2. UINT ThreadGet(LPVOID pParam) {
  3. CEvent eventhread(FALSE, FALSE, L"THREAD"); //建立一个事件
  4. //获取主窗口句柄
  5. HWND hWnd = (HWND)pParam;
  6. CSemaphore mutex(1, 1, L"SendMutex"); //声明全局信号量 用于进程同步控制
  7. CSemaphore mutex2(0, 1, L"SendMutex2");
  8. while (1)
  9. {
  10. //等待事件
  11. mutex.Lock(); //这里上锁
  12. ::WaitForSingleObject(eventhread.m_hObject, INFINITE); //等待刚才创建的事件
  13. //发送消息到主线程显示数据
  14. ::SendMessage(hWnd, WM_RESULT0, 0, 0);
  15. mutex2.Unlock();
  16. }
  17. }

消息队列代码

  1. BEGIN_MESSAGE_MAP(CP1Dlg, CDialogEx)
  2. ON_WM_SYSCOMMAND()
  3. ON_WM_PAINT()
  4. ON_WM_QUERYDRAGICON()
  5. ON_COMMAND(AFX_ID_PREVIEW_CLOSE, &CP1Dlg::Start)
  6. ON_WM_TIMER()
  7. ON_MESSAGE(WM_RESULT0, OnResultsend)
  8. END_MESSAGE_MAP()

OnResultsend函数是用来对缓冲区进行写入数据,并且创建P2进程等待的事件

  1. void CP1Dlg::Start()
  2. {
  3. //这个函数便是用来连接动态链接库 进行写入操作的同时 发送事件用于触发p2进程
  4. CString j;
  5. Sleep(2000);
  6. CEvent event(FALSE, FALSE, L"SENDDATA"); //建立一个事件 //建立一个事件
  7. HINSTANCE hdll = ::LoadLibrary(L"Link.dll"); //加载动态链接库
  8. if (hdll == NULL)
  9. {
  10. MessageBox(L"链接库文件加载失败", L"注意",MB_OK);
  11. }
  12. typedef int(__stdcall *PROC)(int); //定义函数指针
  13. PROC proc = (PROC)GetProcAddress(hdll, "setData"); //得到函数
  14. UpdateData(TRUE);
  15. (*proc)(i);
  16. event.PulseEvent(); //唤醒等待事件
  17. FreeLibrary(hdll);
  18. j.Format(L"%d", i);
  19. e_p1 = L"我已经将第" + j + L"个文件拷贝完成";
  20. UpdateData(FALSE); //显示已经完成
  21. i++;
  22. }

P1进程如果不创建线程直接在进程里编写代码,当进程阻塞是会异常卡顿,所以用线程进行创建事件和传输数据,这样即使阻塞时,也不会出现卡顿。

3.2 P2进程相关代码

P2同样创建个线程,窗口加载便启动线程,thread.cpp文件代码如下,此处代码主要是响应P1中的事件,进行从动态链接库里取数据i,然后发送消息WM_RESULT,其执行机制与P1一样,先从消息队列中找出对应的函数,然后执行该函数。

  1. //数据线程定义
  2. UINT ThreadGet(LPVOID pParam) {
  3. //获取主窗口句柄
  4. HWND hWnd = (HWND)pParam;
  5. //加载链接库
  6. HINSTANCE hdll = ::LoadLibrary(L"Link.dll");
  7. if (hdll==NULL)
  8. {
  9. MessageBox(NULL, L"文件打开失败", L"注意", MB_OK);
  10. return 0;
  11. }
  12. //定义函数指针
  13. typedef int(__stdcall *PROC)();
  14. PROC proc=(PROC)GetProcAddress(hdll,"getData");
  15. //定义事件 名称相同
  16. CEvent event(FALSE, FALSE, L"SENDDATA");
  17. while (1)
  18. {
  19. //等待事件
  20. ::WaitForSingleObject(event.m_hObject, INFINITE);
  21. //从共享缓存区得到数据
  22. int result = (*proc)();
  23. //发送消息到主线程显示数据
  24. ::SendMessage(hWnd, WM_RESULT, result, 0);
  25. }
  26. }

自定义消息执行的函数OnResult函数代码如下

  1. LRESULT CP2Dlg::OnResult(WPARAM wParam,LPARAM lParam) {
  2. CString j;
  3. //再建立一个事件,用于触发p3进程
  4. j.Format(L"%d", wParam);
  5. CEvent event2(FALSE, FALSE, L"SENDDATA2");//此处创建的事件是为了触发p3打印进程
  6. HINSTANCE hdll = ::LoadLibrary(L"Link2.dll");
  7. if (hdll==NULL)
  8. {
  9. MessageBox(L"文件打开失败", L"注意", MB_OK);
  10. }
  11. typedef int(__stdcall * PROC)(int); //
  12. PROC proc = (PROC)GetProcAddress(hdll, "setData");
  13. //UpdateData(TRUE);
  14. //e_p2 = L"我正在将第" + j + L"个文件拷贝到缓存区二";
  15. (*proc)(wParam);
  16. Sleep(2000); //沉睡三秒 仿真模拟拷贝过程
  17. event2.PulseEvent();
  18. FreeLibrary(hdll);
  19. e_p2= L"我已经将第" + j + L"个文件拷贝完成";
  20. UpdateData(FALSE); //窗体的编辑框信息刷新显示
  21. return 0;
  22. }

这部分代码,连接了新的动态链接库,将数据写入到缓冲区二,且创建一个让打印进程P3响应的事件SENDDATA2。

3.3 动态链接库设计

首先在动态链接库头文件里声明要用到的函数,以及语言规范。

  1. #pragma once
  2. //此处是对该链接库要实现函数的一些声明
  3. #ifndef _LINK_H
  4. #define _LINK_H
  5. extern "C" { //extern "C"这个是为了说明该链接库符合C的命名规范
  6. int _stdcall getData();
  7. void _stdcall setData(int a);
  8. }
  9. #endif

动态链接库用作缓存区,进行数据中转站,首先要申请一段内存。将所申请内存段,命名为Shared,用于共享。然后具体的函数便在此处实现。

  1. #include "stdafx.h"
  2. #include <Windows.h>
  3. #pragma data_seg("Shared")
  4. int dwID = 0;
  5. #pragma data_seg() //6、7、8三行代码 时说明了一个内存段
  6. #pragma comment(linker,"/SECTION:Shared,RWS") //要求编译器设置一个名为shared的内存段
  7. int _stdcall getData() {
  8. return dwID;
  9. }
  10. void _stdcall setData(int a) {
  11. dwID = a;
  12. }

最后便是在def文件中,对实现的函数进行引出。

  1. LIBRARY LINK
  2. EXPORTS
  3. getData @1
  4. setData @2

同样,动态链接库Link2,大致与此相同,申请的内存段名字不一样即可。内存段名字命名为Shared2。

  1. #pragma data_seg("Shared2")
  2. int dwID = 0;
  3. #pragma data_seg() //6、7、8三行代码 时说明了一个内存段
  4. #pragma comment(linker,"/SECTION:Shared2,RWS") //要求编译器设置一个名为shared的内存段

3.4 P3进程设计代码

P3进程作用便是打印文件,同理,线程Thread.cpp文件代码如下

  1. //数据线程定义
  2. UINT ThreadGet(LPVOID pParam) {
  3. //获取主窗口句柄
  4. HWND hWnd = (HWND)pParam;
  5. //加载链接库
  6. HINSTANCE hdll = ::LoadLibrary(L"Link2.dll");
  7. if (hdll == NULL)
  8. {
  9. MessageBox(NULL, L"文件打开失败", L"注意", MB_OK);
  10. return 0;
  11. }
  12. //定义函数指针
  13. typedef int(__stdcall *PROC)();
  14. PROC proc = (PROC)GetProcAddress(hdll, "getData");
  15. //定义事件 名称相同
  16. CEvent event2(FALSE, FALSE, L"SENDDATA2");
  17. CSemaphore mutex(1, 1, L"SendMutex");
  18. CSemaphore mutex2(0, 1, L"SendMutex2");
  19. while (1)
  20. {
  21. //等待事件
  22. mutex2.Lock();
  23. ::WaitForSingleObject(event2.m_hObject, INFINITE);
  24. //从共享缓存区得到数据
  25. int result = (*proc)();
  26. //发送消息到主线程显示数据
  27. ::SendMessage(hWnd, WM_RESULT2, result, 0);
  28. mutex.Unlock();
  29. //只有进行了这个释放资源的操作 p1进程才能进行再一次的拷贝操作
  30. }
  31. }

该段代码主要是等待P2进程的事件,收到事件后发送消息。同时在等到消息前设置了mutex2.Lock(),之后便是对mutex的释放资源,对信号量进行控制,这样便能保证P3执行完成之前P1不能进行新的打印。等到事件后便和前两者操作一样,在消息队列中映射到onresult函数,然后执行显示打印过程信息操作,onresult函数代码如下

  1. LRESULT CP3Dlg::OnResult(WPARAM wParam, LPARAM lParam) {
  2. CString j;
  3. j.Format(L"%d", wParam);
  4. Sleep(2000); //模拟打印时间
  5. e_p3 = L"我已经将第" + j + L"个文件打印完成,\r\n正在等待下一个文件";
  6. UpdateData(FALSE); //显示打印完成 实现用户交互
  7. return 0;
  8. }

四.程序运行

只打开P1.exe即可三个进程都启动,打开进程后,首先是P1将文件拷贝到缓冲区一,其它两个进程还在等待,如图所示。

进程刚打开时P1进程情况

进程刚打开P2进程在等待

进程刚打开 P3进程在等待

等待几秒后,进程一完成数据拷贝,便触发进程二拷贝,同理进程二完成拷贝到缓冲区二里后,P3打印进程,便开始打印,详情如图:

P1完成数据拷贝

P2完成数据拷贝

P1进程完成打印

P3进行玩打印后,P1便可以进行下一轮的打印操作,设置的定时器在信号量允许情况下,便进行加一操作,放到缓存区。

第二次打印P1进程

第二次打印过程P2进程

第二次打印过程P3进程

如此,根据定时器和信号量以及事件等功能,程序将有序进行,不会发生覆盖造成没及时打印等问题,运行到第六个文件时执行过程。

五.软件测试

5.1 黑盒测试

黑盒测试一下部分设计的功能。程序中设置了信号量,用于同步进程。信号量的设置有效避免了,因未及时得到打印,被新到的文件覆盖的问题。

当三个进程都开着的时候,程序可以有条不紊的进行。然而当进程三停止(模拟还未打印),这时候进程一便不再进行新文件的赋值操作。

三进程协作工作

P3关闭,P1阻塞

5.2 白盒测试

每个进程进行动态链接库加载时,都设置了个判断语句及messagebox,当加载失败时提示用户,文件打开失败。

  1. CEvent event(FALSE, FALSE, L"SENDDATA"); // 建立一个时间
  2. HINSTANCE hdll = ::LoadLibrary(L"Link.dll"); // 加载动态链接库
  3. if(NULL == hdll)
  4. {
  5. ::MessageBox(NULL, L"链接库文件加载失败", L"注意", MB_OK);
  6. }
  7. typedef_int(__stdcall *PROC)(int); // 定义函数指针

上传的附件 cloud_download 基于MFC的操作系统打印进程模拟.7z ( 1.20mb, 1次下载 )
error_outline 下载需要12点积分

发送私信

去奋斗,去追求,去发现,但不要放弃

11
文章数
11
评论数
最近文章
eject