Visual Studio 2026 で Win32 アプリケーションにアイコンリソースを追加するで実装したアプリケーションにバージョン情報リソースを追加します。バージョン情報の用途や構成を確認した上で、そのバージョン情報リソースをアプリケーションに追加し、アプリケーションでバージョン情報を表示する方法も説明します。
バージョン情報を概観する
Win32 API を使用してデスクトップ Windows アプリを構築するの公式ドキュメントにあるバージョン情報では、バージョン情報を「バージョン情報を使用すると、アプリケーションがファイルを適切にインストールしやすくなり、セットアッププログラムで現在インストールされているファイルを分析できるようになります。」と説明しています。また、バージョン情報についてでは、バージョン情報関数を使用すると、アプリケーションはバージョンリソースに対してファイル情報のクエリを実行し、情報を明確な形式で表示できます。」と説明しており、人間が読むための情報として活用することも示唆されています。
バージョン情報は、Raymond Chen が What can I do if I don’t want my file version number to be a sequence of four integers? で「機械が解析できるバイナリ形式のバージョンと、人間が読める文字列形式のバージョンがある。」と説明しています。それぞれ、VS_FIXEDFILEINFO と StringFileInfo という構造体で定義されています。
バージョン情報リソースを理解する
現実的には、バージョン情報の構造体をすべて理解する必要はないので、リソースファイルに定義するバージョン情報リソースのパラメーターを確認すると理解しやすいです。その中でも Visual Studio 2026 のリソースビューからリソースを編集する画面に含まれる項目を本稿では説明します。
| 項目名 | 例 | 説明 |
|---|---|---|
| FILEVERSION | 1, 0, 0, 0 | ファイルのバイナリ形式バージョン番号 4つの16ビット整数を指定する。 |
| PRODUCTVERSION | 1, 0, 0, 0 | ファイルが含まれる製品のバイナリ形式バージョン番号 4つの16ビット整数を指定する。 |
| FILEFLAGSMASK | 0x3fL | FILEFLAGS のどのビットが有効であるかを示すマスク 全ファイルフラグを表す 0x3f を指定する。 |
| FILEFLAGS | 0x0L | ファイルの属性 リリース版は 0x0、デバッグ版は 0x1 が指定される。 |
| FILEOS | VOS_NT_WINDOWS32 | ファイルの対象オペレーティングシステム |
| FILETYPE | VFT_APP | ファイルの種類 |
| FILESUBTYPE | VFT2_UNKNOWN | ファイルの種類の追加情報 VFT_APP の場合は VFT2_UNKNOWN を指定する。 |
| Block Header | 英語(米国)(040904B0) | 文字列情報に対応する言語と文字セットの組 英語(米国)は 0x0409、Unicode は 0x04B0 である。 |
| CompanyName | Takuya Tsuchida | 会社名 |
| FileDescription | Win32 App | ユーザーに表示されるファイルの説明 |
| FileVersion | 1.0.0 | ファイルの文字列形式バージョン番号 |
| InternalName | Win32App | ファイルの内部名 内部名がない場合は拡張子なしの OriginalFilename を指定する。 |
| LegalCopyright | © 2026 Takuya Tsuchida | 著作権表示 |
| OriginalFilename | Win32App.exe | ファイル作成時のファイル名 |
| ProductName | Win32 App | 製品名 |
| ProductVersion | 1.0.0 | ファイルが含まれる製品の文字列形式バージョン番号 |
製品バージョンとファイルバージョンの差異
製品バージョン (ProductVersion) とファイルバージョン (FileVersion) の差異について説明します。また、そのバイナリ形式と文字列形式の違いについてもあわせて説明します。
まず、製品バージョンはファイルが含まれる製品のバージョンであり、ファイルバージョンはファイル自体のバージョンです。つまり、製品と製品に含まれるファイルには別々のバージョンを付与することができます。製品にひとつだけ EXE ファイルが含まれるということであれば、製品バージョンとファイルバージョンが一致しても問題ありません。
製品バージョンとファイルバージョンが異なる事例として、製品に含まれる DLL ファイルに別のファイルバージョンを付与することや、製品スイートの製品バージョンと製品スイートに含まれる複数の EXE ファイルに別のファイルバージョンを付与することが考えられます。
そして、バイナリ形式のバージョン番号は4つの16ビット整数と定められているので、その形式に準拠する必要があります。文字列形式はそのようなルールがなく自由です。
例えば、セマンティックバージョニングを使用する場合、バイナリ形式は4桁目をゼロ埋めするなどで形式に合わせる必要がありますが、文字列形式はセマンティックバージョニングの表現をそのまま使用できます。つまり、バージョン番号をセマンティックバージョニングによる 1.0.0 とすると、製品が EXE ファイルひとつで構成される場合、ProductVersion と FileVersion は 1.0.0 になり、PRODUCTVERSION と FILEVERSION はゼロ埋めして 1, 0, 0, 0 になります。
製品名とオリジナルファイル名などの差異
製品名 (ProductName) とオリジナルファイル名 (OriginalFilename)、内部名 (InternalName)、ファイル説明 (FileDescription) の差異について説明します。
ProductName は製品名です。Contoso Office のような架空のオフィススイートであれば製品スイート名でよいです。
OriginalFilename はファイル作成時のファイル名で、パスを含まず、拡張子を含みます。ユーザーがファイル名を変更したとしても元のファイルが何であるかを判別できるようにこのファイル名が含まれています。Contoso Office のような架空のオフィススイートであれば、Writer.exe や Calc.exe などのような EXE ファイルがあり、それぞれが OriginalFilename として格納されます。
InternalName は内部名です。内部名がない場合は OriginalFilename から拡張子を取り除いたものを指定することがバージョン情報リソースの解説で示されています。
FileDescription はファイルの説明です。ただ、製品が EXE ファイルひとつで構成される場合、ProductName と同じでよさそうです。Contoso Office のような架空のオフィススイートであれば、それぞれの EXE ファイルに Contoso Office Writer や Contoso Office Calc などを指定することになります。
FileDescription に Win32 App: A sample Win32 desktop application というように記載したくなりますが、あくまでファイルの説明であり、製品の説明ではないので、このような記載は適切ではないようなので注意してください。
例えば、Win32 App という製品名で Win32App.exe をビルドする場合、ProductName と FileDescription は Win32 App で、OriginalFilename は Win32App.exe、InternalName は OriginalFilename から拡張子を取り除いた Win32App になり、それらをバージョン情報として EXE ファイルに追加するとよさそうです。
Win32 アプリケーションにバージョン情報リソースを追加する
Visual Studio 2026 で Win32 アプリケーションにアイコンリソースを追加するで実装したアプリケーションにバージョン情報リソースを追加します。
ソリューションエクスプローラーからリソースを追加します。

