今天在寫的程序的時候,遇到字符串比較.
但是字符串可能會有大小寫的區別,此時怎么辦呢?忽略大小寫進行比較.
利用wcsicmp就比較好, 它將字符串轉換成小寫字符串進行比較,這樣就忽略了大小寫的情況.
posted @
2009-03-03 18:49 Sandy 閱讀(3051) |
評論 (0) |
編輯 收藏
轉自: http://blog.csdn.net/iihero/archive/2009/02/21/3918137.aspx
標 題: 贈與今年的大學畢業生
○胡 適(1932年6月27日)
本文是胡適先生1932年6月27日所作。雖然30年代那個血雨腥風的時代已經過去,現在的時代已經與當時不可同日而語,但是,讀來還是感覺受益匪淺,胡適先生的諄諄教導之情溢于言表。本文中,胡適先生認為,大學生畢業有三條路可走:繼續做學術研究;尋著相當的職業;做官,辦黨,革命。文中分析了大學畢業后遇到的“陷阱墮落的方式”,并給出了三個方子。
這一兩個星期里,各地的大學都有畢業的班次,都有很多的畢業生離開學校去開始他們的成人事業。
學生的生活是一種享有特殊優待的生活,不妨幼稚一點,不妨吵吵鬧鬧,社會都能縱容他們,不肯嚴格地要他們負行為的責任。現在他們要撐起自己的肩膀來挑他們自己的擔子了。在這個國難最緊急的年頭,他們的擔子真不輕!我們祝他們的成功,同時也不忍不依據自己的經驗,贈他們幾句送行的贈言——雖未必是救命毫毛,也許做個防身的錦囊罷!
你們畢業之后,可走的路不出這幾條:絕少數的人還可以在國內或國外的研究院繼續做學術研究;少數的人可以尋著相當的職業;此外還有做官,辦黨,革命三條路;再有就是在家享?;蛘呤I親居了。
走其余幾條路的人,都不能沒有墮落的危險。墮落的方式很多,總括起來,約有這兩大類:
第一是容易拋棄學生時代求知識的欲望。你們到了實際社會里,往往學非所用,往往所學全無用處,往往可以完全用不著學問,而一樣可以胡亂混飯吃,混官做。在這種環境里即使向來抱有求知識學問的人,也不免心灰意懶,把求知的欲望漸漸冷淡下去。況且學問是要有相當的設備的:書籍,實驗室,師友的切磋指導,閑暇的工夫,都不是一個平常要糊口養家的人能容易辦到的。沒有做學問的環境,又誰能怪我們拋棄學問呢?
第二是容易拋棄學生時代理想的人生的追求。少年人初次和冷酷的社會接觸,容易感覺理想與事實相去太遠,容易發生悲觀和失望。多年懷抱的人生理想,改造的熱誠,奮斗的勇氣,到此時候,好像全不是那么一回事了。渺小的個人在那強烈的社會爐火里,往往經不起長時期的烤煉就熔化了,一點高尚的理想不久就幻滅了。抱著改造社會的夢想而來,往往是棄甲拋兵而走,或者做了惡勢的俘虜。你在那牢獄里,回想那少年氣壯時代的種種理想主義,好像都成了自誤誤人的迷夢!從此以后,你就甘心放棄理想人生的追求,甘心做現在社會的順民了。要防御這兩方面的墮落,一面要保持我們求知識的欲望,一面要保持我們對人生的追求。
有什么好方子呢?依我個人的觀察和經驗,有三種防身的藥方是值得一試的。
第一個方子只有一句話:“總得時時尋一兩個值得研究的問題!”問題是知識學問的老祖宗:古往今來一切知識的產生與積聚,都是因為要解答問題——要解答實用上的困難和理論上的疑難。所謂“為知識而求知識”,其實也只是一種好奇心追求某種問題的解答,不過因為那種問題的性質不必是直接應用的,人們就覺得這是無所謂的求知識了。
我們出學校之后,離開了做學問的環境,如果沒有一兩個值得解答的問題在腦子里盤旋,就很難保持求學問的熱心??墒牵绻阌辛艘粋€真有趣的問題逗你去想它,天天引誘你去解決它,天天對你挑釁你無可奈何它——這時候,你就會同戀愛一個女子發了瘋一樣,坐也坐不下,睡也睡不安,沒工夫也得偷出工夫去陪她,沒錢也得縮衣節食去巴結她。沒有書,你自會變賣家私去買書;沒有儀器,你自會典押衣物去置辦儀器;沒有師友,你自會不遠千里去尋師訪友。你只要有疑難問題來逼你時時用腦子,你自然會保持發展你對學問的興趣,即使在最貧乏的知識中,你也會慢慢地,聚起一個小圖書館來,或者設置起一所小試驗室來。所以我說,第一要尋問題。腦子里沒有問題之日,就是你知識生活壽終正寢之時!古人說,“待文王而興者,凡民也。若夫豪杰之士,雖無文王猶興。”試想伽利略和牛頓有多少藏書?有多少儀器?他們不過是有問題而已。有了問題而后他們自會造出儀器來解決他們的問題。沒有問題的人們,關在圖書館里也不會用書,鎖在試驗室里也不會有什么發現。
第二個方子也只有一句話:“總得多發展一點非職業的興趣。”離開學校之后,大家總是尋個吃飯的職業??墒悄銓さ玫穆殬I未必就是你所學的,未必是你所心喜的,或者是你所學的而和你性情不相近的。在這種情況之下,工作往往成了苦工,就感覺不到興趣了。為糊口而做那種非“性之所近而力之所能勉”的工作,就很難保持求知的興趣的生活的理想主義。最好的救濟方法只有多多發展職業以外的正當興趣與活動。
一個人應該有他的職業,也應該有他非職業的玩藝兒,可以叫作業余活動。往往他的業余活動比他的職業還更重要,因為一個人成就怎樣,往往靠他怎樣利用他的閑暇時間。他用他的閑暇來打麻將,他就成了個賭徒;你用你的閑暇來做社會服務,你也許成個社會改革者;或者你用你的閑暇去研究歷史,你也許成個史學家。你的閑暇往往定你的終身。英國19世紀的兩個哲人,彌兒終身做東印度公司的秘書,然而他的業余工作使他在哲學上、經濟學上、政治思想史上都占一個很高的位置;斯賓塞是一個測量工程師,然而他的業余工作使他成為前世紀晚期世界思想界的一個重鎮。古來成大學問的人,幾乎沒有一個不善用他的閑暇時間的。職業不容易適合我們的性情,我們要想生活不苦痛不墮落,只有多方發展。
有了這種心愛的玩藝兒,你就做六個鐘頭抹桌子工作也不會感覺煩悶了。因為你知道,抹了六個鐘頭的桌子之后,你可以回家做你的化學研究,或畫完你的大幅山水,或寫你的小說戲曲,或繼續你的歷史考據,或做你的社會改革事業。你有了這種稱心如意的活動,生活就不枯寂了,精神也就不會煩悶了。
第三個方子也只有一句話:“你得有一點信心。”我們生當這個不幸的時代,眼中所見,耳中所聞,無非是叫我們悲觀失望的。特別是在這個年頭畢業的你們,眼見自己的國家民族沉淪到這步田地,眼看世界只是強權的世界,望極天邊好像看不見一線的光明——在這個年頭不發狂自殺,已算是萬幸了,怎么還能夠保持一點內心的鎮定和理想的信任呢?我要對你們說:這時候正是我們要培養我們的信心的時候!只要我們有信心,我們還有救。
古人說:“信心可以移山。”又說:“只要功夫深,生鐵磨成繡花針。”你不信嗎?當拿破侖的軍隊征服普魯士,占據柏林的時候,有一位教授叫作費希特的,天天在講堂勸他的國人要有信心,要信仰他們的民族是有世界的特殊使命的,是必定要復興的。費希特死的時候,誰也不能預料德意志統一帝國何時可以實現,然而不滿50年,新的統一的德意志帝國居然實現了。
一個國家的強弱盛衰,都不是偶然的,都不能逃出因果的鐵律的。我們今日所受的苦痛和恥辱,都只是過去種種惡因種下的惡果。我們要收獲將來的善果,必須努力種現在新因。一粒一粒地種,必有滿倉滿屋的收,這是我們今日應有的信心。我們要深信:今日的失敗,都由于過去的不努力。我們要深信:今日的努力,必定有將來的大收成。
佛典里有一句話:“福不唐捐。”唐捐就是白白地丟了。我們也應該說:“功不唐捐!”沒有一點努力是會白白地丟了的。在我們看不見想不到的時候,在我們看不見的方向,你瞧!你下的種子早已生根發葉開花結果了!你不信嗎?法國被普魯士打敗之后,割了兩省地,賠了50萬萬法郎的賠款。這時候有一位刻苦的科學家巴斯德終日埋頭在他的化學試驗室里做他的化學試驗和微菌學研究。他是一個最愛國的人,然而他深信只有科學可以救國。他用一生的精力證明了三個科學問題:(1)每一種發酵作用都是由于一種微菌的發展;(2)每一種傳染病都是一種微菌在生物體內的發展;(3)傳染病的微菌,在特殊的培養之下可以減輕毒力,使他們從病菌變成防病的藥苗。
這三個問題在表面上似乎都和救國大事業沒有多大關系。然而從第一個問題的證明,巴斯德定出做醋釀酒的新法,使全國的酒醋業每年減除極大的損失。從第二個問題的證明,巴斯德教全國的蠶絲業怎樣選種防病,教全國的畜牧農家怎樣防止牛羊瘟疫,又教全世界怎樣注重消毒以減少外科手術的死亡率。從第三個問題的證明,巴斯德發明了牲畜的脾熱瘟的療治藥苗,每年替法國農家減除了2000萬法郎的大損失;又發明了瘋狗咬毒的治療法,救濟了無數的生命。所以英國的科學家赫胥黎在皇家學會里稱頌巴斯德的功績道:“法國給了德國50萬萬法郎的賠款,巴斯德先生一個人研究科學的成就足夠還清這一筆賠款了。”巴斯德對于科學有絕大的信心,所以他在國家蒙奇辱大難的時候,終不肯拋棄他的顯微鏡與試驗室。他絕不想他在顯微鏡底下能償還50萬萬法郎的賠款,然而在他看不見想不到的時候,他已收獲了科學救國的奇跡。
朋友們,在你最悲觀失望的時候,那正是你必須鼓起堅強的信心的時候。你要深信:天下沒有白費的努力。成功不必在我,而功力必不唐捐。
(摘自《胡適文存》第4集第4卷《胡適教育論著選》,人民教育出版社)
方子不錯!
總得時時尋一兩個值得研究的問題!
總得多發展一點非職業的興趣。
你得有一點信心。
posted @
2009-02-24 13:14 Sandy 閱讀(177) |
評論 (0) |
編輯 收藏
如何釋放內存?
這里,我不是簡單的new后要delete.是對于系統而言,我怎么做到釋放內存呢?讓系統的可用內存變大.從網上也看到了許多釋放內存的軟件,很驚異它們是如何做到的呢?
有人建議我申請一大塊控件,系統不夠分配了,會引起它自己去整理內存.試了一下,似乎效果不是很好.
還有人建議,這么用,向所有窗口發送一個WM_HIBERNAT消息.
PostMessage(HWND_BROADCAST, WM_HIBERNATE, 0, 0);
似乎效果也不是很好.
有沒有很好的方法處理這個問題呢?
大家知道的話,指點一下啊!
萬分感謝!
posted @
2009-02-23 19:18 Sandy 閱讀(3062) |
評論 (5) |
編輯 收藏
過年前,從china-pub買的。一直也沒有安下心來讀??唇衲暾夜ぷ鞯木硾r,也不得不抓把緊了。也愿與c++博客的各位朋友分享我的學習心得。
步入主題。
這一章開篇介紹了windows函數的幾種返回值:VOID,BOOL,HANDLE,PVOID,LONG/DWORD。讓我們明白,僅僅通過返回值,我們是不能清楚函數調用為什么會失敗的。
windows內部,函數檢測到錯誤會采用什么機制呢?它是采用“線程本地存儲區”的機制來講相應的錯誤代碼與“主調線程”關聯到一起。它可以使不同的線程能獨立運行,不會出現相互干擾對方的錯誤代碼的情況。
函數返回的時候,其返回值會指出已發生的一個錯誤。
我們查看具體是什么錯誤,在相應的函數執行完成后調用GetLastError()即可。
windows中,錯誤有三種表示:
一個消息ID(如ERROR_PATH_NOT_FOUND)
消息文本(如the system cannot find the path specified)
一個編號(盡量避免使用)
調試程序的時候,我們可以配置watch窗口,讓它始終顯示線程的上一個錯誤代碼和錯誤的文本描述。如$err,hr。hr是要顯示錯誤代碼的消息文本。不過我在windows mobile的環境下沒有成功,沒有弄清楚為什么。
那么我們怎么在自己的程序中顯示消息文本呢?文章介紹了利用FormatMessage函數。這里我也介紹一下這個函數的用法:
(下面的介紹摘自:http://m.shnenglu.com/bidepan2023/archive/2008/02/03/42433.html)
DWORD FormatMessage(
DWORD dwFlags,
LPCVOID lpSource,
DWORD dwMessageId,
DWORD dwLanguageId,
LPTSTR lpBuffer,
DWORD nSize,
va_list* Arguments
);
dwFlags:
# FORMAT_MESSAGE_ALLOCATE_BUFFER // 此函數會分配內存以包含描述字串。
# FORMAT_MESSAGE_FROM_SYSTEM, // 在系統的id映射表中尋找描述字串
# FORMAT_MESSAGE_FROM_HMODULE // 在其他資源模塊中尋找描述字串
# FORMAT_MESSAGE_FROM_STRING // 消息ID是個字串,不是個DWORD
#FORMAT_MESSAGE_IGNORE_INSERTS // 允許我們獲得含有%占位符的消息,不傳遞這個標志,就必須在Arguments參數中提供這些占位符的信息
通常為:FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
lpSource:
# 指定了FORMAT_MESSAGE_FROM_HMODULE的話,此參數表示模塊的HANDLE
# 指定了FORMAT_MESSAGE_FROM_STRING的話,此參數表示id字串
通常為:NULL
dwMessageId:
消息ID;如果指定FORMAT_MESSAGE_FROM_STRING,將被忽略。
dwLanguageId:
消息描述所用的語言
通常為:0表示自動選擇
lpBuffer:
#如果未指定FORMAT_MESSAGE_ALLOCATE_BUFFER,則為自己提供的緩沖區
#否則為系統LocalAlloc分配,需要被用戶LocalFree
nSize:
#如果未指定FORMAT_MESSAGE_ALLOCATE_BUFFER,則為自己提供的緩沖區大小
#否則為系統LocalAlloc分配之最小緩沖區大小
Arguments:
通常不使用
例子:
void ShowError()


{
DWORD dwError = GetLastError();

HLOCAL hlocal = NULL;

// Use the default system locale since we look for Windows messages.
// Note: this MAKELANGID combination has 0 as value
DWORD systemLocale = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);

BOOL fOk = FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL, dwError, systemLocale,
(PTSTR) &hlocal, 0, NULL);


