Windows共享内存 C++及C#实现

来源:岁月联盟 编辑:exp 时间:2012-09-08
FileMapping用于将存在于磁盘的文件放进一个进程的虚拟地址空间,并在该进程的虚拟地址空间中产生一个区域用于“存放”该文件,这个空间就叫做File View,系统并同时产生一个File Mapping Object(存放于物理内存中)用于维持这种映射关系,这样当多个进程需要读写那个文件的数据时,它们的File View其实对应的都是同一个File  Mapping  Object,这样做可节省内存和保持数据的同步性,并达到数据共享的目的。
当然在一个应用向文件中写入数据时,其它进程不应该去读取这个正在写入的数据。这就需要进行一些同步的操作。
下边来看一下具体的API。
  
  CreateFileMaping 的用法:  
  HANDLE   CreateFileMapping(                           //返回File   Mapping   Object的句柄  
      HANDLE   hFile,                                         //   想要产生映射的文件的句柄  
      LPSECURITY_ATTRIBUTES   lpAttributes,    //   安全属性(只对NT和2000生效)  
      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,这样在Server和Client之前进行通信。每次Server将数据写入后,我们通过Message的方式通知Client端数据已经准备好,可以读取了。(关于Message我们之前说过,不在细说。)
具体看一下代码:
在看具体的代码这之前,先看一下其中用到的一些常量定义:
 
    [Flags]
    public enum MapProtection
    {
        PageNone = 0x00000000,
        // protection - mutually exclusive, do not or
        PageReadOnly = 0x00000002,
        PageReadWrite = 0x00000004,
        PageWriteCopy = 0x00000008,
        // attributes - or-able with protection
        SecImage = 0x01000000,
        SecReserve = 0x04000000,
        SecCommit = 0x08000000,
        SecNoCache = 0x10000000,
    }
 
    public enum MapAccess
    {
        FileMapCopy = 0x0001,
        FileMapWrite = 0x0002,
        FileMapRead = 0x0004,
        FileMapAllAccess = 0x001f,
}
 
public const short FILE_ATTRIBUTE_NORMAL = 0x80;
public const short INVALID_HANDLE_VALUE = -1;
public const uint GENERIC_READ = 0x80000000;
public const uint GENERIC_WRITE = 0x40000000;
public const uint FILE_SHARE_READ = 0x00000001;
public const uint CREATE_NEW = 1;
public const uint CREATE_ALWAYS = 2;
public const uint OPEN_EXISTING = 3;
 
Windows Message:
 
public const int WM_USER = 0x0400;
public const int WM_USER_DATAREADY = WM_USER + 101;
 
 
下边是Server端的:
 
创建MapView:
 
fileHandle = Win32Wrapper.CreateFileMapping(
    IntPtr.Zero, IntPtr.Zero,
    (int)(MapProtection.PageReadWrite | MapProtection.SecCommit),
    0, 1000000, Win32Wrapper.MappingFileName);
mappingHandle = Win32Wrapper.MapViewOfFile(
    fileHandle, (int)MapAccess.FileMapWrite, 0, 0, new IntPtr(1024));
if (mappingHandle == IntPtr.Zero)
{
    MessageBox.Show("Open mapping file failed!");
}
 
写入信息并通知Client端:
 
 private void btnSend_Click(object sender, EventArgs e)
        {
            //Write message
            string message = string.IsNullOrEmpty(this.sendTxt.Text) ?
                "no message" : this.sendTxt.Text;
            byte[] source = Encoding.ASCII.GetBytes(message);
            byte[] msg = new byte[1024];
            Array.Copy(source, msg, source.Length);
            Marshal.Copy(msg, 0, mappingHandle, msg.Length);
            Win32Wrapper.FlushViewOfFile(mappingHandle, new IntPtr(1024));
 
            //Send message to client
            IntPtr handle = GetClientMainFormHandle();
            if (handle != IntPtr.Zero)
                Win32Wrapper.SendMessage(
                    handle, Win32Wrapper.WM_USER_DATAREADY,
                    IntPtr.Zero, IntPtr.Zero);
        }
 
得到Client端主窗体句柄的代码:
 
 private IntPtr GetClientMainFormHandle()
        {
            string name =
                @"CSharp.MultiProcess.Communication.FileMappingClient";
            Process process = GetProcessByName(name);
            return process.MainWindowHandle;
        }
 
        private Process GetProcessByName(string processName)
        {
            Process[] processes = Process.GetProcesses();
            foreach (Process p in processes)
            {
                //just for debug
                //this method has some question because
                //the visual studio started process name
                //is not same with the release. so yan can
                //close visual studio to test the project.
                //normal, you should use
                //if(p.ProcessName == processName)
 
                //debug
                if (p.ProcessName.StartsWith(processName))
                    return p;
 
                //release
                if (p.ProcessName == processName)
                    return p;
            }
            return null;
        }
 
关闭FileMapping:
 
 private void Server_FormClosing(
            object sender, FormClosingEventArgs e)
        {
            if (mappingHandle != IntPtr.Zero)
            {
                Win32Wrapper.UnmapViewOfFile(mappingHandle);
                Win32Wrapper.CloseHandle(mappingHandle);
            }
            if (fileHandle != IntPtr.Zero)
                Win32Wrapper.CloseHandle(fileHandle);
        }
 
Client端代码:
当有Message收到时接收发送的数据:
 
 protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case Win32Wrapper.WM_USER_DATAREADY:
                   RecevieMessage();
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }
 
        private void RecevieMessage()
        {
            IntPtr handle = Win32Wrapper.OpenFileMapping(
                (int)MapProtection.PageReadWrite, false,
                Win32Wrapper.MappingFileName);
            IntPtr mappingFile = Win32Wrapper.MapViewOfFile(
                handle, (int)MapAccess.FileMapRead,
                0, 0, new IntPtr(1024));
            //Marshal.GetLastWin32Error();
            byte[] msg = new byte[1024];
            Marshal.Copy(mappingFile, msg, 0, 1024);
            string message = Encoding.ASCII.GetString(msg);
            this.listBox1.Items.Add(message);
            Win32Wrapper.UnmapViewOfFile(mappingFile);
            Win32Wrapper.CloseHandle(mappingFile);
            Win32Wrapper.CloseHandle(handle);
        }