VB 版 (精华区)

发信人: bloom (├┝┞┟┠┢┣), 信区: VB
标  题: VB API教程(王国荣版)(七)(转载)
发信站: 哈工大紫丁香 (2000年09月07日18:42:51 星期四), 转信

【 以下文字转载自 cnTemp 讨论区 】
【 原文由 bloom 所发表 】
发信人: Love1976 (狄飞惊), 信区: VisualBasic       
发信站: BBS 水木清华站 (Thu Apr  6 04:22:02 2000)

发信人: coolknight (酷骑士~找工作中), 信区: VB
标  题: VB 與 Windows API 講座(七)
发信站: 武汉白云黄鹤站 (Tue Nov  9 20:12:17 1999), 站内信件

VB 與 Windows API 講座(七)
InterProcess Communication(行程通訊)(上)
 
http://www.kj.com.tw
王國榮
 
IPC(InterProcess Communication, 行程通訊) 對筆者而言, 十分具有紀念價值
,因
為它是筆者第一本書的主題, 早在 Windows 3.1 的時代, 旗標公司出版了一系
列的
Windows 程式設計書籍, 其中第三本書「Windows 程式設計 ─ IPC 篇」就是筆
者在初
生之犢(其實當時也不算年輕,只是第一次寫書)不畏虎的情況下完成的。
 
很不幸的, 從 Windows 3.1(或者稱 Win16) 演進到 Windows 95/NT(統稱 
Win32),由
於架構的大翻修, 筆者這本書至少有一半的內容不再適用於 Windows 95/NT,也
許有人
會問這本書為什麼不改版呢?由於後來 Windows 好用的開發工具(例如 VB)越來越
普遍
, 所以直接談 Windows API 方面的書籍也就越來越少, 在時勢所趨之下,筆者
的那本
書就成為歷史了。
 
------------------------------------------------------------------------
----
----
----
IPC 是哪些東西?您需要它嗎?
------------------------------------------------------------------------
----
----
 
筆者不是一個迷信新技術與艱澀技術的人, 一向偏好使用簡單的方法來解決問題
(如果
可能的話),而 IPC 可以說是比較艱澀的技術, 開始以前, 不妨先問一問自己,
 真的
需要它嗎?以下讓筆者先列出 IPC 方面的主題:
 
◇ Clipboard(剪貼簿)。
◇ DDE(Dynamic Data Exchange, 動態資料交換)。
◇ OLE(Object Linking and Embedded, 物件連結與崁入)。
◇ 記憶體共用。
◇ 訊息傳遞。
◇ Process Synchronization(行程的同步執行, 包含 Mutex、Semaphore、
Event、Wa
itable Timer 等)。
◇ MultiThread(多執行緒)。
 
Clipboard(剪貼簿)
------------------------------------------------------------------------
----
----
 
剪貼簿是 Windows 最早的 IPC 功能, 為了讓不同程式之間可以在執行階段交換
資料,
剪貼簿是 Windows 最早的 IPC 功能, 為了讓不同程式之間可以在執行階段交換
資料,
 Windows 定義了一些常見的資料格式, 例如文字、點陣圖等, 並且利用系統剪
貼簿做
為資料交換的中心,讓資料「提供者」可以把資料複製到剪貼簿, 而資料「使用
者」則
可從剪貼簿之中取得資料。
 
如果我們想要使用剪貼簿, 並不需要呼叫 Windows API, 透過 VB 所提供的全域
物件
─Clipboard,就可以讓 VB 程式擁有剪貼簿的功能。
 
DDE(Dynamic Data Exchange, 動態資料交換)
------------------------------------------------------------------------
----
----
 
使剪貼簿用來交換資料其資料是「死」的, 也就是說當資料「提供者」的資料有
所變動
時,資料「使用者」先前所拿到的資料並不會被更新, 欲更新資料必須由資料提
供者再
把最新的資料放到剪貼簿之中,然後資料使用者重新擷取一次資料, 這對於必須
經常更
新的資料, 實在很不方便。DDE 乃是因應這種需求而生的通訊協定, 它可以讓資
料使
用者自動取得提供資料者最新的資料。
 
DDE 的功能跟剪貼簿一樣已經內建於 VB 之中, 不必透過 Windows API 來完成。
不管
是剪貼簿或 DDE, 執行效能都不高, 筆者以 133 MHz 的電腦測試文字性資料的
交換速
度,結果每秒鐘只能交換 20-40 筆, 不過在所有的 IPC 功能中, 剪貼簿及 DDE
 卻最
容易使用,如果您交換資料的頻率不高, 倒可以考慮採用這兩種方式。
 
 
OLE(Object Linking and Embedded, 物件連結與崁入)
------------------------------------------------------------------------
----
----
 
