C#获得系统内存占用情况

小柊 发表于 2015年08月03日 21时46分25秒

这几天想做一个可以监视服务器状态的程序,所以需要从服务器上获得服务器当前的CPU使用率、内存与磁盘的使用情况。

磁盘是最简单的,.net框架为我们提供了System.IO.DriveInfo类库,使得我们可以轻松的获得各个磁盘空间使用情况,而CPU使用率.net框架也有一个System.Diagnostics.PerformanceCounter类库来服务我们,虽然使用这个类库获得CPU使用率会有明显的卡顿,但也无伤大雅,反正程序也是后台静默运行,程序卡一下也没人会发现。但是内存使用率这个数据的获取就有点麻烦了,翻了翻.net的几个大类库,也没发现什么有关的。

想到之前曾经用Visual Basic 6.0 写过有个类似的程序,所以就去翻原来的程序代码,发现原来是用一个GlobalMemoryStatus的API去完成这个任务的,拿着这个API的名字去百度一找,发现了不少源码。

注意以下代码为错误代码!

[source language="csharp"]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
class Program
{
#region 获得内存信息API
[DllImport("kernel32")]
public static extern void GlobalMemoryStatus(ref MEMORY_INFO meminfo);

//定义内存的信息结构
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_INFO
{
public uint dwLength;
public uint dwMemoryLoad;//正在使用
public uint dwTotalPhys;//物理内存大小
public uint dwAvailPhys;//可使用的物理内存
public uint dwTotalPageFile;//交换文件总大小
public uint dwAvailPageFile;
public uint dwTotalVirtual;//总虚拟内存
public uint dwAvailVirtual;
}
#endregion

static void Main(string[] args)
{
Console.Title = "小柊的笔记";
Console.WriteLine("MemoryStatus:");
Console.WriteLine("{0}/{1}", FormatSize(GetAvailPhys()), FormatSize(GetTotalPhys()));
Console.ReadKey();
}

#region 获得当前可用物理内存大小 + private static uint GetAvailPhys()
/// <summary>
/// 获得当前可用物理内存大小
/// </summary>
/// <returns>当前可用物理内存(B)</returns>
private static uint GetAvailPhys()
{
MEMORY_INFO mi = new MEMORY_INFO();
GlobalMemoryStatus(ref mi);
return mi.dwAvailPhys;
}
#endregion

#region 获得当前已使用的内存大小 + private static ulong GetUsedPhys()
/// <summary>
/// 获得当前已使用的内存大小
/// </summary>
/// <returns>已使用的内存大小(B)</returns>
private static ulong GetUsedPhys()
{
MEMORY_INFO mi = new MEMORY_INFO();
GlobalMemoryStatus(ref mi);
return (mi.dwTotalPhys - mi.dwAvailPhys);
}
#endregion

#region 获得当前总计物理内存大小 + private static uint GetTotalPhys()
/// <summary>
/// 获得当前总计物理内存大小
/// </summary>
/// <returns>总计物理内存大小(B)</returns>
private static uint GetTotalPhys()
{
MEMORY_INFO mi = new MEMORY_INFO();
GlobalMemoryStatus(ref mi);
return mi.dwTotalPhys;
}
#endregion

#region 格式化容量大小 + private static string FormatSize(double size)
/// <summary>
/// 格式化容量大小
/// </summary>
/// <param name="size">容量(B)</param>
/// <returns>已格式化的容量</returns>
private static string FormatSize(double size)
{
double d = (double)size;
int i = 0;
while ((d > 1024) && (i < 5))
{
d /= 1024;
i++;
}
string[] unit = { "B", "KB", "MB", "GB", "TB" };
return (string.Format("{0} {1}", Math.Round(d, 2), unit[i]));
}
#endregion
}
}
[/source]

找到一样的源码那当然是开开心心的复制粘贴到Visual Studio里了。结果一跑就出问题了,程序崩溃了,连Visual Studio都没有捕获到异常,直接崩溃了:

经过调试之后,发现当程序运行到GlobalMemoryStatus(ref mi);这一句的时候崩溃了,琢磨着会不会是因为结构体里的uint类型太小了,导致溢出异常,于是就把结构体里的uint类型全部改成了long类型。

注意以下代码为错误代码!

[source language="csharp"]
#region 获得内存信息API
[DllImport("kernel32")]
public static extern void GlobalMemoryStatus(ref MEMORY_INFO meminfo);

//定义内存的信息结构
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_INFO
{
public long dwLength;
public long dwMemoryLoad;//正在使用
public long dwTotalPhys;//物理内存大小
public long dwAvailPhys;//可使用的物理内存
public long dwTotalPageFile;//交换文件总大小
public long dwAvailPageFile;
public long dwTotalVirtual;//总虚拟内存
public long dwAvailVirtual;
}
#endregion
[/source]

F5运行之,程序跑是跑起来了,也获得了结果,但这个结果怎么看都觉得奇怪:

可用内存11.86G,总内存3.8G?!这什么鬼?!为什么可用内存比总内存还大?!当前开发用环境可是有8G的内存啊!

卧槽这玩意儿在嫖我呢?!

照理说微软提供的API应该没啥问题的啊,那为什么会获得一个错误的结果呢?经过一番寻找我在MSDN的帮助文档里找到了线索:

这段英文的意思翻译成中文的意思差不多就是:

对于内存超过4 GB的计算机,GlobalMemoryStatus函数可能会返回不正确的信息,报告值为-1,表明溢出。出于这个原因,应用程序应使用GlobalMemoryStatusEx函数代替。

很明显,在这里我电脑的8G内存已经超过了GlobalMemoryStatus函数的上限,函数错误溢出,所以获得了错误的结果。

那么好吧,就转用那个GlobalMemoryStatusEx函数好了……

结果百度上找了一圈,就没什么C#调用GlobalMemoryStatusEx函数的案例,全是C++和C的,我真是日了狗了,这还玩个毛线……

不过都走到这一步了,现在说放弃有点可惜,抱着死马当活马医的心态,根据MSDN的文档,自己写了一遍API调用代码,代码如下:

[source language="csharp"]
using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication3
{
class Program
{
#region 获得内存信息API
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GlobalMemoryStatusEx(ref MEMORY_INFO mi);

//定义内存的信息结构
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_INFO
{
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; //保留 这个值始终为0
}
#endregion

static void Main(string[] args)
{
Console.Title = "小柊的笔记";
Console.WriteLine("MemoryStatus:");
Console.WriteLine("{0}/{1}", FormatSize(GetAvailPhys()), FormatSize(GetTotalPhys()));
Console.ReadKey();
}

#region 格式化容量大小 + private static string FormatSize(double size)
/// <summary>
/// 格式化容量大小
/// </summary>
/// <param name="size">容量(B)</param>
/// <returns>已格式化的容量</returns>
private static string FormatSize(double size)
{
double d = (double)size;
int i = 0;
while ((d > 1024) && (i < 5))
{
d /= 1024;
i++;
}
string[] unit = { "B", "KB", "MB", "GB", "TB" };
return (string.Format("{0} {1}", Math.Round(d, 2), unit[i]));
}
#endregion

#region 获得当前内存使用情况 + private static MEMORY_INFO GetMemoryStatus()
/// <summary>
/// 获得当前内存使用情况
/// </summary>
/// <returns></returns>
private static MEMORY_INFO GetMemoryStatus()
{
MEMORY_INFO mi = new MEMORY_INFO();
mi.dwLength = (uint)System.Runtime.InteropServices.Marshal.SizeOf(mi);
GlobalMemoryStatusEx(ref mi);
return mi;
}
#endregion

#region 获得当前可用物理内存大小 + private static ulong GetAvailPhys()
/// <summary>
/// 获得当前可用物理内存大小
/// </summary>
/// <returns>当前可用物理内存(B)</returns>
private static ulong GetAvailPhys()
{
MEMORY_INFO mi = GetMemoryStatus();
return mi.ullAvailPhys;
}
#endregion

#region 获得当前已使用的内存大小 + private static ulong GetUsedPhys()
/// <summary>
/// 获得当前已使用的内存大小
/// </summary>
/// <returns>已使用的内存大小(B)</returns>
private static ulong GetUsedPhys()
{
MEMORY_INFO mi = GetMemoryStatus();
return (mi.ullTotalPhys - mi.ullAvailPhys);
}
#endregion

#region 获得当前总计物理内存大小 + private static ulong GetTotalPhys()
/// <summary>
/// 获得当前总计物理内存大小
/// </summary>
/// <returns&amp;gt;总计物理内存大小(B)&amp;lt;/returns&amp;gt;
private static ulong GetTotalPhys()
{
MEMORY_INFO mi = GetMemoryStatus();
return mi.ullTotalPhys;
}
#endregion
}
}
[/source]

F5执行之,惊喜的发现程序不仅可以运行,还可以正确的获得内存信息!

P.S.:这边显示总内存只有7.86G并不是函数问题,而是Win7有一部分内存为硬件保留,所以对系统不可见:

(8G*1024-139)/1024=7.8642578125≈7.86G

也终于是把C#调用GlobalMemoryStatusEx函数写好了

由于WinAPI是非托管代码,所以微软并不推荐开发者在.net Framework中使用WinAPI,而有一些不怎么常用的功能在.net框架中并没有被提供,这时候就需要去调用WinAPI。当各位读者在以后的编程学习中发现有需要的API在网上找不到对应的开发资料时,可以去查阅微软的MSDN,查看微软对此API的定义,自己动手写代码,很多原以为很麻烦的事情,当你真正去做的时候你会发现其实也就是这么一回事儿。Good lucky!

 

 

小柊

2015年8月3日 23:06:01

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注