if (!fOk)
{
// Is it a network-related error?
HMODULE hDll = LoadLibraryEx(TEXT("netmsg.dll"), NULL,
DONT_RESOLVE_DLL_REFERENCES);


if (hDll != NULL)
{
fOk = FormatMessage(
FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_ALLOCATE_BUFFER,
hDll, dwError, systemLocale,
(PTSTR) &hlocal, 0, NULL);
FreeLibrary(hDll);
}
}

if (fOk && (hlocal != NULL))

{
OutputDebugString((PCTSTR) LocalLock(hlocal));
LocalFree(hlocal);
}
}
這個是書中的例子的代碼,我只是將它歸結為了一個函數ErrorShow。這樣我們在一個函數的后面調用,直接可以知道錯誤的原因。不過環境我是在smart device 的DEBUG環境下調時的,OutputDebugString會輸出相應的字符串。
這個例子中同時展示了FormatMessage的兩種用法。觀察一下第二個參數就明白了。
visual studio 也提供了一個查詢錯誤的小工具,為Error Lookup。通過以上的示例,我們就知道其相應的工作原理呢。
這本書的源碼的下載地址:http://wintellect.com/Books.aspx
大家如果對windows 編程感興趣的話,不妨下來看看。
posted @
2009-02-20 00:24 Sandy 閱讀(2328) |
評論 (4) |
編輯 收藏
最近在調研文件相關的東西,如MD5值.但是文件有可能很大,所以我們不能一次讀出文件.有人建議用文件映射.查了一下文件映射的內容.記錄下來.
摘自:http://www.yesky.com/405/1756405.shtml
摘要: 本文通過內存映射文件的使用來對大尺寸文件進行訪問操作,同時也對內存映射文件的相關概念和一般編程過程作了較為詳細的介紹。
關鍵詞: 內存映射文件;大文件處理;分配粒度
引言 文件操作是應用
程序最為基本的功能之一,Win32 API和MFC均提供有支持文件處理的函數和類,常用的有Win32 API的CreateFile()、WriteFile()、ReadFile()和MFC提供的CFile類等。一般來說,以上這些函數可以滿足大多數場合的要求,但是對于某些特殊應用領域所需要的動輒幾十GB、幾百GB、乃至幾TB的海量存儲,再以通常的文件處理方法進行處理顯然是行不通的。目前,對于上述這種大文件的操作一般是以內存映射文件的方式來加以處理的,本文下面將針對這種Windows核心編程技術展開討論。
內存映射文件概述 內存文件映射也是Windows的一種內存管理方法,提供了一個統一的內存管理特征,使應用程序可以通過內存指針對磁盤上的文件進行訪問,其過程就如同對加載了文件的內存的訪問。通過文件映射這種使磁盤文件的全部或部分內容與進程虛擬地址空間的某個區域建立映射關聯的能力,可以直接對被映射的文件進行訪問,而不必執行文件I/O操作也無需對文件內容進行緩沖處理。內存文件映射的這種特性是非常適合于用來管理大尺寸文件的。
在使用內存映射文件進行I/O處理時,系統對數據的傳輸按頁面來進行。至于內部的所有內存頁面則是由虛擬內存管理器來負責管理,由其來決定內存頁面何時被分頁到磁盤,哪些頁面應該被釋放以便為其它進程提供空閑空間,以及每個進程可以擁有超出實際分配物理內存之外的多少個頁面空間等等。由于虛擬內存管理器是以一種統一的方式來處理所有磁盤I/O的(以頁面為單位對內存數據進行讀寫),因此這種優化使其有能力以足夠快的速度來處理內存操作。
使用內存映射文件時所進行的任何實際I/O交互都是在內存中進行并以標準的內存地址形式來訪問。磁盤的周期性分頁也是由
操作系統在后臺隱蔽實現的,對應用程序而言是完全透明的。內存映射文件的這種特性在進行大文件的磁盤事務操作時將獲得很高的效益。
需要說明的是,在系統的正常的分頁操作過程中,內存映射文件并非一成不變的,它將被定期更新。如果系統要使用的頁面目前正被某個內存映射文件所占用,系統將釋放此頁面,如果頁面數據尚未保存,系統將在釋放頁面之前自動完成頁面數據到磁盤的寫入。
對于使用頁虛擬存儲管理的Windows操作系統,內存映射文件是其內部已有的內存管理組件的一個擴充。由可執行代碼頁面和數據頁面組成的應用程序可根據需要由操作系統來將這些頁面換進或換出內存。如果內存中的某個頁面不再需要,操作系統將撤消此頁面原擁用者對它的控制權,并釋放該頁面以供其它進程使用。只有在該頁面再次成為需求頁面時,才會從磁盤上的可執行文件重新讀入內存。同樣地,當一個進程初始化啟動時,內存的頁面將用來存儲該應用程序的靜態、動態數據,一旦對它們的操作被提交,這些頁面也將被備份至系統的頁面文件,這與可執行文件被用來備份執行代碼頁面的過程是很類似的。圖1展示了代碼頁面和數據頁面在磁盤存儲器上的備份過程:

圖1 進程的代碼頁、數據頁在磁盤存儲器上的備份
顯然,如果可以采取同一種方式來處理代碼和數據頁面,無疑將會提高程序的執行效率,而內存映射文件的使用恰恰可以滿足此需求。
對大文件的管理
內存映射文件對象在關閉對象之前并沒有必要撤銷內存映射文件的所有視圖。在對象被釋放之前,所有的臟頁面將自動寫入磁盤。通過CloseHandle()關閉內存映射文件對象,只是釋放該對象,如果內存映射文件代表的是磁盤文件,那么還需要調用標準文件I/O函數來將其關閉。在處理大文件處理時,內存映射文件將表示出卓越的優勢,只需要消耗極少的物理資源,對系統的影響微乎其微。下面先給出內存映射文件的一般編程流程框圖:

圖2 使用內存映射文件的一般流程
而在某些特殊行業,經常要面對十幾GB乃至幾十GB容量的巨型文件,而一個32位進程所擁有的虛擬地址空間只有232 = 4GB,顯然不能一次將文件映像全部映射進來。對于這種情況只能依次將大文件的各個部分映射到進程中的一個較小的地址空間。這需要對上面的一般流程進行適當的更改:
1)映射文件開頭的映像。
2)對該映像進行訪問。
3)取消此映像
4)映射一個從文件中的一個更深的位移開始的新映像。
5)重復步驟2,直到訪問完全部的文件數據。
下面給出一段根據此描述而寫出的對大于4GB的文件的處理代碼:
// 選擇文件 CFileDialog fileDlg(TRUE, "*.txt", "*.txt", NULL, "文本文件 (*.txt)|*.txt||", this); fileDlg.m_ofn.Flags |= OFN_FILEMUSTEXIST; fileDlg.m_ofn.lpstrTitle = "通過內存映射文件讀取數據"; if (fileDlg.DoModal() == IDOK) { // 創建文件對象 HANDLE hFile = CreateFile(fileDlg.GetPathName(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { TRACE("創建文件對象失敗,錯誤代碼:%d\r\n", GetLastError()); return; } // 創建文件映射對象 HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL); if (hFileMap == NULL) { TRACE("創建文件映射對象失敗,錯誤代碼:%d\r\n", GetLastError()); return; } // 得到系統分配粒度 SYSTEM_INFO SysInfo; GetSystemInfo(&SysInfo); DWORD dwGran = SysInfo.dwAllocationGranularity; // 得到文件尺寸 DWORD dwFileSizeHigh; __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh); qwFileSize |= (((__int64)dwFileSizeHigh) << 32); // 關閉文件對象 CloseHandle(hFile); // 偏移地址 __int64 qwFileOffset = 0; // 塊大小 DWORD dwBlockBytes = 1000 * dwGran; if (qwFileSize < 1000 * dwGran) dwBlockBytes = (DWORD)qwFileSize; while (qwFileOffset > 0) { // 映射視圖 LPBYTE lpbMapAddress = (LPBYTE)MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS, (DWORD)(qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF), dwBlockBytes); if (lpbMapAddress == NULL) { TRACE("映射文件映射失敗,錯誤代碼:%d\r\n", GetLastError()); return; } // 對映射的視圖進行訪問 for(DWORD i = 0; i < dwBlockBytes; i++) BYTE temp = *(lpbMapAddress + i); // 撤消文件映像 UnmapViewOfFile(lpbMapAddress); // 修正參數 qwFileOffset += dwBlockBytes; qwFileSize -= dwBlockBytes; } // 關閉文件映射對象句柄 CloseHandle(hFileMap); AfxMessageBox("成功完成對文件的訪問"); } |
在本例中,首先通過GetFileSize()得到被處理文件長度(64位)的高32位和低32位值。然后在映射過程中設定每次映射的塊大小為1000倍的分配粒度,如果文件長度小于1000倍的分配粒度時則將塊大小設置為文件的實際長度。在處理過程中由映射、訪問、撤消映射構成了一個循環處理。其中,每處理完一個文件塊后都通過關閉文件映射對象來對每個文件塊進行整理。CreateFileMapping()、MapViewOfFile()等函數是專門用來進行內存文件映射處理用的。
下面分別對這些關鍵函數進行說明:
1)CreateFile():CreateFile()函數是一個用途非常廣泛的函數, 在這里的用法并沒有什么特殊的地方,但有幾點需要注意:一是訪問模式參數dwDesiredAccess。該參數設置了對文件內核對象的訪問類型,其允許設置的權限可以為讀權限GENERIC_READ、寫權限GENERIC_WRITE、讀寫權限GENERIC_READ | GENERIC_WRITE和設備查詢權限0。在使用映射文件時,只能打開那些具有可讀訪問權限的文件,即只能應用GENERIC_READ和GENERIC_READ | GENERIC_WRITE這兩種組合;另一點需要注意的是共享模式參數dwShareMode。該參數定義了對文件內核對象的共享方式,其可能的設置為FILE_SHARE_READ、FILE_SHARE_WRITE和0,并可對其組合使用。其中,設置為0時不允許共享對象;FILE_SHARE_READ和FILE_SHARE_WRITE分別為在要求只讀、只寫訪問的情況下才允許對象的共享。
由于通過內存映射文件可以在多個進程間共享數據,因此在進行這種應用時應當考慮dwShareMode參數設置對運行結果的影響。
2)CreateFileMapping():該函數的作用是創建一個文件映射內核對象,以告知系統文件映射對象需要多大的物理存儲器。創建內存映射文件對象對系統資源幾乎沒有什么影響,也不會影響進程的虛擬地址空間。除了需要用來表示該對象的內部資源之外通常并不用為其分配虛擬內存,但是如果內存映射文件對象是作共享內存之用的話,就要在創建對象時由系統為內存映射文件的使用在系統頁文件中保留足夠的空間。
函數第一個參數hFile為標識要映射到進程的地址空間的文件的句柄。雖然由于內存映射文件的物理存儲器是來自于磁盤上的文件,而非系統的頁文件,使創建內存映射文件就像保留一個地址空間區域并將物理存儲器提交給該區域一樣。第二個參數為指向文件映射內核對象的SECURITY_ATTRIBUTES結構的指針,由此來決定子進程能否繼承得到返回的句柄。通常為其傳遞NULL值,以默認的
安全屬性來禁止返回句柄的被繼承。
接下來的參數用于文件被映射后設定文件映像的保護屬性。其可能的取值為PAGE_READONLY、PAGE_READWRITE和PAGE_WRITECOPY。雖然在創建文件映射對象時,系統并不為其保留地址空間區域,也不將文件的存儲器映射到該區域。但是,在系統將存儲器映射到進程的地址空間中去時,系統必須確切知道應賦予物理存儲器頁面的保護屬性。在設置保護屬性時,必須與用CreateFile()函數打開文件時所指定的訪問標識相匹配,否則將導致CreateFileMapping()的執行失敗。因此這里設置PAGE_READWRITE屬性。除了上述三個頁面保護屬性外,還有4個區(Section)保護屬性也可以一起組合使用:
區保護屬性 |
說明 |
SEC_COMMIT |
為區中的所有頁面在內存中或磁盤頁面文件中分配物理存儲器 |
SEC_IMAGE |
告知系統,映射的文件是一個可移植的EXE文件映像 |
SEC_NOCACHE |
告知系統,未將文件的任何內存映射文件放入高速緩存,多供硬件設備驅動程序開發人員使用 |
SEC_RESERVE |
對一個區的所有頁面進行保留而不分配物理存儲器 |
后面的兩個參數指定了要創建的文件映射對象的最大字節數的高32位值和低32位值,實際也就設定了文件的最大字節數(最大可以處理16EB的文件)。這兩個參數可以滿足確保文件映射對象能夠得到足夠的物理存儲器這一基本條件。在參數設置的大小小于文件實際大小時,系統將從文件映射指定的字節數。這里將其設置為0,將使所創建的文件映射對象將為文件的當前大小,以上兩種情況均無法改變文件的大小。如果設置的參數大于文件的實際大小,系統將會在CreateFileMapping()函數返回前擴展該文件。需要指出的是,文件映射對象的大小是靜態的,一旦創建完畢后將無法更改。如果設置的文件映射對象尺寸偏小將導致無法對文件進行全面的訪問。
在本節開始也曾提到過,創建文件映射對象是不需要花費什么系統資源的,因此遵循"寧多勿缺"的原則,一般應將文件映射對象的大小設置為文件大小的相同值。函數最后的參數將可以為映射對象命名。如果想打開一個已存在的文件映射對象,該對象必須要命名。對該名字字符串的要求僅限于未被其它對象使用過的名字即可。
CreateFileMapping()在成功執行后將返回一個指向文件映射對象的句柄。如果對一個已經存在的文件映射對象調用了CreateFileMapping()函數,進程將得到一個指向現有映射對象的句柄。通過調用GetLastError()可以得到返回值ERROR_ALREADY_EXIST,由此可以判斷當前得到的內存映射對象句柄是新創建的還是打開已經存在的。如果系統無法創建文件映射對象,將導致CreateFileMapping()的執行失敗,返回N U L L句柄值。
3)MapViewOfFile():當創建了一個內存映射文件對象并得到其有效句柄后,該句柄即可用來在進程的虛擬地址空間中映射文件的一個映像。在內存映射文件對象已經存在的情況下,映像可被任意映射或取消映射。在文件映像被映射時,仍然必須由系統來為文件的數據保留一個地址空間區域,并將文件的數據作為映射到該區域的物理存儲器進行提交。在進程的地址空間中,一個足夠大的連續地址空間(通常足以覆蓋整個文件映像)將被指定給此文件映像。盡管如此,內存的物理頁面還是根據在實際使用中的需求而進行分配的。真正分配一個對應于內存映射文件映像頁面的物理內存頁面是在發生該頁的缺頁中斷時進行的,這將在第一次讀寫內存頁面中的任一地址時自動完成。MapViewOfFile()即負責映射內存映射文件的一個映像,
函數的第一個參數為CreateFileMapping()所返回的內存映射文件對象句柄,第二個參數指定了對文件映像的訪問類型,可能取值有FILE_MAP_WRITE、FILE_MAP_READ、FILE_MAP_ALL_ACCESS和FILE_MAP_COPY等幾種,具體的設置要根據文件映射對象允許的保護模式而定。根據前面代碼的設置,這里應該使用FILE_MAP_ALL_ACCESS參數。這種機制為對象的創建者提供了對映射此對象的方式進行控制的能力。接下來的2個參數分別指定了內存映射文件的64位偏移地址的低32位和高32位地址,該地址是從內存映射文件頭位置到映像開始位置的距離。最后的參數指定了視圖的大小,如果設置為0,前面的偏移地址將被忽略,系統將會把整個文件映射為一個映像。MapViewOfFile()如果成功執行,將返回一個指向文件映像在進程的地址空間中的起始地址的指針。如果失敗,則返回NULL。在進程中,可以為同一個文件映射對象創建多個文件映像,這些映像可以在系統中共存和重疊,也可以與對應的文件映射對象大小不相一致,但不能大于文件映射對象的大小。
4)UnmapViewOfFile():當不再需要保留映射到進程地址空間區域中的文件映像數據時,可通過調用UnmapViewOfFile()函數將其釋放。該函數結構非常簡單,只需要提供映像在進程中的起始地址(區域的基地址)作為參數即可。該函數的輸入參數為調用MapViewOfFile()時所返回的指向文件映像在進程的地址空間中的起始地址的指針。在調用MapViewOfFile()后,必須確保在進程退出之前能夠執行UnmapViewOfFile()函數,否則在進程終止之后先前保留的區域將得不到釋放,即使再次啟動進程重復調用MapViewOfFile()系統也總是在進程的地址空間中保留一個新的區域,而此前保留的所有區域將得不到釋放。
一種比較特殊的情況是,對同一個內存映射文件映射了兩個相同的映像的撤消。前面曾經提到過,對于同一個內存映射文件可以有多個映像,這些映像也可以重疊,因此這種情況的存在是合法的。對于這種情況,雖然從表面看上去在單進程的地址空間內是不可能存在兩個基地址完全相同的映像的,這將導致無法對這它們的區分。但是事實上,由MapViewOfFile()所返回得到的基地址只是文件映像在進程地址空間中的起始基地址,因此在映射同一內存映射文件的兩個相同映像時將會產生對內存映射文件同一部分的兩個不同基地址的相同映像,可以用同樣的方法調用UnmapViewOfFile()將其從進程的地址空間中予以撤消。
5)CloseHandle(): 與Win32的大多數對象一樣,在使用完畢之后總是要通過CloseHandle()函數將已打開的內核對象關閉。如果忘記關閉對象,在程序繼續運行時將會出現資源泄漏。雖然在程序退出運行時,操作系統會自動關閉在進程中已經打開但未關閉的任何對象。但是在進程的運行過程中,勢必會積累過多的資源句柄。因此在不再需要使用對象的時候通過CloseHandle()將其予以關閉是有意義的。
小結 本文對內存映射文件在大文件處理中的應用作了較為詳細的闡述。經實際測試,內存映射文件在處理大數據量文件時表現出了良好的性能,比通常使用CFile類和ReadFile()和WriteFile()等函數的文件處理方式具有明顯的優勢。本文所述程序代碼在Windows 2000 Professional下由Microsoft Visual C++ 6.0編譯通過。
posted @
2009-02-17 11:55 Sandy 閱讀(434) |
評論 (0) |
編輯 收藏
今天的任務是要保存一個文件。平常看別人怎么寫,自己還只是看,沒有動手去寫過,對各個API相應的參數不是很了解。今天在運用的時候,還真是遇見了一些問題。
我們先來說說問題:
第一個問題:使用WriteFile的時候,我直接將寬字符串寫進了文件,文件顯示如大家所想,摻雜了很多亂碼。但是很有規則。所以我很快就明白了這需要將寬字符串轉換成ASCII碼。
第二個問題:就是我將文件打開后,又進行了寫文件的操作,此時失敗。所以對這種情況,還沒有想出辦法,是由于CreateFile的參數的某些限制么?
由于這兩個問題,所以我也好好看了一下SDK文檔。
我們先來看一下CreateFile和WriteFile的原型和參數介紹:
HANDLE CreateFile(
LPCTSTR lpFileName, // 文件名
DWORD dwDesiredAccess, // 訪問方式
DWORD dwShareMode, // 共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 設為NULL
DWORD dwCreationDisposition, /// 創建方式
DWORD dwFlagsAndAttributes, // 屬性
HANDLE hTemplateFile
);
BOOL WriteFile(
HANDLE hFile, // 文件句柄
LPCVOID lpBuffer, // 包含寫向文件的數據
DWORD nNumberOfBytesToWrite, // 數據包含的字符串的個數
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped
);
第一次我寫的程序很簡單
BOOL WriteOwnFile(TCHAR* pFileName, TCHAR* pBuffer, DWORD dwLen)
{
HANDLE hFile = CreateFile(pFileName,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (INVALID_HANDLE_VALUE != hFile)
{
DWORD dwSize = 0;
WriteFile(hFile, pBuffer, dwLen, &dwSize, NULL );
CloseHandle(hFile);
return TRUE;
}
return FALSE;
}
這樣是完成了,但是寫出來的文件是亂碼。所以沒有進行字符的轉換,我們需要將pBuffer進行轉換。這就要用到了WideCharToMultiByte.如何用呢?
首先我的方法比較笨,我是這么用的:
char* pchBuffer = new char[dwLen+1];
WideCharToMultiByte(CP_ACP, NULL, pBuffer, -1, pchBuffer, dwLen+1, NULL, FALSE );
WriteFile(hFile, pBuffer, dwLen+1, &dwSize, NULL );
Delete[] pchBuffer;
此時注意,我在WriteFile中用了dwLen+1。結果就是在文件的末尾出現了亂碼,正好多一個亂碼出來。所以WriteFile中nNumberOfBytesToWrite是寫的字符串的數目,是不包括’\0’的。
這個方法笨,是因為我們的函數可以縮減為兩個參數。是因為如下這么寫時,dwLen是所要轉換的字符串的個數,此時轉換的字符串是包括’\0’的。
DWORD dwLen = WideCharToMultiByte(CP_ACP, NULL, pBuffer, -1, NULL, NULL, NULL, FALSE );
所以我們再來看一下改寫以后的代碼
BOOL WriteOwnFile(TCHAR* pFileName, TCHAR* pBuffer)
{
HANDLE hFile = CreateFile(pFileName,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (INVALID_HANDLE_VALUE != hFile)
{
DWORD dwSize = 0;
DWORD dwLen = WideCharToMultiByte(CP_ACP, NULL, pBuffer, -1, NULL, NULL, NULL, FALSE );
char* pchBuffer = new char[dwLen];
WideCharToMultiByte(CP_ACP, NULL, pBuffer, -1, pchBuffer, dwLen, NULL, FALSE );
WriteFile(hFile, pBuffer, dwLen+1, &dwSize, NULL );
delete[] pchBuffer;
CloseHandle(hFile);
return TRUE;
}
return FALSE;
}
這樣感覺代碼好看多了。
對于第二個問題,文件打開的時候文件創建失敗,還沒有想好辦法解決。我在想是不是我的某些認知存在問題,文件打開的時候,是否可以用CreateFile來打開呢?
posted @
2009-02-09 22:14 Sandy 閱讀(52779) |
評論 (13) |
編輯 收藏
今天完成了一個任務,就是在mobile上如何監控文件的操作。這個SDK中有相應的例子,為FileChangeNotif。
如何實現文件監控?
首先要在窗口注冊,這個要用到SHChangeNotifyRegister,這個函數的主要功能就是列舉一個窗口來接收change notifications.
在這個注冊的窗口中,響應WM_FILECHANGEINFO這個消息,來進行我們響應的操作。
如何我們不想監控了,則可以使用SHChangeNotifyDeregister,來移除相應的注冊窗口。
這樣我們就可以實現對一個文件夾內文件的生成,刪除,改名等等操作的監控。
下面我們再具體來談談每一步如何操作。
1、SHChangeNotifyRegister的運用
SHChangeNotifyRegister的原型為
BOOL WINAPI SHChangeNotifyRegister(
HWND hwnd,
SHCHANGENOTIFYENTRY * pshcne
);
其中,hwnd,為接收change notification的窗口;
pshcne是一個指向SHCHANGENOTIFYENTRY結構的指針,它用來指明窗口接收的change notification的類型.如果設為NULL,窗口將接收all file system, network 和 media類型的notifications.
SHCHANGENOTIFYENTRY是什么樣的一個結構,我們看一下它的定義
typedef struct tagSHCHANGENOTIFYENTRY {
DWORD dwEventMask;
LPTSTR pszWatchDir;
BOOL fRecursive;
} SHCHANGENOTIFYENTRY;
dwEventMask 指定發生什么時間來發送notification 消息
pszWatchDir 指定監控路徑,該值為NULL的情況下,是監控所有的文件。
fRecursive指定是否只監控指定路徑還是監控指定路徑及其子文件夾。
知道了這些,我們不妨寫一個這樣的函數,來啟動文件監控。
代碼如下:
BOOL StartFileMonitor(HWND hWnd, LPTSTR lpFilePath)
{
SHCHANGENOTIFYENTRY schneNotifyEntry;
schneNotifyEntry.dwEventMask = SHCNE_ALLEVENTS;
schneNotifyEntry.pszWatchDir = lpFilePath;
schneNotifyEntry.fRecursive = TRUE;
return SHChangeNotifyRegister(hWnd, &schneNotifyEntry);
}
2、如何處理WM_FILECHANGEINFO消息
WM_FILECHANGEINFO 中的參數lParam,指向FILECHANGENOTIFY,含有相關的數據。所以我們在收到該消息后,先作的一部操作就是
FILECHANGENOTIFY *lpfcn = (FILECHANGENOTIFY*)lParam;
FILECHANGENOTIFY的結構為:
typedef struct tagFILECHANGENOTIFY {
DWORD dwRefCount;
FILECHANGEINFO fci;
} FILECHANGENOTIFY;
我們主要用到了其中的fci參數。
FILECHANGEINFO的結構為:
struct _FILECHANGEINFO {
DWORD cbSize;
LONG wEventId;
ULONG uFlags;
DWORD dwItem1;
DWORD dwItem2;
DWORD dwAttributes;
FILETIME ftModified;
ULONG nFileSize;
} FILECHANGEINFO, *LPFILECHANGEINFO;
dwEventId 與SHCHANGENOTIFYENTRY結構中的dwEventMask對應。
dwItem1,dwItem2是事件依賴的值,里面包括了我們需要的文件的完整路徑。如果是進行創建文件的操作,則dwItem1是創建后文件的完整路徑,如果是對文件進行重新命名操作的話,則dwItem2是修改后文件的完整路徑。此處對其他參數不做介紹,大家需要的話,可以查看一下。
我們做完相應的操作后,要知道釋放,此時要用到SHChangeNotifyFree。這個用起來就簡單很多,如SHChangeNotifyFree(lpfcn)。
下面給大家一小段示例代碼,如下
case WM_FILECHANGEINFO:
{
FILECHANGENOTIFY *lpfcn;
FILECHANGEINFO *lpfci;
lpfcn = (FILECHANGENOTIFY *)lParam;
if (NULL == lpfcn)
{
break;
}
// see if the pointer to the file change info structure
lpfci = &(lpfcn->fci);
if (NULL == lpfci)
{
break;
}
else
{
switch (lpfci->wEventId)
{
case SHCNE_RENAME:
{
//……
}
break;
}
}
SHChangeNotifyFree(lpfcn);
}
break;
3、如何停止文件監控
停止文件監控比較簡單,只要使該窗口不接收WM_FILECHANGEINFO消息即可。使用SHChangeNotifyDeregister(hWnd)即可。
以上是我今天學習的一些總結,此外需要注意的一個小地方,在mobile上,把一個文件從一個文件夾拷到另一個文件夾,此時響應的事件是SHCNE_CREATE,二從電腦上拷貝一個文件到mobile上,響應的消息為SHCNE_RENAME。我注意到從電腦上拷貝的話,mobile會先生成一個Temp文件夾內生成一個臨時文件,然后再在我們指定的文件夾內生成一個文件。這個機制我還不是很清楚為什么。
posted @
2009-02-08 16:20 Sandy 閱讀(1608) |
評論 (2) |
編輯 收藏
今天在加數據庫的相關操作時,遇到了一些問題,提示
error C3861: 'CeMountDBVolEx': identifier not found
error C3861: 'CeMountDBVolEx': identifier not found
error C3861: 'CeCreateDatabaseWithProps': identifier not found
error C3861: 'CeCreateSession': identifier not found
error C3861: 'CeOpenDatabaseInSession': identifier not found
我在.cpp文件的開頭加入了
#define EDB
#include <windows.h>
#include <windbase.h>
但是錯誤還依然存在
從網上搜索了一些方法
在博文《mobile數據庫遇到的問題》
http://blog.sina.com.cn/s/blog_4c5ad0740100cvxg.html
它里面建議使用
extern "C"
{
#include <windbase_edb.h>
}
但是使用后,問題變成了lnk的錯誤
error LNK2019: unresolved external symbol
有人在論壇里建議
#include Windbase_edb.h
也是同樣的問題
最后,我問了一下我的同事
他建議我在
stdafx.h 頭文件中添加
#define EDB
#include <windows.h>
#include <windbase.h>
這樣的確解決了問題。
posted @
2009-02-04 13:20 Sandy 閱讀(491) |
評論 (0) |
編輯 收藏
先粘過來,備以后細讀
鏈接地址:http://www.bsdlover.cn/index.php?action/viewnews/itemid/1611/page/1/php/1
進程間通信有以下方法
Using named objects
Waiting for multiple objects
Waiting in a message loop
Using mutex objects
Using semaphore objects
Using event objects
Using critical section objects
Using timer queues
Using waitable timer objects
進程間的通訊實現(IPC)的11種方法
進程通常被定義為一個正在運行的程序的實例,它由兩個部分組成:
一個是操作系統用來管理進程的內核對象。內核對象也是系統用來存放關于進程的統計信息的地方
另一個是地址空間,它包含所有的可執行模塊或DLL模塊的代碼和數據。它還包含動態分配的空間。如線程堆棧和堆分配空間。每個進程被賦予它自己的虛擬地址空間,當進程中的一個線程正在運行時,該線程可以訪問只屬于它的進程的內存。屬于其它進程的內存則是隱藏的,并不能被正在運行的線程訪問。
為了能在兩個進程之間進行通訊,由以下幾種方法可供參考:
在16位時代常使用的方式,CWnd中提供支持
1。窗口消息 標準的Windows消息以及專用的WM_COPYDATA消息 SENDMESSAGE()接收端必須有一個窗口
2。使用共享內存方式(Shared Memory)
a.設定一塊共享內存區域
HANDLE CreateFileMapping(HANDLE,LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCSTR)
產生一個file-mapping核心對象
LPVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAcess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
DWORD dwNumberOfBytesToMap
);
得到共享內存的指針
b.找出共享內存
決定這塊內存要以點對點(peer to peer)的形式呈現
每個進程都必須有相同的能力,產生共享內存并將它初始化。每個進程
都應該調用CreateFileMapping(),然后調用GetLastError().如果傳回的錯誤代碼是ERROR_ALREADY_EXISTS,那么進程就可以假設這一共享內存區 域已經被別的進程打開并初始化了,否則該進程就可以合理的認為自己 排在第一位,并接下來將共享內存初始化。還是要使用client/server架構中只有server進程才應該產生并初始化共享內存。所有的進程都應該使用
HANDLE OpenFileMapping(DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName);
再調用MapViewOfFile(),取得共享內存的指針
c.同步處理(Mutex)
d.清理(Cleaning up) BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);
CloseHandle()
3。動態數據交換(DDE)通過維護全局分配內存使的應用程序間傳遞成為可能
其方式是再一塊全局內存中手工放置大量的數據,然后使用窗口消息傳遞內存 指針.這是16位WIN時代使用的方式,因為在WIN32下已經沒有全局和局部內存 了,現在的內存只有一種就是虛存。
4。消息管道(Message Pipe)
用于設置應用程序間的一條永久通訊通道,通過該通道可以象自己的應用程序
訪問一個平面文件一樣讀寫數據。
名管道(Anonymous Pipes)
單向流動,并且只能夠在同一電腦上的各個進程之間流動。
命名管道(Named Pipes)
雙向,跨網絡,任何進程都可以輕易的抓住,放進管道的數據有固定的格式,而使用ReadFile()只能讀取該大小的倍數??梢员皇褂糜贗/O Completion Ports
5郵件槽(Mailslots)
廣播式通信,在32系統中提供的新方法,可以在不同主機間交換數據,在 WIN9X下只支持郵件槽客戶
6Windows套接字(Windows Socket)
它具備消息管道所有的功能,但遵守一套通信標準使的不同操作系統之上的應 用程序之間可以互相通信。
7Internet通信 它讓應用程序從Internet地址上載或下載文件
8。RPC:遠程過程調用,很少使用,因其與UNIX的RPC不兼容。
9。串行/并行通信(Serial/Parallel Communication)
它允許應用程序通過串行或并行端口與其他的應用程序通信
10。COM/DCOM
通過COM系統的代理存根方式進行進程間數據交換,但只能夠表現在對接口 函數的調用時傳送數據,通過DCOM可以在不同主機間傳送數據。
posted @
2009-02-03 18:53 Sandy 閱讀(2945) |
評論 (0) |
編輯 收藏
今天在與同事討論如何進行進程間的通訊,在網上查找了一些內容,貼出來以備查找.
鏈接地址:
http://www.cnblogs.com/henryzc/archive/2005/11/08/271920.html
1、引言
在Windows程序中,各個進程之間常常需要交換數據,進行數據通訊。WIN32 API提供了許多函數使我們能夠方便高效的進行進程間的通訊,通過這些函數我們可以控制不同進程間的數據交換,就如同在WIN16中對本地進程進行讀寫操作一樣。
典型的WIN16兩進程可以通過共享內存來進行數據交換:(1)進程A將GlobalAlloc(GMEM_SHARE...)API分配一定長度的內存;(2)進程A將GlobalAlloc函數返回的句柄傳遞給進程B(通過一個登錄消息);(3)進程B對這個句柄調用GlobalLock函數,并利用GlobalLock函數返回的指針訪問數據。這種方法在WIN32中可能失敗,這是因為GlobalLock函數返回指向的是進程A的內存,由于進程使用的是虛擬地址而非實際物理地址,因此這一指針僅與A進程有關,而于B進程無關。
本文探討了幾種WIN32下進程之間通訊的幾種實現方法,讀者可以使用不同的方法以達到程序運行高效可靠的目的。
2、Windows95中進程的內存空間管理
WIN32進程間通訊與Windows95的內存管理有密切關系,理解Windows95的內存管理對我們如下的程序設計將會有很大的幫助,下面我們討論以下Windows95中進程的內存空間管理。
在WIN16下,所有Windows應用程序共享單一地址,任何進程都能夠對這一空間中屬于共享單一的地址空間,任何進程都能夠對這一空間中屬于其他進程的內存進行讀寫操作,甚至可以存取操作系統本身的數據,這樣就可能破壞其他程序的數據段代碼。
在WIN32下,每個進程都有自己的地址空間,一個WIN32進程不能存取另一個地址的私有數據,兩個進程可以用具有相同值的指針尋址,但所讀寫的只是它們各自的數據,這樣就減少了進程之間的相互干擾。另一方面,每個WIN32進程擁有4GB的地址空間,但并不代表它真正擁有4GB的實際物理內存,而只是操作系統利用CPU的內存分配功能提供的虛擬地址空間。在一般情況下,絕大多數虛擬地址并沒有物理內存于它對應,在真正可以使用這些地址空間之前,還要由操作系統提供實際的物理內存(這個過程叫"提交"commit)。在不同的情況下,系統提交的物理內存是不同的,可能是RAM,也可能是硬盤模擬的虛擬內存。
3、WIN32中進程間的通訊
在Windows 95中,為實現進程間平等的數據交換,用戶可以有如下幾種選擇:
* 使用內存映射文件
* 通過共享內存DLL共享內存
* 向另一進程發送WM_COPYDATA消息
* 調用ReadProcessMemory以及WriteProcessMemory函數,用戶可以發送由GlobalLock(GMEM_SHARE,...)函數調用提取的句柄、GlobalLock函數返回的指針以及VirtualAlloc函數返回的指針。
3.1、利用內存映射文件實現WIN32進程間的通訊
Windows95中的內存映射文件的機制為我們高效地操作文件提供了一種途徑,它允許我們在WIN32進程中保留一段內存區域,把目標文件映射到這段虛擬內存中。在程序實現中必須考慮各進程之間的同步。具體實現步驟如下:
首先我們在發送數據的進程中需要通過調用內存映射API函數CreateFileMapping創建一個有名的共享內存:
HANDLE CreateFileMapping(
HANDLE hFile, // 映射文件的句柄,
//設為0xFFFFFFFF以創建一個進程間共享的對象
LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // 安全屬性
DWORD flProtect, // 保護方式
DWORD dwMaximumSizeHigh, //對象的大小
DWORD dwMaximumSizeLow,
LPCTSTR lpName // 必須為映射文件命名
);
與虛擬內存類似,保護方式可以是PAGE_READONLY或是PAGE_READWRITE。如果多進程都對同一共享內存進行寫訪問,則必須保持相互間同步。映射文件還可以指定PAGE_WRITECOPY標志,可以保證其原始數據不會遭到破壞,同時允許其他進程在必要時自由的操作數據的拷貝。
在創建文件映射對象后使用可以調用MapViewOfFile函數映射到本進程的地址空間內。
下面說明創建一個名為MySharedMem的長度為4096字節的有名映射文件:
HANDLE hMySharedMapFile=CreateFileMapping((HANDLE)0xFFFFFFFF),
NULL,PAGE_READWRITE,0,0x1000,"MySharedMem");
并映射緩存區視圖:
LPSTR pszMySharedMapView=(LPSTR)MapViewOfFile(hMySharedMapFile,
FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);
其他進程訪問共享對象,需要獲得對象名并調用OpenFileMapping函數。
HANDLE hMySharedMapFile=OpenFileMapping(FILE_MAP_WRITE,
FALSE,"MySharedMem");
一旦其他進程獲得映射對象的句柄,可以象創建進程那樣調用MapViewOfFile函數來映射對象視圖。用戶可以使用該對象視圖來進行數據讀寫操作,以達到數據通訊的目的。
當用戶進程結束使用共享內存后,調用UnmapViewOfFile函數以取消其地址空間內的視圖:
if (!UnmapViewOfFile(pszMySharedMapView))
{ AfxMessageBox("could not unmap view of file"); }
3.2、利用共享內存DLL
共享數據DLL允許進程以類似于Windows 3.1 DLL共享數據的方式訪問讀寫數據,多個進程都可以對該共享數據DLL進行數據操作,達到共享數據的目的。在WIN32中為建立共享內存,必須執行以下步驟:
首先創建一個有名的數據區。這在Visual C++中是使用data_seg pragma宏。使用data_seg pragma宏必須注意數據的初始化:
#pragma data_seg("MYSEC")
char MySharedData[4096]={0};
#pragma data_seg()
然后在用戶的DEF文件中為有名的數據區設定共享屬性。
LIBRARY TEST
DATA READ WRITE
SECTIONS
.MYSEC READ WRITE SHARED
這樣每個附屬于DLL的進程都將接受到屬于自己的數據拷貝,一個進程的數據變化并不會反映到其他進程的數據中。
在DEF文件中適當地輸出數據。以下的DEF文件項說明了如何以常數變量的形式輸出MySharedData。
EXPORTS
MySharedData CONSTANT
最后在應用程序(進程)按外部變量引用共享數據。
extern _export"C"{char * MySharedData[];}
進程中使用該變量應注意間接引用。
m_pStatic=(CEdit*)GetDlgItem(IDC_SHARED);
m_pStatic->GetLine(0,*MySharedData,80);
3.3、用于傳輸只讀數據的WM_COPYDATA
傳輸只讀數據可以使用Win32中的WM_COPYDATA消息。該消息的主要目的是允許在進程間傳遞只讀數據。 Windows95在通過WM_COPYDATA消息傳遞期間,不提供繼承同步方式。SDK文檔推薦用戶使用SendMessage函數,接受方在數據拷貝完成前不返回,這樣發送方就不可能刪除和修改數據:
SendMessage(hwnd,WM_COPYDATA,wParam,lParam);
其中wParam設置為包含數據的窗口的句柄。lParam指向一個COPYDATASTRUCT的結構:
typedef struct tagCOPYDATASTRUCT{
DWORD dwData;//用戶定義數據
DWORD cbData;//數據大小
PVOID lpData;//指向數據的指針
}COPYDATASTRUCT;
該結構用來定義用戶數據。
3.4、直接調用ReadProcessMemory和WriteProcessMemory函數實現進程間通訊
通過調用ReadProcessMemory以及WriteProcessMemory函數用戶可以按類似與Windows3.1的方法實現進程間通訊,在發送進程中分配一塊內存存放數據,可以調用GlobalAlloc或者VirtualAlloc函數實現:
pApp->m_hGlobalHandle=GlobalAlloc(GMEM_SHARE,1024);
可以得到指針地址:
pApp->mpszGlobalHandlePtr=(LPSTR)GlobalLock
(pApp->m_hGlobalHandle);
在接收進程中要用到用戶希望影響的進程的打開句柄。為了讀寫另一進程,應按如下方式調用OpenProcess函數:
HANDLE hTargetProcess=OpenProcess(
STANDARD_RIGHTS_REQUIRED|
PROCESS_VM_REDA|
PROCESS_VM_WRITE|
PROCESS_VM_OPERATION,//訪問權限
FALSE,//繼承關系
dwProcessID);//進程ID
為保證OpenProcess函數調用成功,用戶所影響的進程必須由上述標志創建。
一旦用戶獲得一個進程的有效句柄,就可以調用ReadProcessMemory函數讀取該進程的內存:
BOOL ReadProcessMemory(
HANDLE hProcess, // 進程指針
LPCVOID lpBaseAddress, // 數據塊的首地址
LPVOID lpBuffer, // 讀取數據所需緩沖區
DWORD cbRead, // 要讀取的字節數
LPDWORD lpNumberOfBytesRead
);
使用同樣的句柄也可以寫入該進程的內存:
BOOL WriteProcessMemory(
HANDLE hProcess, // 進程指針
LPVOID lpBaseAddress, // 要寫入的首地址
LPVOID lpBuffer, // 緩沖區地址
DWORD cbWrite, // 要寫的字節數
LPDWORD lpNumberOfBytesWritten
);
如下所示是讀寫另一進程的共享內存中的數據:
ReadProcessMemory((HANDLE)hTargetProcess,
(LPSTR)lpsz,m_strGlobal.GetBuffer(_MAX_FIELD),
_MAX_FIELD,&cb);
WriteProcessMemory((HANDLE)hTargetProcess,
(LPSTR)lpsz,(LPSTR)STARS,
m_strGlobal.GetLength(),&cb);
4、進程之間的消息發送與接收
在實際應用中進程之間需要發送和接收Windows消息來通知進程間相互通訊,發送方發送通訊的消息以通知接收方,接收方在收到發送方的消息后就可以對內存進行讀寫操作。
我們在程序設計中采用Windows注冊消息進行消息傳遞,首先在發送進程初始化過程中進行消息注冊:
m_nMsgMapped=::RegisterWindowsMessage("Mapped");
m_nMsgHandle=::RegisterWindowsMessage("Handle");
m_nMsgShared=::RegisterWindowsMessage("Shared");
在程序運行中向接收進程發送消息:
CWnd* pWndRecv=FindWindow(lpClassName,"Receive");
pWndRecv->SendMessage(m_MsgMapped,0,0);
pWndRecv->SendMessage(m_nMsgHandle,
(UINT)GetCurrentProcessID(),(LONG)pApp->m_hGlobalHandle);
pWndRecv->SendMessage(m_nMsgShared,0,0);
可以按如下方式發送WM_COPYDATA消息:
static COPYDATASTRUCT cds;//用戶存放數據
pWnd->SendMessage(WM_COPYDATA,NULL,(LONG)&cds);
接收方進程初始化也必須進行消息注冊:
UNIT CRecvApp:: m_nMsgMapped=::RegisterWindowsMessage("Mapped");
UNIT CRecvApp::m_nMsgHandle=::RegisterWindowsMessage("Handle");
UNIT CRecvApp::m_nMsgShared=::RegisterWindowsMessage("Shared");
同時映射消息函數如下:
ON_REGISTERED_MASSAGE(CRecvApp::m_nMsgMapped,OnRegMsgMapped)
ON_REGISTERED_MASSAGE(CRecvApp::m_nMsgHandle,OnRegMsgHandle)
ON_REGISTERED_MASSAGE(CRecvApp::m_nMsgShared,OnRegMsgShared)
在這些消息函數我們就可以采用上述技術實現接收進程中數據的讀寫操作了。
5、結束語
從以上分析中我們可以看出Windows95的內存管理與Windows 3.x相比有很多的不同,對進程之間的通訊有較為嚴格的限制。這就確保了任何故障程序無法意外地寫入用戶的地址空間,而用戶則可根據實際情況靈活地進行進程間的數據通訊,從這一點上來講Windows95增強應用程序的強壯性。
參考文獻:
1、 David J.Kruglinski, Visual C++技術內幕, 北京:清華大學出版社,1995.
2、 Microsoft Co. Visual C++ 5.0 On Line Help.
posted @
2009-02-03 18:43 Sandy 閱讀(374) |
評論 (0) |
編輯 收藏