不管是剪貼簿或是 DDE, 想要使用其他程式的資料, 資料使用者必須瞭解資料提
供者
的「資料格式」,這對於資料使用者來說是一大負擔, 因此有了物件交換的概念
(不只
是「資料」交換),所謂物件交換, 要從「服務」的角度來看, 買東西時我們常
說「售
後服務」,在剪貼簿與 DDE 式的資料交換中, 資料提供者在提供資料之後, 就
不管資
料使用者懂不懂資料的內容,可以說完全沒有售後服務, 而所謂「物件」交換則
是指資
料提供者在提供資料之後,還會提供售後服務, 資料使用者可以不必瞭解資料的
格式,
 想要顯示、編輯、更新資料時,只要向資料提供者提出服務的要求即可。
 
提到服務, 順便介紹兩個名詞:Server 及 Client, 由於物件交換的行為是由資
料(物
件)提供者及資料(物件)使用者所構成的,因此我們習慣把資料(物件)提供者稱為
 Serv
er(伺服程式), 而資料(物件)使用者由於居於被服務的地位,因此稱為 Client(
客戶程
式)。
 
初期 OLE 的程式設計真的很困難, 不過在技術漸趨成熟的情況下, OLE 
Server 被發
展成 Windows 物件的標準, 相對於使用 OLE 通訊協定, 使用 OLE Server 所開
發出
來的物件(VB4 及 VB5 所提供的物件均屬之), 實在輕鬆太多了, 所以很多人都
有「用
物件太輕鬆,談 OLE 太沈重」的感覺。
 
 
除了 Client 程式變輕鬆之外, VB5 有了重大的改變, 它提供 ActiveX Control
 及
ActiveX Code Component(都算是 OLE Server) 的開發功能, 使得 VB 程式也可
以輕易
地成為 OLE Server。就 OLE 所衍生出來的 ActiveX 技術方面, ActiveX 
Control 及
 DLL 格式的 ActiveX Code Component 可提供「物件類別」的共用, 而 EXE 格
式的
ActiveX Code Component, 除了可提供物件類別的共用之外, 還可以提供物件及
資料
的共用,如果您已經熟悉 ActiveX 的程式設計, 它除了可以讓您的程式更物件導
向之
外,也是 IPC 的利器。
 
記憶體共用
------------------------------------------------------------------------
----
----
 
剪貼簿、DDE、及 OLE(ActiveX) 都是 VB 內建的功能, 在 VB 的工作環境底下,
比較
容易使用, 至於「記憶體共用」則必須呼叫 Windows API。
 
在 Win16 底下, 記憶體的共用是很容易的, 假設程式 A 的某一個變數要提供給
其他
程式使用,只要將該變數的位址告訴其他程式, 則其他程式就可以直接存取此一
位址的
變數,而達到共用記憶體的目的。
 
以上的共用模式在 Win32 底下則是行不通的, 原因稍後筆者會說明原因, 在 
Win32
底下, 必須呼叫「記憶體對映檔案」(Memory-Mapped File) 方面的 API 函數,
 不同
程式之間才可以共用記憶體, 過程比較麻煩, 不過相對於剪貼簿及 DDE, 記憶
體對映
程式之間才可以共用記憶體, 過程比較麻煩, 不過相對於剪貼簿及 DDE, 記憶
體對映
檔案的執行效能要高出許多, 麻煩一點還是值得的。
 
Process Synchronization(行程的同步執行)
------------------------------------------------------------------------
----
----
 
在多工作業環境底下, 通常是各執行各的, 彼此沒有任何關係, 稱為非同步執
行。非
同步執行在一般情況下並沒有什麼問題,但一旦程式之間必須共用資源時, 就不
得不小
心, 舉例來說, 某一共用的變數 x, 程式 A 及 程式 B 都會執行 x = x + 1 
敘述將
它加一, 讓我們來想一想它可能會出現什麼問題?
 
首先我們必須知道 Windows 為了達到多工作業的目的, 會分配給執行中程式若干
使用
 CPU 的時間, 而一旦時間用盡, 程式就會被暫停, 而輪到另一個程式來執行,
所以
程式在哪一個指令被暫停是很隨機的, 其次我們還要知道 x = x + 1 敘述至少會
被分
解成三個指令來執行:
 
(1) 讀取 x 的值。
(2) 將 x 的值加一, 然後儲存在暫存器中。
(3) 將暫存器的內容寫回 x。
 
假設 x 原來的值等於 10, 而程式 A 執行到指令 (1)(2) 之後被暫停了(此時 
x 等於
 10, 但暫存器的值則等於 11), 接著輪到程式 B 被執行, 由於程式 A 還沒有
將暫
 10, 但暫存器的值則等於 11), 接著輪到程式 B 被執行, 由於程式 A 還沒有
將暫
存器的值寫回 x, 所以程式 B 執行, 所讀取的 x 將等於 10, 並且把加一以後
的值
 11 寫回 x 之中, 後來程式 B 被暫停, 再度輪到程式 A 執行, 但程式 A 並
不知道
 x 已經被加一了, 還是暫存器的值 11 寫回 x, 而使得 x 在執行兩次 x = x +
 1 之
後, 其值只增加了一。
 
