« アンマネージドDLLの動的リンク(1) | トップページ | アンマネージドDLLの動的リンク(3)[最終回] »

2010年10月18日 (月)

アンマネージドDLLの動的リンク(2)

アンマネージドDLLの動的リンク(1)の続きです。前回、方法1によるやり方では、Unicode 文字列を使用するには、構造体を使用する、としていました。しかし改めて、前回のANSI版一括解凍ルーチンを使用して、Unicodeファイル名を持つLHA書庫ファイルを指定しても、どうやら正常に解凍される様です。既定のマーシャリングの関係だと思いますが、どうもよく判りません。今回やろうとしていたメンバ名一覧取得では、しっかりエラーになるのですが...

とりあえず、気を取り直して、Unicode ファイル名に対応した一括解凍を行なってみたいと思います。そこで以下のプログラムコードを書いてみました。方法2は、前回作成した UnmanagedLink クラス使用を前提にしています。

delegate int CommandW(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)]string szCmdLine,
    StringBuilder szOutput, int dwSize);
/// <summary>
/// Unicode 文字列によるLHA書庫一括解凍(方法1)
/// 但しこの処理は、PInvokeMethodGen の制約を無視しているので、必ずエラーを返す
/// </summary>
/// <param name="archiveFile">LHA書庫ファイルのフルパス</param>
private void UnicodeMelt1(string archiveFile)
{
    using (PInvokeMethodGen pimg = new PInvokeMethodGen("Unlha32.dll"))
    {
        StringBuilder log = new StringBuilder(10240);
        CommandW command = (CommandW)pimg.MethodGen(typeof(CommandW), "UnlhaW");
        string unlhaCommand = String.Format("x {0} {1} *", archiveFile,
            Path.GetDirectoryName(archiveFile) + @"\1\");
        int result = command(this.Handle, unlhaCommand, log, log.Capacity - 1);
        // result には、ERROR_NOT_FIND_ARC_FILE(0x8029)が入る筈
    }
}
/// <summary>
/// Unicode 文字列によるLHA書庫一括解凍(方法2)
/// 解凍先は、書庫のあるフォルダの下に"1"のサブフォルダを作成して、
/// その下に解凍する。手抜きなので、ドライブ直下だとうまくいかない筈なのに...
/// </summary>
/// <param name="archiveFile">LHA書庫ファイルのフルパス</param>
private void UnicodeMelt3(string archiveFile)
{
    using (UnmanagedLink ul = new UnmanagedLink("Unlha32.dll"))
    {
        StringBuilder log = new StringBuilder(10240);
        CommandW command = (CommandW)ul.MethodGen(typeof(CommandW), "UnlhaW");
        string unlhaCommand = String.Format("x {0} {1} *", archiveFile,
            Path.GetDirectoryName(archiveFile) + @"\2\");
        command(this.Handle, unlhaCommand, log, log.Capacity - 1);
    }
}

方法1の UnicodeMelt1 は、このままでは実行するとコメント通り、エラーになる筈です。これは、前回最後に触れたように、PInvokeMethodGen クラスの制約である MarshalAsAttribute の指定は効かない点にあります。
方法2では、さずが標準サポートらしく、問題無く動作しました。

では、方法1のエラー回避で、現在私が行なっている、構造体を使用した策を使用すると、

/// <summary>動的リンクで使用するUnicode文字列パラメータを使用する為の構造体</summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct LPWSTR
{
    /// <summary>Unicode文字列パラメータを格納する為のメンバ</summary>
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2048)]
    public string szString;
}

