プログラムを書こう!

実務や自作アプリ開発で習得した役に立つソフトウェア技術情報を発信するブログ

C++/CLIでInvokeRequiredによる異なるスレッドか判断できない状況

はじめに

複数のフォームを切り替えるアプリで、処理スレッドからの処理結果をフォーム上のラベルに表示する処理がありました。
処理結果が返却されるタイミングでどの画面が表示されているかわからないため、すべての画面で同じ処理を行うようにしました。
処理スレッドからラベルを操作するため、いつものようにデリゲートでthis->InvokeRequiredを参照してラベルを操作するようにしましたが、場合によってクラッシュすることがありました。

原因

アプリの起動時に各フォームのインスタンスは生成済みでしたが、その際表示していないフォームも存在しました。
その状態で処理スレッドが動作し各画面のラベルを操作すると、表示していないフォームでクラッシュしていました。
表示していないフォームのデリゲートをデバッグしてみると、InvokeRequiredプロパティを参照してもfalseが返却されていました。
そのためそのままラベルに処理結果を反映させることになり、クラッシュしていました。

クラッシュしていたデリゲートメソッド

delegate ShowResultDelegate();

///
/// 処理結果を表示する。
///
void ShowResult()
{
    // Invokeが必要な場合
    if (this->InvokeRequired)
    {
        ShowResultDelegate^ method = gcnew ShowResultDelegate(this, &SampleForm::ShowResult);
        this->Invoke(method);
    }
    
    // Invokeが不要な場合
    // 表示していないフォームの場合、処理スレッドから呼び出してもこっちが実行されてしまう。
    else
    {
        // ラベルに処理結果を表示する。
        ResultLabel->Text = "処理結果";
    }
}

対応

調べたところ、表示されたかどうかはそのフォームのウィンドウハンドルが存在するかどうかをチェックすればよいようです。
ウィンドウハンドルが存在するどうかをチェックする方法は、次の2つの方法があるようです。

  1. FindWindowメソッドでフォーム自身のウィンドウハンドルを取得し、ウィンドウハンドルが生成されているか判断する。
  2. IsHandleCreatedプロパティでフォーム自身のウィンドウハンドルが生成されているか判断する。

2.のIsHandleCreatedメソッドがフォームに用意されているので、今回はこちらを使うことにしました。

対応したデリゲートメソッド

delegate ShowResultDelegate();

///
/// 処理結果を表示する。
///
void ShowResult()
{
    // このフォームが表示されていない場合
    if (!this->IsHandleCreated)
    {
        // 何もせず、終了する。
        return;
    }
    
    // Invokeが必要な場合
    if (this->InvokeRequired)
    {
        ShowResultDelegate^ method = gcnew ShowResultDelegate(this, &SampleForm::ShowResult);
        this->Invoke(method);
    }
    
    // Invokeが不要な場合
    else
    {
        // ラベルに処理結果を表示する。
        ResultLabel->Text = "処理結果";
    }
}

API Reference
FindWindowメソッド
IsHandleCreatedプロパティ

おわりに

最初は定石通りデリゲートでthis->InvokeRequiredを判断してるのになぜクラッシュするかわかりませんでした。
何はともあれ、原因と対応方法がわかってよかったです。

パーソナルメンターがつくオンラインブートキャンプ

C++/CLIでボタンの枠線を表示しない。

はじめに

ボタンに画像を張り付けて、画像をボタン代わりにしようとしました。
その際ボタンの枠線が表示されたままだったので、ボタンの枠線を消すにはどうしたらよいか調べました。

FlatStyleプロパティ

ボタンのスタイルを設定するFlatStyleプロパティが用意されていました。
実際にはButtonクラスではなく、そのスーパークラスのButtonBaseクラスのプロパティになります。

設定できる値はFlatStyle列挙子に定義されています。
デフォルトは"Standard"になります。

ボタンの枠線を表示しない設定はFlatを指定すればよいようです。
その他にPopup、Systemがありますが、違いはよく分かりませんでした。

