20210124のC#に関する記事は6件です。

Lollipop グラフ上のランダムウォークと Metropolis ウォークの hitting time

ノード数 $N$ のグラフ上のノード $i$ からノード $j$ の hitting time $HT(i, j)$ の上限は、ランダムウォークだと $O(N^3)$、Metropolis ウォークだと $O(N^2)$ だと知られている。これを Lollipop グラフ対象に、具体的に実験をしてみた。

Lollipop グラフ

Lollipop グラフは、ノード数 $M$ の clique のひとつのノードが長さ $N$ の path に繋がったもの。詳しくはWikipediaで。以下の画像は Wikipedia にある $M=8, N=4$ の Lollipop グラフ。
Lollipop_graph.png

Lollipop グラフ上の hitting time

clique のノード数を $M$ とし、path のノード数を $N$ とする。簡単のため、これらは十分大きいとする。
初期ノードを clique のノードのうち、path に直接つながっていないノードとし、数字として -1 を割り当てる (上の図でいうと0 ~ 6 のどれか)。clique と path が繋がっているノードに 0 を割り当て (上の図でいうと7)、path の一番遠くを N と割り当てる (上の図でいうと11)。この場合、hitting time の期待値は以下となる。

\begin{align}
HT(-1, N) &= HT(-1, 0) + \sum_{i=0}^{N-1} HT(i, i+1)\\
HT(i, i+1) &= 1 + p(i, i-1) \left(HT(i-1, i) + HT(i, i+1)\right)
\end{align}

ここで$p(a, b)$ はノード $a$ からノード $b$ に遷移する確率。

ランダムウォークの場合

リーディングオーダーをみる。path 上では同じ hitting time ということを使って計算をすると、以下のように $N^3$ のオーダーで振る舞うことがわかる。

\begin{align}
HT(-1, N) &= HT(-1, 0) + \sum_{i=0}^{N-1} HT(i, i+1)\\
&\approx HT(-1, 0) + NHT(0, 1)\\
&\approx HT(-1, 0) + N(N+ (N-1) HT(-1, 0))\\
&\approx N^2 HT(-1, 0)\\
&\approx MN^2
\end{align}

Metropolis ウォークの場合

Metropolis ウォークはノード数と同じ次元を持つ適当な正の成分を持つベクトル $\mu$ に対して、ノード $u$ からノード $v$ に以下の確率で推移する。

