- 投稿日:2021-01-24T19:19:39+09:00
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 グラフ上の 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$ と振る舞っている。
参考文献
- 投稿日:2021-01-24T17:29:57+09:00
UniRxのUIにまつわる処理3選
様々な使い方のあるUnityのライブラリ「UniRx」の中でも、
UIにまつわる処理を紹介します。その1. ReactiveProperty
その2. BindToButtonOnClick
その3. BindToOnClickその1. ReactiveProperty
Sample1using 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
Sample2using 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
Sample3using 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に変換することができます。
- 投稿日:2021-01-24T09:50:48+09:00
[C#] WPFアプリでディスプレイのON/OFFを取得する
もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4fやりたいこと
Windowsの省電力の設定画面で、設定した時間が経過したらディスプレイの電源をOFFしているときに、ディスプレイの電源が切れたことをC#のプログラムで知りたい。
下記のページを参考に、やってみる。
https://www.366service.com/jp/qa/8cddf991f384dadaa31692fc612bd4e0やり方
「電源設定の変化イベント」Windowsから受け取って、その中に含まれているディスプレイのONOFFの情報を読み取る形で実現する。
手順
まずは、こちらのやり方で、ウインドウメッセージハンドラをフックするメソッドを用意する。(下のC#サンプル中の
WndProc()
がそれにあたる。)
RegisterPowerSettingNotification()
で、自分のアプリ(ウインドウ)に電源設定変更イベントが来るように設定(登録)する。→参照
RegisterPowerSettingNotification()
はUser32.dll
の中に含まれているWin32APIなので、C#から呼べるようにP/Invoke登録してやる。(一緒に使うUnregisterPowerSettingNotification()
関数も一緒に行う)
PBT_POWERSETTINGCHANGE
がきたときのWM_POWERBROADCAST
のlParam
は電源設定を格納したPOWERBROADCAST_SETTING
なので、その中身を見る。→参照メッセージハンドラの中で、
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]
が、欲しいデータ。 そのData[1]の中に何が入っているかは、MSのPower Setting GUIDsのページに書いてある。 今回はGUID_CONSOLE_DISPLAY_STATE
を使うので、下記のデータがとれる。 →上記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/fc6b4bed1b2709d21a03Registering for Power Events
パワーイベントが来てくれるように登録するやり方
https://docs.microsoft.com/en-us/windows/win32/power/registering-for-power-eventsPBT_POWERSETTINGCHANGE event
https://docs.microsoft.com/en-us/windows/win32/power/pbt-powersettingchangeWM_POWERBROADCAST message
https://docs.microsoft.com/en-us/windows/win32/power/wm-powerbroadcastPOWERBROADCAST_SETTING structure (winuser.h)
https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-powerbroadcast_settingPower 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
- 投稿日:2021-01-24T09:50:48+09:00
[C#/C++] WPFアプリでディスプレイのON/OFFを取得する
もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4fやりたいこと
Windowsの省電力の設定画面で、設定した時間が経過したらディスプレイの電源をOFFしているときに、ディスプレイの電源が切れたことをC#のプログラムで知りたい。
下記のページを参考に、やってみる。
https://www.366service.com/jp/qa/8cddf991f384dadaa31692fc612bd4e0やり方
「電源設定の変化イベント」をWindowsから受け取って、その中に含まれているディスプレイのONOFFの情報を読み取る形で実現する。
手順
まずは、こちらのやり方で、ウインドウメッセージハンドラをフックするメソッドを用意する。(下のC#サンプル中の
WndProc()
がそれにあたる。)
RegisterPowerSettingNotification()
で、自分のアプリ(ウインドウ)に電源設定変更イベントが来るように設定(登録)する。→参照
RegisterPowerSettingNotification()
はUser32.dll
の中に含まれているWin32APIなので、C#から呼べるようにP/Invoke登録してやる。(一緒に使うUnregisterPowerSettingNotification()
関数も一緒に行う)
PBT_POWERSETTINGCHANGE
がきたときのWM_POWERBROADCAST
のlParam
は電源設定を格納したPOWERBROADCAST_SETTING
なので、その中身を見る。→参照メッセージハンドラの中で、
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]
が、欲しいデータ。
そのData[1]の中に何が入っているかは、MSのPower Setting GUIDsのページに書いてある。 今回はGUID_CONSOLE_DISPLAY_STATE
を使うので、下記のデータがとれる。
→上記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/fc6b4bed1b2709d21a03Registering for Power Events
パワーイベントが来てくれるように登録するやり方
https://docs.microsoft.com/en-us/windows/win32/power/registering-for-power-eventsPBT_POWERSETTINGCHANGE event
https://docs.microsoft.com/en-us/windows/win32/power/pbt-powersettingchangeWM_POWERBROADCAST message
https://docs.microsoft.com/en-us/windows/win32/power/wm-powerbroadcastPOWERBROADCAST_SETTING structure (winuser.h)
https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-powerbroadcast_settingPower 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
- 投稿日:2021-01-24T00:27:55+09:00
MessagePack for C#で、Genericな変数をIL2CPP環境で使いたいときのメモ
前提
このライブラリの応用?的な使い方の話をしています。
現時点の最新版、2.2.85で動作確認済み。
https://github.com/neuecc/MessagePack-CSharpUnityエディタでは動いてたのに、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クラスで良いのでは???
- 投稿日:2021-01-24T00:00:05+09:00
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();これで安定してグループ化することができました。