はじめに
この記事では、俗に言う「統合アーカイバプロジェクトAPI仕様に準拠したDLL」(以下「統合アーカイバDLL」)を使って書庫(アーカイブ)を展開(解凍)、作成(圧縮)する方法について説明します。さらに、DLL名と関数名を実行時に指定してアンマネージDLL関数を呼び出す、動的PInvokeの方法について説明します。最終的にはこれらを活用して、DLLの情報をXMLファイルに記述することにより対応DLLを増やすことができる、展開、圧縮専用アーカイバ「.NET Archiver」を作成します。
注意事項
この記事は、以下の統合アーカイバDLLのみを考慮して書かれています。この記事で「すべての統合アーカイバDLL」とあれば、以下のDLLすべてという意味です。また、DLLのバージョンが明記されていない場合は、以下に示したバージョンのDLLであるものとします。
DLL名 | バージョン | 著作権者または製作者(敬称略) | 配布先 |
UNLHA32.DLL | 2.02e | Micco | http://www2.nsknet.or.jp/~micco/micindex.html |
UNZIP32.DLL | 5.42 | shoda T. | http://www.csdinc.co.jp/archiver/lib/unzip32.html |
ZIP32J.DLL | 0.37 | 吉岡恒夫 | http://openlab.ring.gr.jp/tsuneo/zip32j/ |
7-ZIP32.DLL | 4.31.00.03 | 秋田稔 | http://akky.cjb.net/ |
CAB32.DLL | 0.98 | 宮内邦昭 | http://www.lightship.co.jp/cab/cab32.html |
TAR32.DLL | 2.28 | 吉岡恒夫 | http://openlab.ring.gr.jp/tsuneo/tar32/ |
UNRAR32.dll | 0.10 | 亀井哲弥 | http://www.csdinc.co.jp/archiver/ |
UNARJ32.DLL | 0.63a | Micco | http://www2.nsknet.or.jp/~micco/micindex.html |
BGA32.dll | 0.37 | 木村利靖 | http://www.csdinc.co.jp/archiver/ |
YZ1.DLL | 0.27 | K.INABA | http://www.kmonos.net/ |
UNGCA32.DLL | 0.11b | 天野晃治 | http://www6.plala.or.jp/amasoft/ |
UNIMP32.dll | 0.17 | HyperBeat | http://www.csdinc.co.jp/archiver/ |
UNHKI32.dll | 0.02 | Silky | http://sirk.tn.st/ |
UNACEV2J.DLL | 0.05 | Niiyama(HEROPA) | http://www31.ocn.ne.jp/~heropa/ |
また、記事内で「Unbel32.dll」(バージョン:0.45、著作権者:島田啓史)に触れた部分もありますが、これは対象外とさせていただきます。私が試した限りでは、正常な動作が確認できなかったためです。
前提知識
この記事はプログラミング初心者を対象としていないため、基本的な事柄については説明しません。不明な点はMSDNなどで調べてください。
またこの記事では、統合アーカイバDLLの使い方についてもある程度の説明はしていますが、各DLLを使用する前には必ずDLLと同梱されているドキュメントをお読みいただき、ライセンスや使い方などを確認してください。
必要な環境
サンプルはMicrosoft Visual Studio 2005で作成されています。実行には、.NET Framework 2.0が必要です。また、すべてのサンプルを実行するためには、「Unlha32.dll」がインストールされていることが必要です。
なお、.NET Framework 1.1以前における方法に関しては、『遅延バインディングによりアンマネージDLL関数を呼び出す』を参考にしてください。
DllImportAttribute属性を使用したPInvoke
「Unlha32.dll」などの統合アーカイバDLLはアンマネージDLLですので、マネージコードからDLL内のAPIを呼び出すには、工夫が必要です。それが、PInvoke(Platform Invoke、P/Invoke、プラットフォーム呼び出し)です。
「Unlha32.dll」のヘッダファイル「UNLHA32.H」によれば、「Unlha32.dll」のAPIであるUnlha
の関数プロトタイプは、次のようなものです。
int WINAPI _export Unlha(const HWND _hwnd, LPCSTR _szCmdLine, LPSTR _szOutput, const DWORD _dwSize);
このUnlha
関数をマネージコードから呼び出すためには、例えば次のようなメソッドを宣言します。
[DllImport("unlha32.dll", EntryPoint = "Unlha", CharSet = CharSet.Ansi, ExactSpelling = true)] private extern static int Unlha( IntPtr hwnd, [MarshalAs(UnmanagedType.LPStr)]string szCmdLine, [MarshalAs(UnmanagedType.LPStr)]StringBuilder szOutput, uint dwSize);
<DllImport("unlha32.dll", _ EntryPoint:="Unlha", _ CharSet:=CharSet.Ansi, _ ExactSpelling:=True)> _ Private Shared Function Unlha( _ ByVal _hwnd As IntPtr, _ <MarshalAs(UnmanagedType.LPStr)> ByVal _szCmdLine As String, _ <MarshalAs(UnmanagedType.LPStr)> ByVal _szOutput As StringBuilder, _ ByVal _dwSize As UInteger) As Integer End Function
DllImportAttribute
のフィールドや、パラメータへの属性もあえて指定していますが、仮にこれらすべてを削除したとしても、問題なく動作するでしょう。また、VB.NETではDeclareステートメントを使う方法もありますが、DllImportAttribute
より指定できる設定が少ない点以外は、ほとんど同じです。両者の違いについて詳しくは、『Should I use Declare or DllImport in VB.NET? What's the difference?』をご覧ください。なぜこのようになるのかについては、MSDNの『アンマネージコードとの相互運用』以下(特に、『アンマネージDLL関数の処理』や『プラットフォーム呼び出しによるデータのマーシャリング』)で詳しく説明されています。この記事はDllImportAttributeの解説が目的ではありませんが、差し当たり必要な知識を簡単に説明しておきます。
まずはじめの悩みどころは、アンマネージ関数のパラメータや戻り値のデータ型をマネージコードではどの型に置き換えたらよいのかという点でしょう。これについては、MSDNの『プラットフォーム呼び出しのデータ型』が参考になります。これをそのまま適用すれば、特に問題は無いでしょう。
ところで、_szCmdLine
パラメータがString
型なのに対して、_szOutput
はStringBuilder
となっています。これは、_szCmdLine
が文字列を渡すだけなのに対して、_szOutput
は文字列がバッファにコピーされるためです。つまり、MSDNの『文字列のマーシャリング』の『Buffersのサンプル』と同じです。
次に、DllImportAttribute
のフィールドについて説明しましょう。これは、MSDNでは、『マネージコードでのプロトタイプの作成』で詳しく解説されています。
まず、EntryPointフィールドで呼び出すDLL関数の名前(エントリポイント)を指定します。上記の例のように、エントリポイントの名前とメソッドの名前が同じ場合は、指定する必要はありません。通常はメソッドの名前をエントリポイントと別の名前にしたい場合に使用します。
CharSetフィールドでは、文字列パラメータのマーシャリング方法と、関数の検索法をCharSet.Ansi
、CharSet.Unicode
、CharSet.Auto
のいずれかで指定します。上の例ではCharSet.Ansi
を指定していますが、これはUnlha
関数の文字列パラメータにAnsi文字列として渡すためです(これらの選択に関しては、『プラットフォーム呼び出しのデータ型』をご覧ください)。デフォルトでCharSet
はCharSet.Ansi
です。
CharSet
をCharSet.Auto
にしてしまったら、どうなるでしょうか? Windows 98系のプラットフォームではANSI形式となるため問題ありませんが、Windows NT系のプラットフォームではUnicode形式となってしまうため、正しく文字列をDLL関数に渡すことができなくなるでしょう(ただし、MarshalAsAttribute
が正しく設定されている場合は、問題ないようです)。 上の例では、ExactSpellingフィールドをtrueにしています。ExactSpelling
フィールドがfalseの場合は、「Unlha」という名前の関数が見つからなかった時に「UnlhaA」という名前の関数を検索しますが、ここではその必要がないためです。なお、ExactSpelling
フィールドのデフォルトはCharSet
がCharSet.Ansi
ならばVB.NETがtrue、C#およびC++がfalseです。詳しくは、MSDNの『DllImportAttribute.ExactSpellingフィールド』にあります。
さて残るは文字列のパラメータに付いたMarshalAsAttribute属性です。これは、型をアンマネージコードにどのようにマーシャリングするかを指定するものです。上記の例では、文字列はANSI型にマーシャリングするため、UnmanagedType.LPStrが指定されています。詳しくは、MSDNの『文字列に対する既定のマーシャリング』の『プラットフォーム呼び出しで使用される文字列』などをご覧ください。
確かではありませんが、MarshalAsAttribute
を指定しなくても、DllImportAttributeのCharSet
フィールドと矛盾がなければ、問題はないようです。
ごく簡単な説明でしたが、これで圧縮、展開に必要なAPIを呼び出すための予備知識は整ったものとします。