rainit2006 / C-Program

C# Program knowledge
0 stars 0 forks source link

文件的version resource / 初期文件(ini) #27

Open rainit2006 opened 6 years ago

rainit2006 commented 6 years ago

rainit2006 commented 6 years ago

rainit2006 commented 6 years ago
typedef struct {
  WORD             wLength;
  WORD             wValueLength;
  WORD             wType;
  WCHAR            szKey;
  WORD             Padding1;
  VS_FIXEDFILEINFO Value;
  WORD             Padding2;
  WORD             Children;
} VS_VERSIONINFO;

Children: An array of zero or one StringFileInfo structures, and zero or one VarFileInfo structures that are children of the current VS_VERSIONINFO structure.

typedef struct tagVS_FIXEDFILEINFO {
  DWORD dwSignature;
  DWORD dwStrucVersion;
  DWORD dwFileVersionMS;
  DWORD dwFileVersionLS;
  DWORD dwProductVersionMS;
  DWORD dwProductVersionLS;
  DWORD dwFileFlagsMask;
  DWORD dwFileFlags;
  DWORD dwFileOS;
  DWORD dwFileType;
  DWORD dwFileSubtype;
  DWORD dwFileDateMS;
  DWORD dwFileDateLS;
} VS_FIXEDFILEINFO;
typedef struct {
  WORD        wLength;
  WORD        wValueLength;
  WORD        wType;
  WCHAR       szKey;
  WORD        Padding;
  StringTable Children;
} StringFileInfo;
例:
    StringTable()
            {
                ValueLength = 0;
                Type = 1;
                Key = "000004b0";               //Unicode code page
                Strings = new List<String>();
            }
rainit2006 commented 6 years ago

BeginUpdateResource 実行可能ファイル内のリソースの追加、削除、置き換えを行うときに UpdateResource 関数に渡すハンドルを取得します。

HANDLE BeginUpdateResource(
  LPCTSTR pFileName,             // 更新するリソースが入っているファイル名への
                                 // ポインタ
  BOOL bDeleteExistingResources  // 削除オプション
);

UpdateResource 実行可能ファイル内のリソースの追加、削除、置き換えを行います。

BOOL UpdateResource(
  HANDLE hUpdate, // 更新ファイルのハンドル
  LPCTSTR lpType, // 更新するリソースタイプのアドレス。
        //RT_VERSION であれば、バージョンリソースを表す。
  LPCTSTR lpName, // 更新するリソース名のアドレス
  WORD wLanguage, // リソースの言語識別子
  LPVOID lpData,  // リソースデータのアドレス
  DWORD cbData    // リソースデータの長さ( バイト数)
);