Flatstyle列挙子

  • Flat
  • Popup
  • Standard
  • System

API Reference
ButtonBase.FlatStyleプロパティ
FlatStyle列挙子

おわりに

最初はCSSのイメージで、ButtonクラスでBorderのようなプロパティを探してみましたが、見つかりませんでした。
その代わりにボタン自体のスタイル設定するプロパティが用意されていました。
経験は時に邪魔することがあります。

知識・スキルの販売サイト【ココナラ】

C++/CLIでアプリが実行されるディレクトリを取得する。

はじめに

不具合調査などのためにログをファイルに出力しておくことはよくあると思います。
今回もログファイルを作成しようと考えましたが、出力場所をどこにするかお客様と相談しました。
その結果、アプリが動作しているディレクトリに作成することになりました。
そのためアプリが実行されるディレクトリの取得方法を調査したので、まとめてみました。

Application.ExecutablePathプロパティ

ApplicationクラスのExecutablePathプロパティで、アプリが実行されたパス名+ファイル名が取得できます。
今回はアプリが実行されたパス名が欲しいので、終端のパス区切り文字の前までを切り出すことにします。

///
/// アプリが実行されたパス名を取得する。
/// @return アプリが実行されたパス名
///
String^ GetExecutePath()
{
    // アプリが実行されたパス名+ファイル名を取得します。
    String^ executePath = Application::ExecutablePath;
    
    // 終端のパス区切り文字"¥"の位置を取得します。
    // "¥"はエスケープシーケンスして"¥¥"とします。
    int index = executePath->LastIndexOf("¥¥");
    
    // 終端のパス区切り文字の位置が取得できた場合
    // 念のためのチェック
    if (index > 0)
    {
        // 先頭からパス区切り文字の前までを切り出します。
        executePath = executePath->Substring(0, index);
    }
    return executePath;
}

実は調査した時はExecutablePathプロパティを使用して解決しまいたが、後日よく調べたらアプリが実行されたディレクトリを示すStartupPathプロパティが存在しました。
なので今回の要件を満たすにはStartupPathプロパティを使用すればよかったです。
しかしせっかくなので調査した当時のメモをそのまま公開します。

API Reference
Application.ExecutablePathプロパティ
Applicaiton.StartupPathプロパティ

おわりに

実装時にがんばって解決したことが、あとでもっといい方法を見つけたりすることはよくあります。
日々精進が大切だと思います。

個人や企業が主催する講座・教室・レッスン・ワークショップが10,000件以上 日本最大級のまなびのマーケット「ストアカ」

C++/CLIのファイル書き込みではまったこと。

はじめに

設定値がソース上にべた書きされている画面があったのですが、それを設定ファイルで外出ししてほしいと依頼があり対応しました。
要件としてはそんなに難しい要素はないためすぐ終わると思っていたのですが、書き込み処理ではまりました。

はまったこと

設定値は画面で変更可能なため、ファイルに保存する際に設定値の部分を上書きしようと考えました。
ところが最後のデータだけある条件でデータ化けが発生してしまいました。
このデータは小数値データだったのですが、有効桁数が小数点2桁と3桁のデータが混在していました。
有効桁数が3桁のデータを保存した後、2桁のデータを保存するとデータ化けしました。

原因はFileModeの設定

原因は書き込み用にファイルを開く際のFileModeの設定でした。
書き込み用にファイルを開く際、ファイルが存在しなくてもエラーにしたくなかったため、FileModeにOpenOrCreateを指定しました。
これによりファイルが存在しない場合はファイルは新規に作成され、ファイルが存在する場合は上書き保存になります。
問題はファイルの上書き保存の場合で、有効桁数小数点3桁のデータを書き込み後、有効桁数小数点2桁のデータを書き込むと、有効桁数小数点3桁で書き込まれた3桁目が残っている状態になっていました。

