Windows核心编程 进程间通信

目录

进程间通信概述

发送消息 WM_COPYDATA

DLL共享段

文件映射

文件相关API

CreateFile

ReadFile

WriteFile

CloseHandle

SetFilePointerEx 设置文件指针

获取文件大小 GetFileSize

结构体 LARGE_INTEGER

文件映射用于读写文件数据

文件映射用于进程间通信(带文件)

文件映射用于进程间通信(无文件)

管道

父子进程之间的匿名管道通信

GetStdHandle

设置安全属性(子进程继承父进程)

STARTUPINFO指定句柄

模拟CMD


进程间通信概述

进程间通信(Inter-Process Communication,IPC)是指不同进程之间进行数据交换、信息共享、同步互斥等操作的机制。

在 Windows 操作系统中,实现进程间通信的方法有多种,包括但不限于:

  1. 管道(Pipe):管道是一种半双工的通信机制,可以实现两个进程之间的通信,支持同步和异步通信。
  2. 命名管道(Named Pipe):与管道类似,但可以通过命名方式在进程间共享。
  3. 共享内存(Shared Memory):可以在多个进程之间共享数据,可以用来实现高效的数据传输。
  4. 消息队列(Message Queue):提供了异步、无关的进程间通信方式,支持发送和接收消息,每个消息都有一个优先级。
  5. 套接字(Socket):可用于不同计算机间的进程间通信,提供了可靠的数据传输,支持 TCP 和 UDP 协议。
  6. WM_COPYDATA 消息:是一种基于 Windows 消息传递机制的进程间通信方式,可以实现小量数据的传输。
  7. Windows 消息队列(Window Message Queue):Windows 操作系统提供了一种基于消息队列的通信机制,可以在不同的进程间传递消息。它通过SendMessage和PostMessage函数向其他进程发送Windows消息,其他进程则可以通过相应的消息回调函数来处理这些消息。

这些方法各有优缺点,具体应该根据实际场景选择合适的通信方式。

发送消息 WM_COPYDATA

不建议使用 SendMessage 函数发送消息

使用SendMessage函数实现进程间最简单的数据传输。SendMessage是Windows API中的一个函数,它可以将消息发送到指定窗口的消息队列中,并等待接收该消息的线程处理完毕。因为Windows中每个进程都至少拥有一个主窗口,所以可以通过在不同的进程中创建主窗口来实现进程间的通信。

使用 SendMessage 发送消息虽然简单,但也有一些缺陷:

  1. 同步阻塞:SendMessage 函数会阻塞发送消息的进程,直到接收到回应为止。如果接收进程无响应,发送进程就会一直阻塞。这种同步阻塞的方式可能会导致进程之间的通信效率较低。
  2. 只能在同一个桌面窗口之间进行通信:SendMessage 函数只能在同一个桌面窗口中的不同线程之间进行通信,不能跨越不同的桌面窗口进行通信。
  3. 数据大小限制:SendMessage 函数发送的数据大小不能超过 WPARAM 类型的数据大小,即 32 位系统下最大只能传输 4 字节的数据,64 位系统下最大只能传输 8 字节的数据。

WM_COPYDATA是一个Windows消息,用于在进程之间传递数据。它使用的结构体是COPYDATASTRUCT,其中包含了以下三个参数:

  1. dwData:一个可以自定义的数据值,可以用于标识传递的数据类型或其他用途。
  2. cbData:传递数据的字节数,不能超过64KB。
  3. lpData:指向实际数据的指针。

使用:通过SendMessage发送

缺点:

  1. 数据的传输是单向的。
  2. 效率低:从A进程拷贝到B进程 数据拷贝了 2 次。先将数据拷贝到高 2 G内存中,然后再从 高 2 G拷贝到目标进程中(发生两次拷贝,所以效率比较低)
  3. 必须是带有窗口之间才能通信(且必须有标题);

原理:通过系统的高2G内存来达到传输,因为高 2 G的内存是所有引用共享的。

适用场景:数据小,发送频繁等不建议使用,大小不限制。