リソースの追加ダイアログからバージョン情報を新規作成します。

バージョン情報の言語を英語(米国)に修正します。これは、ほかのリソースの言語と統一するために設定しているので、ほかのリソースに合わせて設定してください。

バージョン情報リソースの各キーに、バージョン情報リソースを理解するで例示した値を入力します。

これで、Win32App.rc というリソースファイルが下記のように更新されます。
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON ICON "Win32App.ico"
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
IDR_CONTEXTMENU MENU
BEGIN
POPUP "ContextMenu"
BEGIN
MENUITEM "Exit", ID_CONTEXTMENU_EXIT
END
END
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Takuya Tsuchida"
VALUE "FileDescription", "Win32 App"
VALUE "FileVersion", "1.0.0"
VALUE "InternalName", "Win32App"
VALUE "LegalCopyright", "© 2026 Takuya Tsuchida"
VALUE "OriginalFilename", "Win32App.exe"
VALUE "ProductName", "Win32 App"
VALUE "ProductVersion", "1.0.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
ビルドして、エクスプローラーから実行ファイルのプロパティを確認すると下記のようにバージョン情報を表示できます。なお、ファイルバージョンはバイナリ形式を参照していて、製品バージョンは文字列形式を参照しているというのは、エクスプローラーの仕様のようです。

これで Win32 アプリケーションにバージョン情報リソースを追加できました。
Win32 アプリケーションでバージョン情報を表示する
通知アイコンのコンテキストメニューに About を追加して、メッセージボックスでバージョン情報に含まれる値を表示してみます。バージョン情報関数は癖が強いので、ひとつひとつの関数について公式ドキュメントも確認することを推奨します。
最初にメニューの第二階層に About を追加します。

