在VB中用文件映射來進行進程通訊
發表于:2007-07-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 dwDesiredA
clearcase/" target="_blank" >ccess 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很簡單,僅僅從演示的角度將映射地址空間的數據取出并顯示。
試驗時先運行“采樣”, 再運行“管理”,觀察兩個窗體中的樣本數據,我們會發現他們幾乎是同步的。感謝文件映射!
原文轉自:http://www.anti-gravitydesign.com