windows进程间通信(下)

来源:互联网 发布:java手机软件大全 编辑:程序博客网 时间:2024/06/15 03:23

 共享内存(filemapping

参考:CreateFileMappingMSDN翻译和使用心得

http://hi.baidu.com/%C1%FA%D0%A1%D0%A1/blog/item/9450769a20a165b2c8eaf463.html

这时我们使用文件映射实现共享内存。

FileMapping用于将存在于磁盘的文件放进一个进程的虚拟地址空间,并在该进程的虚拟地址空间中产生一个区域用于存放该文件,这个空间就叫做File View,系统并同时产生一个File Mapping Object(存放于物理内存中)用于维持这种映射关系,这样当多个进程需要读写那个文件的数据时,它们的File View其实对应的都是同一个File  Mapping  Object,这样做可节省内存和保持数据的同步性,并达到数据共享的目的。

当然在一个应用向文件中写入数据时,其它进程不应该去读取这个正在写入的数据。这就需要进行一些同步的操作。

下边来看一下具体的API

  
  CreateFileMaping
 的用法:  
  HANDLE   CreateFileMapping(                           //
返回File   Mapping   Object的句柄  
      HANDLE   hFile,                                          //  
 想要产生映射的文件的句柄  
      LPSECURITY_ATTRIBUTES   lpAttributes,     //  
 安全属性(只对NT2000生效)  
      DWORD   flProtect,                                    //  
 保护标致  
      DWORD   dwMaximumSizeHigh,                  //  
 DWORD的高位中存放       

      File Mapping  Object                                  //    的大小  
      DWORD   dwMaximumSizeLow,                   //  
 DWORD的低位中存放

      File Mapping Object                                     //    的大小(通常这两个参数有一个为0    
      LPCTSTR   lpName                                     //   File   Mapping   Object
的名称。  
  );  

 

1) 物理文件句柄
   
 任何可以获得的物理文件句柄,如果你需要创建一个物理文件无关的内存映射也无妨,将它设置成为0xFFFFFFFF(INVALID_HANDLE_VALUE)就可以了.

如果需要和物理文件关联,要确保你的物理文件创建的时候的访问模式和"保护设置"匹配,比如: 物理文件只读,内存映射需要读写就会发生错误。推荐你的物理文件使用独占方式创建。

如果使用 INVALID_HANDLE_VALUE,也需要设置需要申请的内存空间的大小,无论物理文件句柄参数是否有效,这样 CreateFileMapping 就可以创建一个和物理文件大小无关的内存空间给你, 甚至超过实际文件大小,如果你的物理文件有效,而大小参数为0,则返回给你的是一个和物理文件大小一样的内存空间地址范围。返回给你的文件映射地址空间是可以通过复制,集成或者命名得到,初始内容为0

2) 保护设置
  
 就是安全设置, 不过一般设置NULL就可以了, 使用默认的安全配置. win2k下如果需要进行限制, 这是针对那些将内存文件映射共享给整个网络上面的应用进程使用是, 可以考虑进行限制.

3) 高位文件大小
32
位地址空间, 设置为0
4)
 共享内存名称

命名可以包含 "Global" 或者 "Local" 前缀在全局或者会话名空间初级文件映射. 其他部分可以包含任何除了()以外的字符, 可以参考 Kernel Object Name Spaces.

5) 调用CreateFileMapping的时候GetLastError的对应错误
   ERROR_FILE_INVALID    
 如果企图创建一个零长度的文件映射, 应有此报
   ERROR_INVALID_HANDLE  
 如果发现你的命名内存空间和现有的内存映射, 互斥量, 信号量, 临界区同名就麻烦了
   ERROR_ALREADY_EXISTS  
 表示内存空间命名已经存在

使用函数CreateFileMapping创建一个想共享的文件数据句柄,然后使用MapViewOfFile来获取共享的内存地址,然后使用OpenFileMapping函数在另一个进程里打开共享文件的名称,这样就可以实现不同的进程共享数据。

下边是C#是对使用的接口函数声明:

 

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]

public static extern IntPtr CreateFile(string lpFileName,

    int dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes,

    int dwCreationDisposition, int dwFlagsAndAttributes,

    IntPtr hTemplateFile);

 

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]

public static extern IntPtr CreateFileMapping(IntPtr hFile,

    IntPtr lpAttributes, int flProtect,

    int dwMaximumSizeLow, int dwMaximumSizeHigh, string lpName);

 

