Search
Categories
Articles
Rainmeter関連
ファイル置き場
お知り合いなど

スポンサーサイト

--.--.-- | スポンサー広告

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[Rainmeter-dev] Rainmeter on Windows 2000

2010.01.21 | Rainmeter-dev // Issue/互換性問題やバグ修正

2 Comments

RainmeterをWindows 2000上で動かしたときの問題についてのまとめです。
私的なまとめですので、公開されているビルドに反映されているものではありません。ご注意ください。

■更新履歴(※更新のたびに、たぶん一番上にあがってきます)
- 2009.10.28 : Windows 2000に対応してない標準添付プラグインについて
- 2009.11.04 : Aboutダイアログを出そうとするとエラーが表示される問題について
- 2010.01.20 : ResMonプラグインをWindows 2000に対応させる

以下、続きに格納。

2chのスレッドにてWindows 2000上での話があったので、現時点で2000に対応していない部分を調べてみた。
本体は未チェック……。

SysInfoプラグイン
- SysInfoType=OS_BITSの取得に、Windows XP以降対応のGetNativeSystemInfo APIGetProcAddress APIで取得したアドレス経由でなく直接使ってる
  → 直接使わずに、GetProcAddress APIのRemarksにあるような使い方をすると、2kでも読み込めるdllになる

void GetOSBits(WCHAR* buffer)
{
    SYSTEM_INFO systemInfo = {0};
 
    typedef void (WINAPI *FPGETNATIVESYSTEMINFO)(LPSYSTEM_INFO lpSystemInfo);
    FPGETNATIVESYSTEMINFO GetNativeSystemInfo = (FPGETNATIVESYSTEMINFO)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "GetNativeSystemInfo");
    if (GetNativeSystemInfo != NULL)
    {
        GetNativeSystemInfo(&systemInfo);
    }
    else
    {
        GetSystemInfo(&systemInfo);
    }
 
    if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ||
        systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64)
    {
        wcscpy(buffer, L"64");
    }
    else
    {
        wcscpy(buffer, L"32");
    }
}

※2009.11.03 修正 : GetNativeSystemInfo APIが取得できなかったら、代わりにGetSystemInfo APIを使うように変更

ResMonプラグイン
- ResCountType=HANDLEの取得に、Windows XP SP1以降対応のGetProcessHandleCount APIを使ってる
  → 上と同じようにすればエラーを出さなくはできるけど、当然ながら取得はできなくなる(他の取得方法はわからない)
※2010.01.20 追記 : Windows 2000でも、NtQuerySystemInformation APIを使うことでハンドル数を取得できるみたいなので、GetProcessHandleCount APIが存在しない環境ではそちらを使って取得するようにすればよさそう。詳しくはこちら。

WifiStatusプラグイン
- Windows XP SP3(hotfixを当てればSP2でも?)以降に追加されたWlanapi.dllのAPIを使ってる
  → どうしようもない(2kでは使えない)

……と、Aboutダイアログを出そうとして出てきたエラーの分だけ記載してみた。

ResMonは元々XP以降とヘルプに記載されているし、WifiStatusに至ってはそもそもXP以降でしか使えないものをざくざく使ってしまってるので、どうにもできません。ただ、Aboutダイアログ出すときにエラー出るのはなんだかいやーんな感じ。
SysInfoについては、このOS_BITSのためだけに2000で読み込めなくなるのはいただけないと思うので、修正を入れたいけれど、Rainmeter自体のサポート対象OSから2000が除外されてしまうっぽい?(公式にある新しいBetaManualより)のでどうしたものか(※大丈夫でした)。入れるにしても64ビット環境でテストできてないし……どんな修正を入れるにしてもそこがネックすぎて何もできなくなってきた。

* 2009.11.04 追記 *

Aboutダイアログを出したときにエラーが出る理由は、プラグインディレクトリに存在する全てのdllファイルを読み込んで、作者名とバージョン情報を取得しようとして、Windows 2000では読み込めないdllも読み込もうとするためです(上記に挙げたSysInfo/ResMon/WifiStatusの3つ)。
こういうときは読み込まれないようにリネームしておくといいのですが、厄介なのはリネームの仕方によっては読み込まれてしまいます。".dll_"のように、".dll"の後ろに何かくっつけても、昔ながらの8.3形式で考えると、拡張子が".dll"であるとみなされるからです。リネームするときは拡張子を完全に変えてしまうか、ディレクトリを1つ掘ってそっちへ移してしまいましょう。

