2011年11月4日金曜日

Zip 圧縮を外部ライブラリ無しで行うアクティビティ

WF でファイルやフォルダ操作を行う上で必要性を非常に感じているのが Zip 圧縮。バッチスクリプト等での定型処理でも結構使われているのではないでしょうか。

これを実際に .NET で行おうとすると何種類かの選択肢から方式を選ぶことになると思います。

  1. J# ライブラリを利用する
  2. DotNetZip 等の外部ライブラリを利用する
  3. Windows 標準機能を利用する

自分が今まで利用していたのは(1)の方法で、これだと MS 純正コンポーネントのみで済み、かつ結構楽な手段でサンプルもごろごろ転がっていたと思います。ところが、.NET 4 から J# を利用するというのが結構面倒でそのままでは利用できないため WF から利用するのは躊躇していました。
そうなると(2)か(3)なのですが、(2)のように外部ライブラリを利用するのは最も楽で安定するのですが、気持ちとして参照 Dll がどんどん増えていく事が嫌いというのもあり却下。必然的に(3)の方法となってしまいます。

Windows は XP 以降、OS の標準機能として Zip 圧縮と解凍をサポートしています。それを利用するためには COM オブジェクトを通して機能を呼び出してあげる必要があります。既に VBS や VB.NET でのロジックを作成されていたサイトがあったので、そこを参考にしてみました。

   1: Imports System.Activities
   2:  
   3: Public Class ZipCompressActivity
   4:     Inherits AsyncCodeActivity
   5:  
   6:     ''' <summary>空 Zip 書庫のバイナリ</summary>
   7:     Private EMPTYZIP_ARRAY As Byte() = {&H50, &H4B, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
   8:  
   9:     Public Property ArchiveFileName As InArgument(Of String)
  10:     Public Property TargetFileName As InArgument(Of String)
  11:     Public Property Results As OutArgument(Of Boolean)
  12:  
  13:     Private Delegate Function asyncCompress(ByVal archive As String, ByVal target As String) As Boolean
  14:  
  15:     Protected Overrides Function BeginExecute(context As System.Activities.AsyncCodeActivityContext, callback As System.AsyncCallback, state As Object) As System.IAsyncResult
  16:         Dim archive = context.GetValue(Me.ArchiveFileName)
  17:         Dim target = context.GetValue(Me.TargetFileName)
  18:         archive = IO.Path.GetFullPath(archive)
  19:         target = IO.Path.GetFullPath(target)
  20:  
  21:         Dim asyncComp = New asyncCompress(AddressOf CompressFile)
  22:         context.UserState = asyncComp
  23:  
  24:         Return asyncComp.BeginInvoke(archive, target, callback, state)
  25:     End Function
  26:  
  27:     Protected Overrides Sub EndExecute(context As System.Activities.AsyncCodeActivityContext, result As System.IAsyncResult)
  28:         Dim asyncExecute = TryCast(context.UserState, asyncCompress)
  29:         Dim compResult = asyncExecute.EndInvoke(result)
  30:  
  31:         context.SetValue(Me.Results, compResult)
  32:     End Sub
  33:  
  34:     Private Function CompressFile(ByVal archive As String, ByVal target As String) As Boolean
  35:         Dim result As Boolean = False
  36:         If Not IO.File.Exists(archive) Then Me.CreateEmptyZip(archive)
  37:  
  38:         Dim shl As Object = Nothing
  39:         Dim zipFolder As Object = Nothing
  40:         Dim zipFolderFilesCount As Object = Nothing
  41:         Dim targetItem As Object = Nothing
  42:         Try
  43:             shl = CreateObject("Shell.Application")
  44:             zipFolder = shl.NameSpace(IO.Path.GetFullPath(archive))
  45:             zipFolderFilesCount = zipFolder.Items().Count
  46:             targetItem = shl.NameSpace(IO.Path.GetFullPath(target & "\..")).ParseName(IO.Path.GetFileName(target))
  47:  
  48:             zipFolder.CopyHere(targetItem)
  49:             Do Until zipFolder.Items().Count > zipFolderFilesCount
  50:                 System.Threading.Thread.Sleep(500)
  51:             Loop
  52:             result = True
  53:         Catch ex As Exception
  54:         Finally
  55:             targetItem = Nothing
  56:             zipFolderFilesCount = Nothing
  57:             zipFolder = Nothing
  58:             shl = Nothing
  59:         End Try
  60:  
  61:         Return result
  62:     End Function
  63:  
  64:     ''' <summary>Zip 空書庫の作成</summary>
  65:     Private Sub CreateEmptyZip(ByVal archive As String)
  66:         Using fs As New IO.FileStream(archive, IO.FileMode.Create)
  67:             fs.Write(EMPTYZIP_ARRAY, 0, EMPTYZIP_ARRAY.Length)
  68:         End Using
  69:     End Sub
  70:  
  71: End Class

ソースとしてはこのような感じでアクティビティにできました。注意する点は、COM オブジェクトを呼び出しているので遅延バインディングを利用しています。参照設定をかけて利用していませんので、タイプミスは実行時例外になります。また、途中で shl.NameSpace(~) のように呼び出しを行っていますが、ここも対象となるパスを渡すのですが String を渡した場合は結果が Null (Nothing) になってしまうので、なんらかのメソッドの戻り値を直接渡す必要がある、というのが気を付けるところだと思います。

アクティビティの機能としては、指定された 1 ファイルを圧縮する、というものですので複数ファイルを圧縮する際は ForEach アクティビティなどの子供として利用してあげればいいかと思います。

0 件のコメント:

コメントを投稿