WinAPI|SetThreadExecutionStateを使ってスリープなどへの移行を防ぐ
WinAPIを使って、アプリ起動中に
- スリープモード
- ディスプレイの自動電源OFF
- スクリーンセーバー
への移行を抑止する方法について、調べて実験した内容を書いていきます。
SetThreadExecutionState API
Win32APIのSetThreadExecutionStateAPIをコールすることで、スリープモードなどの移行を抑止できます。
何をどのように抑止するかは引数で決めることができます。
// スタンバイ移行までのタイマーをリセットする // caution: 数十秒に1回ほど,繰り返し呼び出す必要がある ::SetThreadExecutionState(ES_SYSTEM_REQUIRED); // スタンバイを抑止 // memo: AppのInitInstanceなどで1度呼べばOK ::SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_CONTINUOUS); // ディスプレイ自動OFFやスクリーンセーバーの突入までのタイマーをリセットする // caution: 数十秒に1回ほど,繰り返し呼び出す必要がある ::SetThreadExecutionState(ES_DISPLAY_REQUIRED); // ディスプレイ自動OFFやスクリーンセーバーの突入を抑止 // memo: AppのInitInstanceなどで1度呼べばOK ::SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS); // 抑止を解除 ::SetThreadExecutionState(ES_CONTINUOUS);
引数
| 引数 | 説明 | 使いどころ |
|---|---|---|
ES_SYSTEM_REQUIRED |
システムがシステムアイドルタイマをリセットして、コンピュータをスリープ状態にするのを防ぐことができる。 | 処理中などスリープしてほしくない時 |
ES_DISPLAY_REQUIRED |
ディスプレイのアイドルタイマーをリセットして、ディスプレイを強制的にオンにします。 | ビデオ再生など,長時間ユーザー操作がない状態 |
ES_CONTINUOUS |
次にES_CONTINUOUSを呼び出すまで,実行を維持する必要があることをシステムに通知する。 |
1回の呼び出しで済ませたいとき,抑制を解除するとき |
戻り値
| 成功 | 前のスレッド実行状態を返す |
| 失敗 | NULL |
注意事項
- 自動的な突入の回避は可能だが,ユーザー操作によるスリープは検知不可能
- このAPIが効かないPCがある
- アプリ終了時,抑止するアプリがいなくなったことで自動的にスリープ抑止の解除が行われるが,明示的に抑止解除のコードを明記しておいた方がお行儀がいい。
ES_DISPLAY_REQUIREDは|ES_CONTINUOUSとセットで使えないとの記事が多く見受けられたが,私の環境では正常に動いた。理由は調査中。
テストプログラムで実験
電源要求を確認
アプリケーションとドライバーの電源要求がないか,コマンドプロンプト(管理者権限)で以下のコマンドを打って確認します。
powercfg /requests
電源要求があると、スリープやディスプレイの自動電源OFFが働きませんので,"ない"状態で試しましょう。
テストプログラムを実行
簡単なテストプログラムを作り,自動突入を防げるか確認。
コードはGitHubに載せたので割愛します。
github.com
結果
以下のテストを行い,すべてパスした。
SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_CONTINUOUS)をコールしてシステムスリープ移行の抑止 |
OK |
SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS)をコールしてディスプレイの自動電源OFF移行の抑止 |
OK |
SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS)をコールしてスクリーンセーバー移行の抑止 |
OK |
SetThreadExecutionState(ES_SYSTEM_REQUIRED)をTimerで30秒ごとにコールしてシステムスリープ移行の抑止 |
OK |
SetThreadExecutionState(ES_DISPLAY_REQUIRED)をTimerで30秒ごとにコールしてディスプレイの自動電源OFFの抑止 |
OK |
SetThreadExecutionState(ES_DISPLAY_REQUIRED)をTimerで30秒ごとにコールしてスクリーンセーバー移行の抑止 |
OK |
SetThreadExecutionState(ES_CONTINUOUS)をコールして抑止の解除→Windowsの設定通りの動作をする |
OK |
ではまた。
written by @mogmo1012
.NET CoreでShift JISを扱うためのおまじない
.NET Frameworkでは問題なく扱えるが,
.NET Coreの場合はひと手間加えてやらないとShift JISが扱えない。
ひと手間加えないとどうなるか
using (var reader = new StreamReader(file, System.Text.Encoding.GetEncoding("Shift_JIS"))) { // ... }
このコードだと,ビルドは通るが,実行時に以下のような例外が発生する。
ArgumentException: 'shift_jis' is not a supported encoding name. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method.
対策
Encoding.RegisterProviderをShift JISを扱う前にコールすることで,Windowsのシステムが提供するエンコーディングの全てを利用できるようになる。
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); // memo: Shift-JISを扱うためのおまじない using (var reader = new StreamReader(file, System.Text.Encoding.GetEncoding("Shift_JIS"))) { // ... }
それではまた。
written by @mogmo1012
std::filesystemのcreate_directories関数を使おうとしたらはまった
std::filesystems::create_directories()を下のコードのようにして書いたら,Debug版では複数ディレクトリの作成に成功するが,Release版では作成できなくなるという現象に出会った。
bool CreateDirectories() { CString path = _T("C:\\DummyDir\\DummySubDir\\Test"); return std::filesystem::create_directories((LPCTSTR)path); // 成功した場合trueを返す }
現象
返り値はtrueなのにフォルダが生成できていない現象が起きてしまいす。何度やっても結果は同じ...
自分なりの原因究明
知人と話し合い予想した原因を記しておく。
この
std::filesystem::create_directories関数はオーバーロード関数となっていて,この時使っていたのは引数がひとつしかないstd::filesystem::create_directories(std::filesystem::path path)です。
受け付ける引数の型がstd::filesystem::path型なのに対し,(LPCTSTR)pathという形で入力していたため,std::filesystem::pathのコンストラクタで例外が発生してしまったことが原因?で今回のような現象が発生したと思われる...
でもその考察だと,次の章で試した3,4番の方法は失敗するはずなのでは?と思った私。。。
うーん。わからん。
正しく言語化できているか自信ないのと,本当に原因がこれなのか自信がない。
とりあえずの解決方法
5の方法が確実と判断する。
bool CreateDirectories() { CString path = _T("C:\\DummyDir\\DummySubDir\\Test"); // 1: failed: これだとできない //return std::filesystem::create_directories((LPCTSTR)path); // 2: pass: path型に変換してから引数に入力すると問題なくフォルダが生成された。 std::filesystem::path path1(path.GetString()); return std::filesystem::create_directories(path1); // 3: pass: なぜかこれは成功する std::filesystem::path path2(path.GetString()); bool result3 = std::filesystem::create_directories(path2); return result3; // 4: pass: なぜかこれも成功する std::error_code errCode; return std::filesystem::create_directories((LPCTSTR)path, errCode); // 5: pass: これが一番安全 std::filesystem::path path5(path.GetString()); std::error_code errCode5; bool result5 = std::filesystem::create_directories(path5, errCode5); return result5; }
VC++のUnicodeコンソールアプリケーションで作るときの初期処理
mainではなくwmainを使う
ユニコードアプリの場合,wmain( int argc, wchar_t *argv[ ], wchar_t *envp[ ] )を使用します。
デフォルトのコードではmainとなっているので,関数名と引数を適宜変更しましょう。
コマンドライン引数も,ユニコードの場合はワイド文字列データ型のwchar_tにしましょう。
Debug版のアプリケーションは起動するが,リリース版で起動せず終了してしまう現象が発生。
コンソールに日本語出力できるようにする
wprintfやstd::wcoutを使ってコンソールに文字列を出力しますが,ユニコードアプリの時はひと手間加えてあげる必要があります。
また,どちらか片方を使うように統一したほうが良さそうです。
wprintfを使用する
setlocale(LC_ALL, "japanese");ロケール設定を日本にすると,日本語が出力されます。
int wmain() { int nRetCode = 0; HMODULE hModule = ::GetModuleHandle(nullptr); wprintf(_T("ぐんないわーるど\n)"); // うつらない setlocale(LC_ALL, "japanese"); // memo: add: wprintfでShift-JISを出力するためのおまじない【注意】std:coutで日本語出力はされなくなる。 wprintf(_T("はろーわーるど\n)"); // 表示される ... }
std::wcoutを使う
必要に応じてstd::wcout.imbue(std::locale("japanese"))を設定してあげると日本語が表示されるようになる。
int wmain() { int nRetCode = 0; HMODULE hModule = ::GetModuleHandle(nullptr); std::wcout << _T("はろーわーるどできん") << std::endl; // 表示されるか確認 std::wcout.imbue(std::locale("japanese")); // std::wcout.imbue(std::locale("ja")); // これでもいい std::wcout << _T("はろーわーるど!") << std::endl; // 表示される ... }
引数を受けとる
wmain関数の引数を以下のように設定します。
arcCountはarcValue[]の要素数,arcValue[]は実際の文字列が入力されます。
arcValue[0]は必ず自身のアプリケーション名になるので,必ず1以上の値になります。
ドラッグアンドドロップで受け付けたフォルダパスなどは2番目の配列に入ります。
int wmain(int argCount, wchar_t* argValue[])
キーを入力するまでポーズする
キー入力を受け付けるわけではなく,単純にポーズさせたい時はsystem("pause")でポーズすることができます。
system("pause");
補足:system関数(ワイド文字列を使う場合は_wsystem)はプログラム内から別のプログラムを起動するための関数です。
引数にはコマンドプロンプトで打ち込む文字列と同様の文字を入力してあげます。
文字列を扱うときはマクロを使う
万が一プロジェクトの文字セットを変更が発生したり,記述ミスをしてしまったときのために,以下の内容はマクロを使って定義しておくと安心です。
データ型
| 移植性のないデータ型 | 置き換えに使うマクロ |
|---|---|
| char, wchar_t | _TCHAR |
| char*、 LPSTR (Win32 データ型)、 LPWSTR | LPTSTR |
| const char*、 LPCSTR (Win32 データ型)、 LPCWSTR | LPCTSTR |
文字列
_T("ハローワールド")と定義しておけば条件によってユニコード文字列として扱ってくれます。
CString
CStringは_TCHARをベースとして使用しているため,文字セットをさほど気にせず扱うことができます。
具体的には,MBCSシンボルまたはUNICODEシンボルがコンパイル時に定義されているかどうかにしたがって、char型またはwchar_t型のいずれかをサポートする動作のようです。
引用:
CString の使用 | Microsoft Docs
MFCを使ったユニコード文字セットのWindowsコンソールアプリケーションを作る
VS2019でGoogle TestとGoogle Mockを導入する
C++の単体テストをやりたいから、Google Testプロジェクトテンプレートを使ってテスト環境を整えてみるよ!
Google TestとGoogle Mockってなに?
| Google Test | ユニットテストフレームワークの 1 種です。正式名称は、Google C++ Testing Frameworkといい、開発環境に依存しないテストができるようになります。 |
|---|---|
| Google Mock | モックフレームワークの 1 種です。正式名称は、Google C++ Mocking Frameworkといい、モックオブジェクトを作成できるようになることで、より柔軟なテストが可能になります。 |
Google Test プロジェクトテンプレートを使えるようにする
Visual Studio Installer を起動して、[C++ によるデスクトップ開発]の[Test Adapter for Google Test]にチェックをつけてインストールする。

参考:docs.microsoft.com
Google Testプロジェクトテンプレートを使って新規プロジェクトを作成する
Visual Studio 2019を起動して、[Google Test]を選択し、[次へ]をクリックする。

プロジェクト構成を設定して、[作成]をクリックする。
私の場合、プロジェクト名は「テストするプロジェクト名 + "Test"」とすることが多い。
Google MockをNuGetパッケージからインストールする
[ソリューション エクスプローラー]から先ほど追加したテストプロジェクトの[参照]を右クリックし、コンテキストメニューから[NuGetパッケージの管理]をクリックして[Nuget: (プロジェクト名)]タブを開く。
[参照]タブで"googlemock"を検索し、ライブラリをダウンロードする。
今回は 「googlemock.v140.windesktop.static.rt-dyn」の安定版「Ver. 1.7.0.1」をインストールした。
インストール後の[Nuget: (プロジェクト名)]タブの[インストール済み]タブは下のような画面になる。

フォルダ名を変更する
googlemock.v140.windesktop.static.rt-dyn の場合、パッケージのincludeディレクトリの直下のフォルダ名が「gtest」になっているので、「gmock」に変更する。
gtest.hとgmock.hをインクルードする
テストプロジェクトのcppファイルに下の 2 行を追加してビルドが通れば導入準備は完了です٩(ˊᗜˋ*)و
#include "gtest/gtest.h" #include "gmock/gmock.h"
CString引数の渡し方
12月までMFCのアプリを作ることになりました。
@mogmo1012です。
久しぶりにMFC触るとCStringの扱いかたをいつも忘れてしまう……
値渡し (CString text)
そのまま書くと値渡しになる。
長い文字列を渡すとメモリ食う。
void CSampleClass::A(){ text = "aaa"; Convert(text); std::cout << text << std::endl; // output: "aaa" } void CSampleClass::Convert(CString text){ text = "bbb" }
参照渡し (CString& text)
引数に値を渡せば、その変数のアドレスを使って処理してくれる。
関数内で書き換えたくない場合はconstをつける。
例)foo(const CString& text)
void CSampleClass::A(){ text = "aaa"; Convert(text); std::cout << text << std::endl; // output: "bbb" } void CSampleClass::Convert(CString& text){ text = "bbb" }
値渡し、ポインタ渡し、参照渡しについて
参考URLを2つ張っておきます。
cpp-lang.sevendays-study.com
qiita.com










