Programming Field - Win16 プログラミング

2. 特殊ポインタ (near, far, huge)

Win32 のプログラミングをしている人は、文字列の「LPSTR」と「const char*」や、ポインタの「PINT」と「LPINT」の違いは特に考える必要がありません。しかし、Win16 には far ポインタnear ポインタhuge ポインタというものがあり、「P」や「LP」の違いと関係しています。しかも、これを上手く使い分けなければ意図した動作が起こらないため注意して使う必要があります。

※ このページでは far または __far を FAR、near または __near をNEAR としています。これは、windows.h をインクルードすれば定義され、Win32 では空のマクロとして定義されるので互換性を(ある程度)保つことも出来るからです。

各ポインタの説明

near ポインタ

near ポインタを宣言する場合は以下の構文です。

type NEAR* var_name;

near ポインタが意味するのは、「このプログラム内のメモリ」ということです。ちなみに、near の単語は英語では「近くの」などという意味です。sizeof 演算子でこのポインタのサイズを調べると、「2」(バイト) という結果が出ます。1 バイトは 8 ビットなので、このポインタは 16 ビットポインタです。「はじめに」でも書きましたが、メモリのアドレスの範囲は16 ビットで、near ポインタはこの範囲内のデータを指すことになります。

near ポインタは接頭辞(せっとうじ、いわゆる頭文字)に NP または単に P をつけて、NPSTR や PSTR としているのがよく見られます。ただし、NEAR も FAR もつけないポインタに接頭辞 P をつけるほうがよく見られるため、この辺の混同に注意してください (特に下記参照)。

far ポインタ

far ポインタを宣言する場合は以下の構文です。

type FAR* var_name;

far は意味の上では near と対の関係にあるのは言うまでもありませんが、far ポインタは 16:16 ポインタと言うこともあります。これは、far ポインタが 16 ビットのアドレスデータを 2 つ持つことからそう言われています。この 16 ビットのアドレスデータの 1 つは「オフセット」、もう 1 つは「セレクタ」と呼ばれています。なお、これを取得するマクロは、オフセットは OFFSETOF マクロ、セレクタは SELECTOROF マクロです。また、この 2 つからfar ポインタを作るには MAKELP マクロを使います。定義は以下の通りです。

#define MAKELONG(low, high) ((LONG)(((WORD)(low)) | (((DWORD)((WORD)(high))) << 16)))
#define LOWORD(l)           ((WORD)(DWORD)(l))
#define HIWORD(l)           ((WORD)((((DWORD)(l)) >> 16) & 0xFFFF))

#define MAKELP(sel, off)    ((void FAR*) MAKELONG((off), (sel)))
#define SELECTOROF(lp)      HIWORD(lp)
#define OFFSETOF(lp)        LOWORD(lp)

※ これらのマクロは通常は使用する必要がありませんが、Windowsメッセージの引数で指定する場合などでアドレスデータを分割・結合する際に使用します。

far ポインタを使うと、2 つのアドレスデータの 1 つ (おそらくセレクタ) がプログラムのアドレス空間を示しているので、異なるモジュールでも利用可能です。多くの API 関数のポインタ引数が far ポインタであるのもこの理由です。

far ポインタは接頭辞に LP をつけて、LPSTR や lpData とすることが定番となっています。また、C の標準ライブラリの関数で、far ポインタのメモリを扱う関数は、頭に _f の付いた _fmalloc_ffree を使います (fopen の f とは別物なので注意してください)。つまり、far ポインタのメモリを動的に確保する場合は、_fmalloc を使います。

メモ

ポインタを宣言する時に NEAR を省略した場合は、たいていのコンパイラは far ポインタとします。ただし、コンパイラによっては near ポインタとする可能性もあります。これを確認するコードの例は以下の通りです。

CHAR szBuffer[255];
sprintf(szBuffer, "Pointer size: %d", (int) sizeof((void*) 0));
MessageBox(NULL, szBuffer, NULL, MB_OK);

これで、サイズが 2 の場合は near ポインタ、4 の場合は far ポインタとなります。なお、Open Watcom のコンパイラでは 4 という結果で、既定のポインタを far としているようです。

huge ポインタ

huge ポインタを宣言する場合は以下の構文です。

type huge* var_name;

※ huge に関しては、#define による HUGE の宣言がありません。 Win32 と互換性を保つ場合、NEAR、FAR と同じような宣言を行ってください。

huge ポインタを利用すると、near や far では利用できなかった 64KB 以上のメモリを扱うことが出来ます。これは、huge ポインタが 32 ビットのアドレスを使用するからです。ちなみに、huge の単語は英語で「巨大な」などといった意味です。

このポインタは、far ポインタと同じようにDLL などのモジュールに直接渡すことが出来ます。しかしながら、ポインタのサイズは far ポインタと同じ 4 です。

huge ポインタには、これといった接頭辞はありません。しいて言うなら、lp などが付きます。また、huge ポインタを確保するには halloc、解放するには hfree を使います。メモリのコピーは型変換して _fmemcpy を使うのが妥当でしょう。ただし、この関数では 64KB 以上のコピーが出来ないので注意してください。(なお、API に hmemcpy というのがあるので、それを利用するのも手です。)

※ halloc の定義は以下のとおりです (Open Watcom の場合)。

void huge* halloc(long n, size_t size);

このとき、n は割り当てるメモリサイズ、size は配列の要素数を指定します。したがって、 単純に 0x20000 バイトのメモリを割り当てたい場合、n に 0x20000、size に 1 を指定します。

メモ

Win32 にも、一部の関数で huge ポインタの名残がちょっと残っています。分かりやすい例は、IsBadHugeReadPtrIsBadHugeWritePtr です。Win32 では Huge を除いた関数と動作は同じですが、Win16 ではこれらの関数は huge ポインタを扱います。

実際の使い方

実際にプログラミングをする際は、そこまでシビアになる必要はありません。たいていの場合、コンパイラが適度に far <=> near とポインタを変換してくれます。ただし、GetDOSEnvironment 関数など、API で文字列を返す関数では、これを near ポインタの変数に代入したり、次のようなコードを使うと上手くいかないので注意してください。

#include <stdarg.h>

void __cdecl TestFunc(int n, ...)
{
    va_list va;
    va_start(va, n);
    MessageBox(NULL, va_arg(va, PSTR), NULL, MB_OK);
    va_end(va);
}

TestFunc(1, GetDOSEnvironment());

だいたい見ても分かる通り、va_arg マクロを使う際に、ポインタを PSTR としていることから上手くいかなくなっています。これを LPSTR にすれば上手くいきます。また、DLL などを作る際のポインタ引数は、必ず far ポインタにするようにしてください。これを near ポインタとしてしまうと、far ポインタにするときに、このポインタを「自分のプログラム内のアドレス」と間違えて解釈して「自分のプログラム内のアドレス」を指す far ポインタを作ってしまいます。つまり、このポインタは無効となります。

また、DLL でエクスポートする関数を作る時、必ず far を付けて関数を宣言し、ポインタには必ず near を使わないようにします。以下がその例です。

void FAR __stdcall __export ExportFunc(LPCSTR lpszMsg)
{
    MessageBox(NULL, lpszMsg, "DLL Function", MB_OK);
}

<< 前へ | 次へ >>

最終更新日: 2004/08/10