C#でRAM合計容量(GB)を正しく計算する方法|Win32_PhysicalMemoryのCapacityを合算する理由とサンプルコード

Windows / .NET で「搭載している RAM の合計容量(GB)」を取得しようとしたとき、WMI の Win32_PhysicalMemory をそのまま読んだだけでは思った値にならないことがあります。本記事では「Capacity を合算する必要があるのか?」という疑問に答えつつ、C# での安全なコード例や、代替手段・ハマりどころまでまとめて解説します。

目次

Win32_PhysicalMemory の Capacity は「1枚あたり」なので合計が必要

まず結論から言うと、WMI の Win32_PhysicalMemory クラスの Capacity プロパティは、物理メモリモジュール(DIMM)1枚ごとの容量 です。そのため、PC に複数枚のメモリが載っている環境では、各モジュールの Capacity をすべて足し合わせて、合計容量を求める必要があります。

イメージしやすいように、デスクトップ PC でよくある構成を表にしてみます。

スロットCapacity(バイト)人間が見る容量
DIMM185899345928 GB クラス
DIMM285899345928 GB クラス
DIMM385899345928 GB クラス
DIMM485899345928 GB クラス
合計34359738368約 32 GB

もし、最初の 1レコードだけを読んで「これが合計だろう」と勘違いしてしまうと、32 GB マシンを 8 GB と誤認識してしまいます。このようなバグを防ぐために、以下のようなロジックが必要です。

  • Win32_PhysicalMemory から Capacity を列挙
  • 各レコードの Capacity を 合計(totalCapacity += capacity;) する
  • 最後に GB / GiB など任意の単位に変換して表示

C# / WMI で RAM 合計容量(GiB)を安全に取得するコード例

ここからは、実際の C# コードで RAM 合計容量を取得する安全な実装例を紹介します。

Win32_PhysicalMemory から合計容量を計算するヘルパーメソッド

以下は、Win32_PhysicalMemory の Capacity を合算し、GiB(2 の 30 乗バイト)単位の整数値を返すヘルパーメソッドです。


using System;
using System.Management; // System.Management 参照が必要(.NET Framework / .NET 5+ の場合は NuGet)

public static class MemoryHelper
{
    public static ulong GetTotalMemoryGiB_Wmi()
    {
        try
        {
            ulong totalBytes = 0UL;

            using (var searcher = new ManagementObjectSearcher(
                       "SELECT Capacity FROM Win32_PhysicalMemory"))
            using (var results = searcher.Get())
            {
                foreach (ManagementObject mo in results)
                {
                    if (mo["Capacity"] != null &&
                        UInt64.TryParse(mo["Capacity"].ToString(), out var cap))
                    {
                        // ★ 複数モジュール分をきちんと合算するのがポイント
                        totalBytes += cap;
                    }
                }
            }

            // GiB(2^30 バイト)に変換して整数値で返す
            return totalBytes / (1024UL * 1024UL * 1024UL);
        }
        catch (Exception ex)
        {
            // ログを出すなど、プロジェクトの方針に合わせてハンドリング
            // Logger.Error("GetTotalMemoryGiB_Wmi failed.", ex);
            return 0;
        }
    }
}

コードのポイント解説

  • ulong 型を使う
    物理メモリ容量は数十 GB ~ 数百 GB に達することもあるため、符号なし 64bit 整数(ulong)を使うのが安全です。32bit の int にキャストしてしまうと簡単にオーバーフローします。
  • 1024 は 1024UL と書く
    totalBytes が ulong なので、割り算側も UL(ulong リテラル)にしておくと、型の不一致によるコンパイルエラーや不必要なキャストを避けられます。
  • using で WMI リソースを解放
    ManagementObjectSearcher や ManagementObjectCollection はどちらも IDisposable なので、using を使ってクリーンアップするのがベストプラクティスです。
  • Capacity が null の可能性を考慮
    何らかの理由で WMI 情報が取り切れない場合、null が返ってくることがあるため、null チェック + TryParse で安全にパースしています。

GiB・GB を好きな形式で表示するサンプル

