Win32 APIやDLL関数を呼び出すには?.NET TIPS

求める機能が.NET Frameworkのクラス・ライブラリに存在しない場合、その代わりに、.NETプログラムからWindowsシステムのAPIであるWin32 APIを直接呼び出せる。C#およびVB.NETでの実現方法を解説する。

» 2003年05月09日 05時00分 公開
[泉祐介デジタルアドバンテージ]
「.NET TIPS」のインデックス

連載目次

 .NET Frameworkのクラス・ライブラリには、アプリケーション構築用として非常に多くの機能が用意されている。しかし、WindowsシステムのAPIであるWin32 APIの機能のすべてが、クラス・ライブラリとして用意されているわけではない。このような機能を.NETプログラムから使用する必要がある場合には、ここで紹介する方法によりWin32 APIを直接呼び出すことができる。また、.NET以前の環境でDLLファイルとして作成されたライブラリも同様に呼び出し可能である。

 .NET Framework上で動作するプログラムから、従来形式の(.NETアセンブリなどではない)DLLファイルがエクスポートしている関数を呼び出すには、DllImport属性(System.Runtime.InteropServices名前空間)を利用し、その関数が外部にあることをあらかじめ宣言しておけばよい。これは、Win32 APIを呼び出す場合にも同様である。

 例えば、マシンに内蔵されているブザー(高機能なサウンド・デバイスではない)からビープ音を鳴らすBeepというWin32 APIがWindowsには用意されている。このAPIの書式は次のとおりである(パラメータなどの詳細についてはMSDNドキュメントを参照)。

BOOL Beep(DWORD dwFreq, DWORD dwDuration);



 このAPIをC#で利用するには、適当なクラスの内部に以下のような宣言を加えればよい。

[DllImport("kernel32.dll")]
extern static bool Beep(uint dwFreq, uint dwDuration);


 この宣言により、通常のメソッドと同様に、プログラム内からBeep関数を呼び出すことができるようになる。

 DllImport属性は、プログラムから利用する関数をエクスポートしているDLLのファイル名を指定するための属性である。Win32 APIに対するDLLファイル名は、MSDNドキュメントにある各APIの解説で、「インポート ライブラリ」という項目に記述されたファイル名から知ることができる。具体的には、kernel32.libと記述されていればkernel32.dll、user32.libであればuser32.dll、gdi32.libであればgdi32.dllとなる。一般に、ほとんどの場合はインポート・ライブラリの欄に記述されたファイル名の拡張子をdllに変更したものを指定すればよい。先のBeep関数は、ドキュメントによればインポート・ライブラリはkernel32.libであるため、DllImport属性のパラメータとしてkernel32.dllを指定している。

 また、DllImport属性を付けたWin32 APIやDLL関数の宣言では、関数の実体が外部にあることを表すextern修飾子と、静的なメンバであることを表すstatic修飾子を必ず指定する。なお、ここではpublicやprivateなどのアクセス修飾子は省略したが、必要であれば指定することもできる。

 BOOLやDWORDといったパラメータや戻り値の型に関しては、対応するC#の型を指定する必要がある。例えば、前出の例ではBOOLに対してbool、DWORDに対してuintを指定している。主要なWin32 APIでの型名と、それに対応するC#の型は以下の表のようになっている。もちろん、この対応表はWin32 APIではない一般的なDLL関数に対しても利用できる。

APIでの型名
(括弧内は対応するC言語の型)
対応するC#の型
(括弧内は.NET Frameworkでの型名)
HANDLE (void *) System.IntPtr
BYTE (unsigned char) byte (System.Byte)
SHORT (short) short (System.Int16)
WORD (unsigned short) ushort (System.UInt16)
INT (int)
LONG (long)
int (System.Int32)
UINT (unsigned int) 
DWORD, ULONG (unsigned long)
uint (System.UInt32)
BOOL (long) bool (System.Boolean)
CHAR (char) char (System.Char)
LPSTR (char *)
LPWSTR (wchar_t *)
System.Text.StringBuilder
LPCSTR (const char *)
LPCWSTR (const wchar_t *)
string (System.String)
FLOAT (float) float (System.Single)
DOUBLE (double) double (System.Double)
Win32 APIでの型名と対応するC#の型
WindowsのDLL(Win32 API)と.NET Frameworkとでは型の管理方法が違うため、実際には型の相互変換(マーシャリング)が行われる。なお、BOOL型の実体はLONG型と同じなので、boolの代わりにintを指定することも可能である。

 Win32 APIの具体的な利用例として、Beep関数を利用したサンプル・プログラムを1つ挙げておこう。以下のリストは、マシン内蔵のスピーカから「ドレミファソラシド」を鳴らすプログラムである。マシン内蔵のブザーを使用するため、音を鳴らすといってもサウンド・デバイスは必要ない。

