在Windows环境下的IPC

Windows环境下的IPC

1进程与内存保护

    在Windows 16位环境下(Windows 3.1/3.2),所有Windows应用程序共享单一地址,任何进程都能够对这一空间中的内容(包括属于其他进程的内存)进行读写操作,甚至可存取操作系统本身的数据,这样就可能破坏其他程序的数据段代码。

出于安全的考虑,Windows 32环境下每个进程都有自己的地址空间,一个WIN32进程不能存取另一个进程的内存数据。两个进程可以用具有相同值的指针寻址,但将被映射到不同的物理地址。所读写的只是它们各自的数据,这样就减少了进程之间的相互干扰。比如:在进程A声明了一个指针P,这个指针只能被本进程A使用,在其他进程B中使用P,将会导致致命的问题,Windows禁止其操作。这样实现了对每个进程执行环境进行保护和隔离。这也是操作系统要实现的基本功能。

WIN32环境下,每个WIN32进程拥有4GB的地址空间,但并不代表它真正拥有4GB的实际物理内存,而只是操作系统利用CPU的内存分配功能提供的虚拟地址空间。在一般情况下,绝大多数虚拟地址并没有物理内存于它对应,在真正可以使用这些地址空间之前,还要由操作系统提供实际的物理内存(这个过程叫“提交”commit)。在不同的情况下,系统提交的物理内存是不同的,可能是RAM,也可能是硬盘模拟的虚拟内存。

 

2进程间通信(IPC

专业术语:IPC,Inter-Process Communication,进程间通信(通讯)

但在实际应用过程中,Windows各个进程之间常常需要交换数据,进行数据通讯。WIN32 API提供了许多函数使我们能够方便高效的进行进程间的通讯,通过这些函数我们可以控制不同进程间的数据交换,就如同在WIN16中对本地进程进行读写操作一样。

 

 

 

3Windows 16位环境下的进程间通信

典型的WIN16两进程可以通过共享内存来进行数据交换:

(1)       进程A将GlobalAlloc(GMEM_SHARE...)API分配一定长度的内存;

(2)       进程A将GlobalAlloc函数返回的句柄传递给进程B(通过一个登录消息);

(3)       进程B对这个句柄调用GlobalLock函数,并返回的指针访问数据。

这种方法在WIN32中可能失败,这是因为GlobalLock函数返回指向的是进程A的内存,由于进程使用的是虚拟地址而非实际物理地址,因此这一指针仅与A进程有关,而于B进程无关。WIN32环境下,各进程间不能相互访问内存。每个进程的代码段都访问内存的虚拟地址,由操作系统完成到物理地址的映射。代码能处理的同一个虚拟内存地址将被Windows映射到不同的物理地址上。

 

4 Windows 32位环境下的进程间通信

下面描述了WIN32下进程之间通信的几种实现方法,SSIPCall只是选择了其中一种作为实现方法。了解各类通信方法,有助于全面了解Windows 的各类IPC机制。

WIN32环境下,为实现进程间平等的数据交换,用户可以有如下几种选择:

n         使用剪贴板

16位时代常使用的方式,CWnd中提供支持。

因为剪贴板使用的场合很多,容易出现冲突的情况,且效率不高。

 

n         动态数据交换(DDE)

其方式在一块全局内存中手工放置大量的数据,然后使用窗口消息传递内存指针。这是16位WIN时代使用的方式,因为在WIN32下已经没有全局和局部内存了,现在的内存只有一种就是虚存。所以,这种方法在WIN32下无效。

 

n         消息管道(匿名管道Anonymous Pipes、命名管道Named Pipes)

用于设置应用程序间的一条永久通讯通道,通过该通道可以象自己的应用程序   访问一个平面文件一样读写数据。

²        匿名管道(Anonymous Pipes)

单向流动,并且只能够在同一电脑上的各个进程之间流动。

²        命名管道(Named Pipes)

双向,跨网络,任何进程都可以轻易的抓住,放进管道的数据有固定的格式,而使用ReadFile()只能读取该大小的倍数。

该方式缺陷:服务端只能运行在基于Windows NT 内核的Windows系统中。

 

n         邮件槽(Mailslots)

    广播式通信,在WIN32系统中提供的新方法,可以在不同主机间交换数据,实现了跨网络,单在WIN9X下只支持邮件槽客户。服务端必须运行在Windows NT/2000/XP。

 

n         Windows套接字(Windows Socket)、TCP/IP方式

它具备消息管道所有的功能,但遵守一套通信标准使的不同操作系统之上的应用程序之间可以互相通信。这种方式用于网络方面比较好,但用于本地进程间的通信,感觉有点浪费,效率方面没有SSIPCall的实现高。

 

n         COM/DCOM

通过COM系统的代理存根方式进行进程间数据交换,但只能够表现在对接口函数的调用时传送数据,通过DCOM可以在不同主机间传送数据。

 

n         RPC

远程过程调用,调用方法比较复杂。很少使用,因其与UNIX的RPC不兼容。

 

n         串行/并行通信(Serial/Parallel Communication)

它允许应用程序通过串行或并行端口与其他的应用程序通信。

 

n         使用内存映射文件

1)       设定一块共享内存区域          

        产生一个file-mapping核心对象:

