mogmo .NET

C#/XAML/VB6たまにC++についてメモ程度に書いていく。あくまで自分用。責任は取れません。

WinAPI|SetThreadExecutionStateを使ってスリープなどへの移行を防ぐ

WinAPIを使って、アプリ起動中に

への移行を抑止する方法について、調べて実験した内容を書いていきます。

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);

docs.microsoft.com

引数

引数 説明 使いどころ
ES_SYSTEM_REQUIRED システムがシステムアイドルタイマをリセットして、コンピュータをスリープ状態にするのを防ぐことができる。 処理中などスリープしてほしくない時
ES_DISPLAY_REQUIRED ディスプレイのアイドルタイマーをリセットして、ディスプレイを強制的にオンにします。 ビデオ再生など,長時間ユーザー操作がない状態
ES_CONTINUOUS 次にES_CONTINUOUSを呼び出すまで,実行を維持する必要があることをシステムに通知する。 1回の呼び出しで済ませたいとき,抑制を解除するとき

戻り値

成功 前のスレッド実行状態を返す
失敗 NULL

タイマーを使う場合

Windowsの設定の最小単位が1分なので,タイマー間隔は30秒程度にしておけば問題ないと思われる。
ただし,APIを使えば細かい1分以下の設定もできるようなので,要件によってポーリング間隔を調整すると良い。
f:id:mogmo811:20201228190453j:plain
f:id:mogmo811:20201228190519j:plain

注意事項

  • 自動的な突入の回避は可能だが,ユーザー操作によるスリープは検知不可能
  • このAPIが効かないPCがある
  • アプリ終了時,抑止するアプリがいなくなったことで自動的にスリープ抑止の解除が行われるが,明示的に抑止解除のコードを明記しておいた方がお行儀がいい
  • ES_DISPLAY_REQUIREDは|ES_CONTINUOUSとセットで使えないとの記事が多く見受けられたが,私の環境では正常に動いた。理由は調査中。

テストプログラムで実験

電源要求を確認

アプリケーションとドライバーの電源要求がないか,コマンドプロンプト(管理者権限)で以下のコマンドを打って確認します。

powercfg /requests

電源要求があると、スリープやディスプレイの自動電源OFFが働きませんので,"ない"状態で試しましょう。

スリープ,スクリーンセーバー,ディスプレイの自動電源OFFを確認

Windowsの設定を調整して,まずは設定がちゃんと働くか確認

テストプログラムを実行

簡単なテストプログラムを作り,自動突入を防げるか確認。
コードは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.

原因

.NET CoreはASCIIとUnicodeのみエンコーディングをサポートしているため,Shift JISなんて扱ってないよ!って言われてしまう

対策

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のコンストラクタで例外が発生してしまったことが原因?で今回のような現象が発生したと思われる...

cpprefjp.github.io


でもその考察だと,次の章で試した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;
}

さいごに

その解釈間違ってますって場合はコメントやTwitterでDMくださいm(_ _)m

written by @mogmo1012

VC++のUnicodeコンソールアプリケーションで作るときの初期処理

mainではなくwmainを使う

ユニコードアプリの場合,wmain( int argc, wchar_t *argv[ ], wchar_t *envp[ ] )を使用します。
デフォルトのコードではmainとなっているので,関数名と引数を適宜変更しましょう。
コマンドライン引数も,ユニコードの場合はワイド文字列データ型のwchar_tにしましょう。
Debug版のアプリケーションは起動するが,リリース版で起動せず終了してしまう現象が発生。

コンソールに日本語出力できるようにする

wprintfstd::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関数の引数を以下のように設定します。
arcCountarcValue[]の要素数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

引用:
Unicode のサポート | Microsoft Docs

文字列

_T("ハローワールド")と定義しておけば条件によってユニコード文字列として扱ってくれます。

CString

CString_TCHARをベースとして使用しているため,文字セットをさほど気にせず扱うことができます。
具体的には,MBCSシンボルまたはUNICODEシンボルがコンパイル時に定義されているかどうかにしたがって、char型またはwchar_t型のいずれかをサポートする動作のようです。
引用:
CString の使用 | Microsoft Docs

次回

VC++のコンソールアプリケーションでstdやWinAPI使ってファイル操作をする

written by @mogmo

MFCを使ったユニコード文字セットのWindowsコンソールアプリケーションを作る

まずはプロジェクトを作成する

以下の手順でMFCフレームワークを使用したC++コンソールアプリケーションが作成できます。
使用IDEVisual Studio 2019
OS:Windows 10

手順

  • [新しいプロジェクトの作成]画面で[Windowsデスクトップウィザード]テンプレートを選択する

f:id:mogmo811:20201216134015p:plain

  • [新しいプロジェクトを構成します]画面でプロジェクト名等を設定

f:id:mogmo811:20201216134732p:plain

  • [Windowsデスクトップ プロジェクト]画面で
    • [アプリケーションの種類]を[コンソールアプリケーション]に設定
    • [追加のオプション]の[プリコンパイル済みヘッダー]と[MFCヘッダー]にチェック

f:id:mogmo811:20201216134100p:plain

  • ビルド構成が[Debug]の状態でビルド&実行すると,以下のようなデバッグコンソールが起動することを確認する

f:id:mogmo811:20201216134126p:plain

  • プロジェクトを右クリックして[プロパティ]をクリックして[(プロジェクト名)プロパティページ]を開く

f:id:mogmo811:20201216134145p:plain

各種設定を変更する
f:id:mogmo811:20201216134356p:plain

f:id:mogmo811:20201216134407p:plain

f:id:mogmo811:20201216134423p:plain

f:id:mogmo811:20201216134439p:plain

次回

ユニコード文字セットを使ったコンソールアプリケーション開発に必要な準備を行っていきます。
mogmo811.hatenablog.com

written by @mogmo1012

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 Mock:はじめの一歩 (1/2):CodeZine(コードジン)

Google Test プロジェクトテンプレートを使えるようにする

Visual Studio Installer を起動して、[C++ によるデスクトップ開発]の[Test Adapter for Google Test]にチェックをつけてインストールする。
f:id:mogmo811:20201113094701j:plain
参考:docs.microsoft.com

Google Testプロジェクトテンプレートを使って新規プロジェクトを作成する

Visual Studio 2019を起動して、[Google Test]を選択し、[次へ]をクリックする。
f:id:mogmo811:20201113094905j:plain

プロジェクト構成を設定して、[作成]をクリックする。
私の場合、プロジェクト名は「テストするプロジェクト名 + "Test"」とすることが多い

Google MockをNuGetパッケージからインストールする

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

フォルダ名を変更する

googlemock.v140.windesktop.static.rt-dyn の場合、パッケージのincludeディレクトリの直下のフォルダ名が「gtest」になっているので、「gmock」に変更する。

gtest.hとgmock.hをインクルードする

テストプロジェクトのcppファイルに下の 2 行を追加してビルドが通れば導入準備は完了です٩(ˊᗜˋ*)و

#include "gtest/gtest.h"
#include "gmock/gmock.h"

参考サイト

opencv.jp
qiita.com



ではまた🍙

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