其實這篇是多寫的,因為前一篇提到的 Transactional NTFS 官方只提供 Win32 API 而已,不提供包裝好的 managed code 用的 library... 因此現階段想始用它,P/Invoke 是逃不掉的... 這篇就先來複習一下,想要在 C# 裡呼叫 unmanaged code 該怎麼用吧。這邊的例子為了配合後面幾篇,就同樣的以檔案處理為例。
這篇我不想去長篇大論的討論 P/Invoke 那堆規則及語法,也不想去討論那堆 Marshal 的觀念等等... 想學好 P/Invoke 就別看我這篇了,應該去 MSDN 看... 這篇我只想交待一下該如何配合 windows api 來作檔案處理而已。因為這些是往後要用到 Transactional NTFS 必要的技巧,TxF 新的 API 都是跟 win32 標準檔案處理的 API 一一對應的,弄懂了如何用 win32 api 操作檔案,你大概就學會八成的 TxF 了... 想用 TxF ... 熟悉點 P/Invoke 是應該的...
先從最單純的 MoveFile 開始吧,它沒有扯到啥 pointer 或是 handle ... 算是最單純的例子.. 先來看看這 API 原生的樣子:
BOOL WINAPI MoveFile( __in LPCTSTR lpExistingFileName, __in LPCTSTR lpNewFileName );
這 API 做啥事就不用多說了,把 lpExistingFileName 這檔案搬到 lpNewFileName 去... 搬成功還是失敗,就用 BOOL 值把結果傳回來。這時就要透過 P/Invoke 的用法,想辦法產生一個可以對應到 unmanaged code 的 C# function ... 我先把宣告方式貼出來,再來解試 code ...
1: [DllImport("kernel32.dll")]
2: static extern bool MoveFile(string lpExistingFileName, string lpNewFileName);
寫過 C / C++ 的人,大概對 extern 這個 keyword 不陌生吧? extern 這修飾字代表這個 function 是外來的,C# 正好拿來用在這裡。外來的 DLL 都是 function 型態,所以一定是 static, 沒有綁著任何一個物件... 而標在上面的 Attribute: DllImport, 則是標上這個 function 是來自那個 DLL。
在這個例子裡,這樣就足夠了,直接在程式裡試著寫一段 C# (managed code) 來試看看結果吧:
1: public class PInvokeTest
2: {
3: [DllImport("kernel32.dll")]
4: static extern bool MoveFile(string lpExistingFileName, string lpNewFileName);
5: public static void Main(string[] args)
6: {
7: string srcFileName = @"C:\file1.txt";
8: string dstFileName = @"C:\file2.txt";
9: Console.Write("move file: from [{0}] to [{1}] ... ", srcFileName, dstFileName);
10: if (MoveFile(srcFileName, dstFileName) == true)
11: {
12: Console.WriteLine("OK!");
13: }
14: else
15: {
16: Console.WriteLine("FAIL!");
17: }
18: }
19: }
程式執行前,看一下 C:\ 的 DIR *.TXT 指令執行結果:
沒錯,有 c:\file1.txt 這檔案... 接著來執行範例程式:
執行成功。再重新看一下 C:\ 的 DIR *.TXT 指令執行結果:
看來程式很順利的呼叫了 win32 api 裡定義的 MoveFile( ... ) ... 這種範例有點不入流,要處理檔案總不可能只有這樣吧? 接著我們再來看看需要 Open File 加上讀寫檔案內容的應用。
Windows 是個以 HANDLE 為主的作業系統,一般在寫 C / C++ 程式,都是以指標(POINTER)的方式在處理資料,但是在 windows 裡,作業系統提供的資料用的指標,則特別以 "HANDLE" 來稱呼,它比一般的 POINTER 來說多了一些管理的動作。因此你開啟的檔案,或是建立的視窗,通通都以 HANDLE 來稱呼,而不是單純的用 POINTER (雖然它也是個 POINTER 啦)。接下來的例子就來看看 HANDLE 的應用。
1: public class PInvokeTest2
2: {
3: [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
4: public static extern IntPtr CreateFile(
5: string lpFileName,
6: uint dwDesiredAccess,
7: uint dwShareMode,
8: IntPtr SecurityAttributes,
9: uint dwCreationDisposition,
10: uint dwFlagsAndAttributes,
11: IntPtr hTemplateFile
12: );
13: [DllImport("kernel32.dll", SetLastError = true)]
14: static extern bool CloseHandle(IntPtr hObject);
15: public static void Main(string[] args)
16: {
17: IntPtr pFile = CreateFile(
18: @"c:\file1.txt",
19: 0x80000000,
20: 0x00000001,
21: IntPtr.Zero,
22: 3,
23: 0,
24: IntPtr.Zero);
25: Stream fs = new FileStream(pFile, FileAccess.Read);
26: TextReader tr = new StreamReader(fs);
27: Console.WriteLine(tr.ReadToEnd());
28: tr.Close();
29: fs.Close();
30: CloseHandle(pFile);
31: }
32: }
這個例子裡,很 "神奇" 的把 unmanaged code 拿到的指標 (IntPtr), 丟給 System.IO.* 底下的 FileStream 來用,竟然還能成功的開啟檔案並且把資料讀出來... 沒錯,MS寫的東西本來就都是同一家人,System.IO 那堆東西就是這樣包出來的。 這段範例程式除了看起來複雜一點之外,跟那堆要查文件才知道是啥意思的 flags 之外,其它都很簡單,不過就 Open File, 然後讀出內容,接著 Close ...
較特別的是,在 P/Invoke 的世界裡,都用 struct System.IntPtr 這個型別,來代表 unmanaged 世界裡常用到的 POINTER.. 藉著這個型別,我們就可以把兩個世界的橋樑給搭起來。不過當你執行這個範例時,編譯器會給你一個很礙眼的警告:
'System.IO.FileStream.FileStream(System.IntPtr, System.IO.FileAccess)' is obsolete: 'This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead. http://go.microsoft.com/fwlink/?linkid=14202'
沒錯,這是老用法了,一如用指標有數不盡的缺點,新的語言都想盡辦法扔掉它 (POINTER: 我是無辜的...),連包裝過的 IntPtr 也不例外。在 .NET Framework 2.0 裡就不再建議你用這個建構式了,請改用 SafeFileHandle .... 既然在 windows 的世界裡,一切系統的資源都是以 HANDLE 來處理,自然的在 managed code 裡也有對應的型別,它就是 System.Runtime.InteropServices.SafeHandle 。接著再來看看改用 SafeHandle 的版本:
1: public class PInvokeTest3
2: {
3: [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
4: static extern SafeFileHandle CreateFile(
5: string fileName,
6: uint fileAccess,
7: uint fileShare,
8: IntPtr securityAttributes,
9: uint creationDisposition,
10: uint flags,
11: IntPtr template);
12: public static void Main(string[] args)
13: {
14: SafeFileHandle pFile = CreateFile(
15: @"c:\file1.txt",
16: 0x80000000,
17: 0x00000001,
18: IntPtr.Zero,
19: 3,
20: 0,
21: IntPtr.Zero);
22: Stream fs = new FileStream(pFile, FileAccess.Read);
23: TextReader tr = new StreamReader(fs);
24: Console.WriteLine(tr.ReadToEnd());
25: tr.Close();
26: fs.Close();
27: pFile.Close();
28: }
29: }
執行的結果,當然也是順利的把文字檔內容印到 CONSOLE 裡了,我就不再多貼,直接看程式碼。
其實這裡有點偷吃步,用的是 SafeFileHandle, 而不是 SafeHandle。雖然兩者有繼承關係啦。不過後面我就都把它當成 SafeHandle 看待。這個版本的程式,除了把 IntPtr 換成 SafeFileHandle 之外,及最後的 CloseHandle( pFile ) 改成直接呼叫 SafeHandle.Close( ) 之外,沒有太大的不同了。有啦,把 IntPtr 跟 CloseHandle 兩者包裝在同一個 SafeHandler 裡是安全的多,至少 SafeHandle 實作了 IDispose 的介面,在適當的情況下,它至少會自動的被呼叫 Dispose( ) 回收資源...
看到這裡,以前老搞不清楚的 FileStream 為什麼有幾個怪怪的 constructor, 自從研究了 TxF 之後,意外的晃然大悟... 算是額外的收穫吧! 原來就是要配合 P/Invoke 使用...
其實說穿了,呼叫 win32 api 大概就這幾招了,幾種 unmanaged code 裡用到的型別,指標都可以對應之後,程式寫起來就簡單了。這篇我特意漏掉一部份,就是各種用 uint 來當作 flags 的型別,沒有轉到對應的 enum 列舉型別,我是覺的這是 option 啦,畢竟查查文件就有,多列上來我得多打好多字... 篇幅有限 XD
這幾個例子如果都看懂後,那麼 TxF 就沒有障礙了! 接下來就看下回分解了 :D 下回會看到第一個 "交易式" 的檔案處理範例! 沒灌 windows vista / 2008 / 7 / 2008 R2 的讀者門,快去準備吧!
參考資訊:
其實想用 TxF (Transactional NTFS, 交易式的 NTFS) 已經很久了,不過老是被一些雜事卡著,到過年期間才有空好好研究一下。這篇主要是介紹而已,就不講太多 Code, 先以瞭解 TxF 是什麼,及如何善用它等等方面為主。詳細的用法,就等後面幾篇吧!
Transactional NTFS, 中文是 “交易式NTFS”,或是常見到的縮寫 “TxF”,早期的相關文章也有人寫 “TxFS”。這是在 Windows Vista / 2008 推出時,首次正式提供的功能。雖然它叫作 Transactional NTFS, 實際上它並不是一個新的檔案系統,而是一組新的 API (跟原有的檔案處理 API 幾乎是一對一的對應), 支援你用交易的方式操作檔案。一起推出的還有 Transactional Registry (TxR),一樣是有對應的 windows API,只不過它處理的對像是 windows registry,不是檔案…。
用這種方式處理檔案的讀寫動作,有種很神奇的感覺,過去都只在資料庫裡有機會這樣用,現在檔案的處理也可以了。配合像是 DTC 這類交易協調器的支援,甚至可以把檔案的處理及資料庫的處理,通通都包裝成一個交易來進行,一但任何一個環節失敗,都可以回復到最初的狀態,感覺好像是在用 DB,而不是寫檔案… 目前官方並沒有推出 managed code 的含式庫,現在要用只有幾種選擇:
這些用起來都有點不踏實,畢竟用 P/Invoke 不是長久之計,總覺的遲早會被替換掉。不過即使如此,這項還是掩蓋不了這技術的價值。我貼一段自己寫的 sample code,讓還沒用過的人體會一下,寫檔案還支援交易處理的 “爽度” …
// 建立 KTM transaction object
IntPtr transaction = CreateTransaction(
IntPtr.Zero,
IntPtr.Zero,
0, 0, 0, 0,
null);
string[] files = new string[] {
@"c:\file1.txt",
@"c:\file2.txt",
@"c:\file3.txt"};
try
{
foreach (string file in files)
{
// 使用支援交易的 delete file API
if (DeleteFileTransactedW(file, transaction) == false)
{
// 刪除失敗
throw new InvalidOperationException();
}
}
// 認可交易
CommitTransaction(transaction);
}
catch (Exception ex)
{
// 還原交易
RollbackTransaction(transaction);
}
CloseHandle(transaction);
範例裡用到的幾個 method, 像是 CreateTransaction()
, DeleteFileTransactedW()
, CommitTransaction()
, RollbackTransaction()
… 等等,都是透過 P/Invoke 的方式呼叫的 win32 api… 除了用的型別不如 pure .net class library 般直覺之外,這樣的 code 也已經很簡單了,短短卅行就可以搞定…
雖然這樣的 code 實在不大合我胃口,但是它畢竟是個堪用的方案… 對於 code 有潔癖的,可以考慮其它的用法。前面是最基本的 API call,如果你不滿意,MS自家的技術 DTC (Distributed Transaction Coordinator) 當然也支援 TxF。DTC 可以提供額外的好處,就是允許你做分散式的交易管理。意思是你配合 DTC,就可以把 Local File I/O 跟 database access 整合在同一個交易範圍內。
這邊的 sample code 我就不貼了,在 managed code 裡去呼叫到 COM 的那堆介面 (啥 QueryInterface
的) 實在跟 .NET programming 的 style 有點格格不入… 在 C# 的世界裡,應該用 TransactionScope
才對。在 MS 的世界裡,TxF + TxR + DB 都可以是 TransactionScope
內的一部份。這部份的 Sample Code 我一樣先不貼了,不然貼一堆 code 又沒篇幅說明,感覺很混…
其實,MS 該做的都做了,唯一缺的就是它竟然沒正式的併入 .NET Framework 內的一員… 如果 TxF 真的是你想用的東西,倒是有個 OpenSource Project 可以考慮一下: AlphaFS, 它的目標是能替換掉 namespace System.IO.*, 所以很多你常用的 class library, 它都有對等一樣用法的版本,當然它提供了更多的功能及改善… 其中 TxF 的支援就在內,你想用 TxF 來開發軟體的話,這是個不錯的選擇…
總之,這篇只是個開始,目的是想先 “預覽” 一下 TxF 的能耐,及未來它配 DTC / TransactionScope
後,能怎麼應用它的方式,還有其它可用的相關資源。接下來我會陸續整理一些相關的研究心得.. (別太期待,大概一兩週生一篇就很偷笑了 XD),下回見 !
參考資訊:
繼上一篇,介紹了如何用windows server内建的磁碟鏡像 (Mirror) 更換硬碟後,這次剛好有windows 2003,就拿來試了一下...
廢話就不多說了,先來看一下 2003 的步驟,再來看看跟 2008 / 2008R2 差在那裡:
整個步驟跟前一篇都差不多,主要都是靠鏡像(MIRROR)搬完資料後,再把新硬碟多的可用空間併進來。唯一的差別就在這裡: 2003 的 "延伸磁區" (英文: extend volume) 是可以把兩個硬碟,或是兩個分割區併在一起使用。在磁碟管理員還是看的到這些分割區的存在。這就有點像 JBOD (Just a Bunch Of Disks) 的模式。
不過在 2008 之後 (其實 vista 也是),這個功能就改了。原本的名字 extend volumn 現在改成直接擴大原分割區,把原分割區後面可用的空間都納進來,就像你砍掉再重建一個大的分割區一樣,只是資料會留著不會掉。當然有 extend 也有相對的 shrink volume, 這功能會把分割區縮小,騰出空間來讓你多切一個分割區...。而原本 JBOD 模式的功能,則改為 span disk, 用起來效果就如上圖一樣,當然你願意的話,也可以把多顆硬碟 (可以不同容量) 通通併成一個來使用,最多可以併到 32 顆硬碟...。
沒經過這次更換硬碟,還真沒發現 2008 / vista 總算內建這組 extend / shrink volume 的功能進來。雖然很陽春,不過已經很實用了,在過去這種動作是得搬出像 partition magic 這類軟體才能做的到,而這種東西每次用起來心裡都會毛毛的,深怕一不小心就把資料都給毀了...。
這篇小品文章就記到這裡,希望有幫到需要的人 :D
老是寫一堆像外星文,沒人看的懂的 multi-threading 文章,偶爾也來換換口味吧。前陣子把 SERVER 的兩顆 750GB HDD (RAID1) 升級了一下,升級成兩顆 1.5TB HDD (RAID1)。更換硬碟是小事,不過這個硬碟上有些服務,如網站,資料庫,還有一些重要資料,跟分享資料夾,想一想要更換也是挺囉唆的...。
想了幾個辦法,不過都不符合我既懶又挑毛病的個性... 原本考慮的更換方式有:
想來想去,我用的是 windows server, 有內建的 Mirror set, 就拿來用一用好了。我真正作的是把 mirror set 的兩顆都升級,不過為了簡化說明,我底下的例子就只以替換一般的硬碟就好,反正道理是一樣的。
說穿了不值錢,就是用 mirror 的磁碟複製特性,加上 extend volume 的功能,我除了需要關機裝上新硬碟之外,其它包含資料複製的所有時間,原服務都不用中斷 (當然速度會慢一點),所有服務的設定也都不用修改,算是既無腦又防呆的完美方案... 只要簡單的按幾下滑鼠就可以達成我的目標。
直接來看看怎麼作的吧! 很簡單,先利用 mirror 把資料轉移到新硬碟... 然後中斷 mirror, 再用 extend volume 把磁區大小調大就可以收工了。來看分解步驟:
之後就大功告成了。這方法不但簡單,而且整個過程中,全程 D:\ 都可以正常的使用。除了 (1) --> (2) 需要關機裝硬碟之外, (2) ~ (5) 全程,放在 D:\ 的 SQL DB,IIS 網站,還有 pagefile 通通都正常運作中。有了 windows server 的磁碟陣列還真是好用啊 :D
不過,事情也是有黑暗面的... 這個方法是有幾個小缺點啦...
偶爾換個口味,貼些小品文章,這邊我也不是很專業,有啥更好的作法也歡迎留 comment 啊 :D
1.6.0.0 出來一陣子了,不過到過年才有空升級... 主要的原因只有一個,就是最近 spam comments 實在太多了 =_=,新版對於這類問題的處理比較像樣一點..
其它改進還有 nested comments 跟其它一堆改進,就不一一列出來了,有興趣的人可到官方網站去看看。
試了一下,升級後沒啥大問題,除了 CSS 有點走樣之外... 如果各位有發現什麼地方漏掉了,請再通知我 :D
祝大家新年快樂 :D