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 file
  LONG lDistanceToMove,        // 低 4G 地址
  PLONG lpDistanceToMoveHigh,  // 高 4G 地址,两个DWORD拼接成一个32位
  DWORD dwMoveMethod           // 文件指针偏移【或从标志中选择】
);
=============================== 设置文件指针 =====================================
BOOL SetFilePointerEx(  
  HANDLE hFile,                    // handle to file
  LARGE_INTEGER liDistanceToMove,  // 64位的地址值
  PLARGE_INTEGER lpNewFilePointer, // new file pointer
  DWORD dwMoveMethod               // starting point);   
    

获取文件大小 GetFileSize

===================== 获取文件大小 GetFileSize/GetFileSizeEx =====================
BOOL GetFileSizeEx(  
  HANDLE hFile,              // handle to file
  PLARGE_INTEGER lpFileSize  // file size
);
DWORD GetFileSize(  
  HANDLE hFile,           // handle to file
  LPDWORD 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 file
  LPSECURITY_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拼接成一个QWord
  DWORD 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 mode
  BOOL 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 pipe
  LPVOID lpBuffer,                // data buffer
  DWORD nBufferSize,              // size of data buffer
  LPDWORD lpBytesRead,            // number of bytes read
  LPDWORD lpTotalBytesAvail,      // number of bytes available
  LPDWORD 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()
{
	//获取父进程句柄 GetstdHandle
	HANDLE 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()
{
	//获取父进程句柄 GetstdHandle
	HANDLE 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("写入文件失败");

	}

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

百度搜索框中的下拉提示关键词提取

效果图 代码有点多&#xff0c;绑定资源了 导出excel如下 贴心养眼背景图鼠标点击小爱心

pat实现基于邻接矩阵表示的深度优先遍历

void DFS(Graph G, int v) {visited[v] 1;printf("%c ", G.vexs[v]);for (int i 0; i < G.vexnum; i) {if (!visited[i] && G.arcs[v][i]) DFS(G, i);} }

C# 读写FDX-B(ISO11784/85)动物标签源码

本示例使用的发卡器&#xff1a;EM4305 EM4469 ISO11784/85协议125K低频FXD-B动物标签读写发卡器-淘宝网 (taobao.com) using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using S…

API 设计:使用 Node.js 和 Express.js 的综合教程

API&#xff08;应用程序编程接口&#xff09;设计涉及创建一个高效而强大的接口&#xff0c;允许不同的软件应用程序相互交互。 说明 本教程将指导您使用 Node.js 和 Express.js 作为核心技术来规划、设计和构建 API。但是&#xff0c;这些原则可以应用于任何语言或框架。我们…

APP软件外包开发需要注意的问题

在进行APP软件开发时&#xff0c;有一些关键问题需要特别注意&#xff0c;以确保项目的成功和用户满意度。以下是一些需要注意的问题&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 清晰的需求定义&a…

详解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}把…

统计二叉树中的伪回文路径 : 用位运用来加速??

题目描述 这是 LeetCode 上的 「1457. 二叉树中的伪回文路径」 &#xff0c;难度为 「中等」。 Tag : 「DFS」、「位运算」 给你一棵二叉树&#xff0c;每个节点的值为 1 到 9 。 我们称二叉树中的一条路径是 「伪回文」的&#xff0c;当它满足&#xff1a;路径经过的所有节点值…

一个正整数转为2进制和8进制,1的个数相同的第23个数是什么?

package cn.com;import java.lang.*;//默认加载public class C2 {//10进制转8进制static int HtoO(int n){int cnt 0;while(n!0){cntn%8;n/8;}return cnt;}//10进制转2进制static int HtoB(int n){int cnt 0;while(n!0){cntn%2;n/2;}return cnt;}public static void main(Str…

Windows服务设置多个服务依赖项避免服务启动失败找不到数据库

添加多个服务依赖项建议通过命令行的方式添加&#xff1a; winr键打开命令行 cmd 命令行添加命令如下&#xff1a; sc config "thinvent-auth" depend "MySQL57"/"RabbitMQ"/"Redis" sc config "服务A" depend "服务…

C#,《小白学程序》第十四课:随机数(Random)第一,几种随机数的计算方法与代码

1 文本格式 /// <summary> /// 《小白学程序》第十四课&#xff1a;随机数&#xff08;Random&#xff09;第一&#xff0c;几种随机数的计算方法与代码 /// 本课初步接触一下随机数。 /// </summary> /// <param name"sender"></param> ///…

LiveVIS视图库1400-如何切换数据库?默认使用的数据库是什么?如何切换到Mysql/MariaDB?

LiveVIS视图库1400-如何切换数据库&#xff1f;默认使用的数据库是什么&#xff1f;如何切换到Mysql/MariaDB? 1、切换成Mysql/Mariadb数据库1.1 连接数据库1.2 创建数据库实例1.3 配置.ini文件1.4 重启完成切换 1、切换成Mysql/Mariadb数据库 LiveVIS 默认使用 sqlite3 文件…

重新开启GPT Plus充值通道——基于前端开发者工具

chatGPT PLUS充值通道的关闭 由于chatGPT用户激增&#xff0c;近日&#xff0c;OpenAI的CEO Sam Altman宣布需要暂停新用户对ChatGPT Plus的订阅。在X上&#xff0c;他表达了对于确保用户体验的承诺&#xff0c;同时也提到了用户可以通过应用程序内的通知功能来了解服务恢复的…

笔记本电脑可以投屏到电视吗?Win、Mac、Linux分别怎么投屏?

如果你的电视是安卓电视&#xff0c;那么答案是&#xff1a;完全可以&#xff01; 不管你的笔记本电脑是Windows系统、macOS系统还是Linux系统&#xff0c;你都可以借助AirDroid Cast的电脑客户端或网页版&#xff0c;将电脑屏幕投屏到安卓智能电视上。 首先&#xff0c;你需要…

Unity技美35——再URP管线环境下,配置post后期效果插件(post processing)

前两年在我的unity文章第10篇写过&#xff0c;后效滤镜的使用&#xff0c;那时候大部分项目用的还是unity的基础管线&#xff0c;stander管线。 但是现在随着unity的发展&#xff0c;大部分项目都用了URO管线&#xff0c;甚至很多PC端用的都是高效果的HDRP管线&#xff0c;这就…

网络数据结构skb_buff原理

skb_buff基本原理 内核中sk_buff结构体在各层协议之间传输不是用拷贝sk_buff结构体&#xff0c;而是通过增加协议头和移动指针来操作的。如果是从L4传输到L2&#xff0c;则是通过往sk_buff结构体中增加该层协议头来操作&#xff1b;如果是从L4到L2&#xff0c;则是通过移动sk_…

井盖位移传感器怎么监测井盖安全

井盖在城市基础设施建设中扮演着不可或缺的角色&#xff0c;虽然看似并不起眼但确实是城市规划中一个重要的组成部分。在城市规划建设之初都需要首先考虑排水系统的设计&#xff0c;而井盖作为排水系统的一个重要组成部分&#xff0c;一旦出现问题便会造成交通中断或者环境受影…

【洛谷 P1636】Einstein学画画 题解(图论+欧拉通路)

Einstein学画画 题目描述 Einstein 学起了画画。 此人比较懒~~&#xff0c;他希望用最少的笔画画出一张画…… 给定一个无向图&#xff0c;包含 n n n 个顶点&#xff08;编号 1 ∼ n 1 \sim n 1∼n&#xff09;&#xff0c; m m m 条边&#xff0c;求最少用多少笔可以画…

C#,《小白学程序》第十七课:随机数(Random)第四,移动平均值(Moving Average)的计算方法与代码

1 文本格式 /// <summary> /// 《小白学程序》第十七课&#xff1a;随机数&#xff08;Random&#xff09;第四&#xff0c;移动平均值的计算方法与代码 /// 继续学习数据统计&#xff0c;移动平均值的计算方法 /// 移动平均值就是一定步长内数值的平均值&#xff0c;用…

C#,《小白学程序》第十六课:随机数(Random)第三,正态分布的随机数的计算方法与代码

1 随机数的问题 用 C# Random 类生成的随机数是平均分布的。也就是各数据段的出现的次数差不多。彩票号码属于这种随机数。 而很多很多常见的随机数&#xff0c;比如&#xff1a;成绩&#xff0c;却是符合正态分布的。 因而很多时候需要生成符合正态分布规律的随机数。 2 文…

20年的大厂技术总监给云原生从业者的建议

云原生是一种构建和运行应用程序的方法&#xff0c;是一套技术体系和方法论。云原生的英文可拆解为Cloud和Native。Cloud表示应用程序位于云中&#xff0c;而不是传统的数据中心&#xff1b;Native表示应用程序设计之初就被考虑部署到云的环境&#xff0c;为云而生&#xff0c;…