Windows APIを使ってみよう

WindowsでC言語を扱うのであれば、まずはWindows APIを使ったコーディングを行ってみよう。教育機関でC言語を習う場合、C言語に標準で備わっている関数や、POSIXで定められている関数を使うことが多いはずだ。Windows APIはこうした関数とは異なっているので最初は慣れないかもしれない。しかし結局のところ、慣れの程度が大きく、慣れてしまえばWindows APIは扱いやすいのだ。

Windows APIで開発するということは、動作するプラットフォームがWindowsに限定されることを意味する。そのままではLinuxでコンパイルして実行したり、Macでコンパイルして実行したりといったことはできない(専用のソフトウェアを使わない限り)。しかし、PCにおけるWindowsのシェアは7割から8割ほどと見られている。Windowsで使えるということは、こうした最大プラットフォームで利用できることを意味している。

これまでWindows APIを使ったことがなくても、C言語の経験があるならWindows APIの利用はさほど難しくない。マニュアルを引きながら利用したい機能を持ってきて使えばよい。簡単な例を取り上げてみようと思う。

POSIX系のutil_file.c

これまで、csv2tsvを作成する過程でutil_file.cというファイルを作成した。次のような短いファイルだ。

util_file.c POSIX版

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

char *file2str(const char *filepath) {
  struct stat st;
  int filesize, c;

  char *buf, *p;

  FILE *fp;

  stat(filepath, &st);
  filesize = st.st_size;

  buf = calloc(filesize + 1, sizeof(char));
  p = buf;

  fp = fopen(filepath, "r");
  for (int i = 0; i < filesize; i++) {
    c = fgetc(fp);
    if (EOF == c) {
      break;
    }
    *p = (char)c;
    ++p;
  }

  return buf;
}

短いファイルではあるが、POSIX的なコーディングで基本的なコードが入っているので、Windows APIへの書き換えを始めるにはよい材料だと思う。例えば、上記のソースコードでは次の辺りがPOSIX的な書き方となる。

  • stat(2)でファイルのステータスを取得
  • calloc(3)でメモリを確保(ヒープ)
  • fopen(3)でファイルをオープン
  • fgetc(3)でファイルからデータを取得

教育機関でC言語の使い方を習う場合、POSIX系の関数を使ったコーディングを学ぶことが多いだろう。上記は基本的な関数の一つなので、C言語を習った場合はそのときに扱ったことがあるのではないだろうか。

util_file.cで行っている処理の流れは次のようなものだ。

  1. ファイルサイズを取得する
  2. ファイルの中身を収めることができるサイズのメモリを確保する
  3. ファイルを開き、ファイルの中身を確保したメモリへコピーしていく
  4. 確保したメモリへのアクセスをリターンする

処理として特に難しいものはない。この処理一つ一つをWindows APIへ置き換えていけば、Windows API版のutil_file.cが完成するわけだ。

Windows API版のutil_file.c

では、先程のファイルをWindows APIで置き換えてみよう。先に書き換えたものを掲載しておく。次のような感じだ。関数名や変数名などの流儀がWindows APIとPOSIX系とでは異なるので雰囲気が違っているが、どっちも同じC言語で書いてある。

util_file.c Windows API版

#include <stdio.h>
#include <windows.h>

char *file2str(const char *filepath) {
  /*
   * 指定されたファイルを開く
   */
  HANDLE hFile;

  // ファイルを開く
  hFile = CreateFile(filepath, // ファイル名
    GENERIC_READ,              // 読み込みのために開く
    0,                         // 共有しない
    NULL,                      // デフォルトセキュリティ
    OPEN_EXISTING,             // 既存のファイルを開く
    FILE_ATTRIBUTE_NORMAL,     // 通常のファイル
    NULL);                     // 属性およびフラグなし

  // ファイルが開けなかった場合の処理
  if (INVALID_HANDLE_VALUE == hFile) {
    fprintf(stderr, "no such file: %s\n", filepath);
    exit(EXIT_FAILURE);
  }

  /*
   * ファイルサイズを取得し、ファイルサイズ+1のメモリを確保
   */
  DWORD dwFileSize;
  dwFileSize = GetFileSize(hFile, NULL);

  // ヒープを生成
  HANDLE hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0, 0);

  // ヒープからメモリを確保
  char *buf, *p;
  int buflen = dwFileSize + 1;
  buf = (char*)HeapAlloc(hHeap, 
    HEAP_ZERO_MEMORY, 
    sizeof(char) * buflen);
  p = buf;

  /*
   * ファイルの中身をメモリへ読み込み
   */
  DWORD dwBytesRead;
  while (ReadFile(hFile, p, buflen, &dwBytesRead, NULL) 
    && dwBytesRead > 0) {
    p += dwBytesRead;
  }

  /*
   * ファイルを閉じる
   */
  CloseHandle(hFile);

  /*
   * ヒープを閉じる
   */
  CloseHandle(hHeap);

  return buf;
}

以下、それぞれを細かく見てみよう。