システムクロック(時計の速度)を調整するプログラム(32/64bit)

icon 項目のみ表示/展開表示の切り替え

概要

Windowsの時計は、起動時に電池でバックアップされたリアルタイムクロック(RTC)の日時を読み出し、それ以降はRTCの値は取得せず、割り込みにより時刻値をカウントしています。
本プログラムは時刻値のカウントする値を修正することにより時計の進む速度を調整します。
例えば、下側の加算値を上側の加算値を2倍にすれば時計が2倍の速度で動作します。
実行には管理者権限が必要なので、管理者権限を取得せずに実行した場合は、管理者権限を要求するため管理者のパスワード入力を求めてきます。(Windows Vista,Windows 7の場合)

Windows上の時計の実装

Windowsの時計は、起動時に電池でバックアップされたリアルタイムクロック(RTC)の日時を読み出します。RTCは32.768kHz水晶振動子を元に動作しており、パソコン電源Oon/Offに関係なくカウント処理しています。
それ以降はRTCの値は取得せず、割り込みにより時刻値を規定の加算値をカウントしています。RTCの呼び出しは非常に時間がかかるのでこれを嫌っているようです。これをシステムクロックと表記しRTCと区別しています。
RTCは昔は単体のMC146818AなどのICでしたが、現在はほとんどのIOがサウスブリッジに統合されているため、パソコンによって違いがなくなってきました。RTCもサウスブリッジの中に実装されています。I/O範囲及び割り込み番号については昔から互換性があるようです。
割り込み処理ですので、CPUの負荷が異常に高い状態が続けば割り込みがうまく処理されず、時計が遅れることがあります。
割り込み間隔と加算値はGetSystemTimeAdjustment APIにより時刻の加算値とその間隔を取得できます。
パソコンのシステムによって値は異なりますが、最近のマルチコア環境では間隔が15.6m秒、加算値も15.6m秒が取得されます。
GetSystemTimeAdjustment API取得値
環境 加算値(100n秒) 間隔(100n秒)
Core i7-3820 + X79 156001 156001
Core i3 M370 + HM57 156001 156001
Pentium 4 641 + ICH7/R 156001 156001
このことより64Hzにより割り込みが発生していることがわかります。Sleep API等は1m秒単位で設定ができますが、実際の動作はこの15.6m秒単位となります。スレッドの切り替えも15.6m秒が基本となります。
時刻修正がされるとRTC及びシステムクロックともに修正されます。
時刻の加算値を変更はSetSystemTimeAdjustment APIで行います。このAPIは管理者権限が必要となります。この加算値を倍にすれば時計も倍の速度で動きます。
インターネットによる時刻修正は初期値が7日に一度となっています。
Windows Timeサービスの動作により1時間に1回、RTCとシステムクロックとの差をチェックし1分を超えるとRTCの時刻をシステムクロックに設定するなどいろいろな動作があります。
テストをしたわけではないがおおむね次の動作をしているようである。

Windows Timeサービス動作中

インターネットから時刻を取得しシステムクロックとRTCを修正(7日ごと)

Windows Timeサービスが動作をしていない場合

1時間に1回、RTCとシステムクロックを比較し1分以上の差がある場合RTCの日時をシステムクロックに上書きする。

プログラムに管理者権限を要求するようにマニフェストを修正

2014.07.24

Visual C++のプロジェクトのプロパティを開き、構成のプロパティの中のリンカ・マニフェストファイルを選びます。
UACの実行レベルをrequireAdministratorに変更します。

テスト環境

コンパイラ

Visual C++ 2008/2013 Express 32/64bit マルチバイト/UNICODE

実行環境

Windows XP Professional Service Pack 3 32bit(Virtual Box上の仮想マシーン)
Windows 7 Enterprise Service Pack 1 64bit

プログラムソースの概要

systimeadj.cpp

WinMain

管理者権限を取得し、ダイアログボックスを開きます。
管理者権限が取得できなかった場合は、時刻加算値の修正はできません。
Windows 7、Windows Vistaの場合、管理者のパスワードを要求するようにしています。

DlgProc

WM_INITDIALOG
ダイアログボックスの初期化時に呼び出されます。
GetSystemTimeAdjustment APIにより時刻の加算値とその間隔を取得します。
管理者権限が取得できていない場合は、一部のコントロールを非アクティブにします。
WM_COMMAND
各ボタンがクリックされた場合とエディットコントロールのテキストが変更された場合に呼び出されます。
IDC_ADDSET_EDIT
加算値が修正されたら呼び出されます。メッセージをチェックし加算値から時計の速度の比率と1時間あたりの時計の進む秒数を計算して表示します。
IDC_QUIT_BUTTON
終了ボタンがクリックした場合に呼び出され、EndDialog APIによりダイアログボックスを閉じます。
IDC_ADDSET_BUTTON
加算値修正ボタンがクリックされた場合に呼び出されます。
加算値を取得し、SetSystemTimeAdjustment APIにより加算値を修正します。
IDC_ORGSET_BUTTON
初期値設定ボタンをクリックされた場合に呼び出され、加算値をSetSystemTimeAdjustment APIにより通常値に戻します。

