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

スポンサーサイト

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

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

[Rainmeter-dev] いろいろ

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

0 Comments

こまごまとした修正点や改善点のまとめです。
私的なまとめですので、公開されているビルドに反映されているものではありません。ご注意ください。

■更新履歴(※更新のたびに、たぶん一番上にあがってきます)
- 2009.04.20 : NetIn/NetOutでInterfaceを指定してCumulative=1とすると、正常な値が表示されない
- 2009.11.06 : Meter=STRINGでPercentual=1のときに、NumOfDecimalsの設定が有効にならない
- 2009.11.06 : Meter=STRINGでNumOfDecimals=0のときに、32ビットを越える値を表示できない
- 2009.11.06 : PerfMonプラグインで、32ビットを越える値を取得できない
- 2009.11.10 : ログに多バイト文字を出力できない(2009.11.29 追記)

以下、続きに格納。

■NetIn/NetOutでInterfaceを指定してCumulative=1とすると、正常な値が表示されない

NetInとNetOutの総転送量を表示しようとして、Interface=1、Cumulative=1を指定してみたものの、どうも実際の転送量とは違う値が表示されるんです。Cumulative=0のものは明らかにIn>Outになっているのに、=1だと何故かInもOutも同じ値になってるんですよね。
挙動として変だし、確認のためRainmeter.iniに書き出されたStatを見てみると……どうやら2つ記録されているうちの2つ目の値と同じっぽい。でも、表示したいのは1つ目のほう(Interface=1を指定してるのだから)。こちらは実際の転送量とあってそう。

Interface=1で2つ目が取れてるということは、じゃあ1つ目はInterface=0にしたら取れる?
……というわけにはいきませんね。Interface=0にすると、全インターフェースの合計になっちゃいます。

じゃあ「消えた1つ目」はどうやって取得すればいいの?ということで……。

処理の流れとかの細かいところはすっ飛ばして、該当部分はMeasureNet::GetNetStatsValue関数にあります(221-242行目)。
Interfaceが0でなかったときの処理ですが、下のコードだと、m_Interfaceには1以上の値が入っているので、どう頑張ってもc_StatValues[0]と[1]にアクセスできません。

    else
    {
        // Get the selected interface
        if (m_Interface <= c_StatValues.size() / 2)
        {
            switch (net)
            {
            case NET_IN:
                value.QuadPart += c_StatValues[m_Interface * 2 + 0].QuadPart;
                break;
 
            case NET_OUT:
                value.QuadPart += c_StatValues[m_Interface * 2 + 1].QuadPart;
                break;
 
            case NET_TOTAL:
                value.QuadPart += c_StatValues[m_Interface * 2 + 0].QuadPart;
                value.QuadPart += c_StatValues[m_Interface * 2 + 1].QuadPart;
                break;
            }
        }
    }

本来のInterface=1の値はc_StatValues[0](=in)、c_StatValues[1](=out)に入っていて、以降も同じ順に値が格納されていっています。やっぱり、先ほど表示された値はInterface=2のものだったわけですね。Rainmeter.iniの値とも合います。

これだと1つズレて表示されてしまうので、以下のように修正します。

    else
    {
        // Get the selected interface
        if (m_Interface <= c_StatValues.size() / 2)
        {
            switch (net)
            {
            case NET_IN:
                value.QuadPart += c_StatValues[(m_Interface - 1) * 2 + 0].QuadPart;
                break;
 
            case NET_OUT:
                value.QuadPart += c_StatValues[(m_Interface - 1) * 2 + 1].QuadPart;
                break;
 
            case NET_TOTAL:
                value.QuadPart += c_StatValues[(m_Interface - 1) * 2 + 0].QuadPart;
                value.QuadPart += c_StatValues[(m_Interface - 1) * 2 + 1].QuadPart;
                break;
            }
        }
    }

そのまんまですね。これで1つ目を取ってこれるようになりました。

* * *

■Meter=STRINGでPercentual=1のときに、NumOfDecimalsの設定が有効にならない

例えば、メモリ使用率を小数点以下も表示したくてNumOfDecimals=1と設定しても、その指定は無視されて、整数で表示されます。
数値から文字列に変換する処理はCMeasure::GetStringValue関数で行っています。そのうち、Percentual=1だったときの該当部分は下のようになってます。

    if(percentual)
    {
        swprintf(buffer, L"%i", (UINT)(100.0 * GetRelativeValue()));
    }

