HOME | プログラミングTClock |


古いWindowsへの対応

TClock Lightは、Windows 95以降のどのWindowsでも動かせるようにしつつ、新しいWindowsの機能も使う、という方針です。そのため、このページに書かれているような対策をとります。

マクロWINVER、_WIN32_WINNT、_WIN32_IE

Windowsのバージョンを考慮せずにプログラムを書いてコンパイルすると、Windows 98やMe以降でしか動かないものができてしまうことがあります。

マイクロソフトのSDKに含まれるヘッダファイル(BCCでもインストールされます)では、マクロWINVER、_WIN32_WINNT、_WIN32_IEが使われています。それぞれ、Windowsのバージョン、Windows NT(2000、XP)のバージョン、IEのバージョンを表します。これらのマクロの数値が新しければ、Windowsの新しい機能のためのマクロ、構造体、APIの関数宣言がインクルードされます。

しかし、そうした新しい機能がインクルードされると、古いWindowsでは動かないプログラムになることがあります。TClock Lightでは、各ヘッダファイル(tclock.h、tcdll.h、tcprop.h、common.h)の先頭で次のように定義して、Windowsのバージョンをわざと古くします。

#define _WIN32_IE    0x0200
#define _WIN32_WINNT 0x0400
#define WINVER       0x0400

つまり、IEのバージョンは2.0、NTは4.0、Windowsは4.0(=Windows 95)とします。

新しいマクロや構造体のコピー

しかし、マクロWINVER、_WIN32_WINNT、_WIN32_IEの値を古くすると、今度は新しい機能を使いたくても使えなくなってしまいます。そこで、

新しいマクロや構造体を使う場合は、SDKのヘッダファイルから必要な部分だけコピペする。

という手を使います。必要なマクロや構造体があったら、インクルードディレクトリでgrepをかけて探します。BCCではC:\borland\bcc55\Include、Visual C++ 6.0ではC:\Program Files\Microsoft Visual Studio\VC98\Include、Visual C++ .NETではC:\Program Files\Microsoft Visual Studio .NET\Vc7\PlatformSDK\Includeです。

たとえば、タスクバーの「取っ手を消す」に必要なメッセージと構造体は、commctrl.hから取ってきて、TClock Lightのソースに加えます(dll/newapi.h)。

#define CCM_FIRST               0x2000
#define CCM_SETCOLORSCHEME      (CCM_FIRST + 2)
#define RB_SETCOLORSCHEME   CCM_SETCOLORSCHEME
typedef struct tagCOLORSCHEME {
   DWORD            dwSize;
   COLORREF         clrBtnHighlight;
   COLORREF         clrBtnShadow;
} COLORSCHEME, *LPCOLORSCHEME;

新しいAPIの利用

古いWindowsにはない新しいAPIを使うと、古いWindowsではTClockを起動することさえできなくなります。そこで、

新しいAPIは静的にリンクせずに、必要なときに動的にロードする。

ようにプログラムします。

今のところ、新しいAPIを使っているのはtcdll.tclockだけなので、dllの下のソースを例に説明します。ウィンドウを半透明化するAPI SetLayeredWindowAttributesの使い方を見てみましょう。このAPIは、Windows 2000とXPだけがサポートしています。

まず、SDKのヘルプでSetLayeredWindowAttributesを探し(Googleで検索すれば早い)、APIがどのDLLに含まれるか調べます。「Import library User32.lib」とあるので、user32.dllに含まれていることが分かります。

SetLayeredWindowAttributes Function (MSDN)

ソースにuser32.dllのモジュールハンドルを加えます(dll/newapi.c)。

static HMODULE g_hmodUser32 = NULL;

SDKのヘッダファイル(winuser.h)から、SetLayeredWindowAttributesの宣言を持ってきます。

WINUSERAPI
BOOL
WINAPI
SetLayeredWindowAttributes(
    HWND hwnd,
    COLORREF crKey,
    BYTE bAlpha,
    DWORD dwFlags);

これを元にして、SetLayeredWindowAttributesへの関数ポインタを作ります(dll/newapi.c)。

static BOOL (WINAPI *g_pSetLayeredWindowAttributes)(HWND,COLORREF,BYTE,DWORD) = NULL;

次のように、関数InitUser32を作ります。LoadLibraryでuser32.dllを動的にロードして外部変数に保存し、GetProcAddressでAPIの関数ポインタを取り出て外部変数に保存します(dll/newapi.c)。

void InitUser32(void)
{
    if(g_hmodUser32) return;
    
    g_hmodUser32 = LoadLibrary("user32.dll");
    if(g_hmodUser32 != NULL)
    {
        (FARPROC)g_pSetLayeredWindowAttributes
            = GetProcAddress(g_hmodUser32, "SetLayeredWindowAttributes");
    }
}

SetLayeredWindowAttributesのラッパーとなる関数MySetLayeredWindowAttributesを作ります。この関数は、最初に呼ばれるとInitUser32を呼び出します(dll/newapi.c)。

BOOL MySetLayeredWindowAttributes(HWND hwnd, COLORREF crKey,
    BYTE bAlpha, DWORD dwFlags)
{
    if(g_hmodUser32 == NULL) InitUser32();
    
    if(g_pSetLayeredWindowAttributes)
        return g_pSetLayeredWindowAttributes(hwnd, crKey, 
                bAlpha, dwFlags);
    else return FALSE;
}

半透明化の機能を使いたいときは、ラッパー関数MySetLayeredWindowAttributesを呼び出します(dll/taskbar.cのSetLayeredTaskbar)。

    MySetLayeredWindowAttributes(hwndTaskbar, 0, (BYTE)alpha, LWA_ALPHA);

TClockを終了するときは、関数EndNewAPIを呼び出します(dll/main2.cのEndClock)。

    EndNewAPI();      // newapi.c

EndNewAPIには、user32.dllを解放する処理を加えておきます(dll/newapi.c)。

void EndNewAPI(void)
{
    (中略 ほかのDLLの解放)
    
    if(g_hmodUser32 != NULL) FreeLibrary(g_hmodUser32);
    g_hmodUser32 = NULL;
    g_pSetLayeredWindowAttributes = NULL;
}

TClock Lightを改良するときに、tcdll.tclockで新しいAPIを使いたい場合は、この例に沿ってdll/newapi.hとdll/newapi.cを拡張するとよいでしょう。

Windowsのバージョンチェック

TClock Lightでは、Windowsのバージョンをチェックして処理を振り分けたり(tcdll.tclock)、ダイアログの部品の有効/無効を切り替えたり(tcprop_ja.exe)しています。

common/utl.cの中には、Windowsのバージョンを取り出す関数CheckWinVersionがあります。この関数の戻り値は次のようなビットフラグでチェックできます。

#define WIN95   0x01   // 95,98,Me
#define WIN98   0x02   // 98,Me
#define WINME   0x04   // Me
#define WINNT   0x08   // NT4,2000,XP
#define WIN2000 0x10   // 2000,XP
#define WINXP   0x20   // XP

次の例は、Windows Me、2000、XPの場合に処理をするものです。

int ver = CheckWinVersion();

if((ver & WINME) || (ver & WIN2000)) DoSomething();

IE 4以降の判別とXPのテーマの判別

また、common/utl.cの中には、IE 4以降のタスクバーかどうかを調べる関数IsIE4と、Windows XPのテーマが使われているかどうかを調べる関数IsXPVisualStyleがあります。この2つの関数はBOOLを返します。この2つの関数の戻り値も、処理の振り分けやダイアログの部品の有効/無効の切り替えに使われます。