SendMessage( (HWND) hWnd,              // handle to destination window WM_COPYDATA,              // message to send(WPARAM) wParam,          // handle to window (HWND)(LPARAM) lParam           // data (PCOPYDATASTRUCT));

参数解析:

  1. 目的地窗口句柄
  2. 消息ID
  3. 传递数据的窗口句柄。你也可以不填;
  4. 指向包含要传递的数据的COPYDATASTRUCT结构体的指针。

当一个进程发送WM_COPYDATA消息给另一个进程时,它需要填充COPYDATASTRUCT结构体的这三个参数,并使用SendMessage或PostMessage函数将消息发送给目标进程的窗口句柄。接收进程在处理WM_COPYDATA消息时,可以使用CopyMemory或其他函数从COPYDATASTRUCT结构体中读取传递的数据。

需要注意的是,因为数据是通过拷贝传递的,所以当接收进程修改数据时,不会对发送进程产生影响。另外,由于数据拷贝的性质,WM_COPYDATA在传递大量数据时可能会导致性能问题,因此需要注意数据大小的限制。

typedef struct tagCOPYDATASTRUCT { ULONG_PTR dwData; 		//对缓冲区类型的描述【相当于一个标志】DWORD     cbData; 		//缓冲区地址PVOID     lpData; 		//缓冲区大小  二者合二为一描述一个缓冲区
} COPYDATASTRUCT, *PCOPYDATASTRUCT; 

创建两个工程,一个发送、一个接收

发送端

接收端:

在接收方使用类向导,选择消息,创建WM_COPYDATA消息

BOOL CMFCTestDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{CString strFmt;strFmt.Format("Size:%d,:%s", pCopyDataStruct->cbData,pCopyDataStruct->lpData);AfxMessageBox(strFmt);return CDialogEx::OnCopyData(pWnd, pCopyDataStruct);
}

发送方

void CMFCTestDlg::OnBnClickedButton1()
{//获取编辑框内容CString strText;GetDlgItemText(EDT_SEND, strText);//获取窗口句柄HWND hWnd = ::FindWindow(NULL,"接收标题");COPYDATASTRUCT cds;cds.dwData = NULL;cds.cbData = strText.GetLength()+1;cds.lpData = strText.GetBuffer();::SendMessage(hWnd,WM_COPYDATA,(WPARAM)GetSafeHwnd(),(LPARAM)&cds);
}

使用WM_COPYDATA消息进行进程间通信,我修改A进程的发送数据,B进程接收到数据会受影响吗?
如果A进程通过WM_COPYDATA消息向B进程发送数据,那么B进程接收到的数据不会受到A进程后续的修改影响。因为WM_COPYDATA消息是将A进程的数据拷贝一份并发送给B进程的,之后B进程操作的是自己的一份拷贝,和A进程的数据无关。所以,A进程修改数据不会影响B进程接收到的数据。

其中,发送进程通过发送WM_COPYDATA消息向接收进程发送数据。在接收进程接收到数据后,发生了两次内存拷贝,第一次是将数据拷贝至共享内存中,第二次是从共享内存中读取数据。最后,接收进程向发送进程发送响应消息。WM_COPYDATA可以携带少量数据;效率比较低 ,是因为WM_COPYDATA先将数据拷贝到高2G系统内存中,再从高2G内存中拷贝到目标进程中,发生两次拷贝。

WM_COPYDATA 消息进行进程间通信的主要缺陷有:

  1. 消息大小受限:WM_COPYDATA 消息传递的数据大小受到系统限制,默认为 4MB。如果需要传递更大的数据,就需要将数据分成多个块来传递。
  2. 性能问题:WM_COPYDATA 消息需要进行两次内存拷贝,一次是从发送进程的地址空间复制数据到内核缓冲区,另一次是从内核缓冲区复制数据到接收进程的地址空间,这可能会影响系统性能。
  3. 安全性问题:WM_COPYDATA 消息传递的数据不进行加密,如果传递的数据包含敏感信息,可能会被非法获取。

DLL共享段

当不同的进程加载同一个 DLL 时,该 DLL 的代码和数据将在每个进程的虚拟地址空间中有所不同,但是该 DLL 的所有实例都共享同一份物理内存。这就是所谓的共享 DLL,可以在多个进程中重用。

当一个进程加载一个 DLL 时,该进程会将该 DLL 的实例映射到该进程的虚拟地址空间中。如果多个进程加载同一个 DLL,则每个进程都将其映射到其自己的虚拟地址空间中。虽然每个进程都有自己的虚拟地址空间,但是它们共享同一个物理内存,这样就可以实现共享代码和数据的目的。
共享 DLL 的一个主要好处是节省了内存空间,因为多个进程可以共享同一份物理内存,这可以减少系统资源的消耗。此外,共享 DLL 还可以提高系统的性能和稳定性,因为它们可以在多个进程中重用,而不需要每个进程都加载自己的 DLL 实例。这可以减少系统资源的浪费,提高系统的性能和可靠性。

发消息有局限性,用sendMessage第一个参数限制了只能给带有窗口的进程发送消息,所以用dll共享段。

关于dll,在之前的学习中,如果dll导出一个全局变量,给不同的进程使用的时候,这个全局变量在不同的进程中是不会相互影响的,因为有写时拷贝;dll共享段:允许全局变量在不同的进程之间操作,那时候数据是共享的;

创建了一个包含共享变量和函数的 DLL 文件的基本流程

  1. 创建一个 DLL 文件,其中包含定义在共享段中的变量和函数。
  2. 使用 #pragma data_seg 指令指定变量在共享段中的位置,使用 #pragma comment(linker, “/SECTION:,READ,WRITE,SHARED”) 指令将共享段导出。
    1. 注意:#pragma comment 语句不能有中文空格;
    2. 注意: dll里面可以共享结构体和类,但是不能共享指针;
  3. 编写应用程序,将 DLL 文件加载到内存中。
  4. 在应用程序中定义与 DLL 中共享变量相同的变量,并将其放置在共享段中。
  5. 调用 DLL 中的函数以读取或修改共享变量。

下面是一个示例,其中创建了一个包含共享变量和函数的 DLL 文件,并在应用程序中加载该 DLL,从而实现进程间通信:

1. 定义共享段,并定义导出变量,注意导出需要初始化,未初始化不给实际内存。

#pragma  data_seg("CR40SharedSection") //开始
__declspec(dllexport) DWORD g_dwVal = 0;
#pragma  data_seg()//结束的位置

2. 链接选项将此共享段声明为可共享。

#pragma comment(linker, "/SECTION:CR40SharedSection,RWS")

3. 使用方:加上extern,且不能给值。只是单纯的声明,声明该变量在其他文件中找

__declspec(dllimport) extern DWORD g_dwVal;

在上面的代码中,使用 #pragma data_seg 将** g_nVal** 变量放置在名为 dll_share 的共享段中。使用 #pragma comment(linker, "/SECTION:dll_share ,RWS") 指令将共享段导出,并使其可读、可写和可共享。

dll共享段的变量不能通过监视窗口查看,查看方法:给个中间变量进行中转查看。

使用方

#pragma comment(lib,"DLLShare.lib")
__declspec(dllimport) int g_nVal;
void CMFCTestDlg::OnBnClickedButton1()
{SetDlgItemInt(EDT_SHOW,g_nVal);
}void CMFCTestDlg::OnBnClickedButton2()
{g_nVal = GetDlgItemInt(EDT_WRITE);}

如果全局变量不初始化的话,进程间不共享。
在DLL共享段中,已经初始化为0的全局变量和未初始化的全局变量之间的主要区别在于它们被处理的方式不同。
已经初始化为0的全局变量通常被编译器放在数据段(.data段)中,因此它们在DLL加载到内存时已经被初始化为0。因为它们已经被初始化,所以它们的值可以被不同的进程共享,而且不同进程中的值都是相同的。
未初始化的全局变量通常被编译器放在BSS段(.bss段)中这些变量在程序加载时会被清零。因为它们在程序加载时才被初始化,所以它们的值在不同的进程中是不同的,不能被不同进程共享。
因此,在使用DLL共享段时,我们应该尽可能地将全局变量初始化为0,以确保它们可以被不同进程共享,并且在不同进程中的值都是相同的。

未初始化的全局变量默认值是0,存放在BSS段中是为了节省空间,因为它们的默认值已经是0,可以在程序运行时清零,不需要在程序文件中存储它们的初始值。
已经初始化为0的全局变量,在程序文件中需要存储它们的初始值,所以它们通常会被编译器放在数据段(.data段)中,而不是BSS段。但是,存放在数据段中的全局变量通常不需要在程序运行时进行初始化,因为它们的初始值已经被编译器放在程序文件中了。因此,在程序加载到内存时,数据段中的全局变量已经被初始化为它们的初始值,其中初始化为0的全局变量的值也已经是0了。
需要注意的是,如果在使用DLL共享段时,全局变量初始化为0的话,通常建议将它们放在数据段中,以确保它们可以被不同进程共享,并且在不同进程中的值都是相同的。但是在一些特定的场景中,全局变量初始化为0的话也可以放在BSS段中,这取决于具体的实现方式和编译器设置。

文件映射

进程间通信中的文件映射是一种共享内存的方式,通过将一个文件映射到多个进程的虚拟地址空间,实现进程之间的数据共享。

文件映射是一种进程间通信的机制,可以通过将一个文件映射到多个进程的虚拟地址空间来实现数据的共享。在文件映射中,可以分为有文件和无文件的区别。

有文件的文件映射是指将一个实际的文件映射到多个进程的虚拟地址空间中。这意味着,映射的数据源是一个文件,进程可以通过读取、写入共享内存区域来访问和修改文件的内容。在这种情况下,文件映射的数据是持久的,即文件的内容在进程结束后仍然存在。

无文件的文件映射是指在进程间通信中使用文件映射的机制,但并不依赖于实际的文件。相反,进程可以通过创建一个匿名的文件映射对象,将其映射到多个进程的虚拟地址空间中。在这种情况下,映射的数据源并不是一个实际的文件,而是系统内存中的一块共享内存区域。进程可以通过读取、写入共享内存区域来进行进程间的数据交换。与有文件的文件映射相比,无文件的文件映射的数据是临时的,即在进程结束后会被释放。

无文件的文件映射通常用于临时共享数据、进程间通信等场景,不需要将数据持久保存在文件中。相比起有文件的文件映射,无文件的文件映射更加灵活和高效,因为它不需要文件的读写操作,而是直接在内存中进行数据的交换。然而,无文件的文件映射也需要注意数据同步和共享内存的管理,以确保数据的一致性和安全性。

文件相关API

CreateFile 函数是 Win32 API 中用于打开或创建文件或设备的主要函数之一,还有一些与之相关的函数,如下所示:

  1. OpenFile 函数:与 CreateFile 函数类似,用于打开文件,但是它比 CreateFile 函数更受限制,只能打开一些预定义的文件类型。
  2. ReadFile 函数:用于从文件或设备中读取数据。
  3. WriteFile 函数:用于向文件或设备中写入数据。
  4. CloseHandle 函数:用于关闭已打开的文件或设备句柄。
  5. FlushFileBuffers 函数:用于将文件或设备的缓冲区中的数据刷新到磁盘或设备中。
  6. SetFilePointer 函数:用于设置文件或设备指针的位置。
  7. GetFileSize 函数:用于获取文件或设备的大小。
  8. CreateFileMapping 函数:用于创建文件映射对象,实现进程间共享内存的目的。

CreateFile

HANDLE CreateFile(LPCTSTR lpFileName,    // 文件或设备的名称DWORD dwDesiredAccess, // 访问权限DWORD dwShareMode,     // 共享模式LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全描述符DWORD dwCreationDisposition, // 文件的创建方式DWORD dwFlagsAndAttributes,  // 文件属性HANDLE hTemplateFile  // 模板文件句柄
);

参数说明:

  1. lpFileName:文件名或设备名,可以是一个字符串指针或一个字符数组,表示要创建或打开的文件或设备的名称。可以是绝对路径名,也可以是相对路径名。
  2. dwDesiredAccess:访问权限,是一个32位的无符号整数,指定要对文件或设备进行的访问类型。其值可以是以下常量之一或它们的组合:如果需要打开文件或设备以读取它们的内容,则必须将 dwDesiredAccess 设置为 GENERIC_READ 或 GENERIC_ALL;如果需要向文件或设备写入数据,则必须将其设置为 GENERIC_WRITE 或 GENERIC_ALL;如果需要同时进行读写,则必须将其设置为 GENERIC_READ | GENERIC_WRITE 或 GENERIC_ALL
    • GENERIC_READ:读取文件数据或从设备读取数据。
    • GENERIC_WRITE:向文件写入数据或向设备写入数据。
    • GENERIC_EXECUTE:运行文件或设备。
    • GENERIC_ALL:具有完全访问权限的文件或设备。
  3. dwShareMode:共享模式,是一个32位的无符号整数,指定其他进程可以对文件或设备进行哪些类型的访问。其值可以是以下常量之一或它们的组合:如果要允许其他进程访问文件或设备,则必须将 dwShareMode 设置为 FILE_SHARE_READFILE_SHARE_WRITE 或它们的组合;如果要防止其他进程访问文件或设备,则可以将其设置为 0
    • FILE_SHARE_READ:其他进程可以读取文件或设备。
    • FILE_SHARE_WRITE:其他进程可以写入文件或设备。
    • FILE_SHARE_DELETE:其他进程可以删除文件。
    • 0:不共享文件或设备。
  4. lpSecurityAttributes:安全描述符,是一个指向 SECURITY_ATTRIBUTES 结构体的指针,用于指定文件或设备的安全属性。该参数可以为 NULL,表示不需要安全属性。
  5. dwCreationDisposition:创建方式,是一个32位的无符号整数,指定要创建或打开的文件或设备的行为。其值可以是以下常量之一:通常,如果要创建新文件,则应将 dwCreationDisposition 设置为 CREATE_NEW 或
    • CREATE_NEW:如果文件不存在,则创建文件;如果文件存在,则打开失败。
    • CREATE_ALWAYS:如果文件不存在,则创建文件;如果文件存在,则覆盖原文件。
    • OPEN_EXISTING:如果文件存在,则打开文件;如果文件不存在,则打开失败。
    • OPEN_ALWAYS:如果文件存在,则打开文件;如果文件不存在,则创建文件。
    • TRUNCATE_EXISTING:如果文件存在,则将其截断为零长度;如果文件不存在,则打开失败。

通常,如果要创建新文件,则应将 dwCreationDisposition 设置为 CREATE_NEW 或不完整,以下是剩余的参数解释:

  1. dwFlagsAndAttributes:文件或设备属性,是一个32位的无符号整数,用于指定文件或设备的属性。其值可以是以下常量之一或它们的组合:可以将以上常量按位或组合在一起,以指定文件或设备的多个属性。
    • FILE_ATTRIBUTE_ARCHIVE:文件或目录是存档文件或目录。
    • FILE_ATTRIBUTE_COMPRESSED:文件或目录已压缩。
    • FILE_ATTRIBUTE_DIRECTORY:文件或目录是目录。
    • FILE_ATTRIBUTE_HIDDEN:文件或目录是隐藏的。
    • FILE_ATTRIBUTE_NORMAL:文件或目录没有其他属性。
    • FILE_ATTRIBUTE_READONLY:文件或目录是只读的。
    • FILE_ATTRIBUTE_SYSTEM:文件或目录是系统文件或目录。
  2. hTemplateFile:模板文件句柄,是一个用于指定要创建的文件或设备的模板文件的句柄。该参数通常为 NULL

返回值:如果函数调用成功,则返回一个文件句柄,该句柄可用于读取、写入、关闭、重命名或删除文件。如果函数调用失败,则返回 INVALID_HANDLE_VALUE。函数调用失败后,可以调用 GetLastError() 函数来获取错误代码。

使用CreateFile函数创建文件时,需要注意以下几点:

  • 在创建文件之前,需要确保路径名和文件名的格式是正确的。
  • 在指定访问权限和共享模式时,需要根据实际需求进行设置,以便其他进程能够访问需要共享的部分。
  • 在指定文件的创建方式时,需要考虑文件是否存在,以及是否需要覆盖已有文件。
  • 在指定文件属性时,需要根据实际需求进行设置,例如是否需要将文件设置为只读、隐藏等。
  • 在指定模板文件句柄时,需要确保模板文件的属性和新文件的属性相匹配。

ReadFile

ReadFile 函数用于从文件中读取数据,并将读取的结果存储在指定的缓冲区中。如果读取成功,函数将返回实际读取的字节数,并将该值存储在 lpNumberOfBytesRead 参数中。

BOOL ReadFile(HANDLE       hFile,//文件句柄,即文件的唯一标识符,用于标识需要读取的文件。LPVOID       lpBuffer,//缓冲区指针,指向用于存放读取结果的缓冲区。DWORD        nNumberOfBytesToRead,//要读取的字节数。LPDWORD      lpNumberOfBytesRead,//实际读取的字节数,由函数返回。LPOVERLAPPED lpOverlapped//异步操作参数,用于指定异步操作的相关信息。
);

下面是每个参数的解释:

  • hFile:要读取的文件的句柄。句柄是一个标识文件或其他对象的唯一整数值,它允许操作系统跟踪对象的状态和位置。句柄可以由调用CreateFileOpenFile函数返回。
  • lpBuffer:指向用于接收读取数据的缓冲区的指针。读取的数据将被存储在这个缓冲区中。
  • nNumberOfBytesToRead:要读取的字节数。这个参数指定从文件中读取的字节数,最大为DWORD类型的最大值。
  • lpNumberOfBytesRead:指向实际读取的字节数的指针。这个参数用于返回实际读取的字节数。如果没有读取任何字节,则此参数的值为零。
  • lpOverlapped:指向异步I/O操作的重叠结构体的指针。如果要进行异步I/O操作,则必须传递指向重叠结构体的指针。否则,此参数应为NULL。

总之,ReadFile函数的作用是从指定的文件中读取指定数量的字节,并将读取的数据存储到指定的缓冲区中。如果读取成功,函数将返回TRUE,并且实际读取的字节数将存储在lpNumberOfBytesRead参数中。如果读取失败,则返回FALSE。

返回值

  • 如果函数调用成功,返回值为非零(TRUE)。
  • 如果函数调用失败,返回值为零(FALSE)。此时,可以使用 GetLastError 函数获取错误码

注意事项

  • 在调用 ReadFile 函数之前,需要通过 CreateFile 函数打开需要读取的文件,并获取文件句柄。
  • 要确保缓冲区足够大,能够存储 nNumberOfBytesToRead 个字节的数据。
  • 如果读取的字节数小于 nNumberOfBytesToRead,可能是因为到达了文件末尾或者遇到了错误。此时需要检查 GetLastError 函数返回的错误码来判断出现了什么问题。

WriteFile

WriteFile函数是一个非常常用的函数,用于实现数据的写入操作。它是Windows操作系统中文件和设备操作的重要组成部分,可以帮助我们方便地读写文件和设备,实现数据交换和通信。

BOOL WriteFile(HANDLE hFile,              // 文件或设备对象的句柄LPCVOID lpBuffer,          // 待写入数据的缓冲区DWORD nNumberOfBytesToWrite,// 待写入数据的长度LPDWORD lpNumberOfBytesWritten, // 实际写入的数据长度LPOVERLAPPED lpOverlapped  // 重叠操作结构指针
);

参数说明

  • hFile:指定文件或设备对象的句柄,必须是使用CreateFile函数创建或打开的句柄。
  • lpBuffer:指向待写入数据的缓冲区,可以是一个字符数组、结构体、或者其他类型的数据。
  • nNumberOfBytesToWrite:指定待写入数据的长度,以字节数为单位。
  • lpNumberOfBytesWritten:指向一个DWORD类型的变量,用于接收实际写入的数据长度。
  • lpOverlapped:指向一个OVERLAPPED结构体,用于指定重叠操作的相关信息,通常设置为NULL。

返回值:函数返回一个BOOL类型的值,表示是否成功写入数据。如果函数执行成功,返回值为TRUE,否则返回FALSE。使用WriteFile函数可以向已经打开的文件、设备或管道中写入数据。它可以用于写入二进制数据、文本数据等各种类型的数据

注意事项

  1. 写入的数据不能超过文件或设备的可用空间;
  2. 如果文件或设备已经被其他程序打开,可能会发生访问冲突,需要进行异常处理;
  3. 如果需要进行异步写入操作,需要使用重叠操作和异步I/O技术;
  4. 写入数据时需要保证数据的正确性和完整性。

CloseHandle

BOOL CloseHandle(HANDLE hObject // 待关闭句柄
);

参数说明:

  1. HANDLE hObject:待关闭句柄。该参数是一个HANDLE类型的句柄,指定要关闭的句柄。句柄是操作系统为每个打开的对象分配的唯一标识符,用于引用该对象。
  2. 返回值:BOOL类型,表示是否成功关闭句柄。如果函数执行成功,返回值为TRUE,否则返回值为FALSE。

注意事项

CloseHandle函数只是关闭句柄并释放相关的系统资源,并不会对句柄所代表的对象进行其他的操作。例如,如果关闭了一个文件的句柄,该文件并不会被删除或关闭。因此,在使用CloseHandle函数关闭句柄之前,需要确保相关的对象已经完成了所需的操作。
此外,CloseHandle函数只能关闭通过CreateFile等打开对象的句柄。如果使用其他方式获得了句柄,例如使用malloc函数分配内存后得到的指针,这些句柄不能用CloseHandle函数关闭。
总之,CloseHandle函数是Windows操作系统中一个重要的API函数,用于关闭一个句柄并释放相关的系统资源。我们应该在使用完句柄后及时调用CloseHandle函数,以确保程序的正常运行。

SetFilePointerEx 设置文件指针

文件指针 SetFilePointer/SetFilePointerEx	【可以指定大于4G的文件】
DWORD SetFilePointer(  HANDLE hFile,                // handle to fileLONG lDistanceToMove,        // 低 4G 地址PLONG lpDistanceToMoveHigh,  // 高 4G 地址,两个DWORD拼接成一个32位DWORD dwMoveMethod           // 文件指针偏移【或从标志中选择】
);
=============================== 设置文件指针 =====================================
BOOL SetFilePointerEx(  HANDLE hFile,                    // handle to fileLARGE_INTEGER liDistanceToMove,  // 64位的地址值PLARGE_INTEGER lpNewFilePointer, // new file pointerDWORD dwMoveMethod               // starting point);   

获取文件大小 GetFileSize

===================== 获取文件大小 GetFileSize/GetFileSizeEx =====================
BOOL GetFileSizeEx(  HANDLE hFile,              // handle to filePLARGE_INTEGER lpFileSize  // file size
);
DWORD GetFileSize(  HANDLE hFile,           // handle to fileLPDWORD lpFileSizeHigh  // high-order word of file size
); 

 

 

结构体 LARGE_INTEGER

=============================== 结构体 LARGE_INTEGER ===============================
PS:共用体,可以直接用QuadPart,同时也可以拿高8位和低8位
typedef union _LARGE_INTEGER { struct {DWORD LowPart; LONG  HighPart; };LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER; 

测试代码

// FileOpt.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//#include <iostream>
#include <Windows.h>
using namespace std;
int main()
{HANDLE hFile = CreateFile(R"(E:\CR41\第2阶段\Windows\07-进程间通信\FIleMap\test.txt)",GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL,                  //安全属性OPEN_EXISTING,         //打开已经存在的文件FILE_ATTRIBUTE_NORMAL, //文件属性 默认NULL);if (hFile == INVALID_HANDLE_VALUE){cout << "打开文件失败" << endl;return 0;}char aryBuf[MAXBYTE] = {};DWORD dwBytesReaded = 0;BOOL bRet = ReadFile(hFile,aryBuf,sizeof(aryBuf),&dwBytesReaded,NULL);if (!bRet){cout << "读取文件失败" << endl;}DWORD dwRet = SetFilePointer(hFile, 0x2000, NULL, FILE_BEGIN);if (dwRet == INVALID_SET_FILE_POINTER){cout << "移动文件指针失败" << endl;}BYTE aryBufForWrite[] = { "hello world file hahahahaha." };DWORD dwBytesWrited = 0;bRet = WriteFile(hFile,aryBufForWrite,sizeof(aryBufForWrite),&dwBytesWrited,NULL);if (!bRet){cout << "写入文件失败" << endl;}//关闭文件CloseHandle(hFile);return 0;
}     

文件映射用于读写文件数据

使用文件映射(File Mapping)操作文件的具体步骤如下:

  1. 打开文件:首先,需要打开需要操作的文件,可以使用标准的文件操作函数,如CreateFile等来打开文件。
  2. 创建文件映射对象:使用CreateFileMapping函数创建一个文件映射对象,该函数会返回一个句柄,该句柄可以用于后续的文件映射操作。
  3. 映射文件到内存:使用MapViewOfFile函数将文件映射到内存中,该函数也会返回一个指针,该指针指向文件在内存中的起始位置。
  4. 执行读写操作:通过操作内存中的数据来进行读写操作,内存中的数据会自动同步到文件中。
  5. 取消文件映射:使用UnmapViewOfFile函数取消文件映射,释放内存空间。
  6. 关闭文件句柄:使用CloseHandle函数关闭文件句柄和文件映射对象句柄。
============================ CreateFileMapping ==============================
HANDLE CreateFileMapping(  HANDLE hFile,                       // handle to fileLPSECURITY_ATTRIBUTES lpAttributes, // 安全属性DWORD flProtect,                    // 保护属性,从表中选【对象用途】DWORD dwMaximumSizeHigh,            // high-order DWORD of size【通常填 0】DWORD dwMaximumSizeLow,             // low-order DWORD of size 【通常填 0】LPCTSTR lpName                      // 进程间共享就填值,否则填NULL【映射对象的名字,用于打开映射对象】
);
功能:创建一个文件映射对象。参数:
hFile:要映射的文件句柄。
lpAttributes:安全性属性,用于控制该文件映射对象的访问权限。
flProtect:访问保护方式,可以是 PAGE_READONLY、PAGE_READWRITE 等。
dwMaximumSizeHigh 和 dwMaximumSizeLow:文件映射对象的最大大小,以字节为单位。可以使用GetFileSize函数获取文件的大小。
lpName:文件映射对象的名称,可以为 NULL。
返回值:如果函数执行成功,则返回文件映射对象的句柄;如果执行失败,则返回 NULL。dwMaximumSizeHigh 和 dwMaximumSizeLow 填 0 文件多大,文件映射对象就有多大。--------------------------- 保护属性表 ----------------------------------
PAGE_READONLY 只读
PAGE_READWRITE 读写--------------------------- 保护属性表 ----------------------------------================================ MapViewOfFile =============================LPVOID MapViewOfFile(HANDLE hFileMappingObject,   //文件映射对象句柄DWORD dwDesiredAccess,       // 映射进内存的保护属性【读写,可读可写】DWORD dwFileOffsetHigh,      //从文件的哪个位置开始映射,两个DWORD拼接成一个QWordDWORD dwFileOffsetLow,       // SIZE_T dwNumberOfBytesToMap  // 指定映射文件的大小
);
功能:将一个文件映射到进程的地址空间。参数:hFileMappingObject:文件映射对象的句柄。
dwDesiredAccess:访问权限,可以是 FILE_MAP_READ(读取权限)、FILE_MAP_WRITE(写入权限)等。
dwFileOffsetHigh 和 dwFileOffsetLow:文件偏移量,表示文件映射的起始位置,以字节为单位。
dwNumberOfBytesToMap:映射的字节数。
lpBaseAddress:指向映射的起始地址。如果指定为 NULL,则由系统自动分配地址。
返回值:如果函数执行成功,则返回映射视图的指针;如果执行失败,则返回 NULL。   
--------------------------- 内存属性表 ----------------------------------
FILE_MAP_WRITE 	 	写(可读可写:只需给写)
FILE_MAP_READ	 	读
FILE_MAP_ALL_ACCESS 所有 = FILE_MAP_WRITE
FILE_MAP_COPY 		拷贝=============================== OpenFileMapping ===============================
HANDLE OpenFileMapping(DWORD dwDesiredAccess,  // access modeBOOL bInheritHandle,    // 是否继承句柄LPCTSTR lpName          // 打开已经映射到内存的对象【必须是已经映射的对象名称】
);BOOL UnmapViewOfFile( LPCVOID lpBaseAddress ); // 映射视图的首地址,通过Map
ViewOfFile获取。
=============================== UnmapViewOfFile ===============================
BOOL UnmapViewOfFile( LPCVOID lpBaseAddress ); // 映射视图的首地址,通过MapViewOfFile获取。

进程最大内存是4G ,但是3 环的最大内存从理论讲是2G,当文件大于2G的时候,就不够了,所以需要将文件一段一段的映射,这段内存将由我们指定。

测试代码

HANDLE hFile = CreateFile("E:\\CR40\\windows\\05\\Dll共享段\\Debug\\Use.exe",GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,//共享读NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);shared_ptr<HANDLE> pFileHandle(&hFile, [](HANDLE hFile) {CloseHandle(hFile); });if (pFileHandle.get() == INVALID_HANDLE_VALUE){std::cout << "打开文件失败" << std::endl;return 0;}//创建文件映射对象HANDLE hFileMap = CreateFileMapping(hFile, NULL,PAGE_READWRITE,0, 0,//整个文件"CR40SharedMappingFile");shared_ptr<HANDLE> pFileMap(&hFileMap, [](HANDLE hFileMap) {CloseHandle(hFileMap); });if (pFileMap.get() == NULL){std::cout << "创建文件映射对象失败" << std::endl;return 0;}LPVOID pBuff = MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS, //可读可写0, 0, //从文件头开始0x1000);//映射0x1000到内存if (pBuff == NULL){std::cout << "创建文件映射对象失败" << std::endl;return 0;}//使用示例:打开同一个文件。A修改,B也被修改,B修改,A里面也被修改。//取消映射UnmapViewOfFile(pBuff);//由于使用了智能指针,所以不需要手动释放。

文件映射用于进程间通信(带文件)

将A进程已经映射进虚拟内存,B通过打开映射对象对齐进行操作使用。
PS:安全属性需注意,容易出错。

A方: A创建文件映射对象,第五个参数需要填写文件对象名称。
B方:

  1. 打开文件已经映射的对象`CreateFileMapping/OpenFileMapping`2. 将文件映射到内存`MapViewOfFile`3. 使用4. 将文件从内存撤销映射`UnmapViewOfFile`5. 关闭文件映射对象`Closehandle`6. 关闭文件`CloseHandle`

测试代码

=======================================================A 方==============================================//打开文件//创建映射文件对象,注意需要填写映射对象名称,方便B方使用//将文件对象映射进内存//对文件操作//撤销映射等反初始化操作=======================================================B方 ===============================================//打开文件映射对象HANDLE hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "CR40SharedMappingFile");shared_ptr<HANDLE> pFileMap(&hFileMap, [](HANDLE hFileMap) {CloseHandle(hFileMap); });	//智能指针。if (pFileMap.get() == NULL){std::cout << "创建文件映射对象失败" << std::endl;return 0;}//将对象指的文件映射进内存LPVOID pBuff = MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS, //可读可写0, 0, //从文件头开始0x1000);//映射0x1000到内存if (pBuff == NULL){std::cout << "创建文件映射对象失败" << std::endl;return 0;}//使用//取消映射UnmapViewOfFile(pBuff);

文件映射用于进程间通信(无文件)

创建步骤:

  1. 创建文件映射对象`CreateFileMapping`
  2. 将文件映射到内存`MapViewOfFile`
  3. 使用。。。。
  4. 将文件从内存撤销映射`UnmapViewOfFile`
  5. 关闭文件映射对象`Closehandle`
  6. 关闭文件`CloseHandle`

使用步骤:

  1. 打开文件映射对象CreateFileMapping/OpenFileMapping
  2. 将文件映射到内存MapViewOfFile
  3. 使用。。。。
  4. 将文件从内存撤销映射UnmapViewOfFile
  5. 关闭文件映射对象Closehandle
  6. 关闭文件CloseHandle

测试代码

=======================================================A 方==============================================
唯一区别:创建文件映射对象时候的参数不同//创建文件映射对象HANDLE hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,		//保护属性0, 0x1000,			//注意点:必须添值"CR40SharedMappingFile");if (hFileMap == NULL){std::cout << "创建文件映射对象失败" << std::endl;return 0;}LPVOID pBuff = MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS, //可读可写0, 0, //从文件头开始0x1000);//映射0x1000到内存if (pBuff == NULL){std::cout << "创建文件映射对象失败" << std::endl;return 0;}//使用//取消映射UnmapViewOfFile(pBuff);return 0;=======================================================B方 ===============================================//打开文件映射对象HANDLE hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "CR40SharedMappingFile");shared_ptr<HANDLE> pFileMap(&hFileMap, [](HANDLE hFileMap) {CloseHandle(hFileMap); });	//智能指针。if (pFileMap.get() == NULL){std::cout << "创建文件映射对象失败" << std::endl;return 0;}//将对象指的文件映射进内存LPVOID pBuff = MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS, //可读可写0, 0, //从文件头开始0x1000);//映射0x1000到内存if (pBuff == NULL){std::cout << "创建文件映射对象失败" << std::endl;return 0;}//使用//取消映射UnmapViewOfFile(pBuff);

管道

概念:可跨进程的队列。实现进程之间的数据传输。

种类:命名管道,匿名管道。

命名管道:主要用于服务器。【双向传输】

匿名管道:主要用于父子进程之间的数据传输。【单向传输】

命名管道:命名管道是一种有名字的管道,它可以被多个进程同时使用。命名管道在创建时必须指定一个唯一的名称,其他进程可以通过该名称来访问管道。命名管道使用CreateNamedPipe()函数创建,使用CreateFile()函数打开。命名管道适用于本地进程之间的通信和远程进程之间的通信。

匿名管道:匿名管道是一种无名的管道,它只能被创建它的进程和它的子进程使用。匿名管道使用CreatePipe()函数创建,它返回两个句柄,一个是读句柄,一个是写句柄。这两个句柄只能被创建它们的进程和它的子进程使用。

创建匿名管道,首先你要明白什么是管道. 管道你可以想象成一个管子。我们通过这个管子发送数据.

通过上图,我们就知道其实创建了两个管道,分别是父进程读取的管道以及子进程读取的管道,相应的子进程也可以对父进程读取的管道进行传输数据,父进程就可以读取了这段话可能难以理解,你可以这样想,我父进程读取子进程使用第一个管道,那么反过来正子进程的话也是使用第一个管道,因为子进程写,我们父进程才能读。

相关API


BOOL CreatePipe(PHANDLE               hReadPipe,管道读取句柄PHANDLE               hWritePipe,管道写入矩形LPSECURITY_ATTRIBUTES lpPipeAttributes,	安全属性结构体,决定是否能够继承管道DWORD                 nSize,填NULL,使用默认缓冲区大小
);hReadPipe:一个指向用于保存管道的读端句柄的指针。
hWritePipe:一个指向用于保存管道的写端句柄的指针。
lpPipeAttributes:用于设置管道的安全属性,一般为 NULL,表示使用默认安全属性。
nSize:用于设置管道的缓冲区大小,一般为 0,表示使用系统默认值。功能:为各种函数创建对象提供安全设置。
例如CreateFile、CreatePipe、CreateProcess、RegCreateKeyEx或RegSaveKeyEx。
typedef struct _SECURITY_ATTRIBUTES {DWORD  nLength;					//此结构体大小,必填LPVOID lpSecurityDescriptor;		//BOOL   bInheritHandle;			//决定是否能够被继承
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;读:`ReadFile`写:`WriteFile`获取句柄:`GetStdHandle`
HANDLE GetStdHandle(  DWORD nStdHandle   // input, output, or error device
);STD_INPUT_HANDLE 标准输入
STD_OUTPUT_HANDLE 标准输出
STD_ERROR_HANDLE 标准错误 查看管道是否有数据可读:`PeekNamedPipe`
BOOL PeekNamedPipe(  HANDLE hNamedPipe,              // handle to pipeLPVOID lpBuffer,                // data bufferDWORD nBufferSize,              // size of data bufferLPDWORD lpBytesRead,            // number of bytes readLPDWORD lpTotalBytesAvail,      // number of bytes availableLPDWORD lpBytesLeftThisMessage  // unread bytes
);
hNamedPipe:一个命名管道或匿名管道的句柄。
lpBuffer:一个指向缓冲区的指针,用于存储从管道中读取的数据。如果为 NULL,则不读取数据。
nBufferSize:缓冲区的大小。
lpBytesRead:一个指向变量的指针,用于存储已读取的字节数。
lpTotalBytesAvail:一个指向变量的指针,用于存储当前管道中可用的字节数。
lpBytesLeftThisMessage:一个指向变量的指针,用于存储当前消息中剩余的字节数。该参数只对消息式管道有效,对字节流式管道没有意义。

CreatePipe函数创建一个匿名管道并返回两个句柄:管道的读取句柄和管道的写入句柄。读取句柄对管道具有只读访问权限,并且写入句柄对管道具有只写访问权限。若要使用管道进行通信,管道服务器必须将管道句柄传递到另一个进程。 通常,这是通过继承完成的;也就是说,进程允许子进程继承句柄。 该过程还可以使用 DuplicateHandle 函数复制管道句柄,并使用某种形式的进程间通信(例如 DDE 或共享内存)将其发送到无关的进程。

管道服务器可以将读取句柄或写入句柄发送到管道客户端,具体情况取决于客户端是否应使用匿名管道来发送信息或接收信息。 若要从管道读取,请在调用 ReadFile 函数时使用管道的读取句柄。 当另一个进程写入管道时, ReadFile 调用返回。 如果管道的所有写入句柄已关闭,或在完成读取操作之前发生错误,则 ReadFile 调用也可以返回。

若要写入管道,请在调用 WriteFile 函数时使用管道的写入句柄。 WriteFile 调用在向管道写入指定的字节数或发生错误之前,不会返回。 如果管道缓冲区已满,并且有更多的字节需要写入,则 WriteFile 不会返回,直到另一个进程从管道中读取内容,从而提供更多的缓冲区空间。 管道服务器在调用 CreatePipe时指定管道的缓冲区大小。

匿名管道不支持异步 (重叠) 读和写操作。 这意味着不能将 ReadFileEx 和 WriteFileEx 函数与匿名管道一起使用。 此外,当将这些函数与匿名管道一起使用时,将忽略 ReadFile和 WriteFile的 lpOverlapped 参数。

匿名管道存在,直到所有管道句柄都已关闭。 进程可以使用 CloseHandle 函数关闭其管道句柄。进程终止时,所有管道句柄也会关闭。使用具有唯一名称的命名管道实现匿名管道。 因此,通常可以将句柄传递给需要命名管道的句柄的函数。

测试代码

//创建管道
void CMyTestPipDlg::OnClickedBtnCreate()
{SECURITY_ATTRIBUTES sa = {};sa.nLength = sizeof(sa);sa.bInheritHandle = TRUE;if (!CreatePipe(&m_hRead, &m_hWrite, &sa, 0)){AfxMessageBox("管道创建失败");}
}//写按钮
void CMyTestPipDlg::OnClickedBtnWrite()
{CString str;GetDlgItemText(EDT_WRITE, str);DWORD dwBytesWrited = 0;if (!WriteFile(m_hWrite, str.GetBuffer(), str.GetLength(), &dwBytesWrited, NULL)){AfxMessageBox("管道写入失败");}
}
//读按钮
void CMyTestPipDlg::OnClickedBtnRead()
{
//检查管道中是否有数据可读DWORD dwBytesAvail = 0;if (!PeekNamedPipe(m_hRead, NULL, 0, NULL, &dwBytesAvail, NULL)){return;}//如果管道中有数据,则读出if (dwBytesAvail > 0){CString str;DWORD dwBytesRead = 0;if (!ReadFile(m_hRead,str.GetBufferSetLength(dwBytesAvail), dwBytesAvail, &dwBytesRead,NULL)){AfxMessageBox("管道读取失败");}str.ReleaseBuffer(dwBytesRead);SetDlgItemText(EDT_READ, str);}
}

管道读取被阻塞的问题: 

  1. 在父进程读取管道的操作之前,先往管道中写入一些数据,这样可以确保在读取操作执行时,管道中已经有数据可以读取。
  2. 使用非阻塞读取操作,这样可以避免读取操作被阻塞。可以使用 Windows API 函数 PeekNamedPipe() 来查询管道中是否有数据可以读取。如果返回值为 0,表示管道中没有数据可以读取;如果返回值不为 0,则可以调用 ReadFile() 函数来读取管道中的数据。
  3. 在父进程中创建一个线程,让这个线程负责读取管道中的数据。这样可以避免读取操作阻塞主线程,从而避免程序无响应的问题。可以使用 Windows API 函数 CreateThread() 来创建线程,然后在线程中调用 ReadFile() 函数来读取管道中的数据。同时,主线程可以继续执行其他操作,不必等待管道中的数据读取完成。

父子进程之间的匿名管道通信

基本步骤:

  1. 获取标准输入输出的句柄
  2. 继承句柄
  3. 创建管道的时候;
  4. 创建子窗口的时候;
  5. 创建进程时需要STARTUPINFO指定句柄

GetStdHandle

GetStdHandle函数获取标准输入、标准输出或标准错误设备的句柄。

参数:指定要为其返回句柄的标准设备。该参数可以是以下值之一

  • STD_INPUT_HANDLE——标准输入处理
  • STD_OUTPUT_HANDLE——标准输出处理
  • STD_ERROR_HANDLE——标准错误处理

设置安全属性(子进程继承父进程)

两个地方的继承属性设置为TRUE:

  • 创建管道的时候;
  • 创建子窗口的时候;
//创建管道
void CMFCTestDlg::OnBnClickedCreate()
{SECURITY_ATTRIBUTES sa = {};sa.nLength = sizeof(sa);sa.bInheritHandle = TRUE; if (!CreatePipe(&m_hRead, &m_hWrite, &sa, 0)){AfxMessageBox("管道创建失败");}
}

STARTUPINFO指定句柄

结构体中下图中三个参数:

测试代码如下

父进程源码

void CMFCTestDlg::OnBnClickedCreate()
{SECURITY_ATTRIBUTES sa = {};sa.nLength = sizeof(sa);sa.bInheritHandle = TRUE;if (CreatePipe(&m_hChildRead, &m_hParentWrite, &sa, 0)){AfxMessageBox("创建管道成功");}else{AfxMessageBox("创建失败");}if (CreatePipe(&m_hParentRead, &m_hChildWrite, &sa, 0)){AfxMessageBox("创建管道成功");}else{AfxMessageBox("创建失败");}//创建子进程STARTUPINFO si;PROCESS_INFORMATION pi;ZeroMemory(&si, sizeof(si));si.cb = sizeof(si);si.dwFlags = STARTF_USESTDHANDLES; //使用句柄si.hStdInput = m_hChildRead;si.hStdOutput = m_hChildWrite;si.hStdError = m_hChildWrite;ZeroMemory(&pi, sizeof(pi));// Start the child process. if (!CreateProcess(NULL, // No module name (use command line). "MyChildProcess", // Command line. NULL,             // Process handle not inheritable. NULL,             // Thread handle not inheritable. TRUE,            // Set handle inheritance to FALSE. 0,                // No creation flags. NULL,             // Use parent's environment block. NULL,             // Use parent's starting directory. &si,              // Pointer to STARTUPINFO structure.&pi)             // Pointer to PROCESS_INFORMATION structure.){AfxMessageBox("CreateProcess failed.");}else{AfxMessageBox("CreateProcess sucess.");}// Close process and thread handles. CloseHandle(pi.hProcess);CloseHandle(pi.hThread);}void CMFCTestDlg::OnBnClickedRead()
{//判断管道中是否有数据可读DWORD dwBytesAvail = 0;if (PeekNamedPipe(m_hParentRead, NULL, 0, NULL, &dwBytesAvail, NULL)){if (dwBytesAvail > 0){//从管道读取数据char szBuff[MAXBYTE] = {};DWORD dwBytesReaded = 0;BOOL bRet = ReadFile(m_hParentRead, szBuff, dwBytesAvail, &dwBytesReaded, NULL);if (!bRet){AfxMessageBox("读取文件失败");}//显示到界面SetDlgItemText(EDT_SHOW, szBuff);}}}
void CMFCTestDlg::OnBnClickedWrite()
{//获取数据CString strData;this->GetDlgItemText(EDT_DATA, strData);//从管道写入数据DWORD dwBytesReaded = 0;BOOL bRet = WriteFile(m_hParentWrite, strData.GetBuffer(), strData.GetLength(), &dwBytesReaded, NULL);if (!bRet){AfxMessageBox("写入文件失败");}}

子进程源码

void CMFCTestDlg::OnBnClickedRead()
{//获取父进程句柄 GetstdHandleHANDLE hRead = GetStdHandle(STD_INPUT_HANDLE);//判断管道中是否有数据可读DWORD dwBytesAvail = 0;if (PeekNamedPipe(hRead, NULL, 0, NULL, &dwBytesAvail, NULL)){if (dwBytesAvail > 0){//从管道读取数据char szBuff[MAXBYTE] = {};DWORD dwBytesReaded = 0;BOOL bRet = ReadFile(hRead, szBuff, dwBytesAvail, &dwBytesReaded, NULL);if (!bRet){AfxMessageBox("读取文件失败");}//显示到界面SetDlgItemText(EDT_SHOW, szBuff);}}
}void CMFCTestDlg::OnBnClickedWrite()
{//获取父进程句柄 GetstdHandleHANDLE hWrite = GetStdHandle(STD_OUTPUT_HANDLE);//获取数据CString strData;this->GetDlgItemText(EDT_DATA, strData);//从管道写入数据DWORD dwBytesReaded = 0;BOOL bRet = WriteFile(hWrite, strData.GetBuffer(), strData.GetLength(), &dwBytesReaded, NULL);if (!bRet){AfxMessageBox("写入文件失败");}
}

管道创建与使用

1. 创建管道,并传给子进程
void CParentDlg::OnBnClickedButton1()
{//管道的读写句柄,一般作为成员变量HANDLE m_hRead = NULL;HANDLE m_hWrite = NULL;//创建管道SECURITY_ATTRIBUTES sa = {};	//管道安全属性结构体sa.nLength = sizeof(sa);sa.bInheritHandle = TRUE;		//指定该管道句柄可以继承BOOL bRet = ::CreatePipe(&m_hRead, &m_hWrite, &sa, 0);	//使用默认缓冲区大小if (!bRet){AfxMessageBox("创建管道失败");return;}//创建子进程STARTUPINFO si = {};si.cb = sizeof(si);si.dwFlags = STARTF_USESTDHANDLES;	//指明标准输入输出句柄si.hStdInput = m_hRead;				//给将继承的输入句柄赋值PROCESS_INFORMATION pi;				//ZeroMemory(&pi, sizeof(pi));		//等同于 PROCESS_INFORMATION pi = {};// Start the child process. if (!CreateProcess(NULL, // No module name (use command line). "Child.exe",		// Command line. NULL,             // Process handle not inheritable. NULL,             // Thread handle not inheritable. TRUE,            // Set handle inheritance to FALSE. 0,                // No creation flags. NULL,             // Use parent's environment block. NULL,             // Use parent's starting directory. &si,              // Pointer to STARTUPINFO structure.&pi)             // Pointer to PROCESS_INFORMATION structure.){AfxMessageBox("CreateProcess failed.");return;}CloseHandle(pi.hProcess);	//释放进程句柄CloseHandle(pi.hThread);	//释放线程句柄
}====================== 向管道写入数据【WriteFile】 =====================
3. 通过写入句柄向管道写入数据
void CParentDlg::OnBnClickedButton2()
{CString strBuf;GetDlgItemText(EDT_WRITE, strBuf);BOOL bRet = WriteFile(m_hWrite, strBuf.GetBuffer(), strBuf.GetLength(), NULL, NULL);if (!bRet){AfxMessageBox("写入管道失败");}
}=======================子进程的读取和写入 ======================void CChildDlg::OnBnClickedButton1()
{//1.获取管道读取或泽写入句柄HANDLE hInput = ::GetStdHandle(STD_INPUT_HANDLE);DWORD dwBytesAvail = 0;//2. 检查管道内是否有数据。BOOL bRet = ::PeekNamedPipe(hInput, NULL, 0, NULL, &dwBytesAvail, NULL);if (!bRet){AfxMessageBox("无法查看管道剩余数据");return;}//3. 管道中有数据再读if (dwBytesAvail > 0){CString strBuf;DWORD dwBytesToRead = 0;BOOL bRet = ::ReadFile(hInput,strBuf.GetBufferSetLength(MAXBYTE),MAXBYTE,&dwBytesToRead,NULL);if (!bRet){AfxMessageBox("读取数据失败");return;}strBuf.ReleaseBuffer(dwBytesToRead);SetDlgItemText(EDT_READ, strBuf);}
}  

模拟CMD

void CMFCTestDlg::OnBnClickedCreate()
{SECURITY_ATTRIBUTES sa = {};sa.nLength = sizeof(sa);sa.bInheritHandle = TRUE;if (!CreatePipe(&m_hChildRead, &m_hParentWrite, &sa, 0)){AfxMessageBox("创建失败");}if (!CreatePipe(&m_hParentRead, &m_hChildWrite, &sa, 0)){AfxMessageBox("创建失败");}//创建子进程STARTUPINFO si;PROCESS_INFORMATION pi;ZeroMemory(&si, sizeof(si));si.cb = sizeof(si);si.dwFlags = STARTF_USESTDHANDLES; //使用句柄si.hStdInput = m_hChildRead;si.hStdOutput = m_hChildWrite;si.hStdError = m_hChildWrite;ZeroMemory(&pi, sizeof(pi));// Start the child process. if (!CreateProcess(NULL, // No module name (use command line). "cmd.exe", // Command line. NULL,             // Process handle not inheritable. NULL,             // Thread handle not inheritable. TRUE,            // Set handle inheritance to FALSE. CREATE_NO_WINDOW,  // No creation flags. NULL,             // Use parent's environment block. NULL,             // Use parent's starting directory. &si,              // Pointer to STARTUPINFO structure.&pi)             // Pointer to PROCESS_INFORMATION structure.){AfxMessageBox("CreateProcess failed.");}// Close process and thread handles. CloseHandle(pi.hProcess);CloseHandle(pi.hThread);}void CMFCTestDlg::OnBnClickedRead()
{//判断管道中是否有数据可读DWORD dwBytesAvail = 0;if (PeekNamedPipe(m_hParentRead, NULL, 0, NULL, &dwBytesAvail, NULL)){if (dwBytesAvail > 0){//从管道读取数据char szBuff[MAXBYTE] = {};DWORD dwBytesReaded = 0;BOOL bRet = ReadFile(m_hParentRead, szBuff, sizeof(szBuff)-1, &dwBytesReaded, NULL);if (!bRet){AfxMessageBox("读取文件失败");}//显示到界面m_strOutData += szBuff;SetDlgItemText(EDT_SHOW, m_strOutData);}}}
void CMFCTestDlg::OnBnClickedWrite()
{//获取数据CString strData;this->GetDlgItemText(EDT_DATA, strData);strData += "\r\n";//从管道写入数据DWORD dwBytesReaded = 0;BOOL bRet = WriteFile(m_hParentWrite, strData.GetBuffer(), strData.GetLength(), &dwBytesReaded, NULL);if (!bRet){AfxMessageBox("写入文件失败");}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/216327.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python中生成随机数技巧与案例

在Python中生成随机数有几种方式&#xff0c;其中常用的方式包括使用random模块、numpy库以及secrets模块。这里给你举例说明一下&#xff1a; random模块 import random random_floatrandom.random() print(f生成一个0-1的随机浮点数:{random_float})random_intrandom.randin…

递归算法学习——二叉树的伪回文路径

1&#xff0c;题目 给你一棵二叉树&#xff0c;每个节点的值为 1 到 9 。我们称二叉树中的一条路径是 「伪回文」的&#xff0c;当它满足&#xff1a;路径经过的所有节点值的排列中&#xff0c;存在一个回文序列。 请你返回从根到叶子节点的所有路径中 伪回文 路径的数目。 示例…

两巨头Facebook 和 GitHub 联手推出 Atom-IDE

9月13日&#xff0c;GitHub 宣布与 Facebook 合作推出了 Atom-IDE —— 它包括一系列将类 IDE 功能带到 Atom 的可选工具包。初次发布的版本包括更智能、感知上下文的自动完成&#xff1b;导航功能&#xff0c;如大纲视图和定义跳转(outline view and goto-definition)&#xf…

docker部署phpIPAM

0说明 IPAM&#xff1a;IP地址管理系统 IP地址管理(IPAM)是指的一种方法IP扫描&#xff0c;IP地址跟踪和管理与网络相关的信息的互联网协议地址空间和IPAM系统。 IPAM软件和IP的工具,管理员可以确保分配IP地址仍然是当前和足够的库存先进的IP工具和IPAM服务。 IPAM简化并自动化…

详解STUN与TR111

STUN协议定义了三类测试过程来检测NAT类型&#xff1a; Test1&#xff1a;STUN Client通过端口{IP-C1:Port-C1}向STUN Server{IP-S1:Port-S1}发送一个Binding Request&#xff08;没有设置任何属性&#xff09;。STUN Server收到该请求后&#xff0c;通过端口{IP-S1:Port-S1}把…

【Proteus仿真】【STM32单片机】智能垃圾桶设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用报警模块、LCD1602液晶模块、按键模块、人体红外传感器、HCSR04超声波、有害气体传感器、SG90舵机等。 主要功能&#xff1a; 系统运行后&…

常见树种(贵州省):017柳树、喜树、珙桐、木棉、楝、枫杨、竹柏、百日青、翅荚香槐、皂荚、灯台树

摘要&#xff1a;本专栏树种介绍图片来源于PPBC中国植物图像库&#xff08;下附网址&#xff09;&#xff0c;本文整理仅做交流学习使用&#xff0c;同时便于查找&#xff0c;如有侵权请联系删除。 图片网址&#xff1a;PPBC中国植物图像库——最大的植物分类图片库 一、柳树 …

毅速:3D打印随形透气钢为解决模具困气提供了新助力

在模具行业中&#xff0c;困气是一个较常见的问题。解决困气问题的方法有很多&#xff0c;透气钢就是其一。传统的制造的透气钢往往存在一些不足&#xff0c;如加工难度大、无法满足复杂形状的需求等。随着3D打印技术的发展&#xff0c;一种新型的随形透气钢技术逐渐崭露头角&a…

ZC-OFDM模糊函数原理及仿真

文章目录 前言一、ZC 序列二、ZC-OFDM 信号1、OFDM 信号表达式2、模糊函数表达式三、MATLAB 仿真1、MATLAB 核心源码2、仿真结果①、ZC-OFDM 模糊函数②、ZC-OFDM 距离分辨率③、ZC-OFDM 速度分辨率前言 本文进行 ZC-OFDM 的原理讲解及仿真,首先看一下 ZC-OFDM 的模糊函数仿真…

2023.11.25-istio安全

目录 文章目录 目录本节实战1、安全概述2、证书签发流程1.签发证书2.身份认证 3、认证1.对等认证a.默认的宽容模式b.全局严格 mTLS 模式c.命名空间级别策略d.为每个工作负载启用双向 TLS 2.请求认证a.JWK 与 JWKS 概述b.配置 JWT 终端用户认证c.设置强制认证规则 关于我最后 本…

Ceph分布式存储系统的介绍及详细安装部署过程:详细实战版(保姆级)

Ceph简介 Ceph是一个统一的分布式存储系统&#xff0c;设计初衷是提供较好的性能、可靠性和可扩展性。 Ceph项目最早起源于Sage就读博士期间的工作&#xff08;最早的成果于2004年发表&#xff09;&#xff0c;并随后贡献给开源社区。 在经过了数年的发展之后&#xff0c;目前…

管理类联考——数学——汇总篇——知识点突破——代数——函数——记忆

文章目录 整体文字提炼图像绘画 考点记忆/考点汇总——按大纲 本篇思路&#xff1a;根据各方的资料&#xff0c;比如名师的资料&#xff0c;按大纲或者其他方式&#xff0c;收集/汇总考点&#xff0c;即需记忆点&#xff0c;在通过整体的记忆法&#xff0c;比如整体信息很多&am…