共享内存实现进程间大数据的交换

引言
  进程间的数据交换和共享是一种非常重要和实用的技术。大、中型软件的开发设计多是由众多程序设计人员的合作完成,通常一个程序设计人员只负责其中一个 或几个模块的开发,这些模块可以是动态链接库也可以是应用程序或是其他形式的程序组件。这些独立开发出来的程序模块最终需要作为一个整体来运行,即组成一 个系统,在系统运行期间这些模块往往需要频繁地进行数据交换和数据共享,对于动态链接库同其主调应用程序之间的数据交换是非常容易实现的,但是在两个应用 程序之间或是动态链接库同其主调应用程序之外的其他应用程序进行数据交换就比较困难了。尤其是在交换数据量过大、交换过于频繁的情况下更是难以实现,本文 即对此展开讨论,并提出了一种通过共享内存来实现进程见大数据量快速交换的一种方法。

  通讯方式的比较和选择

  进程间通讯的方式有很多,常用的有共享内存、命名管道和匿名管道、发送消息等几种方法来直接完成,另外还可以通过socket口、配置文件和注册表等 来间接实现进程间数据通讯任务。以上这几种方法各有优缺点,具体到在进程间进行大数据量数据的快速交换问题上,则可以排除使用配置文件和注册表的方法;另 外,由于管道和socket套接字的使用需要有网卡的支持,因此也可以不予考虑。这样,可供选择的通讯方式只剩下共享内存和发送消息两种。由于数据量比较 大,这样在使用消息进行通讯时就无法通过消息参数将数据直接携带到接收方,只能以地址传送的方式进行。当一个应用程序向另一个应用程序发送数据时将会发出 WM_COPYDATA系统消息,因此可以考虑通过向消息队列插入WM_COPYDATA消息的方法来实现数据在进程间的拷贝。

  在使用WM_COPYDATA消息时,由第一个消息参数指定发送窗口的句柄,第二个消息参数则为一同数据相关的数据结构COPYDATASTRUCT的指针,此结构原形声明如下:
typedef struct tagCOPYDATASTRUCT {
DWORD dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT;


  其中,只需将待发送数据的首地址赋予lpData、并由cbData指明数据块长度即可。消息发出后,接收方程序在WM_COPYDATA消息的响应 函数中通过随消息传递进来的第二个参数完成对数据块的接收。但是在使用WM_COPYDATA消息时,只能用SendMessage()函数发送而不能使 用PostMessage(),这两个函数虽然功能非常相似都是负责向指定的窗口发送消息,但是SendMessage()函数发出消息后不是马上返回, 而是在接收方的消息响应函数处理完之后才能返回,并能够得到返回结果。在此期间发送方程序将被阻塞,SendMessage()后面的语句不能被继续执 行。而PostMessage()函数在发出消息后马上返回,其后语句能够被立即执行,但是无法获取消息的执行结果。可见,在交换数据量较大的情况下实现 数据频繁而又快速的交换用发送WM_COPYDATA消息的方法也是不合适的,当数据传输过于频繁时将有可能导致数据的丢失。

  比之以上几种进程间通讯方法,共享内存有着明显的优势。共享内存是通过直接操作内存映射文件来进行的,而内存映射文件又是进行单机数据共享的最低层机 制,前面几种数据交换方式在低层都是通过内存映射文件来进行的。因此使用共享内存可以以较小的开销获取较高的性能,是进行大数据量数据快速交换的最佳方 案。

  共享内存的使用

  在Windows操作系统下,任何一个进程不允许读取、写入或是修改另一个进程的数据(包括变量、对象和内存分配等),但是在某个进程内创建的文件映 射对象的视图却能够为多个其他进程所映射,这些进程共享的是物理存储器的同一个页面。因此,当一个进程将数据写入此共享文件映射对象的视图时,其他进程可 以立即获取数据变更情况。为了进一步提高数据交换的速度,还可以采用由系统页文件支持的内存映射文件而直接在内存区域使用,显然这种共享内存的方式是完全 可以满足在进程间进行大数据量数据快速传输任务要求的。下面给出在两个相互独立的进程间通过文件映射对象来分配和访问同一个共享内存块的应用实例。在本例 中,由发送方程序负责向接收方程序发送数据,文件映射对象由发送方创建和关闭,并且指定一个唯一的名字供接收程序使用。接收方程序直接通过这个唯一指定的 名字打开此文件映射对象,并完成对数据的接收。

  在发送方程序中,首先通过CreateFileMapping()函数创建一个内存映射文件对象,如果创建成功则通过MapViewOfFile() 函数将此文件映射对象的视图映射进地址空间,同时得到此映射视图的首址。可见,共享内存的创建主要是通过这两个函数完成的。这两个函数原形声明如下:
HANDLE CreateFileMapping(HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCTSTR lpName);
LPVOID MapViewOfFile(HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
DWORD dwNumberOfBytesToMap);
  CreateFileMapping()函数参数hFile指定了待映射到进程地址空间的文件句柄,如果为无效句柄则系统会创建一个使用来自页文件而 非指定磁盘文件存储器的文件映射对象。很显然,在本例中为了数据能快速交换,需要人为将此参数设定为INVALID_HANDLE_VALUE;参数 flProtect设定了系统对页面采取的保护属性,由于需要进行读写操作,因此可以设置保护属性PAGE_READWRITE;双字型参数 dwMaximumSizeHigh和dwMaximumSizeLow指定了所开辟共享内存区的最大字节数;最后的参数lpName用来给此共享内存设 定一个名字,接收程序可以通过这个名字将其打开。MapViewOfFile()函数的参数hFileMappingObject为 CreateFileMapping()返回的内存文件映像对象句柄;参数dwDesiredAccess再次指定对其数据的访问方式,而且需要同 CreateFileMapping()函数所设置的保护属性相匹配。这里对保护属性的重复设置可以确保应用程序能更多的对数据的保护属性进行有效控制。 下面给出创建共享内存的部分关键代
hRecvMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT, 0, 1000000, "DataMap");
if (hRecvMap != NULL)
{
lpData = (LPBYTE)MapViewOfFile(hRecvMap, FILE_MAP_WRITE, 0, 0, 0);
if (lpData == NULL)
{
CloseHandle(hRecvMap);
hRecvMap = NULL;
}
}
// 通知接收程序内存文件映射对象的视图已经打开
HWND hRecv = ::FindWindow(NULL, DECODE_PROGRAMM);
if (hRecv != NULL)
::PostMessage(hRecv, WM_MAP_OPEN, 0, 0);
  数据的传送实际是将数据从发送方写到共享内存中,然后由接收程序及时从中取走即可。数据从发送方程序写到共享内存比较简单,只需用memcpy()函 数将数据拷贝过去,关键在于能及时通知接收程序数据已写入到共享内存,并让其即使取走。在这里仍采取消息通知的方式,当数据写入共享内存后通过 PostMessage()函数向接收方程序发送消息,接收方在消息响应函数中完成对数据的读取:
// 数据复制到共享内存
memcpy(lpData, RecvBuf, sizeof(RecvBuf));
// 通知接收方接收数据
HWND hDeCode = ::FindWindow(NULL, DECODE_PROGRAMM);
if (hDeCode != NULL)
::PostMessage(hDeCode, WM_DATA_READY, (WPARAM)0, (LPARAM)sizeof(RecvBuf));
  当数据传输结束,即将退出程序时,需要将映射进来的内存文件映射对象视图卸载和资源的释放等处理。这部分工作主要由UnmapViewOfFile()和CloseHandle()等函数完成:
HWND hDeCode = ::FindWindow(NULL, DECODE_PROGRAMM);
if (hDeCode != NULL)
::PostMessage(hDeCode, WM_MAP_CLOSE, 0, 0);
if (lpData != NULL)
{
UnmapViewOfFile(lpData);
lpData = NULL;
}
if (hRecvMap != NULL)
{
CloseHandle(hRecvMap);
hRecvMap = NULL;
}


  在接收程序中,在收到由发送放发出的WM_MAP_OPEN消息后,由OpenFileMapping()函数打开由名字"DataMap"指定的文 件映射对象,如果执行成功,继续用MapViewOfFile()函数将此文件映射对象的视图映射到接收应用程序的地址空间并得到其首址:
m_hReceiveMap = OpenFileMapping(FILE_MAP_READ, FALSE, "DataMap");
if (m_hReceiveMap == NULL)
return;
m_lpbReceiveBuf = (LPBYTE)MapViewOfFile(m_hReceiveMap,FILE_MAP_READ,0,0,0);
if (m_lpbReceiveBuf == NULL)
{
CloseHandle(m_hReceiveMap);
m_hReceiveMap=NULL;
}
  当发送方程序将数据写入到共享内存后,接收方将收到消息WM_DATA_READY,在响应函数中将数据从共享内存复制到本地缓存中,再进行后续的处 理。同发送程序类似,在接收程序数据接收完毕后,也需要用UnmapViewOfFile()、CloseHandle()等函数完成对文件视图等打开过 资源的释放:
// 从共享内存接收数据
memcpy(RecvBuf, (char*)(m_lpbReceiveBuf), (int)lParam);
……
// 程序退出前资源的释放
UnmapViewOfFile(m_lpbReceiveBuf);
m_lpbReceiveBuf = NULL;
CloseHandle(m_hReceiveMap);
m_hReceiveMap = NULL;
  小结

  经实际测试,使用共享内存在处理大数据量数据的快速交换时表现出了良好的性能,在数据可靠性等方面要远远高于发送WM_COPYDATA消息的方式。 这种大容量、高速的数据共享处理方式在设计高速数传通讯类软件中有着很好的使用效果。本文所述代码在Windows 2000下由Microsoft Visual C++ 6.0编译通过。