p(u, v) = \left\{
\begin{array}{ll}
\frac{1}{deg(u)}\min\left[1, \frac{\deg(u)\mu_v}{\deg(v)\mu_u}\right] & (v \in N(u)) \\
1 - \sum_{v \in N(u)} p(u, v) & (u = v)\\
0 & otherwise
\end{array}
\right.

こで $N(u)$ はノード $u$ と接続されているノードの集合。この方法で歩くと、十分大きな $M$ に対し $p(1, 0) \approx 1/M$ となり非常に小さい。リーディングオーダーのみを見るためにこの項を無視すれば、以下の通りオーダーが $NM$ となり、単純なランダムウォークより効率がよいことがわかる。

\begin{align}
HT(-1, N) &= HT(-1, 0) + \sum_{i=0}^{N-1} HT(i, i+1)\\
&\approx HT(-1, 0) + HT(0, 1)\\
&\approx HT(-1, 0) + (M+ (M-1) HT(-1, 0))\\
&\approx M HT(-1, 0)\\
&\approx MN
\end{align}

数値実験

clique と path のノード数が同じとする。また、Metropolis ウォークで使用するベクトル $\mu$ は定数ベクトルとする。
ノード数 $N$ を2から6までで、シミュレーション回数を1000回とした。

コード

            var maxCliqueSize = 10 + 1;
            var cliqueSizes = Enumerable.Range(2, maxCliqueSize);
            var simulationSize = 1000;
            foreach (var cliqueSize in cliqueSizes)
            {
                var pathLength = cliqueSize;
                var graph = Graph.GraphFamilies.Lollipop(cliqueSize, pathLength);

                var start = graph.Nodes.First();
                var end = graph.Nodes.Last();

                var steps = Enumerable.Range(0, simulationSize).AsParallel().Select(i =>
                {
                    var config = new RandomWalkConfig(start);
                    var randomWalk = new RandomWalk(graph, config);
                    // var stationaryDist = graph.Nodes.ToDictionary(n => (INode)n, n => 1.0);
                    // var config = new MetropolisWalkConfig(start, stationaryDist);
                    // var randomWalk = new MetropolisWalk(graph, config);
                    // var beta = 0.5;
                    // var config = new BetaRandomWalkConfig (start, beta);
                    // var randomWalk = new BetaRandomWalk(graph, config);
                    while (!randomWalk.LocationHistory.Last().Equals(end))
                        randomWalk.Walk();
                    return randomWalk.LocationHistory.Count() - 1;
                });

                var average = steps.Average();
                var std = steps.StandardDeviation();
                Console.WriteLine(cliqueSize.ToString() + "\t" + average.ToString() + "\t" + std.ToString());
            }
// random walk
void Walk()
        {
            var candidates = NodeToConnectedNodes[LocationHistory.Last()];
            var count = candidates.Count();
            var p = 1.0 / count;
            var probs = Enumerable.Range(0, count).Select(i => p);

            var cat = new Probability.Distribution.Discrete.Univariate.Categorical();
            var catParam = new Probability.Parameter.Discrete.Univariate.Categorical(probs);
            var sample = cat.GetSamples(catParam, 1).First();

            LocationHistory = LocationHistory.Concat(new List<INode>() { candidates.ElementAt(sample) });
        }
// metropolis walk
void Walk()
        {
            // u to v
            var u = LocationHistory.Last();
            var vs = NodeToConnectedNodes[u];
            var degu = vs.Count();

            var vToDegv = vs.ToDictionary(v => v, v => NodeToConnectedNodes[v].Count());
            var transitionProbabilities = vToDegv.Keys.Select(v => (new double[]
            {
                1,
                degu * Config.NodeToStationaryProbability[v] / (vToDegv[v] * Config.NodeToStationaryProbability[u])
            }
                ).Min() / degu);

            var probs = (new double[] { 1 - transitionProbabilities.Sum() }).Concat(transitionProbabilities);

            var cat = new Probability.Distribution.Discrete.Univariate.Categorical();
            var catParam = new Probability.Parameter.Discrete.Univariate.Categorical(probs);
            var sample = cat.GetSamples(catParam, 1).First();

            var next = sample == 0 ? u : vs.ElementAt(sample - 1);
            LocationHistory = LocationHistory.Concat(new List<INode>() { next });
        }

結果

平均 Hitting time は以下のようになった。

ノード数 ランダムウォーク Metropolis ウォーク
2 9.234 12.411
3 28.261 28.819
4 67.889 57.211
5 126.573 89.546
6 237.192 132.52

おおよそランダムウォークだと $N^3$、Metropolis ウォークだと $3.5 N^2$ と振る舞っている。

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UniRxのUIにまつわる処理3選

様々な使い方のあるUnityのライブラリ「UniRx」の中でも、
UIにまつわる処理を紹介します。

その1. ReactiveProperty
その2. BindToButtonOnClick
その3. BindToOnClick

その1. ReactiveProperty

Sample1
using UnityEngine;
using UniRx;

public class Sample1 : MonoBehaviour
{
    //int level; とほぼ同じ使い方ができる。
    ReactiveProperty<int> level = new ReactiveProperty<int>();

    void Start()
    {
        level.Subscribe(Talk);
        //level.Subscribe(arg => Talk(arg)); のようなラムダ式でも可
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            level.Value++;  //.Valueで中身にアクセスする
        }
    }

    void Talk(int level)
    {
        Debug.Log("level up!" + level);
    }
}

Spaceを押すとlevelが上がり、levelが上がると当時にlevel up!と表示される。

ReactivePropertyは、ざっくり言うと、値の変更を通知してくれる変数のようなものです。
new ReactiveProperty()で作成し、Valueから値を取得・設定します。
(UniRxのReactivePropertyについて より引用。詳しい使い方も書かれています。)

Subscribe関数から、値の変更時に呼ばれて欲しい関数を登録することができます。
値と常に同期して欲しいことの多いUIの変更に便利です。

その2. BindToButtonOnClick

Sample2
using UnityEngine;
using UnityEngine.UI;
using UniRx;

public class UniRxSample2 : MonoBehaviour
{
    [SerializeField] Button button;

    //bool can_press; とほぼ同じ使い方ができる。
    ReactiveProperty<bool> can_press = new ReactiveProperty<bool>();

    // Start is called before the first frame update
    void Start()
    {
        can_press.BindToButtonOnClick(button, _ => Click());
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.T))
        {
            can_press.Value = true;
        }

        if (Input.GetKeyDown(KeyCode.F))
        {
            can_press.Value = false;
        }
    }

    void Click() { Debug.Log("Click"); }
}