[DllImport("kernel32", SetLastError = true)]

public static extern bool FlushViewOfFile(IntPtr lpBaseAddress,

    IntPtr dwNumBytesToFlush);

 

[DllImport("kernel32", SetLastError = true)]

public static extern IntPtr MapViewOfFile(

    IntPtr hFileMappingObject, int dwDesiredAccess, int dwFileOffsetHigh,

    int dwFileOffsetLow, IntPtr dwNumBytesToMap);

 

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)]

public static extern IntPtr OpenFileMapping(

    int dwDesiredAccess, bool bInheritHandle, string lpName);

 

[DllImport("kernel32", SetLastError = true)]

public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);

 

[DllImport("kernel32", SetLastError = true)]

public static extern bool CloseHandle(IntPtr handle);

 

我们在示例里Server端建立的一个FileMapping,命名为:@"Global\MyFileMappingObject";这样我们在Client端就可以打开同名的FileMapping

 

 DDE

DDE(Dynamic Data Exchange) 动态数据交换是一种进程间通信形式。

Windows操作系统系统发提供的DDE数据交换本质上是一组Windows消息,再通过剪贴板或公享内存等方法进行数据交换。

我们在这里不讲解具体的DDE数据交换的协议及消息,有兴趣请参考:

http://msdn.microsoft.com/en-us/library/ms648711(VS.85).aspx

这里我们通过一个例子来简单给出一个应用,只说一下例子中用到的消息及DDE API。例子是这样的,有时我们希望当我们的application打开时,用户如果双击application生成的文件可以使用当前打开的application打开这一文件,而不是新启动一个application打开文件。那么这一需求我们可以通过DDE来实现。

刚才说到DDE本质上是使用Windows的消息机制,那么对于上面的需求,当用户双击要打开的文档时,如果windows消息可以通知到我们的application,并且告知要打开的文件是什么,那么我们就可以在当前已经打开的application中处理这一消息,从而实现需求。Windows可不可以做到这一点呢,方法就是通过注册表。所以先来说一下注册表实现文件关联。

1. 注册表实现文件关联:

参考:http://houzhengqing.blog.163.com/blog/static/227549872010019103637181/

我们要加入的注册表项有:

文件扩展名以其默认值:

HKEY_CLASSES_ROOT\.ccc = cccFileAssoc

以文件扩展名默认值为键中保存了对于当前文件类型的描述:        

HKEY_CLASSES_ROOT\cccFileAssoc = cccDescription

当双击.ccc的文件时,如果DDE没有设置时如何打开该文件:

       // Command to execute when application is not running or dde is not

 // present and Open command is issued.

 HKEY_CLASSES_ROOT\cccFileAssoc\shell\open\command = currentApplication %1

说明:其中 open是动作,也就是说当前是打开文件,也可以有其它动作,如:print, edit等。其中,currentApplication是打开该文件的应用程序的路径,如果设置了环境变量,可以是可执行文件的名称。“%1”会被替换为文件的绝对路径

以下为DDE文件关联的设置:

当打开文件时如何处理(当然也可以是print,edit等)

// DDE execute statement for Open.

HKEY_CLASSES_ROOT\cccFileAssoc\shell\open\ddeexec = [Open(%1)]

这里的server, topic,当用户双击文件时,Windows会将这两个值送给application,这样application就可以通过这两个值来判断自己是不是可以处理这个消息。在例子中我们可以看到。

// The server name your application responds to.

HKEY_CLASSES_ROOT\cccFileAssoc\shell\open\ddeexec\application = cccServer

// Topic name your application responds to.

HKEY_CLASSES_ROOT\cccFileAssoc\shell\open\ddeexec\topic = cccTopic

好,知道了这些,我们来加注册表就不成问题了。在我们的应用程序启动是我们添加上注册表,实现如下:

//HKEY_CLASSES_ROOT\.ccc = cccFileAssoc

        //File type name.

        //HKEY_CLASSES_ROOT\cccFileAssoc = cccDescription

        // Command to execute when application is not running or dde is not

        // present and Open command is issued.

        //HKEY_CLASSES_ROOT\cccFileAssoc\shell\open\command = currentApplication %1

        // DDE execute statement for Open.

        //HKEY_CLASSES_ROOT\cccFileAssoc\shell\open\ddeexec = [Open(%1)]

        // The server name your application responds to.

        //HKEY_CLASSES_ROOT\cccFileAssoc\shell\open\ddeexec\application = cccServer

        // Topic name your application responds to.

        //HKEY_CLASSES_ROOT\cccFileAssoc\shell\open\ddeexec\topic = cccTopic

        

 共享DLL