HANDLE CreateFileMapping(HANDLE,LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD,LPCSTR)

得到共享内存的指针:

      LPVOID MapViewOfFile(

         HANDLE hFileMappingObject,   DWORD  dwDesiredAcess,

         DWORD  dwFileOffsetHigh, DWORD  dwFileOffsetLow,

         DWORD  dwNumberOfBytesToMap );  

2)       找出共享内存

每个客户端进程使用先获得句柄:

HANDLE OpenFileMapping(DWORD dwDesiredAccess,

                                 BOOL bInheritHandle, LPCTSTR lpName);

        再调用MapViewOfFile(),取得共享内存的指针

3)       同步处理(Mutex)

4)       清理(Cleaning up)

BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);

             CloseHandle();

内存映射文件机制的本质,是Windows操作在系统核心内存区域开了块内存,然后每个进程把这块内存映射到自己可以访问的虚内存地址中。对每个进程来说,似乎在操作各自的内存区域,而实际上所有的操作被映射到Windows核心的共享的内存区域。

Win核心内存区域

进程1

 

 

内存地址 p1

进程2

 

内存地址 p2

 

 

 

映象机制

 

 

 

 

 

 

 

 

 

 

 


3:内存映象机制模型图

 

n         通过共享内存DLL共享内存

共享数据DLL允许进程以类似于Windows 3.1 DLL共享数据的方式访问读写数据,多个进程都可以对该共享数据DLL进行数据操作,达到共享数据的目的。在WIN32中为建立共享内存,必须执行以下步骤:

  1)首先创建一个有名的数据区。这在Visual C++中是使用data_seg pragma宏。使用data_seg pragma宏必须注意数据的初始化:

  #pragma data_seg("MYSEC")

  char MySharedData[4096]={0};

  #pragma data_seg()

  2)然后在用户的DEF文件中为有名的数据区设定共享属性。

  LIBRARY TEST

  DATA READ WRITE

  SECTIONS

  .MYSEC READ WRITE SHARED

  完成这两步,每个附属于DLL的进程都将接受到属于自己的数据拷贝,一个进程的数据变化并不会反映到其他进程的数据中。所以,还要做下面的一步。

3)在DEF文件中适当地输出数据。

以下的DEF文件项说明了如何以常数变量的形式输出MySharedData

  EXPORTS

  MySharedData CONSTANT

  4)最后在应用程序(进程)按外部变量引用共享数据。

  extern _export "C"{char * MySharedData[]};

  5)进程中使用该变量应注意间接引用。

  m_pStatic=(CEdit*)GetDlgItem(IDC_SHARED);

m_pStatic->GetLine(0,*MySharedData,80);

 

n         向另一进程发送WM_COPYDATA消息

传输只读数据可用Win32中的WM_COPYDATA消息。该消息允许在进程间传递只读数据,但不能处理返回,可用共享内存弥补回传数据。SDK文档推荐用户使用SendMessage函数,接受方在数据拷贝完成前不返回,这样发送方就不可能删除和修改数据。

  SendMessage(hwnd,WM_COPYDATA,wParam,lParam);

  其中wParam设置为包含数据的窗口(也既数据源窗口)的句柄。lParam指向一个COPYDATASTRUCT的结构:

  typedef struct tagCOPYDATASTRUCT{

  DWORD dwData;//用户定义数据

  DWORD cbData;//数据大小

  PVOID lpData;//指向数据的指针

   } COPYDATASTRUCT;