set_privilege

管理者権限の取得及び解放を実施します。
管理者権限が取得できない場合はFALSEを返します。

ソースコード

systimeadj.cpp


//      システムタイムの更新間隔及び加算値を取得・設定 GetSystemTimeAdjustment,SetSystemTimeAdjustment
//      2014/7/23

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <commctrl.h>
#include "resource.h"

#pragma comment(lib,"comctl32.lib")

//      ダイアログボックスプロシージャー
LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

//      管理者権限の設定
BOOL set_privileges(LPTSTR name, BOOL bEnable=TRUE);

bool privileges_flag=true;      //      管理者権限を取得できている場合true

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,LPSTR lpsCmdLine, int nCmdShow){
        InitCommonControls();

        if(set_privileges(SE_SYSTEMTIME_NAME)==0){
                MessageBox(0,_TEXT("時刻の加算値を修正するためには管理者権限で実行する必要があります。"),_TEXT("エラー"),MB_OK);
                privileges_flag=false;
        }


        DialogBox(hCurInst, TEXT("DLG1"), 0, (DLGPROC)DlgProc);
        if(privileges_flag==true)
                set_privileges(SE_SYSTEMTIME_NAME,FALSE);
        return (int)0;
}

//      ダイアログボックスプロシージャー

LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
        static DWORD OrgTimeAdjustment,OrgTimeIncrement;
        static BOOL     OrgTimeAdjustmentDisabled;
        static DWORD TimeAdjustment,TimeIncrement;
        static BOOL     TimeAdjustmentDisabled;
        TCHAR buf[256];

        switch (msg) {
                case WM_INITDIALOG:{
                        if(GetSystemTimeAdjustment(&OrgTimeAdjustment , &OrgTimeIncrement , &OrgTimeAdjustmentDisabled )){
                                _stprintf_s(buf,sizeof(buf)/sizeof(TCHAR),_TEXT("%.4fm秒"),double(OrgTimeAdjustment)/10000.0);
                                SetWindowText(GetDlgItem(hDlg,IDC_ORGADD_EDIT),buf);
                                if(privileges_flag==true){
                                        SetWindowText(GetDlgItem(hDlg,IDC_ADDSET_EDIT),buf);
                                        _stprintf_s(buf,sizeof(buf)/sizeof(TCHAR),_TEXT("%.4fm秒"),double(OrgTimeIncrement)/10000.0);
                                }else{
                                        EnableWindow(GetDlgItem(hDlg,IDC_ADDSET_EDIT),FALSE);
                                        EnableWindow(GetDlgItem(hDlg,IDC_ADDSET_BUTTON),FALSE);
                                        EnableWindow(GetDlgItem(hDlg,IDC_ORGSET_BUTTON),FALSE);
                                }
                                SetWindowText(GetDlgItem(hDlg,IDC_ORGINT_EDIT),buf);
                                _stprintf_s(buf,sizeof(buf)/sizeof(TCHAR),_TEXT("%s"), OrgTimeAdjustmentDisabled==TRUE ? _TEXT("TRUE") : _TEXT("FALSE"));
                                SetWindowText(GetDlgItem(hDlg,IDC_ORGF_EDIT),buf);

                        }else{
                                MessageBox(0,_TEXT("GetSystemTimeAdjustment関数の実行に失敗しました。"),_TEXT("エラー"),MB_OK);
                                EndDialog(hDlg,FALSE);
                        }
                        return TRUE;
                }
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                                case IDC_ADDSET_EDIT:{
                                        if(HIWORD(wParam)==EN_CHANGE){  // エディットボックスのテキストがが変更された
                                                GetWindowText(GetDlgItem(hDlg,IDC_ADDSET_EDIT),buf,sizeof(buf)/sizeof(TCHAR));
                                                double v=_tcstod(buf,NULL)*10000.0;
                                                double org=OrgTimeIncrement;
                                                double s=v/org*3600-3600;
                                                _stprintf_s(buf,sizeof(buf)/sizeof(TCHAR),_TEXT("時計速度%.5f倍 進み%.2f秒/h"), v/org , s);
                                                SetWindowText(GetDlgItem(hDlg,IDC_TIMEDIFF_LABEL),buf);
                                        }
                                        return FALSE;
                                }
                                case IDC_QUIT_BUTTON:{
                                        EndDialog(hDlg,TRUE);
                                        return TRUE;
                                }
                                case IDC_ADDSET_BUTTON:{
                                        GetWindowText(GetDlgItem(hDlg,IDC_ADDSET_EDIT),buf,sizeof(buf)/sizeof(TCHAR));
                                        double v=_tcstod(buf,NULL);
                                        TimeIncrement=(DWORD)(v*10000);
                                        if(SetSystemTimeAdjustment(TimeIncrement,FALSE)==0){
                                                MessageBox(0,_TEXT("SetSystemTimeAdjustment関数の実行に失敗しました。"),_TEXT("エラー"),MB_YESNO);
                                                EndDialog(hDlg,FALSE);
                                                return FALSE;
                                        }
                                        return FALSE;
                                }
                                case IDC_ORGSET_BUTTON:{
                                        _stprintf_s(buf,sizeof(buf)/sizeof(TCHAR),_TEXT("%.4fm秒"),double(OrgTimeAdjustment)/10000.0);
                                        SetWindowText(GetDlgItem(hDlg,IDC_ADDSET_EDIT),buf);
                                        if(SetSystemTimeAdjustment(OrgTimeIncrement,OrgTimeAdjustmentDisabled)==0){
                                                MessageBox(0,_TEXT("SetSystemTimeAdjustment関数の実行に失敗しました。"),_TEXT("エラー"),MB_YESNO);
                                                EndDialog(hDlg,FALSE);
                                                return FALSE;
                                        }
                                        return TRUE;
                                }
                                default:
                                        return FALSE;

                        }
                        default:
                                return FALSE;
        }
        return TRUE;
}