以上是非同步執行可能遭遇的問題, 要解決這個問題, 必須讓程式 A 與程式 
B 執行
 x = x + 1 敘述時進入「同步執行」(Synchronization)狀態, 參考圖-1,當程
式 A
開始執行 x = x + 1 時, 就禁止其他程式執行此一敘述, 萬一程式 B 也要執行
此一
敘述, 則必須等候程式 A 先執行完 x = x + 1 敘述之後, 程式 B 才可以執行

 
圖-1 執行 x = x + 1 時, 程式 A 與程式 B 應該進入「同步執行」狀態
 
在共用資源的情況下, 程式之間的同步執行是十分必要的, VB 並沒有內建同步
執行的
機制,不過我們可以呼叫 Windows API 讓 VB 程式具有此一功能。
 
「行程的同步執行」雖然不提供資料的交換與共用, 但卻是保證資料交換與共用
能夠正
確完成不可或缺的功能。
 
MultiThread(多執行緒)
------------------------------------------------------------------------
----
----
 
 
偶而會有讀者問筆者:「VB 如何撰寫 MultiThread 的程式?」, 答案是不能也
,筆者
不知道讀者聽到這個答案後有何感想?覺得 VB 不夠好, 轉而使用其他的開發工
具?
 
筆者從來不覺得 VB 是 100 分的產品, 但卻是最喜歡的產品, 關於 
MultiThread 的
不能, 筆者則另有一套替代方案, 在本專欄後續的介紹中, 筆者會進一步解釋
 Mult
iThread 的意義, 並且與您討論可行的替代方案。
 

------------------------------------------------------------------------
----
----
如何傳遞資料給另一個程式?
------------------------------------------------------------------------
----
----
 
列入一大堆 IPC 的功能, 唉!筆者又自找苦吃了, 怎麼介紹它們呢?在篇幅有
限的情
況下,理論部分暫且不談太多(讓筆者偷懶一下), 還是以實務問題為出發, 並且
說明
解決的方案。第一個問題是:如何傳遞資料給另一個程式?
 
談到資料的傳遞, 筆者第一個想到的 API 函數是 SendMessage, 其主要用途是
傳遞「
訊息」給另一個「視窗」,雖然與我們期望的傳遞「資料」給另一個「程式」不盡
相同
, 但我們仍然可以利用以下方法讓 SendMessage 達到此一目的:
 
(1) 呼叫 SendMessage 之前, 先將資料複製到剪貼簿或共用記憶體之中, 則資
料接受
(1) 呼叫 SendMessage 之前, 先將資料複製到剪貼簿或共用記憶體之中, 則資
料接受
程式在收到訊息之後,再讀取剪貼簿或共用記憶體之中的資料, 其效果也相當於
傳遞資
料。
(2) 在資料接收程式端, 先寫好視窗程序, 則由於視窗程序是資料接收程式的一
部份
,傳遞訊息給視窗程序就等於傳遞訊息給資料接收程式。
 
關於第(1)點, 筆者在此先使用剪貼簿來存放欲傳遞的資料, 剪貼簿的使用很簡
單,資
料傳遞者是呼叫 Clipboard.SetText 或 Clipboard.SetData 將文字資料或圖片資
料複
製到剪貼簿中,而資料接收者則是呼叫 Clipboard.GetText 及 Clipboard.
GetData 讀
取剪貼簿之中的資料,關於第(2)點, 您可以先參考 Run!PC 48 期瞭解視窗程序
的寫法
。整個傳遞資料的過程筆者表示成圖-2:
 
圖-2 運用 SendMessage 及剪貼簿來傳遞資料
 
接著讓筆者以實例來說明, 請下載筆者準備的檔案, 然後同時啟動執行 
Receive1.vb
p 及 Send1.vbp 專案, 執行之後, 按下 Send1 的「送出」鈕, 結果 Receive1
 在收
到資料後, 便會把資料顯示在對應的欄位中, 如圖-3:
 
圖-3 筆者提供的 Send1 及 Receive1 程式
 
首先讓我們來看傳遞資料的 Send1 程式, 其中的 Command1_Click 事件程序如下

 
' 複製資料到剪貼簿
' 複製資料到剪貼簿
Clipboard.SetText Text1.Text
 
' 尋找標題為 "Receive1 - 資料接收者" 的視窗
hWndRecv = FindWindow(0&, "Receive1 - 資料接收者")
 
' 對 hWndRecv 送出編號等於 WM_USER + 1001 的訊息
Call SendMessage(hWndRecv, WM_USER + 1001, 0, ByVal 0&)
 
以上程式值得注意的地方有:(1) 利用 FindWindow 取得標題為 "Receive1 - 資
料接收
者" 的視窗 Handle, (2) SendMessage 的作用是對 hWndRecv 送出編號等於 
WM_USER
 + 1001 的訊息, 而相對的, 在資料接收程式的視窗程序中,所收到的 Msg 參
數也會
等於 WM_USER + 1001。
 