该结构用来定义用户数据。

 

在接受端的窗口过程函数里面,做处理即可。下面是样板代码:

 

LRESULT  CALLBACK MyWndProc(HWND hWnd, UINT message,

                           WPARAM wParam, LPARAM lParam)

{

    switch(message)

    {

    case WM_COPYDATA:

        {

            HWND hWndCall = (HWND) wParam;

            PCOPYDATASTRUCT pCopy = (PCOPYDATASTRUCT)lParam;

            if (pCopy && ( pCopy->dwData == SSIPC_COPYDATA_ID ) )

            {

                LPBYTE pBuff = (LPBYTE) (pCopy->lpData)  ;

                DWORD len = pCopy->cbData;

 

                ...... // 具体的业务代码在此

 

            }

            break;

        }

    default:

        return DefWindowProc(hWnd, message, wParam, lParam);

    }

    return 0;

}

 

 

n         直接调用ReadProcessMemory和WriteProcessMemory函数实现进程间通讯

通过调用ReadProcessMemory以及WriteProcessMemory函数用户可以按类似与Windows 3.1的方法实现进程间通讯,在发送进程中分配一块内存存放数据,可以调用GlobalAlloc或者VirtualAlloc函数实现:

      m_hGlobalHandle = GlobalAlloc(GMEM_SHARE,1024);

可以得到指针地址:

mpszGlobalHandlePtr=(LPSTR)GlobalLock(m_hGlobalHandle);

在接收进程中要用到用户希望影响的进程的打开句柄。为了读写另一进程,应按如下方式调用OpenProcess函数:

  HANDLE hTargetProcess=OpenProcess(

  STANDARD_RIGHTS_REQUIRED|

  PROCESS_VM_REDA| PROCESS_VM_WRITE|

  PROCESS_VM_OPERATION,//访问权限

  FALSE,//继承关系

      dwProcessID);//进程ID

      为保证OpenProcess函数调用成功,用户所影响的进程必须由上述标志创建。