进程在Windows中运行起来的时候,所用到的所有DLL会以内存映射的形式,或都叫做文件映射的形式映射到进程的地址空间中来,从而进程可以找到对应的DLL并且调用其中的方法,使用其中的资源。

因为在不同进程中地址空间不同,所以共享数据段中不可以使用指针。

 

1.    共享数据段的建立

先建立一个c++ DLL工程:

加入SharedDll.h文件,内容如下:

 

#include <windows.h>

#include <tchar.h>

#include "WinNT.h"

 

void __stdcall SetData(LPSTR s);

 

void __stdcall GetData(LPSTR s);

 

可以看到这其中有两个方法,分别是我们用来存取数据的。可以在不同的进程中使用。 

那么数据存在哪里呢,这里就是真的的共享数据段放在哪里了。

加入新文件SharedDll.cpp,其中如下的内容。

 

#include "SharedDll.h"

 

#pragma data_seg(".MYSEC")

char MySharedData[4096]={0};

#pragma data_seg()

 

void __stdcall SetData(LPSTR s)

{

 strcpy(MySharedData, s);

}

 

void __stdcall GetData(LPSTR s)

{

 strcpy(s, MySharedData);

}

 

说明如下:

#include "SharedDll.h"

引用进来我们刚才写好的.h头文件。

void __stdcall SetData(LPSTR s)

{

 strcpy(MySharedData, s);

}

 

void __stdcall GetData(LPSTR s)

{

 strcpy(s, MySharedData);

}

这两个方法不用说就是我们用来存取数的真正方法了。那么可以看到数据是被保存在MySharedData这个数组中了。那么我们来看它的定义。

 

#pragma data_seg(".MYSEC")

char MySharedData[4096]={0};

#pragma data_seg()

 

定义没有什么好奇怪的,但是在定义的前后分别的点东西,这个很重要,这个啊就是用来说明这是一个数据段的。(".MYSEC")是这个数据段的标识,或者说是名字。这样是不是就可以了呢,还不行,这样定义之后,我们的数据只是在了一个自己段里,并没有什么其它的不同,如何让其成为一个共享的段呢,还要一点工作。

 

要想设置其成为一个共享的段有两种方法:

·         加入def文件说明之:

加入一个新文件SharedDll.def,其中内容如下:

LIBRARY     "SharedDll"

 

SECTIONS

     .MYSEC READ WRITE SHARED

    

    EXPORTS

    

    SetData @1

    GetData @2

 

说明:

SECTIONS

     .MYSEC READ WRITE SHARED

这一句啊,就是来说明我们当前的段是一个共享的数据段的。下边:

    EXPORTS

    

    SetData @1

    GetData @2

是说当前的Dll会导出这两个方法到外边,共com调用。

然后就是告诉编译器这个文件是我们的一个定义文件,做法,右键点击工程,打开属性,configuration perperties->linker->input,module definition file填入SharedDll.def

 

·         第二种方法,那么上边说的文件sharedDll.def还是需要的,但可以把这两句

SECTIONS

     .MYSEC READ WRITE SHARED

写到代码时去,做法是这样的:

SharedDll.def文件中删除这两句,在SharedDll.cpp文件中加入如下:

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

好了就可以了。和第一种方法达到的效果是一样的。

 

好了到这里我们就完成了共享数据段的建立,现在让我们来看看如何用它来达到数据传递的目的。

2.  数据传递

引用我们定义的存取方法。

[DllImport("SharedDll.dll")]

 public extern static void SetData(string s);

 

[DllImport("SharedDll.dll")]

public extern static void GetData(StringBuilder s);

 

当然我们要保证c#的代码可以找到SharedDll.dll文件,所以将C# 工程的输出我SharedDll工程的输出设置为同一文件夹。

这样我们可以在两个不同的进程中使用这一接口来达到数据传递的目的了。

 

做到来就很简单了。不多写了。

 

 NamedPipe

命名管道是一种具有唯一名称,可以在管道服务器以多个客户端之间进行单向及双向通信的进程间通信技术。任何进程都可以做为管道服务器或管道客户端实现命名管道通信。这里所说的管道服务器是指创建管道的进程,而管道客户端是连接已创建管道的进程。

