C#でdllのファイルバージョン取得について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
Mu

C#でdllのファイルバージョン取得について

#1

投稿記事 by Mu » 9年前

お世話になります。
C#でWin32APIを使ってDLL等のファイルバージョンを取得したいです。
有るサイトの以下サンプルソースを使ってみたのですが、
2個目のVerQueryValueでエラーもでずにアプリが終了してしまいます。
どうすればうまく取得できますでしょうか。
よろしくお願いしますm(__)m

コード:

public class FileVersionInfo
{
	[DllImport("version.dll")]
	private static extern bool GetFileVersionInfo(string sFileName,
		 int handle, int size, byte[] infoBuffer);
	[DllImport("version.dll")]
	private static extern int GetFileVersionInfoSize(string sFileName,
		 out int handle);

	// The third parameter – “out string pValue” – is automatically 
	// marshaled from ANSI to Unicode: 
	[DllImport("version.dll")]
	unsafe private static extern bool VerQueryValue(byte[] pBlock,
		 string pSubBlock, out string pValue, out uint len);
	// This VerQueryValue overload is marked with ‘unsafe’ because 
	// it uses a short*: 
	[DllImport("version.dll")]
    unsafe private static extern bool VerQueryValue(byte[] pBlock,
		 string pSubBlock, out short* pValue, out uint len);

	public static unsafe string GetFileVersionNameInfo(string path)
	{
		string name = null;
		int handle = 0;
		int size = GetFileVersionInfoSize(path, out handle);
		if (size != 0)
		{
			byte[] buffer = new byte[size];

			if (GetFileVersionInfo(path, handle, size, buffer))
			{
				uint len = 0;
				short* subBlock = null;
				if (VerQueryValue(buffer, @"\VarFileInfo\Translation", out subBlock, out len) && len > 2)
				{
					string spv = @"\StringFileInfo\" + subBlock[0].ToString("X4") + subBlock[1].ToString("X4") + @"\FileDescription";
                    byte * pVersion = null;
					string versionInfo;
					if (VerQueryValue(buffer, spv, out versionInfo, out len))
					{
						name = versionInfo;
					}
				}
			}
		}
		if (name == null)
		{
			name = Path.GetFileNameWithoutExtension(path);
		}
		return name;
	}
}

Mu

Re: C#でdllのファイルバージョン取得について

#2

投稿記事 by Mu » 9年前

追記
最終的にはUnicode版のWin32APIで実現したいと思っています。

YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: C#でdllのファイルバージョン取得について

#3

投稿記事 by YuO » 9年前

この場合,byte[]を使うことはできません。
マネージ型はfixedステートメントの範囲外では,自由にメモリ上の位置が変更され得ます。
このため,byte[]をそのまま渡すと,その内部へのポインタは,マネージ型の配列の位置が変更された時点 (いつかは不明) で無効になります。
Marshal.AllocHGlobalなりMarshal.AllocCoTaskMemなりでアンマネージのメモリを確保してそれを利用する必要があります。

あと,
Mu さんが書きました:2個目のVerQueryValueでエラーもでずにアプリが終了してしまいます。
というのは,Visual Studioごと終了してしまうのですか。
それとも,エラー検出等なしにVisual Studioのデバッグモードから編集モードへ移行してしまうのでしょうか。
また,アンマネージコードのデバッグに関する設定はどうなっていますか。

Mu

Re: C#でdllのファイルバージョン取得について

#4

投稿記事 by Mu » 9年前

YuO様
お世話になります。
Vs2015なのですが、デバッグ設定に「ネイティブコードデバッグを有効にする」があり、
そちらをチェックすることで、エラーメッセージが表示されるようになりました。

ソースコードも以下のように変更して実行したのですが、この場合、エラーは
以下のような内容が表示されます。

ハンドルされない例外が 0x77013F83 (ntdll.dll) で発生しました(Win32ApiTest.exe 内):
0xC0000374: ヒープは壊れています。 (パラメーター: 0x7702DDD8)。

何かお気づきの点など無いでしょうか・・・・。

コード:

public class FileVersionInfo
{
	[DllImport("version.dll")]
	private static extern bool GetFileVersionInfo(string sFileName,
		 int handle, int size, IntPtr pInfoBuffer);

	[DllImport("version.dll")]
	private static extern int GetFileVersionInfoSize(string sFileName,
		 out int handle);

	// The third parameter – “out string pValue” – is automatically 
	// marshaled from ANSI to Unicode: 
	[DllImport("version.dll")]
	unsafe private static extern bool VerQueryValue(IntPtr pBlock,
		 string pSubBlock, out string pValue, out uint len);

	// This VerQueryValue overload is marked with ‘unsafe’ because 
	// it uses a short*: 
	[DllImport("version.dll")]
	unsafe private static extern bool VerQueryValue(IntPtr pBlock,
		 string pSubBlock, out short* pValue, out uint len);