//      管理者権限の設定

BOOL set_privileges(LPTSTR name, BOOL bEnable){
    HANDLE hToken;
    LUID luid;
    TOKEN_PRIVILEGES tokenp;
    BOOL bRet;

    //  プロセストークンを取得
    bRet = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
    if(!bRet)
        return FALSE;
    //  特権に対応するLUIDを取得
    bRet = LookupPrivilegeValue(NULL, name, &luid);
    if(bRet){

        //      LUIDと特権の属性を指定する
        tokenp.PrivilegeCount = 1;
        tokenp.Privileges[0].Luid = luid;
        tokenp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;

        //      特権を有効にする
        AdjustTokenPrivileges(hToken, FALSE, &tokenp, 0, 0, 0);
        bRet = GetLastError() == ERROR_SUCCESS;
    }
    CloseHandle(hToken);
    return bRet;
}

resource.h


#define IDC_ADDSET_BUTTON               113
#define IDC_ORGSET_BUTTON               114
#define IDC_QUIT_BUTTON         115
#define IDC_TIMEDIFF_LABEL              119

#define IDC_ORGADD_EDIT 200
#define IDC_ORGINT_EDIT 210
#define IDC_ORGF_EDIT 220
#define IDC_ADDSET_EDIT 230

resource.rc


#include <windows.h>
#include "resource.h"

DLG1 DIALOG DISCARDABLE 0, 0, 289, 116
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP| WS_CAPTION | WS_SYSMENU  | DS_SETFONT
CAPTION "SysTimeAdj"
FONT 9, "MS Shell Dlg"
{
 CONTROL "GetSystemTimeAdjustment関数による取得値", -1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 7, 7, 229, 12

 CONTROL "加算値", -1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 14, 26, 32, 17
 CONTROL "", IDC_ORGADD_EDIT, "EDIT", WS_CHILD | WS_DLGFRAME | WS_VISIBLE | ES_AUTOHSCROLL, 46, 24, 48, 12
 CONTROL "間隔", -1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 108, 26, 32, 12
 CONTROL "", IDC_ORGINT_EDIT, "EDIT", WS_CHILD | WS_DLGFRAME | WS_VISIBLE | ES_AUTOHSCROLL, 140, 24, 48, 12
 CONTROL "状態", -1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 202, 26, 32, 18
 CONTROL "", IDC_ORGF_EDIT, "EDIT", WS_CHILD | WS_DLGFRAME | WS_VISIBLE | ES_AUTOHSCROLL, 234, 24, 48, 12

 CONTROL "SetSystemTimeAdjustment関数設定値", -1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 7, 50, 200, 12
 CONTROL "加算値", -1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 14, 69, 32, 12
 CONTROL "", IDC_ADDSET_EDIT, "EDIT", WS_CHILD | WS_DLGFRAME | WS_VISIBLE | ES_AUTOHSCROLL, 46, 67, 48, 12
 CONTROL "", IDC_TIMEDIFF_LABEL, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 108, 69, 174, 12

 CONTROL "加算値修正", IDC_ADDSET_BUTTON, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 9, 95, 64, 14
 CONTROL "初期値を設定", IDC_ORGSET_BUTTON, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 112, 95, 64, 14
 CONTROL "終了", IDC_QUIT_BUTTON, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 216, 95, 64, 14
}

実行ファイルとソースファイルのダウンロード