can_pressというReactivePropertyの値が、Tキーを押すとtrueに、Fキーを押すとfalseに変更される。can_pressの値が変更されるとbuttonのinteractableも変更される。

BindToButtonOnClickは、ボタンに"押下された際の処理"と"押せるかどうか"、つまりonClickとinteractableを同時に登録するような関数です。
bool型のReactivePropertyから呼び出すことができます。

ちなみに、can_press.SubscribeToInteractableでinteractableのみ登録ができます。

ButtonのOnClickAsObservable()関数は、onClickをIObservableに変換する関数です。
Where関数で、bool型の関数を入力することができます。そのbool型の関数の結果がtrueの場合のみしか登録した関数を呼びださない、という処理が可能になります。

その3. BindToOnClick

Sample3
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UniRx;

public class UniRxSample3 : MonoBehaviour
{
    [SerializeField] private Button button;

    void Start()
    {
        button.BindToOnClick(_ => Observable.FromCoroutine(LevelingUp));
    }

    IEnumerator LevelingUp()
    {
        int level = 0;
        for (int i = 0; i < 100; i++)
        {
            level++;
            yield return null;
        }
        Debug.Log("level up!");
    }
}

buttonを押すと、コルーチンが呼ばれ、完了するまでButtonのinteractableがfalseとなる。

BindToOnClickはButtonにIObservableを返す関数を登録することができる関数です。
入力したIObservableが完了するまで、interactableがfalseとなります。
アニメーションを伴う処理や、サーバーと通信する処理などをする間、Buttonを押せなくするのに便利です。
CoroutineはFromCoroutine関数でIObservableに変換することができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#] WPFアプリでディスプレイのON/OFFを取得する

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

やりたいこと

Windowsの省電力の設定画面で、設定した時間が経過したらディスプレイの電源をOFFしているときに、ディスプレイの電源が切れたことをC#のプログラムで知りたい。
image.png

下記のページを参考に、やってみる。
https://www.366service.com/jp/qa/8cddf991f384dadaa31692fc612bd4e0

やり方

「電源設定の変化イベント」Windowsから受け取って、その中に含まれているディスプレイのONOFFの情報を読み取る形で実現する。

手順

  • まずは、こちらのやり方で、ウインドウメッセージハンドラをフックするメソッドを用意する。(下のC#サンプル中のWndProc()がそれにあたる。)

  • RegisterPowerSettingNotification()で、自分のアプリ(ウインドウ)に電源設定変更イベントが来るように設定(登録)する。→参照

    • RegisterPowerSettingNotification()User32.dllの中に含まれているWin32APIなので、C#から呼べるようにP/Invoke登録してやる。(一緒に使うUnregisterPowerSettingNotification()関数も一緒に行う)
  • PBT_POWERSETTINGCHANGEがきたときのWM_POWERBROADCASTlParamは電源設定を格納したPOWERBROADCAST_SETTINGなので、その中身を見る。→参照

    • POWERBROADCAST_SETTINGWinUser.hに定義されている定数なので、C#で使う場合は同じ構成でstructしなおしておいてやる。 image.png
    • また、下記の定数も同じように、C#側で定義しなおしてやる。
      • WinUser.hより
        • WM_POWERBROADCAST
        • PBT_POWERSETTINGCHANGE
        • DEVICE_NOTIFY_WINDOW_HANDLE
      • winnt.hより
        • GUID_CONSOLE_DISPLAY_STATE
  • メッセージハンドラの中で、WM_POWERBROADCASTを拾って処理する。やり方は、

    • メッセージがWM_POWERBROADCASTで、WParamがPBT_POWERSETTINGCHANGEのものの、lParamにPOWERBROADCAST_SETTINGのデータがのっかっているので、それを拾う。
    • POWERBROADCAST_SETTINGのメンバPowerSettingにはGUIDが入っている。   それをみれば、電源設定の中でも何の設定なのかがわかる。(例:GUID_CONSOLE_DISPLAY_STATEならディスプレイの状態、GUID_BATTERY_PERCENTAGE_REMAININGならバッテリののこり容量) →参照
  • 今回は、ディスプレイの状態(ON/OFF)を取りたいので、GUID_CONSOLE_DISPLAY_STATEで判定する。

  • 各GUIDによって、下記のようにlParamをキャストする(下記はPOWERBROADCAST_SETTINGの場合)

var pbs = (POWERBROADCAST_SETTING)Marshal.PtrToStructure(lParam,typeof(POWERBROADCAST_SETTING));
  • その中のData[1]が、欲しいデータ。 image.png そのData[1]の中に何が入っているかは、MSのPower Setting GUIDsのページに書いてある。 今回はGUID_CONSOLE_DISPLAY_STATEを使うので、下記のデータがとれる。 image.png →上記3つがとれるので、ディスプレイが消えたかどうか、は、値が0かどうか、で判定できそう。

実験コード(C++)

C#でどうやるか、を知りたいのだが、まずはC++のダイアログベースアプリでディスプレイのON/OFFを取ってみる。

#include "framework.h"
#include "WindowsProject1.h"
#include "resource.h"

HINSTANCE hInst;
BOOL CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
    hInst = hInstance;
    DialogBox(hInst, L"MyTestDlgBase_Main", NULL, (DLGPROC)MyDlgProc);
    return (int)0;
}