これで resource.h と Win32App.rc が下記のように更新されます。
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Win32App.rc
//
#define IDR_CONTEXTMENU 101
#define IDI_ICON 102
#define ID_CONTEXTMENU_EXIT 40001
#define ID_CONTEXTMENU_ABOUT 40002
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 103
#define _APS_NEXT_COMMAND_VALUE 40003
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
IDR_CONTEXTMENU MENU
BEGIN
POPUP "ContextMenu"
BEGIN
MENUITEM "About", ID_CONTEXTMENU_ABOUT
MENUITEM "Exit", ID_CONTEXTMENU_EXIT
END
END
下記のコードで Win32App.cpp を更新します。
#include <windows.h>
#include <windowsx.h>
#include <format>
#include <vector>
#include "resource.h"
#pragma comment(lib, "Version.lib")
namespace
{
constexpr GUID NOTIFYICON_GUID = {
.Data1 = 0xE7DCA55E,
.Data2 = 0xBD60,
.Data3 = 0x4490,
.Data4 = {0x9B, 0x53, 0xF3, 0xA4, 0x69, 0xCC, 0xCC, 0x27},
};
constexpr auto WM_NOTIFYICON = WM_APP + 1;
// NOLINTNEXTLINE(bugprone-throwing-static-initialization)
const auto WM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated");
constexpr auto ERROR_APP = 0x20000000;
constexpr auto ERROR_APP_ALREADY_RUNNING = ERROR_APP + 1;
constexpr auto ERROR_NIM = 0x20010000;
constexpr auto ERROR_NIM_ADD = ERROR_NIM + NIM_ADD;
constexpr auto ERROR_NIM_DELETE = ERROR_NIM + NIM_DELETE;
constexpr auto ERROR_NIM_SETVERSION = ERROR_NIM + NIM_SETVERSION;
int AddNotifyIcon(const HWND hWnd)
{
NOTIFYICONDATA nid{
.cbSize = sizeof(NOTIFYICONDATA),
.hWnd = hWnd,
.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_GUID | NIF_SHOWTIP,
.uCallbackMessage = WM_NOTIFYICON,
.hIcon = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_ICON)),
.szTip = L"Win32 App",
.uVersion = NOTIFYICON_VERSION_4,
.guidItem = NOTIFYICON_GUID,
}; // NOLINT(clang-diagnostic-missing-designated-field-initializers)
if (!Shell_NotifyIcon(NIM_ADD, &nid)) return ERROR_NIM_ADD;
if (!Shell_NotifyIcon(NIM_SETVERSION, &nid)) return ERROR_NIM_SETVERSION;
return 0;
}
int DeleteNotifyIcon(const HWND hWnd)
{
NOTIFYICONDATA nid{
.cbSize = sizeof(NOTIFYICONDATA),
.hWnd = hWnd,
.uFlags = NIF_GUID,
.guidItem = NOTIFYICON_GUID,
}; // NOLINT(clang-diagnostic-missing-designated-field-initializers)
if (!Shell_NotifyIcon(NIM_DELETE, &nid)) return ERROR_NIM_DELETE;
return 0;
}
void ShowContextMenu(const HWND hWnd, const int x, const int y)
{
const auto hMenu = LoadMenu(nullptr, MAKEINTRESOURCE(IDR_CONTEXTMENU));
const auto hSubMenu = GetSubMenu(hMenu, 0);
SetForegroundWindow(hWnd);
TrackPopupMenu(hSubMenu, TPM_BOTTOMALIGN, x, y, 0, hWnd, nullptr);
}
std::wstring QueryFirstVersionTranslation(const std::vector<BYTE> &versionInfo)
{
struct Translation
{
WORD Language;
WORD CodePage;
};
LPVOID lpBuffer = nullptr;
UINT uLen = 0;
if (!VerQueryValue(versionInfo.data(), L"\\VarFileInfo\\Translation", &lpBuffer, &uLen)) return {};
const auto *translations = static_cast<const Translation *>(lpBuffer);
return std::format(L"{:04x}{:04x}", translations[0].Language, translations[0].CodePage);
}
std::wstring QueryVersionValue(const std::vector<BYTE> &versionInfo, const std::wstring_view translation,
const std::wstring_view name)
{
LPVOID lpBuffer = nullptr;
UINT uLen = 0;
const auto query = std::format(L"\\StringFileInfo\\{}\\{}", translation, name);
if (!VerQueryValue(versionInfo.data(), query.c_str(), &lpBuffer, &uLen)) return {};
return static_cast<const wchar_t *>(lpBuffer);
}
std::wstring GetVersionValue(const std::wstring_view name)
{
WCHAR fileName[MAX_PATH];
const auto dwLen = GetModuleFileName(nullptr, fileName, ARRAYSIZE(fileName));
if (dwLen == 0) return {};
if (dwLen == ARRAYSIZE(fileName)) return {};
const auto dwSize = GetFileVersionInfoSize(fileName, nullptr);
if (dwSize == 0) return {};
std::vector<BYTE> versionInfo(dwSize);
if (!GetFileVersionInfo(fileName, 0, dwSize, versionInfo.data())) return {};
const auto translation = QueryFirstVersionTranslation(versionInfo);
if (translation.empty()) return {};
return QueryVersionValue(versionInfo, translation, name);
}
void ShowAboutDialog(const HWND hWnd)
{
const auto productName = GetVersionValue(L"ProductName");
const auto productVersion = GetVersionValue(L"ProductVersion");
const auto legalCopyright = GetVersionValue(L"LegalCopyright");
const auto message = std::format(L"{} {}\r\n{}", productName, productVersion, legalCopyright);
MessageBox(hWnd, message.c_str(), L"About", MB_OK | MB_ICONINFORMATION);
}
LRESULT CALLBACK WndProc(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
if (const auto nExitCode = AddNotifyIcon(hWnd)) PostQuitMessage(nExitCode);
return 0;
case WM_DESTROY:
PostQuitMessage(DeleteNotifyIcon(hWnd));
return 0;
case WM_NOTIFYICON:
if (LOWORD(lParam) == WM_CONTEXTMENU) ShowContextMenu(hWnd, GET_X_LPARAM(wParam), GET_Y_LPARAM(wParam));
if (LOWORD(lParam) == NIN_SELECT)
{
NOTIFYICONDATA nid{
.cbSize = sizeof(NOTIFYICONDATA),
.hWnd = hWnd,
.uFlags = NIF_INFO | NIF_GUID,
.szInfo = L"Hello, World!",
.szInfoTitle = L"Win32 App",
.dwInfoFlags = NIIF_RESPECT_QUIET_TIME,
.guidItem = NOTIFYICON_GUID,
}; // NOLINT(clang-diagnostic-missing-designated-field-initializers)
Shell_NotifyIcon(NIM_MODIFY, &nid);
}
return 0;
case WM_COMMAND:
if (LOWORD(wParam) == ID_CONTEXTMENU_ABOUT) ShowAboutDialog(hWnd);
if (LOWORD(wParam) == ID_CONTEXTMENU_EXIT) DestroyWindow(hWnd);
return 0;
default:
if (uMsg == WM_TASKBARCREATED)
{
AddNotifyIcon(hWnd);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
} // namespace
int WINAPI wWinMain(_In_ const HINSTANCE hInstance, _In_opt_ HINSTANCE, _In_ LPWSTR, _In_ const int)
{
const WNDCLASS wc{
.lpfnWndProc = WndProc,
.hInstance = hInstance,
.lpszClassName = L"Win32AppMainWindow",
}; // NOLINT(clang-diagnostic-missing-designated-field-initializers)
const auto atomClass = RegisterClass(&wc);
if (!atomClass) return static_cast<int>(GetLastError());
if (FindWindow(MAKEINTATOM(atomClass), nullptr)) return ERROR_APP_ALREADY_RUNNING;
const auto hWnd =
CreateWindow(MAKEINTATOM(atomClass), L"Win32 App", 0, 0, 0, 0, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd) return static_cast<int>(GetLastError());
MSG msg{};
while (const auto bRet = GetMessage(&msg, nullptr, 0, 0))
{
if (bRet == -1) return static_cast<int>(GetLastError());
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return static_cast<int>(msg.wParam);
}
4~5行目で使用する C++ 標準ライブラリをインクルードしています。また、9行目でバージョン情報関数を使用できるよう Version.lib をリンカーの検索対象に指定しています。
#include <format>
#include <vector>
#pragma comment(lib, "Version.lib")
156行目で通知アイコンのコンテキストメニューで About をクリックしたときに ShowAboutDialog 関数を呼び出す処理を追加しています。
case WM_COMMAND:
if (LOWORD(wParam) == ID_CONTEXTMENU_ABOUT) ShowAboutDialog(hWnd);
if (LOWORD(wParam) == ID_CONTEXTMENU_EXIT) DestroyWindow(hWnd);
return 0;
117~124行目で About ダイアログを表示する ShowAboutDialog 関数を定義しています。GetVersionValue 関数でバージョン情報に含まれる値を取得し、メッセージボックスに表示する文字列を構築しています。
void ShowAboutDialog(const HWND hWnd)
{
const auto productName = GetVersionValue(L"ProductName");
const auto productVersion = GetVersionValue(L"ProductVersion");
const auto legalCopyright = GetVersionValue(L"LegalCopyright");
const auto message = std::format(L"{} {}\r\n{}", productName, productVersion, legalCopyright);
MessageBox(hWnd, message.c_str(), L"About", MB_OK | MB_ICONINFORMATION);
}
98~115行目でバージョン情報に含まれる値を取得する GetVersionValue 関数を定義しています。この関数では空文字列を戻り値とする簡易的なエラー処理をしています。GetModuleFileName 関数で現在のプロセスの実行ファイルのパスを取得します。GetFileVersionInfoSize 関数でバージョン情報のサイズを取得します。取得したサイズを使用して GetFileVersionInfo 関数でバージョン情報を取得します。そのバージョン情報を使用して QueryFirstVersionTranslation 関数で翻訳を取得し、バージョン情報と翻訳を使用して QueryVersionValue 関数でキーの値を取得して戻り値としています。
std::wstring GetVersionValue(const std::wstring_view name)
{
WCHAR fileName[MAX_PATH];
const auto dwLen = GetModuleFileName(nullptr, fileName, ARRAYSIZE(fileName));
if (dwLen == 0) return {};
if (dwLen == ARRAYSIZE(fileName)) return {};
const auto dwSize = GetFileVersionInfoSize(fileName, nullptr);
if (dwSize == 0) return {};
std::vector<BYTE> versionInfo(dwSize);
if (!GetFileVersionInfo(fileName, 0, dwSize, versionInfo.data())) return {};
const auto translation = QueryFirstVersionTranslation(versionInfo);
if (translation.empty()) return {};
return QueryVersionValue(versionInfo, translation, name);
}
70~85行目でバージョン情報に含まれる翻訳を取得する QueryFirstVersionTranslation 関数を定義しています。Translation 構造体は翻訳を表現する構造体です。VerQueryValue 関数で \VarFileInfo\Translation に含まれる複数の翻訳を取得し、その最初の翻訳を文字列にしたものを戻り値としています。
std::wstring QueryFirstVersionTranslation(const std::vector<BYTE> &versionInfo)
{
struct Translation
{
WORD Language;
WORD CodePage;
};
LPVOID lpBuffer = nullptr;
UINT uLen = 0;
if (!VerQueryValue(versionInfo.data(), L"\\VarFileInfo\\Translation", &lpBuffer, &uLen)) return {};
const auto *translations = static_cast<const Translation *>(lpBuffer);
return std::format(L"{:04x}{:04x}", translations[0].Language, translations[0].CodePage);
}
このバージョン情報リソースには「英語(米国)」の翻訳のみが含まれているので “040904b0” が戻り値になります。未検証ですが「日本語(日本)」の翻訳のみが含まれている状態でも問題なく動作すると思います。
87~96行目でバージョン情報に含まれる値を取得する QueryVersionValue 関数を定義しています。VerQueryValue 関数で \StringFileInfo\<translation>\<name> に含まれる値を取得して戻り値としています。
std::wstring QueryVersionValue(const std::vector<BYTE> &versionInfo, const std::wstring_view translation,
const std::wstring_view name)
{
LPVOID lpBuffer = nullptr;
UINT uLen = 0;
const auto query = std::format(L"\\StringFileInfo\\{}\\{}", translation, name);
if (!VerQueryValue(versionInfo.data(), query.c_str(), &lpBuffer, &uLen)) return {};
return static_cast<const wchar_t *>(lpBuffer);
}
ビルドして実行し、通知アイコンのコンテキストメニューにある About をクリックすると、下記のようなメッセージボックスが表示されます。

これで Win32 アプリケーションでバージョン情報を表示することができました。
本稿では、Win32 アプリケーションのバージョン情報について概観し、バージョン情報を実行ファイルに追加し、そのバージョン情報をアプリケーションで表示する方法を説明しました。次回は、インストーラーを作成する Visual Studio Installer Projects の記事を書こうと思います。