共享内存操作类(C#源码)(转)

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace ShareMemLib
{
    public class ShareMem
    {
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr CreateFileMapping(int hFile, IntPtr lpAttributes, uint flProtect, uint dwMaxSizeHi, uint dwMaxSizeLow, string lpName);
        
        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr OpenFileMapping(int dwDesiredAccess,[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,string lpName);

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr MapViewOfFile(IntPtr hFileMapping,uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow,uint dwNumberOfBytesToMap);

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern bool UnmapViewOfFile(IntPtr pvBaseAddress);

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern bool CloseHandle(IntPtr handle);

        [DllImport("kernel32", EntryPoint="GetLastError")]
        public static extern int GetLastError ();

        const int ERROR_ALREADY_EXISTS = 183;

        const int FILE_MAP_COPY = 0x0001;
        const int FILE_MAP_WRITE = 0x0002;
        const int FILE_MAP_READ = 0x0004;
        const int FILE_MAP_ALL_ACCESS = 0x0002 | 0x0004;

        const int PAGE_READONLY = 0x02;
        const int PAGE_READWRITE = 0x04;
        const int PAGE_WRITECOPY = 0x08;
        const int PAGE_EXECUTE = 0x10;
        const int PAGE_EXECUTE_READ = 0x20;
        const int PAGE_EXECUTE_READWRITE = 0x40;

        const int SEC_COMMIT = 0x8000000;
        const int SEC_IMAGE = 0x1000000;
        const int SEC_NOCACHE = 0x10000000;
        const int SEC_RESERVE = 0x4000000;

        const int INVALID_HANDLE_VALUE = -1;

        IntPtr m_hSharedMemoryFile = IntPtr.Zero;
        IntPtr m_pwData = IntPtr.Zero;
        bool m_bAlreadyExist = false;
        bool m_bInit = false;
        long m_MemSize=0;

        public ShareMem()
        {
        }
        ~ShareMem()
        {
            Close();
        }

        /// <summary>
        /// 初始化共享内存
        /// </summary>
        /// <param name="strName">共享内存名称</param>
        /// <param name="lngSize">共享内存大小</param>
        /// <returns></returns>
        public int Init(string strName, long lngSize)
        {
            if (lngSize <= 0 || lngSize > 0x00800000) lngSize = 0x00800000;
            m_MemSize = lngSize;
            if (strName.Length > 0)
            {
                //创建内存共享体(INVALID_HANDLE_VALUE)
                m_hSharedMemoryFile = CreateFileMapping(INVALID_HANDLE_VALUE, IntPtr.Zero, (uint)PAGE_READWRITE, 0, (uint)lngSize, strName);
                if (m_hSharedMemoryFile == IntPtr.Zero)
                {
                    m_bAlreadyExist = false;
                    m_bInit = false;
                    return 2; //创建共享体失败
                }
                else
                {
                    if (GetLastError() == ERROR_ALREADY_EXISTS)  //已经创建
                    {
                        m_bAlreadyExist = true;
                    }
                    else                                         //新创建
                    {
                        m_bAlreadyExist = false;
                    }
                }
                //---------------------------------------
                //创建内存映射
                m_pwData = MapViewOfFile(m_hSharedMemoryFile, FILE_MAP_WRITE, 0, 0, (uint)lngSize);
                if (m_pwData == IntPtr.Zero)
                {
                    m_bInit = false;
                    CloseHandle(m_hSharedMemoryFile);
                    return 3; //创建内存映射失败
                }
                else
                {
                    m_bInit = true;
                    if (m_bAlreadyExist == false)
                    {
                        //初始化
                    }
                }
                //----------------------------------------
            }
            else
            {
                return 1; //参数错误     
            }

            return 0;     //创建成功
        }
        /// <summary>
        /// 关闭共享内存
        /// </summary>
        public void Close()
        {
            if (m_bInit)
            {
                UnmapViewOfFile(m_pwData);
                CloseHandle(m_hSharedMemoryFile);
            }
        }

        /// <summary>
        /// 读数据
        /// </summary>
        /// <param name="bytData">数据</param>
        /// <param name="lngAddr">起始地址</param>
        /// <param name="lngSize">个数</param>
        /// <returns></returns>
        public int Read(ref byte[] bytData, int lngAddr, int lngSize)
        {
            if (lngAddr + lngSize > m_MemSize) return 2; //超出数据区
            if (m_bInit)
            {               
                Marshal.Copy(m_pwData, bytData, lngAddr, lngSize);
            }
            else
            {
                return 1; //共享内存未初始化
            }
            return 0;     //读成功
        }

        /// <summary>
        /// 写数据
        /// </summary>
        /// <param name="bytData">数据</param>
        /// <param name="lngAddr">起始地址</param>
        /// <param name="lngSize">个数</param>
        /// <returns></returns>
        public int Write(byte[] bytData, int lngAddr, int lngSize)
        {
            if (lngAddr + lngSize > m_MemSize) return 2; //超出数据区
            if (m_bInit)
            {
                Marshal.Copy(bytData, lngAddr, m_pwData, lngSize);
            }
            else
            {
                return 1; //共享内存未初始化
            }
            return 0;     //写成功
        }
    }
}
测试例程:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using ShareMemLib;

namespace YFShareMem
{
    public partial class frmShareMem : Form
    {
        ShareMem MemDB=new ShareMem();
        public frmShareMem()
        {
            InitializeComponent();
        }

        private void btnOpen_Click(object sender, EventArgs e)
        {
            if (MemDB.Init("YFMemTest", 10240) != 0)
            {
                //初始化失败
                MessageBox.Show("初始化失败");
            }
            else
            {
                btnOpen.Enabled = false;
                chkWrite.Enabled = true;
                tmrTime.Enabled = true;
            }
        }

        private void tmrTime_Tick(object sender, EventArgs e)
        {
            byte[] bytData = new byte[16];
            int intRet = MemDB.Read(ref bytData, 0, 16);
            lstData.Items.Clear();
            if (intRet == 0)
            {
                for (int i = 0; i < 16; i++)
                {
                    lstData.Items.Add(bytData .ToString());
                }

                if (chkWrite.Checked)
                {
                    bytData[0]++;
                    bytData[1] += 2;
                    if (bytData[0] > 200) bytData[0] = 0;
                    if (bytData[1] > 200) bytData[1] = 0;
                    MemDB.Write(bytData, 0, 16);
                }
            }           
        }

    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值