アプリケーション側で「n.n GB」のような表示をしたいケースも多いと思います。以下は、GiB と GB の両方を計算して文字列を作る例です。


public static string FormatMemorySize(ulong totalBytes)
{
    // 2 進接頭辞(GiB)
    double gib = totalBytes / 1024d / 1024d / 1024d;

    // 10 進接頭辞(GB)
    double gb = totalBytes / 1000d / 1000d / 1000d;

    return string.Format(
        "{0:0.0} GiB(約 {1:0.0} GB)",
        gib, gb);
}

// 利用例
public static void PrintTotalMemory()
{
    ulong totalBytes = 0UL;

    // さきほどの GetTotalMemoryGiB_Wmi を少し書き換えて、
    // バイト単位で返すメソッドを用意しておくと使い回ししやすい
    totalBytes = GetTotalMemoryBytes_Wmi();

    Console.WriteLine("RAM 合計: " + FormatMemorySize(totalBytes));
}

GUI アプリであれば、この文字列をラベルやテキストボックスにそのまま流用できますし、ログや診断情報として残しておくのにも便利です。

GB と GiB の違いと、Windows 表示との付き合い方

RAM 容量を表示するときに意外とややこしいのが、「GB」と「GiB」の違いです。ざっくり言うと、「2 の何乗で割るか」「10 の何乗で割るか」の違いです。

単位意味計算式用途の例
GBギガバイト(10 進)1 GB = 109 バイトメーカー表記、ストレージ容量など
GiBギビバイト(2 進)1 GiB = 230 バイトOS 内部、RAM 容量の実測値など

Windows のタスク マネージャーやシステムの「搭載メモリ(RAM)」表示は、実質的には GiB ベース で表示されることが多いですが、表記上は「GB」と書かれていることもあります。そのため、

  • 内部処理:GiB を基準(1024^3 で割る)
  • ユーザー向けの説明やスペック表:GB も併記してあげる

というスタイルにしておくと、ユーザーから「タスク マネージャーの数字と違う」「スペックシートと一致しない」といった問い合わせを減らせます。

「合計だけ分かればよい」場合の、より簡単・高速な方法

ここまで Win32_PhysicalMemory による詳細取得を紹介しましたが、「総量さえ分かればよい」「速度重視で行きたい」というケースでは、もっとシンプルな API を使うこともできます。

Win32_ComputerSystem.TotalPhysicalMemory を使う

同じく WMI ですが、Win32_ComputerSystem クラスの TotalPhysicalMemory プロパティには、すでに合計バイト数が格納されています。ですので、合算処理を自分で書く必要はありません。


using System;
using System.Management;

public static class MemoryHelper
{
    public static ulong GetTotalMemoryBytes_WmiSimple()
    {
        try
        {
            ulong totalBytes = 0UL;

            using (var searcher = new ManagementObjectSearcher(
                       "SELECT TotalPhysicalMemory FROM Win32_ComputerSystem"))
            using (var results = searcher.Get())
            {
                foreach (ManagementObject mo in results)
                {
                    if (mo["TotalPhysicalMemory"] != null &&
                        UInt64.TryParse(mo["TotalPhysicalMemory"].ToString(), out var bytes))
                    {
                        totalBytes = bytes;
                        break; // 1 レコードで十分なので抜ける
                    }
                }
            }

            return totalBytes;
        }
        catch
        {
            return 0;
        }
    }
}

合計値だけが欲しい場面では、この方法がコードも短く、読みやすくなります。ただし、メモリスロットごとの詳細情報(メーカー名、速度、フォームファクタなど)を取得したい場合は、引き続き Win32_PhysicalMemory の利用が必要です。

GlobalMemoryStatusEx(P/Invoke)で高速に取得する

「WMI が遅くて困る」「サービスや常駐ツールで短い間隔で呼びたい」といった場合は、Win32 API の GlobalMemoryStatusEx を P/Invoke で呼び出す方法も有効です。これは WMI より軽量で、高速に結果を返してくれます。


using System;
using System.Runtime.InteropServices;