計算は浮動小数点で行っていますが、整数型にキャストしています。

この部分を、NumOfDecimalsを使っている部分を参考に書き直し、小数点以下部分を有効にしてみます。

    if(percentual)
    {
        if (decimals == 0)
        {
            swprintf(buffer, L"%i", (UINT)(100.0 * GetRelativeValue()));
        } 
        else
        {
            swprintf(buffer2, L"%%.%if", decimals);
            swprintf(buffer, buffer2, 100.0 * GetRelativeValue());
        }
    }

NumOfDecimals=0なら今まで通りの処理をして、それ以外なら指定桁数まで有効にするために書式を弄っています(NumOfDecimals=2なら、"%.2f"となるように)。

小数点以下まで有効にすると、もしかしたらたまに100%を越えたりとか、満たないとかそういったこともあるかもしれませんが……。

* * *

■Meter=STRINGでNumOfDecimals=0のときに、32ビットを越える値を表示できない

HDDの容量などをAutoScaleなどを使わずに表示してみるとよくわかりますが、NumOfDecimals=0で32ビットを越える値を表示しようとするとオーバーフローして表示がおかしくなります(マイナスになったり)。
これも上記のCMeasure::GetStringValue関数の処理によって起こります。

        if(decimals == 0)
        {
            double val = theValue * (1.0 / scale);
            val = (val + ( (val >= 0) ? 0.5 : -0.5 ) );
            swprintf(buffer, L"%i", (UINT)val);
        }
        else
        {
            swprintf(buffer2, L"%%.%if", decimals);
            swprintf(buffer, buffer2, theValue * (1.0 / scale));
        }

NumOfDecimals=0でなければ浮動小数点が使われるので、32ビットを越えても保持されていますが、=0のときは最終的にUINTにキャストされているので、オーバーフローします。この部分を書き換えてやります。

        if(decimals == 0)
        {
            double val = theValue * (1.0 / scale);
            val = (val + ( (val >= 0) ? 0.5 : -0.5 ) );
            swprintf(buffer, L"%lli", (LONGLONG)val);  // for value over 32bits
        }

LONGLONGにキャストして、書式指定子は"%lli"を使っています。

* * *

■PerfMonプラグインで、32ビットを越える値を取得できない(from "Issue 113")

単純に、RainmeterとPerfMonプラグインの間のやりとりがUINTで行われてるからです。以下、PerfData.cpp内の該当部分。

/*
  This function is called when new value should be measured.
  The function returns the new value.
*/
UINT Update(UINT id)
{
    UINT value = 0;
 
    std::map<UINT, PerfMeasure*>::iterator i = g_Measures.find(id);
    if(i != g_Measures.end())
    {
        PerfMeasure* measure = (*i).second;
 
        if(measure)
        {
            // Check the platform
            OSVERSIONINFO osvi;
            ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
            osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
            if(GetVersionEx((OSVERSIONINFO*)&osvi) && osvi.dwPlatformId == VER_PLATFORM_WIN32_NT && osvi.dwMajorVersion > 4)
            {
                ULONGLONG longvalue = 0;
                longvalue = GetPerfData(measure->ObjectName.c_str(), 
                                        measure->InstanceName.c_str(), 
                                        measure->CounterName.c_str());
 
                if(measure->Difference)
                {
                    // Compare with the old value
                    if(!measure->FirstTime) 
                    {
                        value = (UINT)(longvalue - measure->OldValue);
                    }
                    measure->OldValue = longvalue;
                    measure->FirstTime = false;
                }
                else
                {
                    value = (UINT)longvalue;
                }
            }
            else
            {
                LSLog(LOG_DEBUG, L"Rainmeter", L"PerfMon plugin works only in Win2K/XP.");
            }
        }
    }
 
    return value;
}

Rainmeterとプラグインの間のやりとりは、UINTで行うUpdate関数のほかに、doubleで行うUpdate2関数が用意されています。そっちを使うようにすれば、32ビットを越えた値でも受け渡せます。Update2関数を使うようにして、赤字の部分をdoubleに置き換えるだけです。
(PerfData.hにエクスポート関数の指定があるので、そこもUpdate関数からUpdate2関数へ書き換えが必要です)