/// <summary>
/// Unicode 文字列によるLHA書庫一括解凍(方法1)
/// 解凍先は、書庫のあるフォルダの下に"1"のサブフォルダを作成して、
/// その下に解凍する。手抜きなので、ドライブ直下だとうまくいかない筈なのに...
/// </summary>
/// <param name="archiveFile">LHA書庫ファイルのフルパス</param>
private void UnicodeMelt2(string archiveFile)
{
    using (PInvokeMethodGen pimg = new PInvokeMethodGen("Unlha32.dll"))
    {
        StringBuilder log = new StringBuilder(10240);
        CommandU command = (CommandU)pimg.MethodGen(typeof(CommandU), "UnlhaW");
        LPWSTR unlhaCommand;
        unlhaCommand.szString = String.Format("x {0} {1} *", archiveFile,
            Path.GetDirectoryName(archiveFile) + @"\1\");
        command(this.Handle, ref unlhaCommand, log, log.Capacity - 1);
    }
}

これで、動く様になります。
Unicode 文字版では、ANSI 文字版よりは移植性が落ちますが、これも簡単な移植で出来そうです。むしろ、ANSI との整合性があって、後々の保守まで考えると良さそうです。

さて次に、INDIVIDUALINFO 構造体を使用したファイル一覧を取得するコードでの比較です。まずは ANSI 版、と言いたい処ですが、長くなるので省略します。興味ある人は、最後のサンプルコードに含めてますので、これをダウンロードして見て下さい。結論として ANSI 版は、一括解凍と同様の極めて似たプログラムで方法1/2共記述出来ます。こちらも移植は簡単に行なえそうです。そして Unicode版 は以下の様に記述しました。

/// <summary>
/// INDIVIDUALINFO 構造体(Unicode 文字列用)の定義
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct INDIVIDUALINFOW
{
    /// <summary>ファイルのサイズ</summary>
    public uint dwOriginalSize;
    /// <summary>ファイルの圧縮後のサイズ</summary>
    public uint dwCompressedSize;
    /// <summary>格納ファイルのチェックサム用CRC</summary>
    public uint dwCRC;
    /// <summary>Unicode文字列パラメータを格納する為のメンバ</summary>
    public uint uFlag;
    /// <summary>このメンバ作成に使用されたOS</summary>
    public uint uOSType;
    /// <summary>圧縮率(‰)</summary>
    public ushort wRatio;
    /// <summary>ftime の上位バイトと同じ構造で表現された日付</summary>
    public ushort wDate;
    /// <summary>ftime の下位バイトと同じ構造で表現された時間</summary>
    public ushort wTime;
    /// <summary>書庫内ファイル名</summary>
    /// <remarks>szFileName後方のdummy[3]を含める為、Size=516としている</remarks>
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 516)]
    public string szFileName;
    /// <summary>格納ファイルの属性</summary>
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
    public string szAttribute;
    /// <summary>格納ファイルの形式</summary>
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
    public string szMode;
}
delegate IntPtr OpenArchiveW(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)]string szFileName, uint uMode);
delegate int FindFirstW(IntPtr hArc, [MarshalAs(UnmanagedType.LPWStr)]string szFileName, out INDIVIDUALINFOW IInfo);
delegate int FindNextW(IntPtr hArc, out INDIVIDUALINFOW IInfo);
delegate IntPtr OpenArchiveU(IntPtr hWnd, ref LPWSTR szFileName, uint uMode);
delegate int FindFirstU(IntPtr hArc, ref LPWSTR szFileName, out INDIVIDUALINFOW IInfo);
/// <summary>
/// Unicode文字列による書庫一覧取得・表示(方法1)
/// </summary>
/// <param name="archiveFile">LHA書庫ファイルのフルパス</param>
private void UnicodeFileList1(string archiveFile)
{
    lstMember1.Items.Clear();
    using (PInvokeMethodGen pimg = new PInvokeMethodGen("Unlha32.dll"))
    {
        OpenArchiveU openArchive = (OpenArchiveU)pimg.MethodGen(typeof(OpenArchiveU), "UnlhaOpenArchiveW");
        FindFirstU findFirst = (FindFirstU)pimg.MethodGen(typeof(FindFirstU), "UnlhaFindFirstW");
        FindNextW findNext = (FindNextW)pimg.MethodGen(typeof(FindNextW), "UnlhaFindNextW");
        CloseArchive closeArchive = (CloseArchive)pimg.MethodGen(typeof(CloseArchive), "UnlhaCloseArchive");
        LPWSTR szFileName;
        szFileName.szString = archiveFile;
        IntPtr harc = openArchive(this.Handle, ref szFileName, 0);
        if (harc == IntPtr.Zero) return;
        INDIVIDUALINFOW memberInfo;
        szFileName.szString = "*";
        int status = findFirst(harc, ref szFileName, out memberInfo);
        while (status == 0)
        {
            lstMember1.Items.Add(memberInfo.szFileName);
            status = findNext(harc, out memberInfo);
        }
        closeArchive(harc);
    }
}
/// <summary>
/// Unicode文字列による書庫一覧取得・表示(方法1)
/// </summary>
/// <param name="archiveFile">LHA書庫ファイルのフルパス</param>
private void UnicodeFileList2(string archiveFile)
{
    lstMember2.Items.Clear();
    using (UnmanagedLink dl = new UnmanagedLink("Unlha32.dll"))
    {
        OpenArchiveW openArchive = (OpenArchiveW)dl.MethodGen(typeof(OpenArchiveW), "UnlhaOpenArchiveW");
        FindFirstW findFirst = (FindFirstW)dl.MethodGen(typeof(FindFirstW), "UnlhaFindFirstW");
        FindNextW findNext = (FindNextW)dl.MethodGen(typeof(FindNextW), "UnlhaFindNextW");
        CloseArchive closeArchive = (CloseArchive)dl.MethodGen(typeof(CloseArchive), "UnlhaCloseArchive");
        IntPtr harc = openArchive(this.Handle, archiveFile, 0);
        if (harc == IntPtr.Zero) return;
        INDIVIDUALINFOW memberInfo;
        int status = findFirst(harc, "*", out memberInfo);
        while (status == 0)
        {
            lstMember2.Items.Add(memberInfo.szFileName);
            status = findNext(harc, out memberInfo);
        }
        closeArchive(harc);
    }
}

Unlha の関数を4つ使用している関係で、string型を引数に持つかどうかで、回避用構造体 LPWSTR を使用するかどうかを、使い分けてます。まあ、ANSI 版よりは多少移植に手間がかかりますが、大した問題とはならないでしょう。

では、次回にまとめとして、まだ触れていない PInvokeMethodGen クラス使用時の問題点と UnmanagedLink クラスを SafeHandle クラスから継承した場合等に書きたいと思います。

最後に今回の説明で使用したソース一式を、CAB形式にまとめてアップロードしておきます。

「DynamicLink.cab」をダウンロード
(※PInvokeMethodGen クラスのソースは、方法1のサイトから取得して下さい)

« アンマネージドDLLの動的リンク(1) | トップページ | アンマネージドDLLの動的リンク(3)[最終回] »

C#研究」カテゴリの記事

コメント

コメントを書く

コメントは記事投稿者が公開するまで表示されません。

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: アンマネージドDLLの動的リンク(2):

« アンマネージドDLLの動的リンク(1) | トップページ | アンマネージドDLLの動的リンク(3)[最終回] »