	public static unsafe string GetFileVersionNameInfo(string path)
	{
		string name = null;
		int handle = 0;
		int size = GetFileVersionInfoSize(path, out handle);
		if (size != 0)
		{
			IntPtr pBuffer = Marshal.AllocCoTaskMem(size);

			if (GetFileVersionInfo(path, handle, size, pBuffer))
			{
				uint len = 0;
				short* subBlock = null;
				//Marshal.Copy(pBuffer, buffer, 0, size);

				if (VerQueryValue(pBuffer, @"\VarFileInfo\Translation", out subBlock, out len) && len > 2)
				{
					string spv = @"\StringFileInfo\" + subBlock[0].ToString("X4") + subBlock[1].ToString("X4") + @"\FileDescription";
					byte* pVersion = null;
					string versionInfo;
					if (VerQueryValue(pBuffer, spv, out versionInfo, out len))		//※エラー発生
					{
						name = versionInfo;
					}
				}
			}
			Marshal.FreeCoTaskMem(pBuffer);
		}
		if (name == null)
		{
			name = Path.GetFileNameWithoutExtension(path);
		}
		return name;
	}
}

YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: C#でdllのファイルバージョン取得について

#5

投稿記事 by YuO » 9年前

ざっとMSDN見直したら,VerQueryValueの第3引数はLPVOID *です。
つまり,out stringで受けることはできません。
stringは確かにマーシャリングで特殊な扱いを受けますが,それはstringそのものの場合です。
out stringはstringそのものではないので,マーシャリング機構はLPCTSTR *をout stringに対応させてくれたりはしません。

普通にIntPtrで受けて,Marshal.PtrToStringAutoで文字列化すればよいでしょう。
よっぽど効率を求めない限り,unsafeも不要です。

コード:

using System;
using System.Runtime.InteropServices;

class Program
{
    private static class NativeMethods
    {
        [DllImport("Version.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int GetFileVersionInfoSize(
            [In] string lptstrFilename,
            [Out, Optional] out int lpdwHandle);
        [DllImport("Version.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int GetFileVersionInfoSize(
            [In] string lptstrFilename,
            IntPtr lpdwHandle);

        [DllImport("Version.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetFileVersionInfo(
            [In] string lptstrFilename,
            int dwHandle,
            [In] int dwLen,
            [Out] IntPtr lpData);

        [DllImport("Version.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool VerQueryValue(
            [In] IntPtr pBlock,
            [In] string lpSubBlock,
            [Out] out IntPtr lplpBuffer,
            [Out] out int puLen);
    }

    private const string Target = "VersionTest.dll";

    static int Main()
    {
        var size = NativeMethods.GetFileVersionInfoSize(Target, IntPtr.Zero);
        if (size == 0)
        {
            var err = Marshal.GetLastWin32Error();
            Console.WriteLine("GetFileVersionInfoSize");
            return err;
        }

        var block = Marshal.AllocCoTaskMem(size);
        if (!NativeMethods.GetFileVersionInfo(Target, 0, size, block))
        {
            var err = Marshal.GetLastWin32Error();
            Console.WriteLine("GetFileVersionInfo");
            return err;
        }

        IntPtr buffer;
        int bufferLength;
        if (!NativeMethods.VerQueryValue(block, @"\VarFileInfo\Translation", out buffer, out bufferLength))
        {
            var err = Marshal.GetLastWin32Error();
            Console.WriteLine("VerQueryValue for translation");
            return err;
        }

        var localeIds = new int[bufferLength / 4];
        Marshal.Copy(buffer, localeIds, 0, localeIds.Length);

        foreach (var localeId in localeIds)
        {
            var hiword = (int)(((uint)localeId & 0xFFFF0000u) >> 16);
            var loword = localeId & 0xFFFF;
            var subBlock = $@"\StringFileInfo\{loword:x4}{hiword:x4}\FileDescription";

            if (!NativeMethods.VerQueryValue(block, subBlock, out buffer, out bufferLength))
            {
                var err = Marshal.GetLastWin32Error();
                Console.WriteLine($"VerQueryValue for description ({localeId:X8})");
                return err;
            }

            var description = Marshal.PtrToStringAuto(buffer, bufferLength);
            Console.WriteLine(description);
        }

        return 0;
    }
}

Mu

Re: C#でdllのファイルバージョン取得について

#6

投稿記事 by Mu » 9年前

YuO様
お世話になります。
詳細なコード、ありがとうござます。
おかげでバージョン関連の情報取得ができました。
またUnicode版APIに置き換えて260文字オーバのパス名の場合も
取得できました。

ありがとうございましたm(__)m。

Mu

Re: C#でdllのファイルバージョン取得について

#7

投稿記事 by Mu » 9年前

解決マークをつけ忘れてました。

閉鎖

“C言語何でも質問掲示板” へ戻る