public static class NativeMemoryHelper
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct MEMORYSTATUSEX
    {
        public uint dwLength;
        public uint dwMemoryLoad;
        public ulong ullTotalPhys;
        public ulong ullAvailPhys;
        public ulong ullTotalPageFile;
        public ulong ullAvailPageFile;
        public ulong ullTotalVirtual;
        public ulong ullAvailVirtual;
        public ulong ullAvailExtendedVirtual;
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);

    public static ulong GetTotalMemoryBytes()
    {
        var status = new MEMORYSTATUSEX();
        status.dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));

        if (!GlobalMemoryStatusEx(ref status))
        {
            // 必要に応じて Win32 エラーを取得してログに出す
            return 0;
        }

        return status.ullTotalPhys; // 合計物理メモリ(バイト)
    }
}

この方法では、合計物理メモリだけでなく、使用率やページファイルサイズなども同時に取得できるため、モニタリングツールや性能監視系アプリケーションとの相性も良好です。

Microsoft.VisualBasic.Devices.ComputerInfo を使う簡単な方法

もうひとつのお手軽な方法として、Microsoft.VisualBasic 名前空間の ComputerInfo クラスを利用する手もあります。名前は VB ですが、C# からも普通に使用できます。


using Microsoft.VisualBasic.Devices;

public static class MemoryHelper
{
    public static ulong GetTotalMemoryBytes_ComputerInfo()
    {
        var info = new ComputerInfo();
        return info.TotalPhysicalMemory; // ulong
    }
}

プロジェクトに Microsoft.VisualBasic アセンブリの参照を追加するだけで使えるので、WMI や P/Invoke に踏み込みたくない場合に便利です。

よくあるハマりどころとチェックポイント

RAM 合計容量を取得するコードを書いたあとで、実際の端末で試すと「数字が微妙に違う」「ゼロになってしまう」といったトラブルに遭遇することがあります。ここでは、実務でよく見るハマりどころを整理しておきます。

1. totalCapacity += capacity; を書き忘れる・1 レコード目だけ見てしまう

最も多いのが、Win32_PhysicalMemory から最初の 1 件だけ取り出し、それを「合計」と思い込んでしまうパターンです。Windows ノート PC では 1 枚構成も多いため、手元の検証機では気付きにくく、デスクトップ PC やサーバーにデプロイした途端に「想定と違う容量が表示される」ということになりがちです。

必ず以下を徹底しましょう。

  • foreach で全レコードを列挙する
  • 各レコードの Capacity を 足し合わせる(totalCapacity += capacity;)
  • 最終的な totalCapacity を、GB / GiB に変換して利用する

2. WMI が遅い・時々取得に失敗する

WMI は非常に便利ですが、環境によってはクエリが重かったり、サービス状態によって一時的な失敗が発生することがあります。特に、

  • 起動直後のタイミング(まだ各種サービスが安定していない)
  • グループポリシーやセキュリティ対策ソフトの影響が強い環境
  • 多数の WMI クエリを同時に投げているツールが存在する環境

では、タイムアウトや例外が発生しやすくなります。対策としては、

  • アプリ起動時などに1 回だけ取得してキャッシュする
  • 取得失敗時は再試行するか、別の API(GlobalMemoryStatusEx など)にフォールバックする
  • ユーザーからの問い合わせ時にログで原因が追えるよう、例外内容をきちんと記録しておく

といった工夫が有効です。

3. 32bit / 64bit の違いとオーバーフロー

メモリ容量自体は 32bit / 64bit によって変わりませんが、変数の型を雑に決めるとオーバーフロー の原因になります。典型的なアンチパターンは次のようなものです。


// NG 例:int にキャストしてしまう
int totalBytes = 0;

foreach (ManagementObject mo in results)
{
    totalBytes += (int)(UInt64.Parse(mo["Capacity"].ToString()));
}

数十 GB 程度でも簡単に int の上限を超えてしまうため、結果がマイナスになったり異常な値になったりします。必ず ulong など 64bit の型を使用しましょう。

4. OS が認識している容量と「実際に使える容量」が違う

