在VB中用文件映射来进行进程通讯

来源:岁月联盟 编辑:zhu 时间:2008-11-25

  当我们用VB开发应用系统时,可能涉及多进程问题。比如工业上应用较多的数据采集系统,也许就需要两个进程,一个是“采样程序”,另一个是“管理程序”,“采样程序”做单一的采集样本工作,而“管理程序”则对样本进行分析,存储,输出各种图表等等。为了便于维护,“采样程序”与“管理程序”各自作为独立的应用程序而运行,那么“管理程序”怎样才能取得“采样程序”所采集的数据呢?这就是所谓进程间的通信问题。

  在多个应用程序之间交换数据,我们自然会想到磁盘文件,但这种方法在实时系统中是不宜采用的,因为读写磁盘文件的时间效率往往不能满足实时要求。幸运的是,Windows提供了几种高效的进程间交换数据的机制,如管道,邮路和文件映射。以下我们只针对文件映射进行讨论。

  一. 文件映射概念

  所谓文件映射,简单地说,就是将磁盘文件(或部分)映射到某段内存空间,对磁盘文件的访问转变成对内存的访问,显然,这大大提高了访问速度。实际的映射过程是通过几个API函数来实现的,首先需要创建一个“文件映射对象”,而这个对象是共享的,各个进程可将对象映射到自己的内存地址空间,各进程的映射地址不一定相同,但地址中的内容却一定是相同的,各进程对各自的映射地址的访问都归结为对“文件映射对象”的访问。如上所言,我们可以认为“文件映射”是将文件映射到内存供各进程共享。那我们何不直接开辟一块全局内存来共享呢?这在32位Windows中是行不通的,因为全局内存在32位Windows中不是多进程共享的对象。因此,文件映射在进程间通信中扮演了重要的角色。

  二. 示例

  我们姑且把这个示例叫做“数据采集系统”,它由两个工程组成:Sampling.vbp(采样)和Manage.vbp(管理)。

  Sampling.vbp包含两个文件:Form1.frm,Module1.bas。清单如下:

  Form1.frm:
  VERSION 5.00
  Begin VB.Form Form1
  Caption = "Sampling"
  ClientHeight = 1440
  ClientLeft = 48
  ClientTop = 288
  ClientWidth = 4416
  LinkTopic = "Form1"
  ScaleHeight = 1440
  ScaleWidth = 4416
  StartUpPosition = 3 '窗口缺省
  Begin VB.CommandButton cmdStop
  Caption = "Stop"
  Enabled = 0 'False
  Height = 372
  Left = 2160
  TabIndex = 2
  Top = 360
  Width = 972
  End
  Begin VB.CommandButton cmdStart
  Caption = "Start"
  Height = 372
  Left = 840
  TabIndex = 1
  Top = 360
  Width = 972
  End
  Begin VB.TextBox Text1
  Height = 372