為什麼筆者要選擇 WM_USER + 1001 編號的訊息呢?而不是編號 0 或編號 1 的訊
息呢
?首先 0~WM_USER(&H400)-1 是 Windows 訊息的保留區, 也就是說這個範圍的
訊息已
經被 Windows 使用了或者將來可能會使用, 所以應用程式只能使用 WM_USER 以
後的訊

息,至於可以使用 WM_USER 以後的哪一個訊息, 則沒有規定, 只要訊息傳遞者
與訊息
接收者彼此講好就可以了,而筆者選擇 WM_USER + 1001, 自然地, 訊息接受端
也會根
據此一編號來判別訊息。
 
相對於 Send1 程式, Receive1 程式則要準備好視窗程序, 而視窗程序內接受訊
息(資
料)的程式則如下:
料)的程式則如下:
 
If Msg = WM_USER + 1001 Then
Form1.Text1.Text = Clipboard.GetText
ElseIf Msg = WM_USER + 1002 Then
Form1.Text2.Text = Clipboard.GetText
Else
WndProc = CallWindowProc(prevWndProc, hwnd, Msg, wParam, lParam)
End If
 
程式很簡單, 只要判斷所接收的訊息編號(Msg 參數), 然後進行對應的處理即可
,本
例是從剪貼簿中讀取資料, 然後設定到表單的 TextBox。
 
如果我們想從 Receive1 回傳資料給 Send1, 也一樣可以利用 Clipboard.
SetText 方
法把資料複製在剪貼簿之中, 而 Send1 則是在執行完 SendMessage 函數之後,
利用
Clipboard.GetText 方法讀取剪貼簿的資料, 就相當於獲得了 Receive1 的回傳
資料,
筆者所附的 Send1b.vbp 及 Receive1b.vbp 就是具有回傳功能的程式, 整個資料
傳遞
的過程則如圖-4:
 
圖-4 資料的傳遞與回傳
 
資料傳遞的劫鏢事件
------------------------------------------------------------------------
----
------------------------------------------------------------------------
----
----
 
運用 SendMessage 及剪貼簿, 確實可以達到傳遞資料的目的, 但是讓我們想一
想它有
沒有瑕疵?由於 Win32 是真正的多工作業系統, 這意味著任何一個程式在執行的
時候
, 都可能被作業系統暫停而把 CPU 使用權轉移給另一個程式, 在這個前提下,
 讓我
們回顧 Send1 傳遞資料給 Receive1 的過程, 假如在 Send1 複製資料到剪貼簿
之後
Receive1 尚未讀取資料之前,作業系統把 CPU 使用權轉移給第三個程式, 而偏
偏這個
程式會改變剪貼簿的內容,那麼 Receive1 就可能讀不到資料, 或者讀到的是不
正確的
資料, 就好像資料被劫了一樣。
 
怎樣避免資料被劫呢?基本上, 要保護剪貼簿的資料是不可能的事情, 因為剪貼
簿是
所有程式共用的,而 Windows 也沒有提供鎖定剪貼簿的功能, 不過還是聽一聽筆
者接
下來介紹的方法。
 
------------------------------------------------------------------------
----
----
優先等級的設定
------------------------------------------------------------------------
----
----
 
當系統中有多個等待執行的程式時, Windows 會按照優先等級來安排程式的執行
順序,
簡單地說, 就是會把 CPU 的使用權交給優先等級較高的程式, 而在等待執行的
程式佇
簡單地說, 就是會把 CPU 的使用權交給優先等級較高的程式, 而在等待執行的
程式佇
列中,若還有優先等級較高的程式在, 則優先等級較低的程式就不會被執行。
 
一般而言, 程式不應該自己設定優先等級, 因為不當的設定可能會造成其他程式
無法
執行,但是為了解決上述資料被劫的情況, 自己設定優先等級倒不失為一個可行
的方案
,直接來看資料傳遞者有何改變(原程式分別修改成 Send2.vbp 及 Receive2.
vbp):
 
' 此一程式取自 Send2.vbp 的Command1_Click 事件程序
' 存下原來的優先等級
Priority = GetThreadPriority(GetCurrentThread)
' 將優先等級設定到最高 31
Call SetThreadPriority(GetCurrentThread, 31)
Clipboard.SetText Text1.Text
hWndRecv = FindWindow(0&, "Receive2 - 資料接收者")
Call SendMessage(hWndRecv, WM_USER + 1001, 0, ByVal 0&)
' 將原來的優先等級還原
Call SetThreadPriority(GetCurrentThread, Priority)
 
以上程式呼叫了幾個 API 函數 ─ GetCurrentThread、GetThreadPriority、
SetThrea
dPriority,它們的作用分別是:
 
◇ GetCurrentThread:讀取本身的 Thread Handle。
◇ GetThreadPriority:讀取某一 Thread 的優先等級。
◇ GetThreadPriority:讀取某一 Thread 的優先等級。
◇ SetThreadPriority:設定某一 Thread 的優先等級, 有效值是 0~31,數值
越大表
示優先等級越高。
 