ユーザーから見ると、「16 GB 積んでいるのに、システム情報だと 15.8 GB と表示される」といった現象もあります。これは、

  • BIOS / UEFI による予約領域
  • オンボード GPU によるメモリ共有
  • 一部デバイス用に確保されたアドレス空間

などが原因で、OS から見た「利用可能メモリ」がハードウェア上の「物理搭載メモリ」より少なくなることがあるためです。

このため、以下のような表現でユーザーに説明すると誤解が少なくなります。

  • 「物理的に搭載されているメモリ容量」:Win32_PhysicalMemory の合算
  • 「OS から見える合計メモリ容量」:GlobalMemoryStatusEx や ComputerInfo からの値

実運用で役立つ Tips:キャッシュ・ログ・テスト

最後に、実際の業務アプリケーションや社内ツールに組み込む際に役立つ小技をいくつか紹介します。

アプリ起動時に 1 度だけ測定してキャッシュする

メモリモジュールの構成は、基本的に PC を開けて差し替えるまで変わりません。そのため、アプリケーションのライフタイム中に何度も計測し直す必要はまずなく、

  • アプリケーション起動時
  • サービス開始時
  • 「診断情報を更新」ボタンが押されたとき

など、タイミングを決めて 1 回だけ取得し、結果をキャッシュしておくと無駄がありません。WMI が重い環境でも、ユーザー体感を悪化させずに済みます。

ログに「取得元」と「単位」を合わせて残しておく

障害調査や問い合わせ対応では、

  • どの API を使ってメモリ容量を取得したのか(Win32_PhysicalMemory / TotalPhysicalMemory / GlobalMemoryStatusEx など)
  • ログに出している単位が「バイト」なのか「GiB」なのか

があいまいだと、後から解析するときに混乱の元になります。例えば次のように、ログ出力をラップする関数を用意するのも一案です。


public static void LogTotalMemory(ulong totalBytes, string source)
{
    double gib = totalBytes / 1024d / 1024d / 1024d;

    Console.WriteLine(
        "[MEMORY] Source={0}, Bytes={1}, GiB={2:0.00}",
        source, totalBytes, gib);
}

このようにしておけば、「この値は WMI の Win32_PhysicalMemory 合算から出したものか?」「GlobalMemoryStatusEx の値か?」といった判断がしやすくなります。

テスト環境では「スロット数が違うマシン」で動作確認する

Win32_PhysicalMemory の合算ロジックを実装したら、可能であれば次のような複数パターンで動作確認しておくと安心です。

テスト環境メモリ構成例確認したいポイント
ノート PC8 GB × 11 枚構成で正しく 8 GB(前後)になるか
デスクトップ PC8 GB × 2合計 16 GB(前後)になるか
開発用サーバー16 GB × 4大容量環境でもオーバーフローせずに表示できるか

特に、普段ノート PC でのみ開発していると「2 枚以上の構成」をテストし忘れがちです。可能であれば CI などに簡易的なチェックを組み込んでおくと、「1 枚目だけ見てしまうバグ」を未然に防ぎやすくなります。

まとめ:Win32_PhysicalMemory の Capacity は必ず合算する

本記事の要点をまとめると、次のとおりです。

  • Win32_PhysicalMemory の Capacity は DIMM 1 枚ごとの容量であり、PC 全体の合計ではない。
  • PC に複数枚のメモリモジュールが搭載されることを前提に、各 Capacity をすべて合算(totalCapacity += capacity;)する必要がある。
  • C# では ulong を用い、1024UL * 1024UL * 1024UL で割ることで GiB を求められる。
  • 合計値だけ欲しい場合は、Win32_ComputerSystem.TotalPhysicalMemory や GlobalMemoryStatusEx、ComputerInfo などの代替手段も有効。
  • GB と GiB の違いや、OS が認識している容量と実用容量の差異を理解しておくと、ユーザーへの説明やトラブルシュートがスムーズになる。

以上を踏まえれば、提示されたようなコードの totalCapacity += capacity; は正しい実装であり、むしろ「必須の一行」と言って良い部分です。複数モジュール構成を前提にした堅牢な実装にしておき、どのような構成の PC でも正しく RAM 合計容量を取得・表示できるようにしておきましょう。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次