命名管道可以用来在同一机器不同进程之间,也可以实现网络上不同机器进程之间的通信。在MSDN上有对命名管道非常详细的介绍。

请参考:http://msdn.microsoft.com/en-us/library/aa365590(VS.85).aspx

 

一.         命名管道的名称。

 

命名管道的名称是用来与系统中其它命名管道对象进行区分的标识。其规则如下:

\\ServerName\pipe\PipeName

其中ServerName可以远程计算机名称,或是“.”表示本机。

PipeName是表示命名管道的名称。

命名管道的服务器不可以在其它机器上创建命名管理,因些对于服务器来说创建管道时必须使用“.”表示本机,例如:

\\.\pipe\CSharpMultiProcessCommunicationNamedPipe

 

二.         Server端代码的实现:

 

创建一个命名管道:

handle = Win32Wrapper.CreateNamedPipe(

    name,                                       // pipe name

    Win32Wrapper.PIPE_ACCESS_DUPLEX,            // read/write access

    Win32Wrapper.PIPE_TYPE_MESSAGE |            // message type pipe

    Win32Wrapper.PIPE_READMODE_MESSAGE |    // message-read mode

    Win32Wrapper.PIPE_WAIT,                 // blocking mode

    Win32Wrapper.PIPE_UNLIMITED_INSTANCES,      // max. instances

    Win32Wrapper.InAndOutBufferSize,            // output buffer size

    Win32Wrapper.InAndOutBufferSize,            // input buffer size

    Win32Wrapper.NMPWAIT_WAIT_FOREVER,          // client time-out

    secAttr);                                   // default security attrubute

 

等待客户端的连接:

if (!Win32Wrapper.ConnectNamedPipe(

    namedPipeHandle, IntPtr.Zero))

{

    AddMessage("Connected named pipe error");

    return;

}

 

当客户端连接进入,读取客户端数据并返回处理数据:

 

listen = true;

while (listen)

{

    byte[] buffer =

        new byte[Win32Wrapper.InAndOutBufferSize];

    uint outNumberCount;

    bool result = Win32Wrapper.ReadFile(

        namedPipeHandle,

        buffer,

        Win32Wrapper.InAndOutBufferSize,

        out outNumberCount,

        IntPtr.Zero);

 

    if (result)

    {

        string message =

            Encoding.ASCII.GetString(buffer).Trim('\0');

        AddMessage(message);

 

        string returnMessage = "Return message: " + message;

        uint outWriteByte;

        Win32Wrapper.WriteFile(namedPipeHandle,

            Encoding.ASCII.GetBytes(returnMessage),

            (uint)returnMessage.Length,

            out outWriteByte,

            IntPtr.Zero);

    }

    //Win32Wrapper.DisconnectNamedPipe(namedPipeHandle);

}

 

三.         客户端连接服务器方法

当服务器端建立成功,客户端可以向连接到服务器:

 

handle = Win32Wrapper.CreateFile(

    Win32Wrapper.NamedPipePublicName,

    Win32Wrapper.GENERIC_READ | Win32Wrapper.GENERIC_WRITE,

    0,

    IntPtr.Zero,

    Win32Wrapper.OPEN_EXISTING,

    0,

    0);

 

uint mode = Win32Wrapper.PIPE_READMODE_MESSAGE;

if (!Win32Wrapper.SetNamedPipeHandleState(

    handle,

    ref mode,

    IntPtr.Zero,

    IntPtr.Zero))

{

    AddMessage("set mode error");

    return;

}

 

如果SetNamedPipeHandleState成功则说明与服务器连接成功。下边可以向服务器发送数据,并接收服务器的处理数据。

 

string message =

    string.IsNullOrEmpty(this.textBox1.Text) ?

    "no message" this.textBox1.Text;

byte[] msg = Encoding.ASCII.GetBytes(message);

uintoutWriteByte;

if (!Win32Wrapper.WriteFile(

    handle,

    msg,

    (uint)msg.Length,

    out outWriteByte,

    IntPtr.Zero))

{

    AddMessage("write message error!");

    return;

}

 

bool result = false;

do

{

    byte[] buffer =

        new byte[Win32Wrapper.InAndOutBufferSize];

    uint outNumberCount;

    result = Win32Wrapper.ReadFile(

        handle,

        buffer)

0 0