在此筆者必須先說明的是每一個執行中的程式對 Win32 而言, 都是一個 
Thread(執行
緒),而 GetCurrentThread 的用途是取讀自己的 Thread Handle, 藉由 
Thread Hand
le 值, 接著才可以呼叫 GetThreadPriority 及 SetThreadPriority 來讀取或設
定本
身的優先等級。
 
除了資料傳遞者這麼做以外, 資料接收者也要仿照相同的方法來設定優先等級,
而由於
在資料傳遞的過程中, 資料傳遞者與資料接收者都已經將自己的優先等級設定到
最高,
其他程式便無法插進來執行, 剪貼簿的資料也就不會被破壞了。
 
其實將資料傳遞者與資料接收者的優先等級調到最高, 還是無法 100% 避免資料
被劫,
如果說系統中也有其他最高優先等級的程式, 那麼我們還是無法保證在資料傳遞
時,
CPU 不會被轉移給其他程式而這個程式又改變了剪貼簿的內容, 當然, 這種機率
要比
不設定優先等級小得多。
 
------------------------------------------------------------------------
----
----
資料傳遞, DDE 篇
------------------------------------------------------------------------
----
----
----
 
除了剪貼簿之外, DDE 也可以用來傳遞資料, 而且它不必像剪貼簿一樣必須藉助
 Sen
dMessage 來傳遞訊息, DDE 本身已經內建了訊息傳遞的機制。
 
DDE 交換資料的模式大致上可以分成:
 
(1) 先建立對談, 接著當資料有所變動時, DDE Server 程式會「自動」更新 
DDE Cl
ient 程式的資料。
(2) 先建立對談, 接著當資料有所變動時, DDE Server 程式會「通知」DDE 
Client
程式資料已經有所變動, 然後由 DDE Client 程式主動向 DDE Server 程式索求
最新的
資料。
(3) 先建立對談, 接著不管資料是否有所變動, DDE Server 程式一概不通知 
DDE Cl
ient 程式, 但 DDE Client 程式仍然可以主動向 DDE Server 程式索求最新的資
料。

(4) DDE Server 程式與 DDE Client 程式只建立基本的連線關係, 但 DDE 
Client 程
式可以下達指令給 DDE Server 程式。
 
其中第 (4) 種交換資料的模式與一般的資料傳遞最像, 在此筆者將利用此一資料
交換
模式來傳遞資料,如果您對另外三種資料交換模式有興趣, 可參閱 Run!PC 31 期
(85/
08) 的 VB 專欄。
 
在利用剪貼簿傳遞資料的範例程式中, 筆者呼叫 FindWindow API 函數來尋找某
「視窗
在利用剪貼簿傳遞資料的範例程式中, 筆者呼叫 FindWindow API 函數來尋找某
「視窗
標題」(例如 "Receive1 - 資料接收者")的 hWnd, 然後再利用 SendMessage 來
傳送訊
息,讓我們想一想, 此一傳遞資料的模式有什麼問題?
 
如果說同時有兩個程式的視窗標題都等於 "Receive1 - 資料接收者",則 
FindWindow
會找到哪一個視窗的 hWnd 就不是程式可以掌控的, 結果可能會造成資料被傳到
錯誤的
視窗程序中。
 
DDE 並不是採用 FindWindow 來尋找資料傳遞的標的視窗, 而是事先設定好連線
主題(
LinkTopic),而接著 DDE Server 程式及 DDE Client 程式就使用相同的連線主題
來互
傳資料。
 
對 DDE Server 程式而言, 只要將表單的 LinkMode 屬性設定成 "1-來源",那麼
它就
完成了 "專案檔名|表單名稱" 的連線主題, 以 Receive3.vbp 為例, 專案檔名
為 "R
ECEIVE3", 表單名稱為 "Form1",所以將設定 LinkMode 屬性設定成 "1-來源" 
之後,
 它就成為一個含有 "RECEIVE3|Form1" 連線主題的 DDE Server 程式。
 
對於 DDE Client 程式, 若要下達指令(指令之中可以含有資料)給 DDE Server 
程式,
 則必須先使用佈置 TextBox、Label、或 PictureBox 控制元件, 然後設定好控
制元件

的 LinkTopic 屬性及 LinkMode 屬性, 其中 LinkTopic 屬性要等於 DDE Server
 程式
的連線主題,而 LinkMode 屬性則固定設成 vbLinkManual 即可, 以筆者所提供
的 Se
nd3.vbp 為例, 所執行的敘述如下:(請注意:LinkTopic 屬性一定要比 
LinkMode 屬
性先被設定)
性先被設定)
 
Text1.LinkTopic = "RECEIVE3|Form1"
Text1.LinkMode = vbLinkManual
 
在設定 LinkTopic 及 LinkMode 屬性之後, 則下達指令及資料的方法是呼叫 
LinkExe
cute, 在此例中, 筆者將指令設定成 "Cmd1", 而資料則是取自 Text1.Text,
 敘述
則如下:
 
Text1.LinkExecute "Cmd1 " & Text1.Text
 