Left = 120
  TabIndex = 0
  Text = "Text1"
  Top = 840
  Width = 4092
  End
  Begin VB.Timer Timer1
  Enabled = 0 'False
  Interval = 60
  Left = 0
  Top = 0
  End
  End
  Attribute VB_Name = "Form1"
  Attribute VB_GlobalNameSpace = False
  Attribute VB_Creatable = False
  Attribute VB_PredeclaredId = True
  Attribute VB_Exposed = False
  Option Explicit
  Private Sub cmdStart_Click()
  Pub_Timer1Run = False
  Pub_LastTime = Timer()
  Timer1.Enabled = True
  cmdStart.Enabled = False
  cmdStop.Enabled = True
  End Sub
  Private Sub cmdStop_Click()
  Timer1.Enabled = False
  cmdStart.Enabled = True
  cmdStop.Enabled = False
  End Sub
  Private Sub Form_Load()
  Call CreateMap
  End Sub
  Private Sub Form_Unload(Cancel As Integer)
  Call CloseMap
  End Sub
  Private Sub Timer1_Timer()
  Static tm As Single, Dlt As Single
  Static i As Integer
  Static dtNow As Date
  Static S As String
  Static v(1 To Pub_LoopN) As Single
  If Pub_Timer1Run Then Exit Sub
  Pub_Timer1Run = True
  tm = Timer(): dtNow = Now()
  Dlt = tm - Pub_LastTime
  If Sgn(Dlt) = -1 Then '两次时间跨午夜0点
  Dlt = Dlt 86400! '86400 = 24 * 3600
  End If
 Do While Dlt >= Pub_Period
  Pub_LastTime = tm
  Call GetV(v())
  Call GetFromMap(strBuffer)
  If Left(strBuffer, 1) = "*" Then
  S = " " & Format(dtNow, Pub_FormatDT)
  For i = 1 To Pub_LoopN
  S = S & " " & Format(v(i), Pub_FormatV)
  Next i
  strBuffer = S: Call CopyToMap(strBuffer)
  Text1.Text = S
  Else
  'Add to File
  End If 'Left(strBuffer, 1) = "*"
  Exit Do
  Loop
  Pub_Timer1Run = False
  End Sub 'Timer1_Timer
  Private Sub GetV(v() As Single)
  Const MaxV = 4000!
  Dim i As Integer
  Randomize
  For i = 1 To Pub_LoopN
  v(i) = CSng(MaxV * Rnd)
  Next i
  End Sub 'GetV
  Module1.bas:
  Attribute VB_Name = "Module1"
  Option Explicit
  #Const Sampling = True '编译常数Sampling=Ture:采样, =False:管理
  Public DiskFileName As String '实时样本磁盘文件名
  Public MapFileName As String '前者的(内存)映射文件名
  Public FileHandle As Long '磁盘文件句柄
  Public MapHandle As Long '映射文件句柄
  Public MapAddress As Long '映射地址
  Public strBuffer As String '实时样本缓冲
  Public LenBuffer As Long '缓冲区长度
  Public Const Pub_LoopN = 2 '通道数目
  Public Const Pub_FormatDT = "yyyy-mm-dd hh:mm:ss" '日期/时间格式
  Public Const Pub_FormatV = "0000.000" '样本数据格式
  Public Pub_LenDT As Long '日期/时间宽度
  Public Pub_LenV As Long '样本数据宽度
  Public Const Pub_Period = 2! '采样周期(秒)
  Public Pub_LastTime As Single '上次采样时间
  Public Pub_Timer1Run As Boolean '中断例程在运行标志
Public Const FILE_MAP_WRITE = &H2
  Public Const FILE_MAP_READ = &H4
  Public Const PAGE_READWRITE = 4&
  Public Const GENERIC_READ = &H80000000
  Public Const GENERIC_WRITE = &H40000000
  Public Const CREATE_ALWAYS = 2
  Public Const FILE_SHARE_READ = &H1
  Public Const FILE_SHARE_WRITE = &H2
  Public Const FILE_ATTRIBUTE_NORMAL = &H80
  Declare Function lstrcpyn Lib "kernel32" Alias "lstrcpynA" _
  (DesStr As Any, _
  SrcStr As Any, _
  ByVal MaxLen As Long) As Long
  Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
  #If Sampling Then
  Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" _
  (ByVal lpFileName As String, _
  ByVal dwDesiredAccess As Long, _
  ByVal dwShareMode As Long, _
  ByVal lpSecurityAttributes As Long, _
  ByVal dwCreationDisposition As Long, _
  ByVal dwFlagsAndAttributes As Long, _
  ByVal hTemplateFile As Long) As Long
  Declare Function WriteFile Lib "kernel32" _
  (ByVal hFile As Long, _
  lpBuffer As Any, _
  ByVal nNumberOfBytesToWrite As Long, _
  lpNumberOfBytesWritten As Long, _
  ByVal lpOverlapped As Long) As Long
  Declare Function FlushFileBuffers Lib "kernel32" (ByVal hFile As Long) As Long
  #End If
  #If Sampling Then
  Declare Function CreateFileMapping Lib "kernel32" Alias "CreateFileMappingA" _
  (ByVal hFile As Long, _
  ByVal lpFileMappingAttributes As Long, _
  ByVal flProtect As Long, _
  ByVal dwMaximumSizeHigh As Long, _
  ByVal dwMaximumSizeLow As Long, _
  ByVal lpName As String) As Long
  #Else
  Declare Function OpenFileMapping Lib "kernel32" Alias "OpenFileMappingA" _
  (ByVal dwDesiredAccess As Long, _
  ByVal bInheritHandle As Long, _
  ByVal lpName As String) As Long
  #End If
  Declare Function MapViewOfFile Lib "kernel32" _
  (ByVal hFileMappingObject As Long, _
  ByVal dwDesiredAccess As Long, _
  ByVal dwFileOffsetHigh As Long, _
  ByVal dwFileOffsetLow As Long, _
  ByVal dwNumberOfBytesToMap As Long) As Long
  Declare Function UnmapViewOfFile Lib "kernel32" _
  (lpBaseAddress As Any) As Long'
  Public Sub InitVar()
  DiskFileName = "D:ArticleMappingSample"
  MapFileName = DiskFileName & "Map"
  Pub_LenDT = Len(Pub_FormatDT)
  Pub_LenV = Len(Pub_FormatV)
  LenBuffer = 1 Pub_LenDT (Pub_LenV 1) * Pub_LoopN
  strBuffer = String(LenBuffer 1, "*")
  FileHandle = 0
  MapHandle = 0
  MapAddress = 0
  End Sub 'InitVar
  Public Sub CopyToMap(S As String)
  If MapAddress <> 0 Then
  Call lstrcpyn(ByVal MapAddress, ByVal S, LenBuffer 1)
  End If
  End Sub
  Public Sub GetFromMap(S As String)
  If MapAddress <> 0 Then
  Call lstrcpyn(ByVal S, ByVal MapAddress, LenBuffer 1)
  End If
  End Sub
  Public Sub CloseMap()
  If MapAddress <> 0 Then
  Call UnmapViewOfFile(ByVal MapAddress)
  MapAddress = 0
  End If
  If MapHandle <> 0 Then
  Call CloseHandle(MapHandle)
  MapHandle = 0
  End If
  If FileHandle <> 0 Then
  Call CloseHandle(FileHandle)
  FileHandle = 0
  End If
  End Sub 'CloseMap
  #If Sampling Then
  Public Sub CreateMap()
  Dim w As Long
  Call InitVar
  FileHandle = CreateFile(DiskFileName, _
  GENERIC_WRITE Or GENERIC_READ, _
  FILE_SHARE_READ Or FILE_SHARE_WRITE, _
  0, _
  CREATE_ALWAYS, _
  FILE_ATTRIBUTE_NORMAL, _
  0)
  Call WriteFile(FileHandle, ByVal strBuffer, LenBuffer 1, w, 0)
  Call FlushFileBuffers(FileHandle)