つまりこんな感じです。

  1. まず有効桁数小数点3桁のデータ"99.123"を保存します。
  2. 次に有効桁数小数点2桁のデータ"99.45"を保存します。

すると"99.453"が保存されていました。

解決方法

書き込み用のファイルは常に新規保存にするように、FileModeでCreateを指定するように変更しました。
これにより、ファイルがあってもなくても常に新規保存されデータ化けも発生しなくなりました。

API Reference
FileMode

おわりに

久しぶりにドはまりして、問題解決まで数時間四苦八苦しました。
なんとか時間内に解決できてよかったですが、原因がわかってみればちょっと情けない感じです。

~約8,000名の受講生と80社以上の導入実績~
現役エンジニアのオンライン家庭教師CodeCamp

C++/CLIでレジストリに値を書き出す。

はじめに

前回レジストリから値を読み込む方法をまとめましたので、今回はレジストリに値を書き出す方法をまとめてみます。

RegistryKey.SetValueメソッド

レジストリの値の読み込み同様、レジストリへの値の書き出しは、RegistryKey.SetValueメソッドを利用します。
今回はサブキーが存在しない場合はサブキーを作成するようにして、RegistryKeyオブジェクトを取得するようにしてみます。

///
/// レジストリに値を設定する。
/// @param subKey サブキー
/// @param name 項目名
/// @param value 設定する値
///
void SetRegValue(String^ subKey, String^ name, Object^ value)
{
    // サブキーに対するRegistryKeyオブジェクトを取得します。
    // レジストリにサブキーが存在しない場合は、サブキーが生成されます。
    RegistryKey^ rk = Registry::CurrentUser->CreateSubKey(subKey);
        
    // 項目名の項目に値を設定します。
    rk->SetValue(name, value);
}

API Reference
Registry.CreateSubKeyメソッド
RegistryKey.SetValueメソッド

おわりに

iniファイルとレジストリは似たような用途になりますが、個人的には

  • iniファイルは、変更されない設定値(初期値など)用
  • レジストリは、変更される設定値用

で使い分ければよいかと思っています。

~約8,000名の受講生と80社以上の導入実績~
現役エンジニアのオンライン家庭教師CodeCamp

C++/CLIでレジストリから値を読み込む。

はじめに

アプリの設定値などはiniファイルやレジストリに保持するのが一般的だと思います。
今回はレジストリから保存された値を読み込む方法をまとめてみました。

RegistryKeyクラス

レジストリはRegistryKeyクラスで操作します。
対象となるCurrentUserのサブキーのRegistryKeyオブジェクトを取得するには、Registry::CurrentUser->OpenSubKey()を利用します。
そしてある項目名に対する値を取得するには、RegistryKeyクラスのGetValue()メソッドを使用します。
GetValue()メソッドで返却された値はObject型のため、値のデータ型に合わせて適切にキャストする必要があります。

 ///
 /// サブキーのレジストリのキー項目の値を取得します。
 /// @param subKey サブキー
 /// @param name 項目名
 ///
 Object^ GetRegValue(String^ subKey, String^ key)
 {
     // サブキーのRegistryKeyオブジェクトを取得します。
     RegistryKey^ rk = Registry::CurrentUser->OpenSubKey(subKey);

     // レジストリにサブキーが存在しない場合
     if (rk == nullptr)
     {
         return;
     }

     // 項目名に対する値を取得します。
     // 項目名が存在しない場合、nullが返却されます。
     Object^ value = rk->GetValue(name);
     return value;
}

API Reference
Registry::CurrentUser.OpenSubKeyメソッド
RegistryKey.GetValueメソッド

おわりに

iOSでは同じような仕組みで、UserDefaultとKeychainが用意されています。
使いやすさですが、UserDefaultはともかくKeychainの操作方法はクセがあり、使いにくいです。
レジストリへのアクセスの方が素直で使いやすいと思いました。

全国送料無料!IT書、ビジネス書、資格書が豊富なSEshop