OS2/未整理/16bit

0.

まあなんつーか、pmvnc のグローバルフック関係の DLL のソース中にこんなようなコードがあったんよ。

pmvnchk.cpp

extern "C" void EXPENTRY ER_SetHMQ( HMQ h )
{
    hmq = h;

    SEL globalSeg , localSeg;
    DosGetInfoSeg( &globalSeg , &localSeg );
    locPtr = MAKEPLINFOSEG( localSeg );

    if ( hookEnd == NULLHANDLE )
    {
        if ( DosCreateEventSem( NULL , &hookEnd , DC_SEM_SHARED , FALSE ) != 0 )
        {
            hookEnd = NULLHANDLE;
        }
    }
}

んで DosGetInfoSeg は別ヘッダでこんなように定義されている。

dgis.h

(前略)
typedef struct _GINFOSEG
{
(中略)
} GINFOSEG;

typedef GINFOSEG *PGINFOSEG;

typedef struct _LINFOSEG
{
(中略)
} LINFOSEG;

typedef LINFOSEG *PLINFOSEG;

(中略)

typedef SEL FAR16PTR PSEL16;

#define DosGetInfoSeg DOS16GETINFOSEG

extern "C" APIRET16 APIENTRY16 DosGetInfoSeg( PSEL16 ginfSel , PSEL16 linfSel );

#define MAKEPGINFOSEG(sel)  ((PGINFOSEG)MAKEP(sel,0))
#define MAKEPLINFOSEG(sel)  ((PLINFOSEG)MAKEP(sel,0))

つまり16ビット API でありんす。
んで、フックの DLL をコンパイルするには Borland C++ 2.0 for OS/2 が必要なんだそうな。

1. 16bit FAR ポインタ

1.1 32bit FLAT ポインタ ⇔ 16bit FAR ポインタの変換(API)

いちおう DosFlatToSel, DosSelToFlat という API がある。
ツールキットのヘッダでは定義されてないけどインポートライブラリ内には存在するので、適当に関数宣言すれば使える→しまった引数も eax だから APIENTRY じゃダメですな。コンパイラ汎用にしたいときはアセンブラ経由で呼び出すしかないか。

    EXTRN   DosFlatToSel:BYTE
    EXTRN	   DosSelToFlat:BYTE

_emx_32to16:
    mov         eax,dword ptr 4[esp] 
    jmp         near ptr DosFlatToSel 
_emx_16to32:
    mov         eax,dword ptr 4[esp] 
    jmp         near ptr DosSelToFlat 

とはいえ例のタイルドセグメントとか何とかがあるので、ポインタの妥当性をチェックしなくていいならばわりと機械的に変換可能。

#define myDosFlatToSel(p) ((((ULONG)(p) & 0xffff0000UL) << 3) | 0x00070000UL) | (ULONG)(p) & 0xffffUL)
#define myDosSelToFlat(p) ((((ULONG)(p) & 0xfff80000UL) >> 3) | ((ULONG)(p) & 0xffffUL))

ちなみに Borland C++ では 32→16bit ポインタ変換時、(pmvnchk.dll の逆アセンブル出力を見る限りでは、)以下のようなコードがインライン展開されるようだ(レジスタはその時その時で余ってるやつを使う)。

ror eax, 16
shl ax, 3
or al, 7
rol eax, 16

16bit FAR ポインタの利用(C/C++)

商用(だった)OS/2 コンパイラの場合、だいたい何かしらの方法で宣言や typedef に直接記述可能。

OpenWatcom の拡張キーワード(User's Guide から)

__far16, _Far16
16bit FAR ポインタや 16bit 関数の宣言に使う。_Far16 は MS-C 互換。(__far16 はたぶん Borland C++ でも使える)
char _Far16 * my_far16_char_pointer;
USHORT _Far16 my_far16_function(USHORT arg1, USHORT arg2);
_Seg16
16bit FAR ポインタの宣言に使う。(16bit 関数の宣言には使えない)。IBM の C/C++ コンパイラ互換。
char * _Seg16 my_far16_char_pointer;

_Seg16 はポインタ指示(*) の後ろにつける。ポインタに対するこの手の修飾子は(_Far16 みたいに)ふつう * の前につけるもんだが、_Seg16 はちょっと特殊ですね。