* * *

■ログに多バイト文字を出力できない

このせいで、ログがたまに空白行になってたりします。英語圏の人にとっては何も問題ないのですが、多バイト文字を多用する環境では厄介です(WebParserをDebug=1で使っても何も出力されないとか)。場所はLitestep.cppのLSLog関数です。オリジナルはこんな感じ。

        FILE* logFile;
        std::wstring logfile = Rainmeter->GetLogFile();
        if (logFound == 0)
        {
            // Check if the file exists
            logFile = _wfopen(logfile.c_str(), L"r");
            if (logFile)
            {
                logFound = 1;
                fclose(logFile);
 
                // Clear the file
                logFile = _wfopen(logfile.c_str(), L"w");
                fputwc(0xFEFF, logFile);
                fclose(logFile);
            }
            else
            {
                logFound = 2;
            }
        }
 
        if (logFound == 1)
        {
            logFile = _wfopen(logfile.c_str(), L"a+");
            if (logFile)
            {
                /* ログの出力 */
 
                fclose(logFile);
            }
        }

ログファイルを書く際には、UnicodeのBOM(0xFEFF)を書き出そうとしていることから、Unicodeで出力するのかなと思いきや、実際に書き出されるエンコードはANSIです。そもそもBOM自体書き出されません("w"でなく"wb"にすればBOMは書き出せたけど、実際のログ出力はANSIのまま)。

◎回避策 その1
この問題を解消するには、ログ出力前にsetlocaleしてやればいいのですが、Rainmeterで使用しているライブラリの中でもsetlocaleされているので、そちらに支障がでても困ります。仕方ないのでログ出力のときだけ、一時的に多バイト文字が出力できるようにsetlocaleして、出力が終わったら元に戻しておきます。

        if (logFound == 1)
        {
            logFile = _wfopen(logfile.c_str(), L"a+");
            if (logFile)
            {
                char* oldLocale = setlocale(LC_CTYPE, NULL);
                setlocale(LC_CTYPE, "");
 
                /* ログの出力 */
 
                fclose(logFile);
 
                setlocale(LC_CTYPE, oldLocale);
            }
        }

setlocale(LC_CTYPE, "")を実行すると、既定値に設定されるらしい。

◎回避策 その2 (2009.11.29 追記)
VC2005からは、fopen(または_wfopen)の引数にエンコードを指定できるようになっているらしい。例えば、

    logFile = _wfopen(logfile.c_str(), L"a+, ccs=UNICODE");

と書けば、そのファイルへはUnicodeで出力してくれるようになるらしい(エンコードを指定しても、基本的にはBOMによる自動検知)。

ログファイルへの書き出しにUTF-16LEを使うと英語圏ではサイズが倍になってしまうので、ここではUTF-8を指定してみます。0xFEFFを出力している部分は削除しておきます。

        if (logFound == 1)
        {
            logFile = _wfopen(logfile.c_str(), L"a+, ccs=UTF-8");
            if (logFile)
            {
                /* ログを出力 */
 
                fclose(logFile);
            }
        }

Rainmeterでは起動のたびにログファイルを作り直しているので、起動し直すと、ログファイルの先頭にUTF-8のBOM(0xEFBBBF)が書き出され、ログもUTF-8で出力されます。

* * *

これで多バイト文字が出力できる……と思いきや、WebParserからの出力が文字化けします。見てみると、どうやらWebParserのログ出力部分は、1バイト文字列を無理矢理2バイト文字列として出力しているだけでした(tmpStrはstd::string)。

                            wsprintf(buffer, L"WebParser: (Index %2d) %hs", i, tmpStr.c_str());
                            Log(buffer);

これじゃ化けて当然なので、ちゃんと変換してから出力するようにします。

                            wsprintf(buffer, L"WebParser: (Index %2d) %s", i, ConvertToWide(tmpStr.c_str()).c_str());
                            Log(buffer);

これで化けなくなりました。

« [Rainmeter-dev] マルチモニタ対応 デスクトップウィンドウとシェルウィンドウ »

- Comments
0 Comments

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


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

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