// beep.cs

using System;
using System.Runtime.InteropServices;

class BeepProgram {
  [DllImport("kernel32.dll")]
  private extern static bool Beep(uint dwFreq, uint dwDuration);

  private static void Main() {
    Beep(262, 500);  // ド
    Beep(294, 500);  // レ
    Beep(330, 500);  // ミ
    Beep(349, 500);  // ファ
    Beep(392, 500);  // ソ
    Beep(440, 500);  // ラ
    Beep(494, 500);  // シ
    Beep(523, 500);  // ド
  }
}

// コンパイル方法: csc beep.cs

マシン内蔵のスピーカから「ドレミファソラシド」を鳴らすプログラム(beep.cs)
beep.csのダウンロード
beep.vb(Visual Basic .NET版)のダウンロード

参照渡しのパラメータを含むDLL関数の場合

 先のBeep関数では、パラメータとして2つの数値を渡すだけの単純なDLL呼び出しで済むが、一部または全部のパラメータが参照渡しになっているAPIやDLL関数も存在する。

 例えば、マウスボタンの数を取得するWin32 APIであるGetNumberOfConsoleMouseButtons関数の書式は次のようになっている。

BOOL GetNumberOfConsoleMouseButtons(LPDWORD lpNumberOfMouseButtons);



 パラメータのlpNumberOfMouseButtonsはLPDWORD型(DWORD型の値へのポインタ)であるが、実際にはDWORD型の値を参照渡しで渡すことを意味している。

 このような参照渡しのパラメータを含む関数を宣言するには、参照渡しになっているパラメータをrefパラメータとして宣言すればよい。よって、GetNumberOfConsoleMouseButtons関数の宣言は以下のようになる。

[DllImport("kernel32.dll")]
extern static bool GetNumberOfConsoleMouseButtons(ref uint lpNumberOfMouseButtons);


 ただし、呼び出し側から値を渡す必要がないことが分かっている場合はoutパラメータと宣言しても構わない。先のGetNumberOfConsoleMouseButtons関数も値を渡す必要はないので、以下のように宣言することもできる。

[DllImport("kernel32.dll")]
extern static bool GetNumberOfConsoleMouseButtons(out uint lpNumberOfMouseButtons);


定数を使用するDLL関数の場合

 Win32 APIやDLL関数を利用する際に、関数のパラメータとして何らかの定数を指定する場合がある。特にWin32 APIに関しては、それらの定数がプラットフォームSDKに含まれるヘッダ・ファイル(windows.hなど)で定義されているが、C#ではこれらのヘッダ・ファイルを扱うことができないため、必要に応じて定数を自分で定義しなければならない。

 Win32 APIやDLL関数に定数を渡す方法はいくつか考えられるが、必要な定数をconstキーワードで定義する方法が最も一般的だろう。例えば、以下のサンプル・プログラムは、IsProcessorFeaturePresentというWin32 APIを利用して、プロセッサがMMX命令セットをサポートしているかどうかを調べるプログラムである。IsProcessorFeaturePresent関数はプロセッサが特定のプロセッサ機能をサポートしているかどうかを調べるAPIであり、プロセッサ機能の種類を表す定数をパラメータにとる。このプログラムでは、SSE命令セットを表すPF_XMMI_INSTRUCTIONS_AVAILABLE定数を宣言し、それをパラメータとして渡している(詳細はMSDNドキュメント(英語)を参照)。なお、定数の実際の値(この場合は6)を知るには、プラットフォームSDKに含まれるヘッダ・ファイルを参照する必要がある。

