1. [TxF] #2. 先作功課 - 熟悉 P/Invoke 及 Win32 檔案處理...

     

    其實這篇是多寫的,因為前一篇提到的 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 ...

    P/Invoke: MoveFile[copy 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) 來試看看結果吧:

    P/Invoke Sample #1. MoveFile[copy 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 指令執行結果:

    image

    沒錯,有 c:\file1.txt 這檔案... 接著來執行範例程式:

    image

    執行成功。再重新看一下 C:\ 的 DIR *.TXT 指令執行結果:

    image

    看來程式很順利的呼叫了 win32 api 裡定義的 MoveFile( ... ) ... 這種範例有點不入流,要處理檔案總不可能只有這樣吧? 接著我們再來看看需要 Open File 加上讀寫檔案內容的應用。

    Windows 是個以 HANDLE 為主的作業系統,一般在寫 C / C++ 程式,都是以指標(POINTER)的方式在處理資料,但是在 windows 裡,作業系統提供的資料用的指標,則特別以 "HANDLE" 來稱呼,它比一般的 POINTER 來說多了一些管理的動作。因此你開啟的檔案,或是建立的視窗,通通都以 HANDLE 來稱呼,而不是單純的用 POINTER (雖然它也是個 POINTER 啦)。接下來的例子就來看看 HANDLE 的應用。

    [copy code]
       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 的版本:

    [copy code]
       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 的讀者門,快去準備吧!

     

    參考資訊:

    1. 有用的網站: PINVOKE.NET
      http://www.pinvoke.net/index.aspx

      這網站幫你整理好了各種 win32 api 該如何宣告它的 C# signature, 以供 p/Invoke 使用。如果你不熟它的語法,這網站可以幫你不少忙。另外它也提供了一些方便的工具,像是 winform 的查詢工具,或是 visual studio 用的 add-ins
    2. MSDN 的 API 說明: MoveFile
      http://msdn.microsoft.com/en-us/library/aa365239(VS.85).aspx
    3. 什麼是 P/Invoke ? Wiki 的說明:
      http://en.wikipedia.org/wiki/Pinvoke
    4. Safe Handle & Critical Finalization
      (實在是看不懂的中譯... "安全控制代碼和關鍵結束" ???)
      http://msdn.microsoft.com/zh-tw/library/fh21e17c(v=VS.90).aspx

    2010/03/23 系列文章: 交易式 (Transactional) NTFS .NET C# MSDN Transactional NTFS

  2. [TxF] #1. 初探 Transactional NTFS

    其實想用 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 的含式庫,現在要用只有幾種選擇:

    1. 直接用 C / C++ 呼叫 win32 api
    2. 用 P/Invoke,在 C# 裡呼叫 win32 api
    3. 找那些別人 (非官方) 包裝好的 .net class library ..

    這些用起來都有點不踏實,畢竟用 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),下回見 !

    參考資訊:

    1. AlphaFS: Brining Advanced Windows FileSystem Support to .NET
      http://alphafs.codeplex.com/
    2. MSDN magazine (July 2007): Enhance Your Apps With File System Transactions
      http://msdn.microsoft.com/en-us/magazine/cc163388.aspx
    3. B# .NET BLOG: Windows Vista - Introducing TxF In C#
      Part 1: Transacted File Delete
      Part 2: Using System.Transactions and the DTC
      Part 3: CreateFileTransacted Demo
    4. Code Project: Windows Vista TxF / TxR
      http://www.codeproject.com/KB/vista/KTM.aspx
    5. BLOG: Because we can
      http://blogs.msdn.com/because_we_can/archive/2005/05/18/419809.aspx
      Discussion and explanation relating to the Transactional NTFS feature coming in Longhorn, plus any other interesting anecdotes…
    6. Performance Consoderations for Transactional NTFS
      http://msdn.microsoft.com/en-us/library/ee240893(VS.85).aspx
    7. When to Use Transactional NTFS
      http://msdn.microsoft.com/en-us/library/aa365738(VS.85).aspx

    2010/03/18 系列文章: 交易式 (Transactional) NTFS .NET C# MSDN SQL Transactional NTFS 作業系統

  3. [Tips] 用 “磁碟鏡像" 更換硬碟 #2, Windows 2003 的跨距磁區

    上一篇,介紹了如何用windows server内建的磁碟鏡像 (Mirror) 更換硬碟後,這次剛好有windows 2003,就拿來試了一下...

    廢話就不多說了,先來看一下 2003 的步驟,再來看看跟 2008 / 2008R2 差在那裡:

    1. 原本的樣子,磁碟1 (8.00GB) 是舊的硬碟,磁碟2 (16.00GB) 是要換上來的新硬碟:
      image001

    2. 磁碟1 + 磁碟2 做成鏡像 (MIRROR):
      image002


      等待重新同步化 (Resync) 完成:
      image003


    3. Resync 完成後,移除鏡像:
      image004


      移除鏡像完成後的狀態:
      image005 

    4. 用 2003 的延伸磁區,把磁碟2後面沒用到的空間也併進 D: 來
      image006


      合併之後的狀態:
      image007 



    整個步驟跟前一篇都差不多,主要都是靠鏡像(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

    2010/03/11 Tips 技術隨筆 有的沒的

  4. [Tips] 用 “磁碟鏡像" 無痛更換硬碟

    老是寫一堆像外星文,沒人看的懂的 multi-threading 文章,偶爾也來換換口味吧。前陣子把 SERVER 的兩顆 750GB HDD (RAID1) 升級了一下,升級成兩顆 1.5TB HDD (RAID1)。更換硬碟是小事,不過這個硬碟上有些服務,如網站,資料庫,還有一些重要資料,跟分享資料夾,想一想要更換也是挺囉唆的...。

    想了幾個辦法,不過都不符合我既懶又挑毛病的個性... 原本考慮的更換方式有:

    1. 硬上... 新硬碟裝上去,檔案COPY過去,能停的服務就停掉,花一堆時間搬資料,然後再恢復原服務 (如 SQL、IIS等等)。至於比較麻煩的,像是目錄分享的,只好移掉再重建。中斷服務的時間就是從關機裝硬碟,一直到全部完成為止。
    2. 用 disk clone 的工具,如 true image / ghost 之類的軟體。不過這樣通常得停機做 clone, 750GB 也是要執行好一段時間,加上我這次買的是 Advanced Format 的硬碟,用這類工具會有效能的問題,事後還得校正回來… ouch, 算了...

    想來想去,我用的是 windows server, 有內建的 Mirror set, 就拿來用一用好了。我真正作的是把 mirror set 的兩顆都升級,不過為了簡化說明,我底下的例子就只以替換一般的硬碟就好,反正道理是一樣的。

    說穿了不值錢,就是用 mirror 的磁碟複製特性,加上 extend volume 的功能,我除了需要關機裝上新硬碟之外,其它包含資料複製的所有時間,原服務都不用中斷 (當然速度會慢一點),所有服務的設定也都不用修改,算是既無腦又防呆的完美方案... 只要簡單的按幾下滑鼠就可以達成我的目標。

     

    直接來看看怎麼作的吧! 很簡單,先利用 mirror 把資料轉移到新硬碟... 然後中斷 mirror, 再用 extend volume 把磁區大小調大就可以收工了。來看分解步驟:

     

    1. 我原本的磁碟組態是長這個樣子 (圖我是事後用 VM 模擬的),其中 Disk 1 (8.00GB) 就是我要換掉的...
      image 


    2. 關機裝上一顆新的硬碟 Disk 2 之後,變成這樣 (Disk 2 (16.00GB) 是新的硬碟):
      image


    3. 把 disk1 / disk2 做成磁碟鏡像之後,就變成這樣:
       image


    4. 鏡像做好,Resync 完成後,就可以中斷鏡像了。中斷之後變成這個樣子:
      image 


    5. 目前為止,看來磁碟轉移已經完成了,剩下就是想辦法把後面的空間吃進來。接下來的就用 Extend Volume 括大 D: 的大小:
      image 
      image

     

    之後就大功告成了。這方法不但簡單,而且整個過程中,全程 D:\ 都可以正常的使用。除了 (1) --> (2) 需要關機裝硬碟之外, (2) ~ (5) 全程,放在 D:\ 的 SQL DB,IIS 網站,還有 pagefile 通通都正常運作中。有了 windows server 的磁碟陣列還真是好用啊 :D

    不過,事情也是有黑暗面的... 這個方法是有幾個小缺點啦...

    1. 被迫使用 "動態磁碟" :
      dynamic disk 其實不是什麼缺點啦,不過你要是會用到其它 OS,像 linux 之類的,或是用其它的磁碟管理軟體,可能就不認得了。這是缺點之一..
    2. 只有 windows server 可以使用:
      desktop os (windows 2000 pro, xp, vista, win7) 都只支援部份的磁碟管理功能,這作法關鍵的 mirror 是不支援的... 只能乾瞪眼 @@
    3. Extend Volume 只適用 windows 2008 以上的版本:
      記得 windows 2003 只支援到 span volume, 在 disk manager 裡還是會顯示兩個 partition, 只不過只會有一個磁碟機代號,容量會是兩個加起來的而已。一樣啦,是不會有什麼大問題,看起來不大爽而已 XD

    偶爾換個口味,貼些小品文章,這邊我也不是很專業,有啥更好的作法也歡迎留 comment 啊 :D

    2010/03/06 Tips 技術隨筆 敗家 有的沒的

  5. 升級到 BlogEngine.NET 1.6.0.0 了!

    1.6.0.0 出來一陣子了,不過到過年才有空升級... 主要的原因只有一個,就是最近 spam comments 實在太多了 =_=,新版對於這類問題的處理比較像樣一點..

    其它改進還有 nested comments 跟其它一堆改進,就不一一列出來了,有興趣的人可到官方網站去看看。

    試了一下,升級後沒啥大問題,除了 CSS 有點走樣之外... 如果各位有發現什麼地方漏掉了,請再通知我 :D

    祝大家新年快樂 :D

    2010/02/19 BlogEngine.NET 有的沒的