プログラム的にエラーが出ないようにするには、dllを問題なくするか、もしくはRainmeter側から読み込むときに、エラーダイアログを出さない方法で読み込む必要があります。
エラーダイアログを出さないようにする方法は、既にMeasure=FreeDiskSpaceで"No disk"ダイアログが出続けてしまう問題の回避策として導入済みです。同じ方法を使います。

        // Try to get the version and author
        std::wstring tmpSz = Rainmeter->GetPluginPath() + fileData.cFileName;
        UINT oldMode = SetErrorMode(0);
        SetErrorMode(oldMode | SEM_FAILCRITICALERRORS);  // Prevent the system from displaying message box
        SetLastError(ERROR_SUCCESS);
        HMODULE dll = LoadLibrary(tmpSz.c_str());
        DWORD err = GetLastError();
        SetErrorMode(oldMode);  // Reset
        if (dll)
        {
            /* 作者名とバージョンの取得処理 */
 
            FreeLibrary(dll);
        }
        else
        {
            DebugLog(L"Unable to load library: \"%s\", ErrCode=%i", tmpSz.c_str(), err);
        }

赤字の部分が追加されたコードです。LoadLibrary APIを呼び出す前に、SetErrorMode APIを使い、エラー発生時にダイアログを出さないように設定しています。こうすると、もしLoadLibraryでエラーが発生しても、ダイアログを出すことなく処理を続けられます。
とはいえ、ダイアログを出さなくした弊害として、エラーの詳細内容が取得できなくなるのが欠点として挙げられます。GetLastError APIでエラー番号はわかりますが、それをFormatMessage APIで文字列に直しても、本来表示されたエラーほど詳細なものは得られません。本来表示されるエラー内容は原因に直結した文字列を表示してくれるので、原因を知るためにはできれば表示したいものですが……。Aboutダイアログで出さなくても、どうせそのプラグインをスキンで使えば出るだろうし……ということで、ここではこうしておいてもいいのかなぁと。

こんなまどろっこしいことをせず単純に、対応してない標準添付プラグインについては、プラグイン名をハードコーディングして読み込まなくするようにしちゃうのも手かもしれません。

個人的には、エラーダイアログって一般的なユーザーにとっては脅威というか、何もしてないのになんでこうなったのという風にびっくりしてしまう対象だと思うので、出さなくても別に支障がないようなものについては抑えめにしていくほうがいいと思っています(反面、出さないということはそれだけ表面化しづらいし、問題があっても報告するための詳細なログがないということになりかねず、原因の特定が面倒になっちゃいますが。一番いいのは、出さなくても詳細なエラー内容が取得できるといいのですが、なんでそれが用意されていないのか……)。

* 2010.01.20 追記 *

ResMonプラグインではGetProcessHandleCount APIというWindows 2000に存在しないAPIを使っているため、使おうとしてもDLL読み込みに失敗します。
SysInfoプラグインのように、動的に関数のアドレスを取得するようにすれば読み込めるようになりますが、DLLが読み込めるようになるだけで、ハンドル数を取得する別の方法がなければ、ハンドル数の取得はできないままです。

というわけで、別の方法が見つからないと無理かなぁと思っていたのですが、CPU使用率の取得に使っているNtQuerySystemInformation APIの説明を読んでいたらできそうな方法があったので実装してみました。

typedef BOOL (WINAPI *PROCGPHC)(HANDLE,PDWORD);  // GetProcessHandleCount
PROCGPHC g_GetProcessHandleCount = NULL;
 
typedef LONG (WINAPI *PROCNTQSI)(UINT,PVOID,ULONG,PULONG);  // NtQuerySystemInformation
PROCNTQSI g_NtQuerySystemInformation = NULL;
 
#define STATUS_SUCCESS                    0
#define STATUS_INFO_LENGTH_MISMATCH        0xC0000004
 
#define SystemProcessInformation        5
 
typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    BYTE Reserved1[52];
    PVOID Reserved2[3];
    HANDLE UniqueProcessId;
    PVOID Reserved3;
    ULONG HandleCount;
    BYTE Reserved4[4];
    PVOID Reserved5[11];
    SIZE_T PeakPagefileUsage;
    SIZE_T PrivatePageCount;
    LARGE_INTEGER Reserved6[6];
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;
 
