構造体のポインタを引数にとるWindows APIの呼び出し - C#

C#で構造体のポインタを引数にとるWindows APIを呼び出したいことがあります。
構造体のポインタを引数にとるWindows APIの呼び出し方法を紹介します。
今回は、システム時刻を取得するGetSystemTimeとシステム時刻を設定するSetSystemTime APIを呼び出します。

コード例

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace GetSetSystemTimeDemo
{
  public partial class FormMain : Form
  {
    // SYSTEM TIME 
    // SYSTEMTIME構造体
    [StructLayout(LayoutKind.Sequential)]
    public struct SystemTime
    {
      public Int16 wYear;
      public Int16 wMonth;
      public Int16 wDayOfWeek;
      public Int16 wDay;
      public Int16 wHour;
      public Int16 wMinute;
      public Int16 wSecond;
      public Int16 wMilliseconds;
    }

    [DllImport("Kernel32.dll", EntryPoint = "GetSystemTime")]
    static extern void GetSystemTime(IntPtr lpSystemTime);

    [DllImport("Kernel32.dll", EntryPoint = "SetSystemTime")]
    static extern bool SetSystemTime(IntPtr lpSystemTime);

    public FormMain()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      SystemTime st = GetSystemTime();
      textBox1.Text = string.Format("{0:d}-{1:d}-{2:d}", st.wYear, st.wMonth, st.wDay);
    }
    
    private void button3_Click(object sender, EventArgs e)
    {
      DateTime now = DateTime.Now;
      SystemTime st = new SystemTime();
 
      st.wDay = 1;
      st.wMonth = 1;
      st.wYear = 2011;
      st.wHour = 0;
      st.wMinute = 0;
      st.wSecond = 0;

      SetSystemTimeSimple(st);
     }

    public SystemTime GetSystemTime()
    {
      SystemTime sysTime = new SystemTime();
      IntPtr sysTimePtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(sysTime));
      GetSystemTime(sysTimePtr);
      sysTime = (SystemTime)Marshal.PtrToStructure(sysTimePtr, sysTime.GetType());
      Marshal.FreeCoTaskMem(sysTimePtr);
      return sysTime;
    }

    public void SetSystemTimeSimple(SystemTime sysTime)
    {
      IntPtr sysTimePtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(sysTime));
      Marshal.StructureToPtr(sysTime, sysTimePtr, false);
      bool ret = SetSystemTime(sysTimePtr);
      Marshal.FreeCoTaskMem(sysTimePtr);
    }
  }
}

解説

[StructLayout(LayoutKind.Sequential)]
public struct SystemTime
{
  public Int16 wYear;
  public Int16 wMonth;
  public Int16 wDayOfWeek;
  public Int16 wDay;
  public Int16 wHour;
  public Int16 wMinute;
  public Int16 wSecond;
  public Int16 wMilliseconds;
}
にてSystemTime構造体を定義しています。メンバが記述順にメモリに配置されるように [StructLayout(LayoutKind.Sequential)]属性を追加しておきます。

[DllImport("Kernel32.dll", EntryPoint = "GetSystemTime")]
static extern void GetSystemTime(IntPtr lpSystemTime);
 
[DllImport("Kernel32.dll", EntryPoint = "SetSystemTime")]
static extern bool SetSystemTime(IntPtr lpSystemTime);
にて、GetSystemTime, SetSystemTime APIを定義します。

public SystemTime GetSystemTime() メソッド

SystemTime sysTime = new SystemTime();
SystemTime構造体を宣言しnewによりメモリ確保します。

IntPtr sysTimePtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(sysTime));
GetSystemTime()APIに与えるポインタを宣言し、Marshal.AllocCoTaskMem()メソッドによりメモリを確保します。メモリ確保するサイズはSystemTime構造体と同じサイズにします。Marshal.SizeOf()メソッドでサイズを求めます。

GetSystemTime(sysTimePtr);
GetSystemTime() APIを呼び出します。

sysTime = (SystemTime)Marshal.PtrToStructure(sysTimePtr, sysTime.GetType());
sysTimePtrはIntPtr型のためそのままでは実体データにアクセスできません。Marshal.PrtToStructure()メソッドを用いてsysTimePtrのデータをSystemTime構造体のsysTimeにコピーします。

Marshal.FreeCoTaskMem(sysTimePtr);
Marshal.FreeCoTaskMem()メソッドを用い確保したメモリを開放します。

return sysTime;
sysTimeを戻り値として返します。

public SystemTime SetSystemTime() メソッド

IntPtr sysTimePtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(sysTime));
SetSystemTime()APIに与えるポインタを宣言し、Marshal.AllocCoTaskMem()メソッドによりメモリを確保します。メモリ確保するサイズはSystemTime構造体と同じサイズにします。Marshal.SizeOf()メソッドでサイズを求めます。

Marshal.StructureToPtr(sysTime, sysTimePtr, false);
Marshal.StructureToPtr()メソッドを用い、引数で与えられたsysTime SystemTime構造体のデータをメモリ確保したsysTimePtrにコピーします。

bool ret = SetSystemTime(sysTimePtr);
SetSystemTime()APIを呼び出します。

Marshal.FreeCoTaskMem(sysTimePtr);
メモリ確保したsysTimePtrを開放します。

※注意

Windows NT, Windows Vista, Windows 7 などのNT系のOSではSetSystemTime() APIを呼び出しただけではシステム時間の変更はできません。システム時間を変更するには、APIを呼び出す前にSE_SYSTEMTIME_NAME 特権を有効にする必要があります。特権の取得と有効化はこちらの記事にて紹介しています。


著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2011-01-06
iPentec all rights reserverd.