BOOL CALLBACK MyDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp)
{
    GUID a = GUID_CONSOLE_DISPLAY_STATE;
    switch (msg) {
        case WM_INITDIALOG:
            RegisterPowerSettingNotification(hDlg, &a, DEVICE_NOTIFY_WINDOW_HANDLE);
            break;

        case WM_POWERBROADCAST:
            if (wp == PBT_POWERSETTINGCHANGE)
            {
                auto lppbc = (POWERBROADCAST_SETTING*)lp;
                if (lppbc->PowerSetting == GUID_CONSOLE_DISPLAY_STATE) {
                    if (lppbc->Data[0] == 0)   OutputDebugString(L"OFF"); // ディスプレイがOFF
                    else                                OutputDebugString(L"ON");  // ディスプレイON
                }
            }
            break;
    }
    return FALSE;
}

実験コード(C#)

C++でやったことと同じことを、C#でやる。
やってることは同じだが、P/Invokeの設定、定数定義だけが増えている。
(やりたいことが書いてあるのはWndProc()だけだが、そのための準備がたくさん書かれている)

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace WpfApp61
{
    public partial class MainWindow : Window
    {
        #region RegisterPowerSettingNotificationのための準備部分
        private const int WM_POWERBROADCAST = 0x0218;
        private const int PBT_POWERSETTINGCHANGE = 0x8013;
        private static Guid GUID_CONSOLE_DISPLAY_STATE = new Guid(0x6fe69556, 0x704a, 0x47a0, 0x8f, 0x24, 0xc2, 0x8d, 0x93, 0x6f, 0xda, 0x47);
        const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;

        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        private struct POWERBROADCAST_SETTING
        {
            public Guid PowerSetting;
            public uint DataLength;
            public byte Data;
        }

        [DllImport(@"User32.dll", SetLastError = true, EntryPoint = "RegisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
        static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient, ref Guid PowerSettingGuid, uint Flags);

        [DllImport(@"User32.dll", EntryPoint = "UnregisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
        static extern bool UnregisterPowerSettingNotification(IntPtr RegistrationHandle);

        #endregion

        private IntPtr registerConsoleDisplayHandle = IntPtr.Zero;

        public MainWindow()
        {
            InitializeComponent();

            // フックの設定
            var hWnd = new WindowInteropHelper(Application.Current.MainWindow).EnsureHandle();
            HwndSource source = HwndSource.FromHwnd(hWnd);
            source.AddHook(new HwndSourceHook(WndProc));

            // WM_POWERBROADCAST > PBT_POWERSETTINGCHANGE > GUID_CONSOLE_DISPLAY_STATE が取れるように登録
            registerConsoleDisplayHandle = RegisterPowerSettingNotification(hWnd, ref GUID_CONSOLE_DISPLAY_STATE, DEVICE_NOTIFY_WINDOW_HANDLE);
        }

        private void Window_Closed(object sender, EventArgs e)
        {
            if (registerConsoleDisplayHandle != IntPtr.Zero)
                UnregisterPowerSettingNotification(registerConsoleDisplayHandle);
        }

        // メッセージループを記述したメソッド
        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg == WM_POWERBROADCAST)
            {
                switch (wParam.ToInt32())
                {
                    case PBT_POWERSETTINGCHANGE:
                        var pbs = (POWERBROADCAST_SETTING)Marshal.PtrToStructure(lParam, typeof(POWERBROADCAST_SETTING));
                        if (pbs.PowerSetting == GUID_CONSOLE_DISPLAY_STATE)
                        {
                            if (pbs.Data == 0) Debug.WriteLine("--Display OFF");
                            else                        Debug.WriteLine("--Display ON");
                        }
                        break;
                }
            }
            return IntPtr.Zero;
        }
    }
}

思ったこと

ウインドウメッセージハンドラをC#からフックして何かするときは、C++でウインドウメッセージハンドラを書いてみてから、C#のコードを書いてみたらわかりやすい気がした。

例えばWM_POWERBROADCASTって、何番だったっけ?となったときに、C++版だとF12を押せば定数定義に飛べる、とか。
(何番か?を調べるときに、パッとMSの公式ページから定数定義を見つけられなかったので...)

ギモン

似たデータで、GUID_MONITOR_POWER_ONがある。
こっちは、1分経ってディスプレイがOFFしたときに0、何か操作してディスプレイがONしたときに1になってるが、今回やったGUID_CONSOLE_DISPLAY_STATEは、ディスプレイがOFFする数秒前に2になって、実際OFFしたときに0になるっぽい。
どう違う??

参考

実験コードの元にしたページ
ディスプレイパワーがオン/オフに切り替わったときに発生したイベント
https://www.366service.com/jp/qa/8cddf991f384dadaa31692fc612bd4e0

[WPF] ウインドウメッセージハンドラをフックする
https://qiita.com/tera1707/items/fc6b4bed1b2709d21a03

Registering for Power Events
パワーイベントが来てくれるように登録するやり方
https://docs.microsoft.com/en-us/windows/win32/power/registering-for-power-events

PBT_POWERSETTINGCHANGE event
https://docs.microsoft.com/en-us/windows/win32/power/pbt-powersettingchange

WM_POWERBROADCAST message
https://docs.microsoft.com/en-us/windows/win32/power/wm-powerbroadcast

POWERBROADCAST_SETTING structure (winuser.h)
https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-powerbroadcast_setting

Power Setting GUIDs
同じPBT_POWERSETTINGCHANGEで、ここにある種類の情報が取れる
https://docs.microsoft.com/en-us/windows/win32/power/power-setting-guids
バッテリーの残量変化(GUID_BATTERY_PERCENTAGE_REMAINING)とかもとれる。

POWERBROADCAST_SETTING の Data[1] の中身について
https://www.codeproject.com/Articles/1193099/Determining-the-Monitors-On-Off-sleep-Status
※Data[1]の中身が、MSの公式から見つけられなかった..
→あった。下記に、取れるデータの種類(GUID)と、その時の値が何か(Data)が書いてある。
https://docs.microsoft.com/en-us/windows/win32/power/power-setting-guids

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#/C++] WPFアプリでディスプレイのON/OFFを取得する

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

やりたいこと

Windowsの省電力の設定画面で、設定した時間が経過したらディスプレイの電源をOFFしているときに、ディスプレイの電源が切れたことをC#のプログラムで知りたい。
image.png

下記のページを参考に、やってみる。
https://www.366service.com/jp/qa/8cddf991f384dadaa31692fc612bd4e0

やり方

「電源設定の変化イベント」をWindowsから受け取って、その中に含まれているディスプレイのONOFFの情報を読み取る形で実現する。

手順

  • まずは、こちらのやり方で、ウインドウメッセージハンドラをフックするメソッドを用意する。(下のC#サンプル中のWndProc()がそれにあたる。)

  • RegisterPowerSettingNotification()で、自分のアプリ(ウインドウ)に電源設定変更イベントが来るように設定(登録)する。→参照

    • RegisterPowerSettingNotification()User32.dllの中に含まれているWin32APIなので、C#から呼べるようにP/Invoke登録してやる。(一緒に使うUnregisterPowerSettingNotification()関数も一緒に行う)
  • PBT_POWERSETTINGCHANGEがきたときのWM_POWERBROADCASTlParamは電源設定を格納したPOWERBROADCAST_SETTINGなので、その中身を見る。→参照

    • POWERBROADCAST_SETTINGWinUser.hに定義されている定数なので、C#で使う場合は同じ構成でstructしなおしておいてやる。 image.png
    • また、下記の定数も同じように、C#側で定義しなおしてやる。
      • WinUser.hより
        • WM_POWERBROADCAST
        • PBT_POWERSETTINGCHANGE
        • DEVICE_NOTIFY_WINDOW_HANDLE
      • winnt.hより
        • GUID_CONSOLE_DISPLAY_STATE
  • メッセージハンドラの中で、WM_POWERBROADCASTを拾って処理する。やり方は、

    • メッセージがWM_POWERBROADCASTで、WParamがPBT_POWERSETTINGCHANGEのものの、lParamにPOWERBROADCAST_SETTINGのデータがのっかっているので、それを拾う。
    • POWERBROADCAST_SETTINGのメンバPowerSettingにはGUIDが入っている。それをみれば、電源設定の中でも何の設定なのかがわかる。(例:GUID_CONSOLE_DISPLAY_STATEならディスプレイの状態、GUID_BATTERY_PERCENTAGE_REMAININGならバッテリののこり容量) →参照
  • 今回は、ディスプレイの状態(ON/OFF)を取りたいので、GUID_CONSOLE_DISPLAY_STATEで判定する。

  • 各GUIDによって、下記のようにlParamをキャストする(下記はPOWERBROADCAST_SETTINGの場合)

var pbs = (POWERBROADCAST_SETTING)Marshal.PtrToStructure(lParam,typeof(POWERBROADCAST_SETTING));
  • その中のData[1]が、欲しいデータ。 image.png
    そのData[1]の中に何が入っているかは、MSのPower Setting GUIDsのページに書いてある。 今回はGUID_CONSOLE_DISPLAY_STATEを使うので、下記のデータがとれる。 image.png
    →上記3つがとれるので、ディスプレイが消えたかどうか、は、値が0かどうか、で判定できそう。

実験コード(C++)

C#でどうやるか、を知りたいのだが、まずはC++のダイアログベースアプリでディスプレイのON/OFFを取ってみる。

#include "framework.h"
#include "WindowsProject1.h"
#include "resource.h"

HINSTANCE hInst;
BOOL CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
    hInst = hInstance;
    DialogBox(hInst, L"MyTestDlgBase_Main", NULL, (DLGPROC)MyDlgProc);
    return (int)0;
}

BOOL CALLBACK MyDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp)
{
    GUID a = GUID_CONSOLE_DISPLAY_STATE;
    switch (msg) {
        case WM_INITDIALOG:
            RegisterPowerSettingNotification(hDlg, &a, DEVICE_NOTIFY_WINDOW_HANDLE);
            break;

        case WM_POWERBROADCAST:
            if (wp == PBT_POWERSETTINGCHANGE)
            {
                auto lppbc = (POWERBROADCAST_SETTING*)lp;
                if (lppbc->PowerSetting == GUID_CONSOLE_DISPLAY_STATE) {
                    if (lppbc->Data[0] == 0)   OutputDebugString(L"OFF"); // ディスプレイがOFF
                    else                       OutputDebugString(L"ON");  // ディスプレイON
                }
            }
            break;
    }
    return FALSE;
}

実験コード(C#)

C++でやったことと同じことを、C#でやる。
やってることは同じだが、P/Invokeの設定、定数定義だけが増えている。
(やりたいことが書いてあるのはWndProc()だけだが、そのための準備がたくさん書かれている)

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace WpfApp61
{
    public partial class MainWindow : Window
    {
        #region RegisterPowerSettingNotificationのための準備部分
        private const int WM_POWERBROADCAST = 0x0218;
        private const int PBT_POWERSETTINGCHANGE = 0x8013;
        private static Guid GUID_CONSOLE_DISPLAY_STATE = new Guid(0x6fe69556, 0x704a, 0x47a0, 0x8f, 0x24, 0xc2, 0x8d, 0x93, 0x6f, 0xda, 0x47);
        const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;

        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        private struct POWERBROADCAST_SETTING
        {
            public Guid PowerSetting;
            public uint DataLength;
            public byte Data;
        }

        [DllImport(@"User32.dll", SetLastError = true, EntryPoint = "RegisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
        static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient, ref Guid PowerSettingGuid, uint Flags);

        [DllImport(@"User32.dll", EntryPoint = "UnregisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
        static extern bool UnregisterPowerSettingNotification(IntPtr RegistrationHandle);

        #endregion

        private IntPtr registerConsoleDisplayHandle = IntPtr.Zero;

        public MainWindow()
        {
            InitializeComponent();

            // フックの設定
            var hWnd = new WindowInteropHelper(Application.Current.MainWindow).EnsureHandle();
            HwndSource source = HwndSource.FromHwnd(hWnd);
            source.AddHook(new HwndSourceHook(WndProc));

            // WM_POWERBROADCAST > PBT_POWERSETTINGCHANGE > GUID_CONSOLE_DISPLAY_STATE が取れるように登録
            registerConsoleDisplayHandle = RegisterPowerSettingNotification(hWnd, ref GUID_CONSOLE_DISPLAY_STATE, DEVICE_NOTIFY_WINDOW_HANDLE);
        }

        private void Window_Closed(object sender, EventArgs e)
        {
            if (registerConsoleDisplayHandle != IntPtr.Zero)
                UnregisterPowerSettingNotification(registerConsoleDisplayHandle);
        }

        // メッセージループを記述したメソッド
        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg == WM_POWERBROADCAST)
            {
                switch (wParam.ToInt32())
                {
                    case PBT_POWERSETTINGCHANGE:
                        var pbs = (POWERBROADCAST_SETTING)Marshal.PtrToStructure(lParam, typeof(POWERBROADCAST_SETTING));
                        if (pbs.PowerSetting == GUID_CONSOLE_DISPLAY_STATE)
                        {
                            if (pbs.Data == 0) Debug.WriteLine("--Display OFF");
                            else                        Debug.WriteLine("--Display ON");
                        }
                        break;
                }
            }
            return IntPtr.Zero;
        }
    }
}

思ったこと

ウインドウメッセージハンドラをC#からフックして何かするときは、C++でウインドウメッセージハンドラを書いてみてから、C#のコードを書いてみたらわかりやすい気がした。

例えばWM_POWERBROADCASTって、何番だったっけ?となったときに、C++版だとF12を押せば定数定義に飛べる、とか。
(何番か?を調べるときに、パッとMSの公式ページから定数定義を見つけられなかったので...)

ギモン

似たデータで、GUID_MONITOR_POWER_ONがある。
こっちは、1分経ってディスプレイがOFFしたときに0、何か操作してディスプレイがONしたときに1になってるが、今回やったGUID_CONSOLE_DISPLAY_STATEは、ディスプレイがOFFする数秒前に2になって、実際OFFしたときに0になるっぽい。
どう違う??

参考

実験コードの元にしたページ
ディスプレイパワーがオン/オフに切り替わったときに発生したイベント
https://www.366service.com/jp/qa/8cddf991f384dadaa31692fc612bd4e0

[WPF] ウインドウメッセージハンドラをフックする
https://qiita.com/tera1707/items/fc6b4bed1b2709d21a03

Registering for Power Events
パワーイベントが来てくれるように登録するやり方
https://docs.microsoft.com/en-us/windows/win32/power/registering-for-power-events

PBT_POWERSETTINGCHANGE event
https://docs.microsoft.com/en-us/windows/win32/power/pbt-powersettingchange

WM_POWERBROADCAST message
https://docs.microsoft.com/en-us/windows/win32/power/wm-powerbroadcast

POWERBROADCAST_SETTING structure (winuser.h)
https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-powerbroadcast_setting

Power Setting GUIDs
同じPBT_POWERSETTINGCHANGEで、ここにある種類の情報が取れる
https://docs.microsoft.com/en-us/windows/win32/power/power-setting-guids
バッテリーの残量変化(GUID_BATTERY_PERCENTAGE_REMAINING)とかもとれる。

POWERBROADCAST_SETTING の Data[1] の中身について
https://www.codeproject.com/Articles/1193099/Determining-the-Monitors-On-Off-sleep-Status
※Data[1]の中身が、MSの公式から見つけられなかった..
→あった。下記に、取れるデータの種類(GUID)と、その時の値が何か(Data)が書いてある。
https://docs.microsoft.com/en-us/windows/win32/power/power-setting-guids

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MessagePack for C#で、Genericな変数をIL2CPP環境で使いたいときのメモ

前提

このライブラリの応用?的な使い方の話をしています。
現時点の最新版、2.2.85で動作確認済み。
https://github.com/neuecc/MessagePack-CSharp

Unityエディタでは動いてたのに、IL2CPP対応(iOSとかWebGLビルドだと強制)で
FormatterNotRegisteredException 出てキッツい人向けお役立ち情報。

やりたいこと

変数部分をGenericにして、要処で使いわけたいパターンがあるとする。
テストコードとしてはこういう感じ。

public class TestClass
{
    [MessagePackObject(keyAsPropertyName: true)]
    public class TargetClass<T>
    {
        public T param = default;
        public TargetClass(T param)
        {
            this.param = param;
        }
    }

    // Tには [MessagePackObject] が付いた別クラスのインスタンスが来る想定
    public void ExecuteTest<T>(T parameter)
    {
        var bytes = MessagePackSerializer.Serialize(new TargetClass<T>(parameter));
        MessagePackSerializer.Deserialize<TargetClass<T>>(bytes);
    }
}

Unityで通常実行すると普通に通るんだけども、何も考えずIL2CPP設定でビルドすると通らない。
IL2CPP環境では動的コード生成が行えないので、事前にGeneratedResolverを生成しておく必要がある。
事前生成のやり方は、公式ReadMeのAOT Code Generation (support for Unity/Xamarin)の項目で説明されている。
https://github.com/neuecc/MessagePack-CSharp#aot-code-generation-support-for-unityxamarin

問題は、AOTコード生成をやっても上のコードはまだ通らない。
なので、もうひと手間を加える必要があるぞ、というのが今回の記事。

解決方法

    [MessagePackObject(keyAsPropertyName: true)]
    [MessagePack.Union(0, typeof(TargetClass<GenericClass_0>))]
    [MessagePack.Union(1, typeof(TargetClass<GenericClass_1>))]
    [MessagePack.Union(2, typeof(TargetClass<GenericClass_2>))]// 対応させる必要クラス分、Unionをこの要領で追加すること
    public class TargetClass<T>
    {
        public T param = default;
        public TargetClass(T param)
        {
            this.param = param;
        }
    }

Union機能を使うことで解決ができます。
https://github.com/neuecc/MessagePack-CSharp#union
Unionを追加した後に、先述のAOT Code Generationの作業を(再)実行する必要があるので注意してください。
AOT Code Generationで生成したコード内を見ると、対応できてそうかは目視確認できます。

    // 前略
    internal static class GeneratedResolverGetFormatterHelper
    {
        private static readonly global::System.Collections.Generic.Dictionary<Type, int> lookup;

        static GeneratedResolverGetFormatterHelper()
        {
            lookup = new global::System.Collections.Generic.Dictionary<Type, int>(193)
            {
                // 中略
                { typeof(global::TestClass.TargetClass<global::GenericClass_0>), 30 },
                { typeof(global::TestClass.TargetClass<global::GenericClass_1>), 31 },
                { typeof(global::TestClass.TargetClass<global::GenericClass_2>), 32 },
                // 後略

こういう感じで、TargetClass<T> 項目の反映がなされていれば多分OKです。

追記(2021/01/26)

一応ですが、これはバッドノウハウに相当する可能性があります。

Unionを追加した状態でAOT Code Generation手順を忘れると
UnityエディタでDynamicUnionResolver(非IL2CPP環境で、対応コードを自動生成してくれるやつ)が
Union can only be interface or abstract class.というエラーを吐きます。
エラー表示通り、本来Unionはinterfaceとabstractクラスだけを対応するつもりのものっぽいですね。
後々の仕様変更で死ぬ可能性はあるので、その際はまた別途解決策を模索したほうがよさそうです。

Unityエディタで暫定回避をするための対応としては、下記みたいな方法があるんじゃないでしょうか。
- Union部分を#if ENABLE_IL2CPPとか#if !UNITY_EDITORで括って、発生を回避する
- 逆に何もせず、エラーを都度出して見落とさないようにし、AOTコード生成を都度行う運用でカバー

追記2(2021/01/27)

今思ったんだけどこれ、GenericじゃなくてAbstractクラスで良いのでは???

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Word VBAのグループ化が時々失敗する

事象

C#で NetOffice を使ってWordの中のオートシェイプをグループ化する要件があった。その際に時々成功するが、時々失敗するという事象に悩まされたので、ここにメモしておく。

具体的には以下のようなコードで問題が発生した。

private void Group(Word.Document document, string[] names) 
{
    // namesはグループ化対象のShapeたちのNameが入っている
    object[] args = names.OfType<object>().ToArray();
    var range = document.Shapes.Range(args);
    range.Select();
    var group = range.Group(); // <-- ここで落ちる
}

例外の内容

System.UnauthorizedAccessException: 選択した図形はグループ化できません。

原因

2つあった。

Wordが見えてなかった

高速化の観点からWordを非表示にしていて実行していたが、Group化する際にはVisibleがtrueじゃないといけない(Wordが見えてないといけない)ようだ。

Shapeがあるページに遷移していない

グループ化対象のShapeが見えない位置にあると、上記のUnauthorizedAccessException が出るようで、例えば1ページ目にShapeがあれば成功するが、2ページ目にあると失敗するという現象が発生した。

対処

今回の場合、以下のようなVisible操作とページ遷移コードをrange.Group() の前に実行しておく必要があった。

Application.Visible = true;
Application.Selection.GoTo(WdGoToItem.wdGoToPage, WdGoToDirection.wdGoToAbsolute, {該当ページ});
range.Group();

これで安定してグループ化することができました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む