MapHandle = CreateFileMapping(FileHandle, _
  0, _
  PAGE_READWRITE, _
  0, _
  0, _
  MapFileName)
  MapAddress = MapViewOfFile(MapHandle, FILE_MAP_WRITE, 0, 0, 0)
  End Sub 'CreateMap
  #Else
  Public Function OpenMap() As Long
  Call InitVar
  OpenMap = 0
  MapHandle = OpenFileMapping(FILE_MAP_WRITE, False, MapFileName)
  If MapHandle = 0 Then Exit Function
  MapAddress = MapViewOfFile(MapHandle, FILE_MAP_WRITE, 0, 0, 0)
  If MapAddress = 0 Then
  Call CloseHandle(MapHandle)
  MapHandle = 0
  End If
  OpenMap = MapAddress
  End Function 'OpenMap
  #End If 'Sampling
  Manage.vbp也包含两个文件:Form1.frm,Module1.bas。清单如下:
  Form1.frm:
  VERSION 5.00
  Begin VB.Form Form1
  Caption = "Manage"
  ClientHeight = 1440
  ClientLeft = 48
  ClientTop = 288
  ClientWidth = 4416
  LinkTopic = "Form1"
  ScaleHeight = 1440
  ScaleWidth = 4416
  StartUpPosition = 3 '窗口缺省
  Begin VB.CommandButton cmdStart
  Caption = "Start"
  Height = 372
  Left = 1560
  TabIndex = 1
  Top = 240
  Width = 972
  End
  Begin VB.TextBox Text1
  Height = 372
  Left = 120
  TabIndex = 0
  Text = "Text1"
  Top = 840
  Width = 4092
  End
  Begin VB.Timer Timer1