UINT Initialize(HMODULE instance, LPCTSTR iniFile, LPCTSTR section, UINT id)
{
    /* ... */
 
    if (g_GetProcessHandleCount == NULL)
    {
        g_GetProcessHandleCount = (PROCGPHC)GetProcAddress(
                                          GetModuleHandle(L"kernel32"),
                                         "GetProcessHandleCount"
                                         );
    }
    if (g_NtQuerySystemInformation == NULL)
    {
        g_NtQuerySystemInformation = (PROCNTQSI)GetProcAddress(
                                          GetModuleHandle(L"ntdll"),
                                         "NtQuerySystemInformation"
                                         );
    }
 
    /* ... */
}

まず必要な準備から。
GetProcessHandleCount APIを動的に取得するようにしておきます。NtQuerySystemInformation APIは元々隠されているので、使用する値や構造体も定義しておきます。ハンドル数に使えるのは、SYSTEM_PROCESS_INFORMATION構造体のHandleCountです。この構造体1つが1プロセス分にあたります。

UINT Update(UINT id)
{
    /* ... */
 
    BYTE* buf = NULL;
 
    if (counter == HANDLE_COUNT)
    {
        if (g_GetProcessHandleCount == NULL && g_NtQuerySystemInformation)
        {
            ULONG bufSize = 0;
 
            do
            {
                LONG status;
                ULONG size = 0;
 
                status = g_NtQuerySystemInformation(SystemProcessInformation, buf, bufSize, &size);
                if (status == STATUS_SUCCESS) break;
 
                if (status == STATUS_INFO_LENGTH_MISMATCH)
                {
                    if (size == 0)  // Returned required buffer size is always 0 on Windows2000.
                    {
                        if (bufSize == 0)
                        {
                            bufSize = 16384;
                        }
                        else
                        {
                            bufSize *= 2;
                        }
                    }
                    else
                    {
                        bufSize = size;
                    }
 
                    if (buf) delete [] buf;
                    buf = new BYTE[bufSize];
 
                    if (buf == NULL)  // failed
                    {
                        return 0;
                    }
                }
                else  // failed
                {
                    if (buf) delete [] buf;
                    return 0;
                }
            } while (true);
        }
    }
 
    /* ... */
 
    UINT numOfProcesses = bytesNeeded / sizeof ( DWORD );
 
    UINT resourceCount = 0;
    for ( UINT i = 0; i < numOfProcesses; ++i )
    {
        HANDLE hProcess = OpenProcess ( flags, true, aProcesses[i] );
        if ( hProcess != NULL )
        {
            /* ... */
 
            if ( counter == HANDLE_COUNT )
            {
                DWORD tempHandleCount = 0;
 
                if (g_GetProcessHandleCount)
                {
                    g_GetProcessHandleCount ( hProcess, &tempHandleCount );
                }
                else if (g_NtQuerySystemInformation)
                {
                    SYSTEM_PROCESS_INFORMATION* systemProcInfo = (SYSTEM_PROCESS_INFORMATION*)buf;
 
                    while (systemProcInfo)
                    {
                        if (aProcesses[i] == PtrToUlong(systemProcInfo->UniqueProcessId))
                        {
                            tempHandleCount = systemProcInfo->HandleCount;
                            break;
                        }
 
                        systemProcInfo = (SYSTEM_PROCESS_INFORMATION*)
                            ((systemProcInfo->NextEntryOffset == 0) ? NULL : (ULONG_PTR)systemProcInfo + systemProcInfo->NextEntryOffset);
                    }
                }
 
                resourceCount += tempHandleCount;
            }
 
            CloseHandle ( hProcess );
        }
    }
 
    if (buf)
    {
        delete [] buf;
    }
 
    return resourceCount;
}

GetProcessHandleCount APIが存在しない環境では、事前にバッファを確保して内容を取得しておきます。取得には全プロセス分のバッファが必要になりますが、プロセス数×構造体のサイズ(+α)という単純な方法ではダメなようです。必要なバッファサイズはNtQuerySystemInformation APIを呼び出すことで取得できるとドキュメントには記載されていますが、どうもWindows 2000では必ず0が返ってくるようで(XPだとちゃんと必要なバッファサイズが返ってくる)、仕方ないので、成功するまでバッファサイズを16KB分から倍々に増やしていくようにしています。