例:

  [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern IntPtr BeginUpdateResource(
                [In] string pFileName,
                [In, MarshalAs(UnmanagedType.Bool)] bool bDeleteExistingResources);

            [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool UpdateResource(
                [In] IntPtr hUpdate,
                [In] IntPtr lpType,
                [In] IntPtr lpName,
                [In] UInt16 wLanguage,
                [In] IntPtr lpData,
                [In, MarshalAs(UnmanagedType.U4)] int cbData);

            [DllImport("kernel32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool EndUpdateResource(
                [In] IntPtr hUpdate,
                [In, MarshalAs(UnmanagedType.Bool)] bool fDiscard);

   .........................
    public bool Save()
        {
            bool result = false;
            byte[] buffer = _info.ToBinary();
            int size = buffer.Length;
            IntPtr data = Marshal.AllocHGlobal(buffer.Length);
            try
            {
                Marshal.Copy(buffer, 0, data, size);

                IntPtr hUpdate = Win32Native.BeginUpdateResource(_fileName, false);
                if (hUpdate != IntPtr.Zero)
                {
                    result = Win32Native.UpdateResource(hUpdate, Win32Native.RT_VERSION, _resourceID, _langID, data, size);
                    result = Win32Native.EndUpdateResource(hUpdate, false) && result;
                }
            }
            finally
            {
                if(data != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(data);
                }
            }
            return result;
        }

注意:[return: MarshalAs(UnmanagedType.Bool)]的声明!否则在C#里调用函数时返回的是1byte的bool,而C语言里返回的是4byte的bool。不声明的话会发生问题(信息缺失,导致bool的true或false的结果不对)。

//考虑到update失败的case,加上retry处理。
public bool Save()
        {
            bool result = false;
            byte[] buffer = _info.ToBinary();
            int size = buffer.Length;
            IntPtr data = Marshal.AllocHGlobal(buffer.Length);
            try
            {
                Marshal.Copy(buffer, 0, data, size);

                int waitTime = 50;
                int retry = 5;
                result = false;
                while (retry > 0 && !result)
                {
                    IntPtr hUpdate = Win32Native.BeginUpdateResource(_fileName, false);
                    if (hUpdate != IntPtr.Zero)
                    {
                        var result1 = Win32Native.UpdateResource(hUpdate, Win32Native.RT_VERSION, _resourceID, _langID, data, size);
                        var lastError1 = Marshal.GetLastWin32Error();
                        var result2 = Win32Native.EndUpdateResource(hUpdate, false);
                        var lastError2 = Marshal.GetLastWin32Error();
                        result = result1 && result2;
                    }
                    if(!result)
                    {
                        System.Threading.Thread.Sleep(waitTime);
                        waitTime *= 2;
                        retry--;
                    }
                }
            }
            finally
            {
                if(data != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(data);
                }
            }
            return result;
        }
rainit2006 commented 6 years ago

VerQueryValue https://msdn.microsoft.com/ja-jp/library/cc364858.aspx 指定されたバージョン情報リソースから、指定されたバージョン情報を取得します。

pBlock バッファを正しく初期化するために、VerQueryValue を呼び出す前に、GetFileVersionInfoSize と GetFileVersionInfo の各関数を呼び出してください。

BOOL VerQueryValue(
  const LPVOID pBlock, // バージョン情報リソースのバッファ
  LPTSTR lpSubBlock,   // 取得対象の情報の種類
  LPVOID *lplpBuffer,  // バージョン情報へのポインタを格納するバッファ
  PUINT puLen          // バージョンの長さ
);

lpSubBlock:取得対象の情報を示す、NULL で終わる文字列へのポインタを指定します 「\VarFileInfo\Translation」であれば : 1 個の 可変情報構造体に含まれている変換テーブルを指定します。

NativeMethods.VerQueryValue(block, @"\VarFileInfo\Translation", out buffer, out bufferLength))

「\StringFileInfo\lang-codepage\string-name」であれば、言語固有の StringTable 構造体内の 1 つの値を指定します。

var subBlock = $@"\StringFileInfo\{langid:x4}{codepage:x4}\FileVersionまたはProductVersion";
NativeMethods.VerQueryValue(block, subBlock, out buffer, out bufferLength)

バージョン情報に関連してあらかじめ定義されている Unicode 文字列を示します。 image

例:次のサンプルコードは、利用可能なバージョン言語を列挙し、言語ごとに FileDescription 文字列の値を取得する方法を示します。

// 列挙された言語とコードページを格納するために使われる構造体。
struct LANGANDCODEPAGE {
  WORD wLanguage;
  WORD wCodePage;
} *lpTranslate;

// 言語とコードページのリストを読み取る。
VerQueryValue(pBlock,
              TEXT("\\VarFileInfo\\Translation"),
              (LPVOID*)&lpTranslate,
              &cbTranslate);

// 各言語とコードページのファイルの説明を読み取る。
for(i=0; i <(cbTranslate/sizeof(struct LANGANDCODEPAGE)); i++)
{
  wsprintf(SubBlock,
            TEXT("\\StringFileInfo\\%04x%04x\\FileDescription"),
            lpTranslate[i].wLanguage,
            lpTranslate[i].wCodePage);
  // i 番目の言語とコードページのファイルの説明を取得する。
  VerQueryValue(pBlock,
                SubBlock,
                &lpBuffer,
                &dwBytes);
}

PS: wsprintf関数:文字列を書式化して、値をバッファに格納します。 int wsprintf( LPTSTR lpOut, // 出力バッファ LPCTSTR lpFmt, // 書式制御文字列 ... // オプションの引数 );

rainit2006 commented 6 years ago

この時、どのようなデータ形式を取るかはプログラマしだいと言えます 独自の仕様の構造体を作成し、それをバイナリファイルとして保存することも考えられますし 筆者としては、独自の XML アプリケーションを策定し、XML で保存することを推奨します もっとも、この場合は、XML のプログラム技術を身につける必要があります

しかし、そんな面倒なことをしなくても Windows はその手段を用意しています アプリケーションが自分自身のための情報を保存する簡単な方法に レジストリ と 初期化ファイル *.ini のどちらかを選択することができます.

初期化ファイルは、アプリケーションが独自に保有するファイルと Windows が統合して保有する WIN.INI ファイルに分けられます.

初期化ファイルはセクションで項目を区切り、キーを与えることができます つまり、セクションとそれに属する複数のキーで表現されるのです.

[Section1]
KeyID1 = Value1
KeyID2 = Value2
...
[Section2]
...

キーの値を取得するには GetPrivateProfileInt() 関数を使います キーが文字列の値を持っている場合は GetPrivateProfileString() を使います

` ini文件 [ParamOption] ParamA= ParamB=/s /f2"../setup.log" ParamC=

代码: NativeMethods.GetPrivateProfileString("ParamOption", "ParamA", "", sbParamA, 1024, filePath); `