// ssechk1.cs

using System;
using System.Runtime.InteropServices;

class SSECheck1 {
  [DllImport("kernel32.dll")]
  private extern static bool IsProcessorFeaturePresent
    (uint ProcessorFeature);

  // 定数の宣言
  private const uint PF_XMMI_INSTRUCTIONS_AVAILABLE = 6;

  private static void Main() {
    if(IsProcessorFeaturePresent(PF_XMMI_INSTRUCTIONS_AVAILABLE)) {
      Console.WriteLine("SSE is available.");
    } else {
      Console.WriteLine("SSE is not available.");
    }
  }
}

// コンパイル方法: csc ssechk1.cs

プロセッサのSSE命令セットのサポートの有無を調べるプログラム(ssechk1.cs)
ssechk1.csのダウンロード
ssechk1.vb(Visual Basic .NET版)のダウンロード

 独自の列挙型を宣言し、その列挙型を関数のパラメータや戻り値として宣言する方法もある。例として、IsProcessorFeaturePresent関数のパラメータを列挙型として宣言したサンプル・プログラムを以下に示す。

// ssechk2.cs

using System;
using System.Runtime.InteropServices;

// 定数の宣言
enum ProcessorFeatures : uint {
  PF_FLOATING_POINT_PRECISION_ERRATA = 0,
  PF_FLOATING_POINT_EMULATED = 1,
  PF_COMPARE_EXCHANGE_DOUBLE = 2,
  PF_MMX_INSTRUCTIONS_AVAILABLE = 3,
  PF_PPC_MOVEMEM_64BIT_OK = 4,
  PF_ALPHA_BYTE_INSTRUCTIONS = 5,
  PF_XMMI_INSTRUCTIONS_AVAILABLE = 6,
  PF_3DNOW_INSTRUCTIONS_AVAILABLE = 7,
  PF_RDTSC_INSTRUCTION_AVAILABLE = 8,
  PF_PAE_ENABLED = 9,
  PF_XMMI64_INSTRUCTIONS_AVAILABLE = 10
}

class SSECheck2 {
  [DllImport("kernel32.dll")]
  private extern static bool 
    IsProcessorFeaturePresent(ProcessorFeatures ProcessorFeature);

  private static void Main() {
    if(IsProcessorFeaturePresent
        (ProcessorFeatures.PF_XMMI_INSTRUCTIONS_AVAILABLE)) {
      Console.WriteLine("SSE is available.");
    } else {
      Console.WriteLine("SSE is not available.");
    }
  }
}

// コンパイル方法: csc ssechk2.cs

プロセッサのSSE命令セットのサポートの有無を調べるプログラム(ssechk2.cs)
ssechk2ssechk2のダウンロード
ssechk2.vb(Visual Basic .NET版)のダウンロード

 この程度のプログラムでは、列挙型を定義するメリットはあまりなく、むしろ列挙体を定義することがかえって手間になっている。しかし、特に複数の種類の定数を利用する場合は、パラメータを列挙型として宣言しておけば、その列挙型のメンバ以外の値を直接パラメータに渡すことができなくなるので、コーディング上のミスを抑えることが可能になる。また、Visual Studio .NETのIntelliSence機能では、パラメータの型としてこの列挙型が表示されるため、どの定数を指定すればよいかが一目で分かるようになる。これは戻り値についても同様である。必要に応じてconstによる方法と使い分けるとよいだろう。

 なお、文字列や構造体をパラメータに指定するDLL関数呼び出しについては、「TIPS:Win32 APIやDLL関数に文字列や文字列バッファを渡すには? 」や「TIPS:Win32 APIやDLL関数に構造体を渡すには? 」を参照していただきたい。

カテゴリ:クラス・ライブラリ 処理対象:Win32 API
使用ライブラリ:DllImport属性(System.Runtime.InteropServices名前空間)
関連TIPS:Win32 APIやDLL関数に文字列や文字列バッファを渡すには?
関連TIPS:Win32 APIやDLL関数に構造体を渡すには?


「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。