マイコン開発でのリンカ設定

C言語でマイコン開発を行っている中で、関数外で宣言した変数の値が書き換えられない現象に遭遇しました。

static bool _initializedA;           // A
static bool _initializedB = false;   // B

void init() {
  _initializedA = true;
  _initializedB = true;
}

上のようなコードで、Aは関数内で書き換え可能ですが、Bは書き換えされず、関数を抜けても _initializedB の値が false のままという内容でした。コンパイルおよび実行時にも警告やエラーが発生せず、最初理由がよく分かりませんでした。

原因はリンカの設定によるものでした。Bのほうは初期値ありなので.dataセクション(ROM)に配置され、リンカのデフォルトの設定ではROMからRAMへのマップが行われていませんでした。

実はこのときは普段使用しているものとは異なるマイコンでの開発であったため、開発環境も普段とは異なり、デフォルトのリンカの設定がこのようになっているとは知りませんでした。

C言語での開発はオープン系言語と比べると、このような点でも難しく感じます。せめて警告やエラーが発生すればもっと早く気が付けるのですが、言語知識だけでなく、経験や経験に基づく感も必要なようです。

C#のrecord型と、.Net5未満での使用時のエラーについて

随分前に追加されたrecord型を今更ながら使い始めました。

内部的にはクラスと同じ扱いで、プロパティは全て不変とは以前から知っていたのですが、
先日recordもstructと同様にIEquatableが実装されていると聞き、
それならDictionaryのキーなどでも使いやすく、
1行で定義を済ませられるのも楽で良いなと思い使ってみることにした次第です。

ひとまず、以下のようなrecordを定義してみました。

/// <summary> 汎用結果クラス </summary>
/// <param name="Succ"> 成功したか </param>
/// <param name="ErrMsg"> 失敗時のエラーメッセージ </param>
record Result(bool Succ, string ErrMsg = "");

/// <summary> 汎用結果クラス </summary>
/// <param name="Succ"> 成功したか </param>
/// <param name="Value"> 戻り値 </param>
/// <param name="ErrMsg"> 失敗時のエラーメッセージ </param>
record Result(bool Succ, T Value = default, string ErrMsg = "");

よくタプルで成否のbool値と成功時の戻り値、失敗時のエラーメッセージを書いていたのですが、
クラスにするほどでもないけれど一々書くには若干手間だったので、
手軽に定義・使用できて良かったです。

今のところ以下のような場合にrecordを使うのが良いのかなと思っています。
・不変にしたい
・辞書のキーとしてのみ使用する
・特にメソッドなど実装予定なし

どこで使うべきかは正直、手を動かして使ってみないことには見極められない気がしているので、
今後も機を見て使っていこうと思います。