相對的 DDE Server 要有接收 Client 指令的程式, 而 Form_LinkExecute 事件
程序正
是接收 Client 指令的地方, 此一事件程序含有兩個參數:CmdStr 及 Cancel,
 其中
 CmdStr 等於 Client 所傳遞過來的指令及資料, 至於 Cancel 則是 Server 用
來告知
 Client 是否接受此一指令的參數, 若接受, 應將此一參數設定成 False, 以
 Rece
ive3.vbp 為例, Form_LinkExecute 事件程序的程式如下:
 
Private Sub Form_LinkExecute(CmdStr As String, Cancel As Integer)
Select Case Left(CmdStr, 4) ' 指令部分
Case "Cmd1"
Text1.Text = Mid(CmdStr, 6) ' 資料部分
Case "Cmd2"
Text2.Text = Mid(CmdStr, 6) ' 資料部分
Text2.Text = Mid(CmdStr, 6) ' 資料部分
End Select
Cancel = False ' 表示接收指令
End Sub
 
使用 DDE 來傳遞資料, 不必像剪貼簿一樣擔心資料被別的程式破壞, 不過以上
利用
LinkExecute 來傳遞資料的模式, 只能由 Client 程式傳遞資料給 Server 程式
,若要
由 Server 程式回傳資料給 Client 程式, 則先建立 DDE 對談, 也就是使用前
三種
DDE 資料交換模式才辦得到。
 
------------------------------------------------------------------------
----
----
資料傳遞, 失敗篇
------------------------------------------------------------------------
----
----
 
不管是剪貼簿或 DDE, 其執行效能都不高, 並不適合用來傳遞大量的資料(或者
說經常
要傳遞的資料),為了提升執行效能, 過去在 Win16 底下, 筆者經常這麼做:
 
S = "傳遞的資料"
Call SendMessage(hWnd, Msg, 0, S)
 
此一傳遞方式是把 S 字串的「位址」當作 lParam 參數然後傳給另一個視窗,而
接收訊
此一傳遞方式是把 S 字串的「位址」當作 lParam 參數然後傳給另一個視窗,而
接收訊
息的視窗程序則會把 lParam 視為位址, 然後擷取此一位址的字串, 整個傳遞的
示意
圖大致如圖-5:
 
圖-5 過去 Win16 常用的資料傳遞模式
 
這種方法既簡單執行效能又高, 是筆者的最愛, 但是到了 Win32 以後, 它卻不
再適
用,以下且聽筆者說明為什麼 Win32 要奪去筆者的最愛。
 
記憶體管理方式的改變
------------------------------------------------------------------------
----
----
 
對 Win16 來說, 所有程式都被作業系統放在同一個位址空間, 所謂同一個位址
空間表
示若有某一塊記憶體的位址是 xxxxxxxx, 那麼任何一個程式都可以透過此一位址
來存
取這塊記憶體, 不管這塊記憶體屬於哪一個程式所有,這對於記憶體共用確實很
方便,
 但是卻有嚴重的缺點, 因為寫得不好的程式極可能因為弄錯了位址,而破壞了其
他程
式或作業系統的資料及程式碼, 接著就會造成別的程式或整個系統當掉。別以為
只有阿
貓阿狗會寫出這種程式,即使是微軟自己也無法完全避免寫出這種程式, 而這正
是 Wi
n16 最為人所詬病的一件事情。
 
Win32 則透過虛擬記憶體管理技術, 讓每一個程式擁有獨立的虛擬位址空間,所
謂獨立
的虛擬位址空間有兩個意義:(1) 程式所使用的位址都是虛擬的, 這些虛擬位址
將透過
的虛擬位址空間有兩個意義:(1) 程式所使用的位址都是虛擬的, 這些虛擬位址
將透過
 Windows 的記憶體管理方式而對應到某一記憶體的實際位址, 但程式只看到虛擬
位址
,看不到實際位址 (2) 相同的虛擬位址, 對不同程式而言將對應到不同的實際位
址,
因此不管程式如何操作記憶體, 都不會影響到其他程式, 而寫得不好的程式也只
會破
壞到自己的記憶體。
 
不過為了讓應用程式能夠共用記憶體, Windows 還是保留了全系統可定址空間的
一半(
約 2GB)為共用區域, 藉著這塊共用區域, 應用程式之間依然可以共用記憶體,
 只是
要使用這塊共用區域,在手續上會麻煩一點, 接著在「記憶體共用」段落中, 筆
者就
會有所說明。
 
------------------------------------------------------------------------
----
----
記憶體共用
------------------------------------------------------------------------
----
----
 
在 Win32 底下, 想要配置共用記憶體, 唯一的方法就是使用「記憶體對映檔案
」(Me
mory-Mapped File)。記憶體對映檔案是檔案也是記憶體, 相對於一般檔案, 它
的存取
速度要快很多,相對於應用程式的記憶體, 它具有共用的功能, 受限於篇幅, 
筆者暫
時不介紹它在檔案存取方面的應用,而直接以它做為應用程式之間的共用記憶體。

 
配置共用記憶體的方法
配置共用記憶體的方法
------------------------------------------------------------------------
----
----
 