Enabled = 0 'False
  Interval = 60
  Left = 0
  Top = 0
  End
  End
  Attribute VB_Name = "Form1"
  Attribute VB_GlobalNameSpace = False
  Attribute VB_Creatable = False
  Attribute VB_PredeclaredId = True
  Attribute VB_Exposed = False
  Option Explicit
  Private Sub cmdStart_Click()
  If OpenMap() = 0 Then
  MsgBox "采样程序未运行!", vbOKOnly, ""
  Exit Sub
  End If
  Pub_Timer1Run = False
  Timer1.Enabled = True
  cmdStart.Enabled = False
  End Sub
  Private Sub Form_Unload(Cancel As Integer)
  Call CloseMap
  End Sub
  Private Sub Timer1_Timer()
  Static tm As Single, Dlt As Single
  Static i As Integer
  Static dtNow As Date
  Static S As String
  Static v(1 To Pub_LoopN) As Single
  If Pub_Timer1Run Then Exit Sub
  Pub_Timer1Run = True
  Call GetFromMap(strBuffer)
  If Left(strBuffer, 1) = " " Then
  strBuffer = "*" & Mid(strBuffer, 2)
  Call CopyToMap(strBuffer)
  Text1.Text = strBuffer
  End If
  Pub_Timer1Run = False
  End Sub 'Timer1_Timer

  Module1.bas:与Sampling.vbp之Module1.bas几乎完全相同,只是其中编译常数Sampling= False。

  三. 函数描述

  在Module1.bas中用到几个与文件映射有关的API函数,分述如下:

  1.CreateFileMapping:创建文件映射对象

  参数:

  hFile:Long——欲在其中创建映射的一个已经打开的磁盘文件句柄;

  LpFileMappingAttributes:Long——通常用0表示使用默认安全对象;

  FlProtect:Long——打开映射的方式(用API常数表示的读/写或其它);

  DwMaximumSizeHigh,dwMaximumSizeLow:Long——共同表示文件映射的最大长度(前者为高32位,后者为低32位),通常均设为0表示磁盘文件的实际长度;

  LpName: String——指定文件映射对象的名称。

  返回值:Long——新建文件映射对象的句柄。

  2.OpenFileMapping:打开一个现成的文件映射对象

  参数:

  dwDesiredAccess:Long——用API常数表示的对文件映射的访问方式;

  bInheritHandle:Long——返回值对与子进程的继承属性,常设为False;

  lpName:String——准备打开的文件映射对象的名称。

  返回值:Long——指定的文件映射对象的句柄。

  3.MapViewOfFile:将一个文件映射对象映射到当前应用程序空间

  参数:

  hFileMappingObject:Long——文件映射对象的句柄;

  dwDesiredAccess:Long——用API常数表示的对文件映射的访问方式;

  dwFileOffsetHigh,dwFileOffsetLow:Long——共同表示文件中的映射起点(前者为高32位,后者为低32位),通常均设为0表示从文件的起始处开始映射;

  dwNumberOfBytesToMap:Long——要映射的字节数,通常设为0表示映射整个文件映射对象。

  返回值:Long——文件映射在内存中的起始地址。

  4.UnmapViewOfFile:解除当前应用程序中的一个文件映射对象的映射地址空间

  参数:

  lpBaseAddress:要解除映射的文件映射起始地址。

  返回值:Long——非零表示成功,零表示失败。

  Sampling.vbp的启动窗体Form1.frm在装载时创建一个文件映射(CreateMap),这个创建过程分三步:首先,通过CreateFile,WriteFile,FlushFileBuffers建立一个具有指定长度(LenBuffer 1)的磁盘文件DiskFileName;然后,由CreateFileMapping创建一个对应于磁盘文件DiskFileName的文件映射对象MapFileName;最后,用MapViewOfFile将文件映射对象映射到应用程序地址MapAddress。在本例中,磁盘文件建立后便不再与之打交道,以后的操作均针对其映射地址空间。

  采样通过触发定时器Timer1周期性的进行(采样周期Pub_Period)。每次采样首先通过GetV取得原始样本并放入数组v(本例的样本用随机数替代,实际应用中是从RS232或其他设备取得),然后将其存入映射地址空间以便“管理程序”取用。样本在映射地址空间的存放形式为:“x 采样时间 样本值1 样本值2”。其中x是一个标记,当它为空格时表示新样本,为“*”时表示已取用。 为了方便程序处理,设置了一个样本缓冲strBuffer,由它与映射地址空间交换数据,CopyToMap和GetFromMap也是用于这个目的,CopyToMap(S)是复制S到映射地址空间,而GetFromMap(S)是从映射地址空间取值送到S。

  在“采样程序”运行过程中,“管理程序”由于某种原因(如维护程序)可能长时间不取用样本(超过一个采样周期),这时,“采样程序”应当把样本存放到另外的磁盘文件,以免丢失样本。考虑到本文主题和文章篇幅,本例未做处理。Manage.vbp启动窗体Form1.frm很简单,仅仅从演示的角度将映射地址空间的数据取出并显示。试验时先运行“采样”, 再运行“管理”,观察两个窗体中的样本数据,我们会发现他们几乎是同步的。感谢文件映射!