VBGood网站全文搜索 Google

搜索VBGood全站网页(全文搜索)
首页 - 经验之谈 - 在VB中用文件映射来进行进程通讯
发表评论(0)作者:卿 静, 平台:VB6.0+Win98, 阅读:9618, 日期:2001-04-14
在VB中用文件映射来进行进程通讯

四川行政财贸管理干部学院计算机管理系 卿 静

--------------------------------------------------------------------------------


当我们用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:\Article\Mapping\Sample"
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很简单,仅仅从演示的角度将映射地址空间的数据取出并显示。
试验时先运行“采样”, 再运行“管理”,观察两个窗体中的样本数据,我们会发现他们几乎是同步的。感谢文件映射!