有關配置共用記憶體的方法, 請直接參閱筆者所撰寫的敘述:(出現在 MemMap.
vbp 專
案中)
 
hFileMap = CreateFileMapping(-1, ByVal 0&, PAGE_READWRITE, 0, 1024, 
"MemMap"
)
 
此一敘述的作用是配置 1024 bytes(參數 5)的記憶體, 而且將記憶體的名稱設定
為 "
MemMap"(參數 6), 若配置成功, 則 hFileMap 將等於記憶體的 Handle,否則傳
回 0

 
如果您想修改以上敘述, 則參數 5 可設定記憶體的大小, 參數 6 可設定記憶體
的名
稱,其他參數則照抄即可。但請注意如果參數 6 是一個已存在的記憶體名稱(因為
已經
有別的程式使用這個名稱配置了記憶體),則 CreateFileMapping 不會傳回失敗值
, 而
會傳回原記憶體的 Handle, 若您想判斷是否有這種情況發生,則在呼叫 
CreateFileM
apping 之後, 要呼叫 GetLastError API 進行判斷, 方法如下:
 
If GetLastError = ERROR_ALREADY_EXISTS Then
' 表示已經有別的程式使用這個名稱配置了記憶體
End If
End If
 
使用共用記憶體的方法
------------------------------------------------------------------------
----
----
 
配置好記憶體之後, 所得到的是記憶體的 Handle, 使用之前還要呼叫 
MapViewOfFil
e API 函數取得記憶體的位址, 如下:
 
lpView = MapViewOfFile(hFileMap, FILE_MAP_READ Or FILE_MAP_WRITE, 0, 0,
 0)
 
呼叫時參數一要傳入 CreateFileMapping 傳回的記憶體 Handle, 其他參數也是
照抄就
好了,呼叫後的傳回值將等於記憶體的位址。
 
不過 VB 並不能直接操作位址, 為了將資料寫入位址中, 須使用上一期所介紹的
 Rtl
MoveMemory API 函數, 例如:
 
Call RtlMoveMemory( ByVal 位址, 資料, 資料長度)
 
例如:
 
Call RtlMoveMemory( ByVal lpView, ByVal "Test MemMap", 11) ' 寫入字串
Dim bArr(20) As Byte
Dim bArr(20) As Byte
Call RtlMoveMemory( ByVal lpView, bArr(0), 21) ' 寫入Byte陣列
 
如果從位址中讀回資料則是:
 
Call RtlMoveMemory(資料, ByVal 位址, 資料長度)
 
例如:
 
Dim S As String * 11
Call RtlMoveMemory(ByVal S, ByVal lpView, 11) ' 讀回的資料放在字串中
Dim bArr(20) As Byte
Call RtlMoveMemory(bArr(0), ByVal lpView, 21) ' 讀回的資料放在 Byte 陣列

 
釋放共用記憶體
------------------------------------------------------------------------
----
----
 
若要將共用記憶體歸還給系統, 須呼叫 CloseHandle, 如下:
 
Call CloseHandle( hFileMap)
 
呼叫之後, hFileMap 及 lpView 就不再是有意義的 Handle 及位址, 不可以再
使用。
呼叫之後, hFileMap 及 lpView 就不再是有意義的 Handle 及位址, 不可以再
使用。

 
利用 SendMessage 傳遞位址
------------------------------------------------------------------------
----
----
 
當某一程式配置了共用記憶體並且取得了記憶體的位址之後, 若將記憶體的位址
傳給另
一個程式,則另一個程式便可以直接存取此一位址的記憶體, 而達到共用記憶體
的目的
。為了展示此一傳遞資料的模式,請同時執行筆者提供的 MemMap.vbp(資料傳遞者
) 及
 MemRecv.vbp(資料接收者) 專案, 其中 MemMap.vbp 的 Command1_Click 所撰寫
的程
式如下:
 
Private Sub Command1_Click()
Dim S As String, hWndRecv As Long, Length As Long
' 將 Text1.Text 複製到 lpView 位址
S = Text1.Text
Length = LenB(StrConv(S, vbFromUnicode))
Call RtlMoveMemory(ByVal lpView, ByVal S, Length)
 
hWndRecv = FindWindow(0&, "MemRecv - 共用記憶體資料接收者")
 
' wParam 傳入 Text1.Text 的資料長度, lParam 則傳入共用記憶體的位址
' wParam 傳入 Text1.Text 的資料長度, lParam 則傳入共用記憶體的位址
Call SendMessage(hWndRecv, WM_USER + 1003, Length, ByVal lpView)
End Sub
 
其中呼叫 SendMessage 時, lParam 傳入共用記憶體的位址, 而 wParam 則傳入
資料
的長度,而相對的, 在 MemRecv.vbp 程式端, 則利用以下敘述讀取共用記憶體
之中的
資料:
 
Call RtlMoveMemory(ByVal S, ByVal lParam, wParam)
 
利用記憶體名稱共用資料
------------------------------------------------------------------------
----
----
 
