ポインターを使用するための規則

32 ビットと 64 ビットの両方の Microsoft Windows用にコンパイルするようにコードを移植するのは簡単です。 ポインターのキャストに関するいくつかの簡単な規則に従って、コードで新しいデータ型を使用するだけで済みます。 ポインター操作の規則は次のとおりです。

  1. ポインターを intlongULONG、または DWORD にキャストしないでください。

    ポインターをキャストしていくつかのビットをテストする、ビットを設定またはクリアする、またはその内容を操作する必要がある場合は、 UINT_PTR または INT_PTR 型を使用します。 これらの型は、32 ビットと 64 ビットの両方のWindows (たとえば、32 ビット Windowsの場合は ULONG、64 ビット Windowsの場合は _int64) のポインターのサイズにスケーリングする整数型です。 たとえば、次のコードを移植するとします。

    ImageBase = (PVOID)((ULONG)ImageBase | 1);

    移植プロセスの一環として、次のようにコードを変更します。

    ImageBase = (PVOID)((ULONG_PTR)ImageBase | 1);

    必要に応じてUINT_PTRINT_PTRを使用します (必要かどうかわからない場合は、念の場合に使用しても害はありません)。 ポインターを ULONGLONGINTUINT、または DWORD 型にキャストしないでください。

    HANDLEvoid*として定義されているため、下位 2 ビットをテスト、設定、またはクリアするために HANDLE 値を ULONG 値に型キャストすると、64 ビット Windowsでエラーになります。

  2. ポインターを切り捨てるには、 PtrToLong または PtrToUlong 関数を使用します。

    32 ビット値へのポインターを切り捨てる必要がある場合は、 PtrToLong または PtrToUlong 関数 (Basetsd.h で定義) を使用します。 これらの関数は、呼び出し中にポインター切り捨て警告を無効にします。

    これらの関数は慎重に使用してください。 これらの関数のいずれかを使用してポインター変数を変換した後は、もう一度ポインターとして使用しないでください。 これらの関数は、アドレスの上位 32 ビットを切り捨てます。これは通常、ポインターによって最初に参照されたメモリにアクセスするために必要です。 これらの関数を慎重に考慮せずに使用すると、コードが脆弱になります。

  3. 64 ビット コードとしてコンパイルされる可能性があるコードでPOINTER_32値を使用する場合は注意してください。 64 ビット コードでネイティブ ポインターに割り当てられると、コンパイラはポインターを符号拡張し、ポインターを 0 に拡張しません。

  4. 32 ビット コードとしてコンパイルされる可能性があるコードでPOINTER_64値を使用する場合は注意してください。 コンパイラは、ポインターを 0 から拡張するのではなく、32 ビット コードでポインターを符号拡張します。

  5. OUT パラメーターを使用する場合は注意してください。

    たとえば、次のように定義された関数があるとします。

    void func( OUT PULONG *PointerToUlong );

    この関数は次のように呼び出さないでください。

    ULONG ul;
    PULONG lp;
    func((PULONG *)&ul);
    lp = (PULONG)ul;
    

    代わりに、次の呼び出しを使用します。

    PULONG lp;
    func(&lp);
    

    ul から &PULONG* への型キャストはコンパイラ エラーを防ぎますが、この関数は ul のメモリ&に 64 ビットのポインター値を書き込みます。 このコードは 32 ビット Windowsで動作しますが、64 ビット Windowsでデータが破損し、見つけにくい微妙な破損になります。 要するに、C コードでトリックを実行しないでください。簡単でシンプルな方が優れています。

  6. ポリモーフィック インターフェイスには注意してください。

    ポリモーフィック データの DWORD パラメーターを受け入れる関数は作成しないでください。 データにポインターまたは整数値を指定できる場合は、UINT_PTR型または PVOID 型を使用します。

    たとえば、 DWORD 値として型指定された例外パラメーターの配列を受け入れる関数を作成しないでください。 配列は、 DWORD_PTR 値の配列である必要があります。 したがって、配列要素は、アドレスまたは 32 ビットの整数値を保持できます。 (一般的な規則は、元の型が DWORD で、ポインターの幅である必要がある場合は、 DWORD_PTR 値に変換することです。そのため、対応するポインター有効桁数型があります)。 DWORDULONG、またはその他の 32 ビット型をポリモーフィックな方法で使用するコードがある場合 (つまり、パラメーターまたは構造体メンバーにアドレスを保持する必要がある場合)、現在の型の代わりに UINT_PTR を使用します。

  7. 新しいウィンドウ クラス関数を使用します。

    ポインターを含むウィンドウまたはクラスのプライベート データがある場合は、コードで次の新しい関数を使用する必要があります。

    これらの関数は、32 ビットと 64 ビットの両方のWindowsで使用できますが、64 ビット Windowsでは必要です。 次に、これらの関数を使用して移行の準備をします。

    さらに、64 ビット Windowsの新しい関数を使用して、クラスのプライベート データ内のポインターまたはハンドルにアクセスする必要があります。 このようなケースを見つけるのに役立つよう、64 ビット コンパイル中に Winuser.h で次のインデックスが定義されていません。

    • GWL_WNDPROC
    • GWL_HINSTANCE
    • GWL_HWDPARENT
    • GWL_USERDATA

    代わりに、Winuser.h は次の新しいインデックスを定義します。

    • GWLP_WNDPROC
    • GWLP_HINSTANCE
    • GWLP_HWNDPARENT
    • GWLP_USERDATA
    • GWLP_ID

    たとえば、次のコードはコンパイルされません。

    SetWindowLong(hWnd, GWL_WNDPROC, (LONG)MyWndProc);

    次のように変更する必要があります。

    SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)MyWndProc);

    WNDCLASS 構造体の cbWndExtra メンバーを設定するときは、ポインター用の十分な領域を確保してください。 たとえば、現在ポインター値の sizeof(DWORD) バイトを予約している場合は、sizeof (DWORD_PTR) バイトを予約します。

  8. FIELD_OFFSETを使用して、すべてのウィンドウおよびクラス データにアクセスします。

    ハードコーディングされたオフセットを使用してウィンドウ データにアクセスするのが一般的です。 この手法は、64 ビット Windowsには移植できません。 コードを移植しやすくするには、FIELD_OFFSET マクロを使用してウィンドウとクラス のデータ アクセスします。 2 番目のポインターのオフセットが 4 であると想定しないでください。

  9. LPARAMWPARAMLRESULT の種類は、プラットフォームによってサイズが変更されます。

    64 ビット コードをコンパイルする場合、通常はポインターまたは整数型を保持するため、これらの型は 64 ビットに拡張されます。 これらの値を DWORDULONG、UINTINTint、または long の値と組み合わせないでください。 これらの型の使用方法を確認し、誤って値を切り捨てないようにします。