また、業務プロジェクト(C#9.0, .Netframework4.6-4.8)でrecordを宣言すると、
次のようなエラーが出て使用できませんが、
「定義済みの型’System.Runtime.CompileServices.IsExternalInit’は定義またはインポートされていません。」
以下の記事の通り、IsExternalInitクラスを自前でinternalで定義するとエラー解消し、使用できました。
(記事タイトル通り、record型だけでなくinitアクセサも使用できるようになりました)

言語か.netのバージョンが不足しているのだと思い諦めかけたのですが、
調べてみたら解決して良かったです。

以上です。

お手軽リレー接点出力

最近の案件でリレー接点出力を1点だけ制御する必要があったため、「DSD TECH」の「SH-UR01A」というUSB接続のリレー接点出力を使用しました。
仮想COM通信でコマンドを送信するだけで制御が出来るので、とても簡単です。

普段はコンテックなどのI/Oデバイスを使用しますが、1,2点程度の場合は「SH-UR01A」のほうがお手軽そうです。

普段から癖

こんにちはmtjです。

世の中にはマイコン、PC上で動いているソフトであったり様々な物がプログラムで動いています
UI等であれば飲食店だったり電子注文システム、ゲームであったり
センサー等の実際の動きであれば自動配膳、アミューズメント機器であったり

色々動きを見ていると面白い物があります。

自分は昔から気になった機械があれば 分解して中身を確認するような人間でした。
ジャンクのプレイステーション等ゲーム機器であったり ノートPCであったり様々な物を分解してきました。

観察している物がどのようなプログラム、センサー、機械で動いているかを考えるのは楽しいですし勉強にもなります。
現在の知識で動き方が分からないなら自分で情報を集めて調べて こう動いてるかもしれないと考えるのも楽しいですね。

そういった動きに興味があるからこそ今この業界にいるのだと思います。

初めてのCとC++

これまでの業務ではC#を使ってきました。
今回初めてCとC++に携わる機会があり、学習しました。
せっかくなのでModern C, C++ と言われる比較的新しいバージョンです。

C#を使い慣れているとCやC++ではロジックのコード量が増え、
こんなこともできないのか、と思う一方、
特にメモリの扱われ方などはより理解が深まりました。

ただC++は言語仕様としてもかなり複雑で、Cの次に学ぶにはとても難しい印象です。
なのでC++習得するには先にC#等を理解して、オブジェクト指向の特徴や、
最近の言語の機能、プログラミングスタイルなどを知った後のほうが、
C++を使ってどのような設計や関数を目指すべきかということがよく分かると思います。

RustもC++とよく比較されて人気があるようなので、
今後触れてみたいと思います。

WindowsFormsのDataGridViewにセルの情報を参照する右クリックメニュー設定

WindowsFormsのDataGridViewに右クリックメニュー(ContextMenuStrip)を設定し、
クリックされたセルの情報を参照しメニュー制限・機能実行できるようにしました。

最初はセルマウスクリックのイベントを使って、
クリックされたセルのインデックスを保管し、メニューをShowで出せばいいかと思ったのですが、
メニュー表示する座標を上手く計算できませんでした。

そこでどうすべきか調べたところ、
メニューの表示は、素直にContextMenuStripプロパティにメニュー設定してコントロールに任せて、
セルの情報をもとにメニュー制限するなどは、
ContextMenuStripのOpeningイベントで行うのが良いとわかりました。

結果として、以下のようなコードでの実現となりました。

private void menu_Opening(object sender, CancelEventArgs e) {
	// 画面座標
	var posScreen = Cursor.Position;
	// クライアント座標
	var posClient = dgvGroupingPartsNumData.PointToClient(posScreen);
	// クライアント座標をセル情報に変換
	var info = dgvGroupingPartsNumData.HitTest(posClient.X, posClient.Y);

	// info.RowIndex, info.ColumnIndexがクリックされたセルインデックス
	// それらをもとにメニューを一部制限
	// メニュー内機能からセルを参照するためにプロパティなどに保管
}

以上です。

DataGridViewの列幅オートサイズモードによって描画速度が遅い場合の対策

DataGridViewの列幅オートサイズモード(AutoSizeColumnsMode)がAllCellsなどのようにセルの内容によってオートサイズするモードの場合、セルのValueの設定回数が多いと描画が遅くなります。

これを改善するために、列幅オートサイズモードを一旦Noneに変更する拡張メソッドを作成しました。
これにより劇的に速度改善しました。

/// <summary>処理実行中に列幅のオートサイズを停止します。セルのValueの設定回数が多い場合、オートサイズ処理が遅いため、一旦オートサイズを停止することで高速化を行います。</summary>
public static void SuspendColumnAutoSize(this DataGridView dgv, System.Action action) {
    //注意:AutoSizeRowsModeは対象外。Noneに変更しただけで遅いため
 
    //finallyで行う処理
    System.Action finallyAction = () => { };
    //AutoSizeColumnsModeがNoneなどでない場合はNoneに変更する
    if (dgv.AutoSizeColumnsMode.EqualsAny(DataGridViewAutoSizeColumnsMode.None, DataGridViewAutoSizeColumnsMode.Fill).Not()) {
        var mode = dgv.AutoSizeColumnsMode;
        finallyAction += () => dgv.AutoSizeColumnsMode = mode;
        dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
    }
    //列のAutoSizeColumnsModeがNoneなどでない場合はNoneに変更する
    foreach (var col in dgv.Columns.ToList()) {
        if (col.AutoSizeMode.EqualsAny(DataGridViewAutoSizeColumnMode.None, DataGridViewAutoSizeColumnMode.NotSet, DataGridViewAutoSizeColumnMode.Fill).Not()) {
            var mode = col.AutoSizeMode;
            finallyAction += () => col.AutoSizeMode = mode;
            col.AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
        }
    }
    //処理実行
    try {
        action();
    } finally {
        finallyAction();
    }
}

地方住みの苦悩

こんにちは mtj です。

自分は割りと地方の方に住んでおりますが 京都の繁華街も大阪の繁華街も絶妙に1時間ぐらいかかってしまうところに住んでます。
そのせいで何か買い物に行きたい、遊びに行きたいとなってもなかなか及び腰になってしまいます。

住むには静かで問題も少ないいいとこなのですが そういった点だけが悩みです。

往復2時間以上なので何か実物見て買いたい! となっても小旅行の気分です。
移動時間は本等を読むのでそれも味といえば味なんですが

C#でのウィンドウへのキー送信

結論としては、以下の2種類の方法があるとわかりました。

① .NetのSendKeys.SendWaitメソッド:
 → 最前面のウィンドウ限定。引数が単純で使いやすい。
② ・WindowsAPIのPostMessageメソッド:
 → 送信先のウィンドウ指定可能。引数が複雑で使いにくい。必要な引数はSpy++にて調査可能。

以下経緯などです。

今携わっている案件で、プログラムからキー送信を行うことで
外部のアプリをショートカットキーにより自動操作することになりました。

お客様の調査により、
.NetのSendKeys.SendWaitメソッドにより実現可能だとわかっていたのですが、
このメソッドは送信先を指定できず、必ず最前面のウィンドウにキー送信を行うもので、
自動操作中にユーザ操作などで最前面のウィンドウが切り替わってしまうと
誤ったアプリにキーを送信してしまうことになります。

そこで可能ならウィンドウを指定して送信するという方針になり、
社内メンバーからウィンドウのハンドルを引数に与えてキー送信できるメソッドがあると伺いました。

調べてみたところWindowsAPIのPostMessageがそれにあたるとわかり、
試してみたのですが、文字列を引数に与えるだけのSendKeys.SendWaitとは異なり、
引数が複雑(ハンドルとキー内容だけでなく、複数のパラメータを32bitに詰め込んだ値が必要)で、
目的のキーを送信するためにどうすればよいかわかりませんでした。

そこで更に調べてみたところ、VisualStudioに含まれるSpy++というアプリにより、
目的のソフトへキーを送信した際のメッセージをログ出力して、
送信時の引数を調べることができるとわかりました。
参考:
VBAのSendKeys,System.Windows.FormsのSendWaitなどが反応しないときに読む記事 – 適材適所

これにより、無事PostMessageによるキー送信をテストすることに成功しました。

結果的には、
操作対象のアプリはそもそも最前面に表示されている状態でないとキー送信による操作を受け付けないとわかったため、
今回はキー送信前に毎回ウィンドウのActivateする方針に決まりましたが、
PostMessage(+ Spy++による送信内容の調査)は今後何かしらに役に立ちそうだなと感じています。

また余談ですが、
調査中にPostMessageによりゲームにキー送信を行い自動で操作する旨の話をチラッと見かけました。
学生時代に見かけたゲームAIの研究で、
「ゲームの状態を画像処理で読み取り、行動を決定し、外部からゲームを操作するAI」
を見かけたことがあったのですが、
あの実現にはこの辺りのメソッドを呼んでいたのだなと懐かしみながら今更のように思いました。

以上です。

UAC(ユーザアカウント制御)などの有効/無効を取得

UAC(ユーザアカウント制御)や、「UACによる管理者昇格時の同意プロンプト表示」の有効/無効によって処理を変えたい場合があったので取得方法を調べました。

結果、レジストリを調べれば良いことが分かりました。簡単ですね。

以下のプロパティを実装してみました。

/// <summary>UAC(ユーザアカウント制御)が有効か</summary>
public static bool? IsUacEnabled {
    get {
        try {
            var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System");
            if (key == null) return null;
            var value = key.GetValue("EnableLUA");
            if (value == null) return null;
            return value.Equals(1);
        } catch {
            return null;
        }
    }
}
 
/// <summary>UACによる管理者昇格時の同意プロンプト表示が有効か</summary>
public static bool? IsAdminConsentPromptEnabled {
    get {
        try {
            var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System");
            if (key == null) return null;
            var value = key.GetValue("ConsentPromptBehaviorAdmin");
            if (value == null) return null;
            return value.Equals(0).Not();
        } catch {
            return null;
        }
    }
}