ハンドル数の取得部分では、GetProcessHandleCount APIが存在したらそちらを使い、存在しなければ取得したバッファから該当する構造体エントリを探し出し、そのHandleCountの値を使っています。

以上の変更を加えてXP(SP3)上で動かすとGetProcessHandleCount APIが使われ、Windows 2000上で動かすとNtQuerySystemInformation APIが使われました。また、XP(SP3)上でNtQuerySystemInformation APIを使っても、GetProcessHandleCount APIの結果と同じ値を取得することができました。とはいえ、GetProcessHandleCount APIが使える環境ならそちらを使うのが筋でしょう。

これでWindows 2000でもResMonプラグインが動作するようになりました。

別に存在する問題として、システム関連プロセス(csrss.exeなど)の値が取れないという問題があり、タスクマネージャで表示される合計値(たとえばハンドル数)とは数が違ってくることがあります。これは権限上の問題で、システムプロセスへのOpenProcess APIが失敗してハンドルを取得できないためです(プロセスハンドルが取れないと名前の取得ができない)。もしこれを取得できるようにするならば、例えば以下のようなコードを使ってデバッグ権限(SeDebugPrivilege)を有効にしておく必要があります。

    HANDLE hProcessToken = NULL;
 
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hProcessToken))
    {
        LUID luid;
        LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);
 
        TOKEN_PRIVILEGES n;
        n.PrivilegeCount = 1;
        n.Privileges[0].Luid = luid;
        n.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
 
        AdjustTokenPrivileges(hProcessToken, FALSE, &n, 0, NULL, NULL);
    }
    if (hProcessToken != NULL) CloseHandle(hProcessToken);

このデバッグ権限、ものすごく便利なものなのですが、これを有効にしてしまうと(権限上)いろいろとできてしまうので、自分で使うものならまだしも一般配布アプリではできるだけ付けないほうがよさそう。でもこの権限がないと名前が取れないわけで、なんとももどかしい。
こんな権限を付与しなくても、プロセスIDからそのシステムプロセスの名前が取れるような機能が別に用意されればいいんですけどねぇ……。

« [Rainmeter-dev] モニタセレクタ [Rainmeter-dev] ネットワークトラフィックの値がおかしい »

- Comments
2 Comments

どーも、kenz0です。
> OSから2000が除外されてしまうっぽい?
(一応、Forum Devs で公開している新バージョンのchmファイルには2kも載ってますが...)
私の知る限りそのようなことが議論された形跡はありませんし、単純な見落としなら指摘してやる必要がありますし、ましてやメンバー独断で決められたことなら、それ相当の説明を求めなければなりません。
近々行われるサイトリニューアルに伴い、おそらくはこのベータ版がそのままWeb版マニュアルとして採用される見通しですが、この件に関しては追って真相を正し、然るべき議論の場を設けるべきだと思います。
2K以外にもLS関連項目が完全スルーされていることもあり、これらのサポートについて今後どうするかということの議論はいずれにしろ必要になってくることだと思いますので。

SysInfoの修正に関してはそれからでも遅くないと思います。たぶん今週末には1.1のリリース版がでる予定なので、今コミットして万が一(ないとは思いますが)他の部分に問題が発生しても厄介ですし。

64bitの問題は、やはり誰かにテストしてもらう他ないのでは...
どの開発者も32bitと64bit両方でテストしている人はいないと思いますよ。

ちなみに素人考え丸出しで恐縮ですが、32bit物理マシン上の仮想PCに64bit環境って作れないものでしょうかね。

by kenz0 | 10 28, 2009 - URL [ edit ]

>kenz0さん
見てたのは↓のものでした。ただ、"will run"なので意図的に外したとは考えない方がいいのかもしれませんね。
http://rainmeter.net/ManualBeta.htm

>32bit物理マシン上の仮想PCに64bit環境って作れないものでしょうかね。
一応、64ビットCPUがあればできなくはないみたいです。(VMwareだけっぽい?)
http://www.vmware.com/jp/products/server/faqs.html

こういうのを見ると新しいPCが欲しくなりますw

by furu | 10 28, 2009 - URL [ edit ]

管理者にだけ表示を許可する
- Trackbacks
0 Trackbacks


この記事にトラックバックする(FC2ブログユーザー)

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。