除了直接將記憶體的位址傳遞給別的程式之外, 別的程式也可以利用記憶體名稱
來呼叫
 OpenFileMapping 取得記憶體的 Handle, 然後利用記憶體的 Handle 呼叫 
MapViewO
fFile 取得記憶體的位址, 進而使用記憶體, 參考筆者所提供的 MemUse.vbp 專
案,
其中取得共用記憶體 Handle 的程式如下:
 
' 開啟名稱為 "MemMap" 的共用記憶體
hFileMap = OpenFileMapping(FILE_MAP_READ Or FILE_MAP_WRITE, False, 
"MemMap")

 
 
此一敘述是利用 OpenFileMapping 開啟名稱為 "MemMap" 的共用記憶體,開啟之
前必須
有別的程式或自己曾經利用 CreateFileMapping 配置過名稱為 "MemMap" 的共用
記憶體

 
除了呼叫 OpenFileMapping 之外, MemUse 程式之中取得記憶體位址的方法也是
 MapV
iewOfFile, 使用記憶體的方法也是 RtlMoveMemory, 與 MemMap 程式相同,筆
者不再
重述。
 
讓不應該的位址無法存取共用記憶體
------------------------------------------------------------------------
----
----
 
在筆者所提供的 MemMap 範例程式中, 呼叫 MapViewOfFile 取得的記憶體位址之
後,
就把這個位址存成 lpView 共用變數, 然後持續使用這個變數來存取記憶體, 但
如果
您十分著重程式穩定性,則筆者建議您將程式改成:
 
lpView = MapViewOfFile(hFileMap, FILE_MAP_READ Or FILE_MAP_WRITE, 0, 0,
 0)
...利用 lpView 存取共用記憶體
Call UnmapViewOfFile(ByVal lpView)
 
其中 UnmapViewOfFile(ByVal lpView) 的用途是取消記憶體與位址的對映關係,
如此一
來即使有程式企圖破壞 lpView 所在位址的記憶體, 也無法成功, 當然,這麼做
以後
來即使有程式企圖破壞 lpView 所在位址的記憶體, 也無法成功, 當然,這麼做
以後
, 您的程式每次想要使用共用記憶體之前, 要必須先呼叫 MapViewOfFile 取得
記憶體
的位址。
 
共用記憶體的生命週期
------------------------------------------------------------------------
----
----
 
還有一個有趣的問題, 如果說建立共用記憶體的程式結束執行了, 那麼它所建立
的共
用記憶體還可以繼續存在系統之中嗎?
 
答案是肯定的, Windows 對於系統共用資源的管理規則是:只要系統資源被程式
宣告使
用(例如 CreateFileMapping 及 OpenFileMapping 都具有宣告使用的效用), 則
把該資
源的被使用次數加一,相對的, 如果程式呼叫了 CloseHandle(表示不再使用該資
源),
 則資源的被使用次數將被減一,直到資源的被使用次數變成 0 時, 就把資源破
壞掉。

 
因此如果程式所建立的共用記憶體要留給其他程式使用, 則在程式結束以前,不
要呼叫
 CloseHandle 就行了, 當然最後當所有程式都不再使用共用記憶體時,要有一個
程式
要多呼叫一次 CloseHandle 將它歸還給系統。
 
------------------------------------------------------------------------
----
----
 
下回分解
------------------------------------------------------------------------
----
----
 
每次撰寫 Windows API 專欄時, 總有兩種感覺, 其一是「怎麼寫得那麼慢」,
筆者從
不敢奢望這種感覺會消失掉, 畢竟 VB 與 Windows API 的結合還是一塊充滿荊棘
的未
開發之地,另一個感覺則是「這麼快又用掉一篇的篇幅了」, 走筆至此, 本期筆
者又
寫了將近一萬字,但還沒介紹完 IPC 所有應介紹的功能, 特別是 
MultiProcess/Thre
ad 及 Process Synchronization 的部分, 最後只好套句小說的用語了 ─ 下回
分解。


--
" The Matrix is everywhere, it's all around us, here even in this room.
 You
 can see it out your window, or  on your television. You feel it when 
you
 go to work, or go to church or pay your taxes. It is the world that has
 been
 pulled over your eyes to blind you from the truth... Unfortunately, 
no one
can be told what the Matrix is. You have to see it for yourself."
                                                                   
Morphe

※ 来源:.武汉白云黄鹤站 bbs.whnet.edu.cn.[FROM: 202.114.3.124]

--
我并不是在等待奇迹,因为我知道没有奇迹的。
有的,也只是爱情、意志和勇气。
是这些东西的重叠后,而成为奇迹的。
所以,我从未曾想过放弃。

※ 修改:·Love1976 於 Apr  6 04:23:54 修改本文·[FROM: 202.112.140.138]

--
☆ 来源:.哈工大紫丁香 bbs.hit.edu.cn.[FROM: blo0m.bbs@smth.org]
--
※ 转载:.哈工大紫丁香 bbs.hit.edu.cn.[FROM: 202.118.247.254]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:204.644毫秒