用户获得进程的有效句柄,就可用ReadProcessMemory函数读取该进程的内存:

  BOOL ReadProcessMemory(

  HANDLE hProcess, // 进程指针

  LPCVOID lpBaseAddress, // 数据块的首地址

  LPVOID lpBuffer, // 读取数据所需缓冲区

  DWORD cbRead, // 要读取的字节数

  LPDWORD lpNumberOfBytesRead );

      使用同样的句柄也可以写入该进程的内存:

  BOOL WriteProcessMemory(

  HANDLE hProcess, // 进程指针

  LPVOID lpBaseAddress, // 要写入的首地址

  LPVOID lpBuffer, // 缓冲区地址

  DWORD cbWrite, // 要写的字节数

  LPDWORD lpNumberOfBytesWritten );

      如下所示是读写另一进程的共享内存中的数据:

  ReadProcessMemory((HANDLE)hTargetProcess,

(LPSTR)lpsz,m_strGlobal.GetBuffer(_MAX_FIELD),

             _MAX_FIELD,cb);

      WriteProcessMemory((HANDLE)hTargetProcess,

(LPSTR)lpsz,(LPSTR)STARS,

             m_strGlobal.GetLength(),cb);

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Dart Windows 中使用命名管道进行 IPC 通信的代码示例如下: ```dart import 'dart:io'; void main() async { // 创建命名管道 var server = await ServerSocket.bind('\\\\.\\pipe\\my_pipe'); print('Server listening on pipe: \\\\.\\pipe\\my_pipe'); // 等待客户端连接 var client = await server.first; print('Client connected from ${client.address.address}'); // 发送数据 client.write('Hello, client!'); // 接收数据 var data = await client.first; print('Received: $data'); // 关闭管道 await client.close(); await server.close(); } ``` ```dart import 'dart:io'; void main() async { // 连接到命名管道 var client = await Socket.connect('\\\\.\\pipe\\my_pipe'); print('Connected to pipe: \\\\.\\pipe\\my_pipe'); // 接收数据 var data = await client.first; print('Received: $data'); // 发送数据 client.write('Hello, server!'); // 关闭管道 await client.close(); } ``` 这是一个简单的示例,它显示了如何在 Dart 中使用命名管道进行 IPC 通信。需要注意的是, 在 Windows 中,命名管道的名称必须以 "\\.\pipe\" 开头。 ### 回答2: Dart是一种通过Dart编程语言开发应用程序的跨平台解决方案。在Windows平台上,我们可以使用Win32 API来实现命名管道(Named Pipes)IPC通信。 首先,我们需要安装并配置Dart环境以在Windows平台上开发应用程序。在Dart环境配置完成后,我们可以使用dart:ffi库来访问Win32 API。 下面是一个简单的代码示例,展示了如何使用Dart和Win32 API来实现命名管道的IPC通信。这个示例中包含了服务端和客户端两部分的代码: 服务端代码(Server.dart): import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:win32/win32.dart'; void main() { // 创建命名管道对象 var pipeHandle = CreateNamedPipe( TEXT("\\\\.\\pipe\\myPipe"), // 命名管道的名称 PIPE_ACCESS_DUPLEX, // 可读可写的管道 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, // 允许多个客户端连接 0, // 输出缓冲区大小 0, // 输入缓冲区大小 0, //默认超时时间 nullptr // 默认安全属性 ); if (pipeHandle == INVALID_HANDLE_VALUE) { print('命名管道创建失败'); return; } // 等待客户端连接,并处理消息 ConnectNamedPipe(pipeHandle, nullptr); final buffer = calloc<Uint8>(128); // 用于接收消息的缓冲区 while (ReadFile(pipeHandle, buffer, 128, nullptr, nullptr) != 0) { final message = utf8.decode(buffer.asTypedList()); print('接收到客户端的消息:$message'); memset(buffer, 0, 128); // 清空缓冲区 } // 断开命名管道连接并释放资源 DisconnectNamedPipe(pipeHandle); CloseHandle(pipeHandle); } 客户端代码(Client.dart): import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:win32/win32.dart'; void main() { // 打开命名管道 var pipeHandle = CreateFile( TEXT("\\\\.\\pipe\\myPipe"), // 命名管道的名称 GENERIC_READ | GENERIC_WRITE, // 可读可写 0, // 暂不支持共享模式 nullptr, // 默认安全属性 OPEN_EXISTING, // 打开已经存在的命名管道 0, nullptr); if (pipeHandle == INVALID_HANDLE_VALUE) { print('命名管道打开失败'); return; } final message = 'Hello, Server!'; final buffer = calloc<Uint8>(128); // 将消息写入命名管道 WriteFile(pipeHandle, message.toNativeUtf8(), message.length, nullptr, nullptr); // 从命名管道读取服务端的响应 ReadFile(pipeHandle, buffer, 128, nullptr, nullptr); final response = utf8.decode(buffer.asTypedList()); print('接收到服务端的响应:$response'); // 关闭命名管道 CloseHandle(pipeHandle); } 这个示例展示了如何使用Dart和Win32 API来实现命名管道的IPC通信。在服务端代码中,我们创建了一个命名管道对象,并等待客户端连接。随后,我们可以通过管道进行读写操作。在客户端代码中,我们打开已经存在的命名管道,并通过管道发送消息给服务端,并接收服务端的响应。 请注意,为了能够编译和运行上述示例代码,您需要安装Dart SDK、Win32 API相关库,并在Dart环境中配置好FFI支持。同时,还需要导入dart:ffi和package:win32库以访问Win32 API。 希望以上示例对您有所帮助!如果您还有其他问题,请随时提问。 ### 回答3: Dart是一种跨平台编程语言,可以用于开发桌面应用程序。而Windows命名管道是一种用于在本地进程间进行通信的机制,可以在Windows操作系统上实现进程间的数据交换。 在Dart中使用Windows命名管道进行进程间通信涉及到与系统底层API的交互,可以通过dart:ffi库将Dart代码链接到Windows的动态链接库中,并使用相应的API来操作命名管道。 下面是一个示例代码,演示了在Dart中使用Windows命名管道进行进程间通信: ```dart import 'dart:ffi'; import 'dart:io'; // 导入Windows的动态链接库 final DynamicLibrary kernel32 = DynamicLibrary.open('kernel32.dll'); // 定义相关的Win32 API函数 final CreateNamedPipe = kernel32.lookupFunction< IntPtr Function(Pointer<Utf16> lpName, Uint32 dwOpenMode, Uint32 dwPipeMode, Uint32 nMaxInstances, Uint32 nOutBufferSize, Uint32 nInBufferSize, Uint32 nDefaultTimeOut, Pointer<Void> lpSecurityAttributes), int Function(Pointer<Utf16> lpName, int dwOpenMode, int dwPipeMode, int nMaxInstances, int nOutBufferSize, int nInBufferSize, int nDefaultTimeOut, Pointer<Void> lpSecurityAttributes)>( 'CreateNamedPipeW', ); final ConnectNamedPipe = kernel32.lookupFunction< Int32 Function( IntPtr hNamedPipe, Pointer<Void> lpOverlapped), int Function( int hNamedPipe, Pointer<Void> lpOverlapped)>('ConnectNamedPipe'); final WriteFile = kernel32.lookupFunction< Int32 Function(IntPtr hFile, Pointer<Void> lpBuffer, Uint32 nNumberOfBytesToWrite, Pointer<Uint32> lpNumberOfBytesWritten, Pointer<Void> lpOverlapped), int Function(int hFile, Pointer<Void> lpBuffer, int nNumberOfBytesToWrite, Pointer<Uint32> lpNumberOfBytesWritten, Pointer<Void> lpOverlapped)>( 'WriteFile', ); final ReadFile = kernel32.lookupFunction< Int32 Function(IntPtr hFile, Pointer<Void> lpBuffer, Uint32 nNumberOfBytesToRead, Pointer<Uint32> lpNumberOfBytesRead, Pointer<Void> lpOverlapped), int Function(int hFile, Pointer<Void> lpBuffer, int nNumberOfBytesToRead, Pointer<Uint32> lpNumberOfBytesRead, Pointer<Void> lpOverlapped)>( 'ReadFile', ); final CloseHandle = kernel32.lookupFunction<Int32 Function(IntPtr hObject), int Function(int hObject)>('CloseHandle'); // 主函数 void main() { // 创建命名管道 final pipeName = '\\\\.\\pipe\\MyPipe'; final pipeHandle = CreateNamedPipe( pipeName.toNativeUtf16(), 3, 0, 255, 65536, 65536, 0, Pointer.fromAddress(0), ); // 等待客户端连接 final connectResult = ConnectNamedPipe(pipeHandle, Pointer.fromAddress(0)); if (connectResult != 0) { print('连接已建立'); // 向管道写入数据 final message = 'Hello, World!'; final buffer = message.toNativeUtf16(); final bufferLength = buffer.length * 2; final bytesWrittenPtr = Pointer<Uint32>.allocate(); WriteFile(pipeHandle, buffer.cast(), bufferLength, bytesWrittenPtr, Pointer.fromAddress(0)); print('写入成功'); // 从管道读取数据 final bufferSize = 255; final bufferPtr = Pointer<Uint16>.allocate(count: bufferSize); final bytesReadPtr = Pointer<Uint32>.allocate(); ReadFile(pipeHandle, bufferPtr.cast(), bufferSize * 2, bytesReadPtr, Pointer.fromAddress(0)); final receivedMessage = bufferPtr.cast<Utf16>().unpackString(bytesReadPtr.value); print('收到消息: $receivedMessage'); // 关闭管道句柄 CloseHandle(pipeHandle); } } ``` 以上代码演示了如何在Dart中使用Windows命名管道进行进程间通信。首先使用`CreateNamedPipe`函数创建了一个命名管道,然后使用`ConnectNamedPipe`函数等待客户端的连接。连接建立后,向管道写入数据使用`WriteFile`函数,然后从管道读取数据使用`ReadFile`函数。最后通过`CloseHandle`函数关闭管道句柄。 需要注意的是,上述代码仅适用于Windows操作系统,且需要在Windows系统上编译和运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值