- 投稿日:2020-06-25T22:59:44+09:00
C# Win32API ShellExecuteExでWindows 10の「コンピューターの基本的な情報の表示」を表示する
本文
Windows 10ではデスクトップのPCアイコンのプロパティから「コンピューターの基本的な情報の表示」を表示できます。C#では
SHGetKnownFolderIDList
にFOLDERID_ComputerFolder
を与えてLPITEMIDLIST
を取得した後、SEE_MASK_INVOKEIDLIST
を指定してShellExecuteExW
を呼び出すことでこれを表示できます。using System; using System.Runtime.InteropServices; namespace ConsoleApp1 { class Program { static void Main() { var pidlComputer = default(IntPtr); try { pidlComputer = NativeMethods.SHGetKnownFolderIDList( FOLDERID_ComputerFolder, KF_FLAG_DEFAULT, IntPtr.Zero); var sei = new SHELLEXECUTEINFOW { cbSize = (uint)Marshal.SizeOf<SHELLEXECUTEINFOW>(), fMask = SEE_MASK_INVOKEIDLIST, lpVerb = "properties", lpIDList = pidlComputer }; NativeMethods.ShellExecuteExW(ref sei); } finally { Marshal.FreeCoTaskMem(pidlComputer); } } private static class NativeMethods { [DllImport("shell32.dll", ExactSpelling = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool ShellExecuteExW( ref SHELLEXECUTEINFOW pExecInfo); [DllImport("shell32.dll", ExactSpelling = true, PreserveSig = false)] public static extern IntPtr SHGetKnownFolderIDList( [MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken); } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct SHELLEXECUTEINFOW { public uint cbSize; public uint fMask; public IntPtr hwnd; public string lpVerb; public string lpFile; public string lpParameters; public string lpDirectory; public int nShow; public IntPtr hInstApp; public IntPtr lpIDList; public IntPtr lpClass; public IntPtr hkeyClass; public uint dwHotKey; public IntPtr hIconOrMonitor; public IntPtr hProcess; } private static readonly Guid FOLDERID_ComputerFolder = Guid.Parse( "{0AC0837C-BBF8-452A-850D-79D08E667CA7}"); private const uint KF_FLAG_DEFAULT = 0; private const uint SEE_MASK_INVOKEIDLIST = 0x0000000C; } }Windows 10以外ではおそらく普通にPCのプロパティが表示されます。
アンマネージリソースと
try...finally
var pidlComputer = default(IntPtr); try { pidlComputer = NativeMethods.SHGetKnownFolderIDList(...); ... } finally { Marshal.FreeCoTaskMem(pidlComputer); }
SHGetKnownFolderIDList
の戻り値であるpidlComputer
はアンマネージリソースのポインタであり、Marshal.FreeCoTaskMem
で解放する必要があります。try
の中でvar pidlComputer = ...
とするとfinally
のスコープからはpidlComputer
を参照できないので、try
の外側でpidlComputer
を宣言しています。この冗長さを回避するためには
SafeHandle
の派生クラスを作成してusing
と組み合わせます。オブジェクト初期化子による構造体の初期化
var sei = new SHELLEXECUTEINFOW { cbSize = ... };構造体は
new 型名 {field1 = ..., field2 = ..., ...}
の形式で初期化できます。構造体中の
string
のP/Invoke[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct SHELLEXECUTEINFOW { ... public string lpVerb; ... ]P/Invoke時(
DllImport
属性を持つ関数の呼び出し時)、構造体中の文字列は自動的にIntPtr
型へ変換・復元されます。IntPtr
を指定して毎回Marshal.PtrToStringAnsi/Uni
を呼び出すこともできますが、文字列を再利用しなければこちらの方が簡潔です。変換時のANSI/UNICODEはStructLayout
属性のCharSet
で指定します。
UnmanagedType.LPStruct
C#側で値渡しした構造体(
Guid
等)をP/Invoke時にそのポインタとして渡します。C++のREFCLSID
やREFIID
にC#のtype(...).GUID
等を渡すとき、ref Guid ...
とすると必要になる一時的な変数に省略に使います。
- 投稿日:2020-06-25T10:50:23+09:00
C言語でK-meansの実装
大学の課題でCでk-meansを実装したのでメモ。
C言語を書くのはほぼ1年ぶりとかで結構時間がかかった。実装の流れ
200このデータを使用した。
1.各クラスタの重心の初期値を決定する
2.200個の点をそれぞれ一番近い重心のクラスタに分類する
3.分類したクラスタの重心を計算する。
4.前回の重心と同じになるまで繰り返すの流れで実装していく
1.各クラスタの重心の初期値を決定する
コンソールからcsvファイルを読み込みdata[200][2]にx,y座標を格納する。
//Input// fp_in = fopen(argv[1],"r"); if(fp_in==NULL){ printf("fail: cannot open the input-file. Change the name of input-file. \n"); return -1; } while( (input=fscanf(fp_in, "%lf,%lf", &data[count][0], &data[count][1])) != EOF){ //printf("%lf %lf\n", data[count][0], data[count][1]); count++; }Cでは多次元配列は一気に初期値を宣言できず、for文で一個づつ入れていく必要がある。pythonではできるから不便・・・
int k = 4; double newGravity[k][2];//重心を入れておく double oldGravity[k][2]; double clasterLabel[200][3];//データをクラスタ毎に振り分けたときに格納するリスト double dList [k]; //1点から各クラスタ重心との距離を入れておく。最小距離を求める時に使う int flag = 0; //重心が一致しているか確かめる時に使うフラグ int state = 1; //deta[200][2]に各点のx,y座標が格納 //[1] 重心の初期値となるk個の点を選択 for(int i = 0; i < k; i++){ newGravity[i][0] = data[i][0]; //ここで抽出するデータはランダムにするべき 現在は固定している newGravity[i][1] = data[i][1]; oldGravity[i][0] = -1; //初期値 NULLの代わり oldGravity[i][1] = -1; printf("position of claster%d (x:%lf y:%lf)\n",i,newGravity[i][0],newGravity[i][0]); }2.200個の点をそれぞれ一番近い重心のクラスタに分類する
各クラスタの重心との距離を1点ずつ調べていく。注目している点から各クラスタの重心までの距離は配列dに格納。
C言語では配列の最小値を求めるライブラリが存在しないらしく、最小値のindexを戻り値に持つ関数minDisClasterを作成。
また、配列には異なる型のデータを格納できないっぽいので不本意ながらdouble型で定義した。
型の扱いとか含めてpythonはめっちゃ便利で簡単だったなって思う。printf("==========%d try ========\n",state); //[2] 200個の点をk個のクラスタの重心から一番近いクラスタに振り分ける for(int i = 0; i < 200; i++){//各点を調べていく iはデータナンバー jはクラスタナンバー double x = data[i][0]; double y = data[i][1]; for(int j = 0; j < k; j++){//各クラスタとの距離をdListに格納していく double grav_x = newGravity[j][0]; double grav_y = newGravity[j][1]; double dd = pow((x - grav_x),2.0) + pow((y - grav_y),2.0); //d**2の計算 dList[j] = sqrt(fabs(dd)); // |d|これが注目クラスタの重心からの距離 } double clasterNumber = minDisClaster(dList,k); //printf("%d\n", clasterNumber); clasterLabel[i][0] = clasterNumber; //[クラスター番号(ラベル)、x,y]のリストになった インデックスがデータ番号 clasterLabel[i][1] = data[i][0]; clasterLabel[i][2] = data[i][1]; }double minDisClaster(double d[], int k){//最小距離を探し出して、属するクラスタの番号(0スタート)を返す関数 double minDistance = d[0]; double classfire; for(int i = 1; i < k; i++){ if(minDistance > d[i]){ minDistance = d[i]; } } for(int i = 0; i < k; i++){ //printf("minD:%f,d[]:%f,i:%d\n",minDistance, d[i], i); if(minDistance == d[i]){ classfire = (double)i; } } //printf("%d\n",classfire); return classfire; }3.分類したクラスタの重心を計算する。
まず配列を定義、初期化する。
初期化して値が入っていない状態でprintfしようとすると、100桁くらいの数字が出力されてしまうので注意。
そこで詰まって、原因がわからず時間を無駄にしたのでメモ。
また、出力の際は"%d"といった指定演算子にも気を使わなければいけないので注意が必要。//[3] 各クラスタに属する点の座標の平均をとり double sum[k][2]; int countPoint[k]; //各クラスターの要素数をカウントするやつ //全要素を0で初期化 for(int i = 0; i < k; i++){ sum[i][0] = 0.0; sum[i][1] = 0.0; countPoint[i] = 0; }以下で、各クラスターに属する座標の合計値を配列sumに入れていく。
配列の平均値を出すライブラリもないっぽいので、countPointで各クラスターに属するポイントの数を数える。for(int i = 0; i < 200; i++){//iはデータ番号 int label = (int)clasterLabel[i][0]; countPoint[label] += 1; //該当クラスターの要素数を1つ追加 sum[label][0] = (sum[label][0] + clasterLabel[i][1]) ; //x sum[label][1] = (sum[label][1] + clasterLabel[i][2]) ; //y座標の合計 }前回の重心と同じになるまで繰り返す
先ほど求めたクラスターの座標の合計値を平均して、新しい重心の座標を求めた。
ループから抜け出す処理については、各クラスター毎に前回と同じかどうか判定して、同じだったらflagをインクリメント
すべてのクラスタでflagがたったらbreakとした。
なんでこんなに面倒なことをしたのかは自分でもわからない。ifの条件式に1つにまとめれただろうに・・
stateはループ回数を求めている。//sumに各座標の合計値が入ってるから その平均を出せばいい //sum[label][0] = [クラスター番号][[x平均]になってるはず //新たな重心を入力 //[3-2] 古い重心と新しい重心が同じ点かどうか判定 同じ点の場合は終了 for(int i = 0; i < k; i++){ oldGravity[i][0] = newGravity[i][0]; oldGravity[i][1] = newGravity[i][1]; newGravity[i][0] = sum[i][0]/countPoint[i];//sum を 要素数 で割って平均にしている newGravity[i][1] = sum[i][1]/countPoint[i]; if(oldGravity[i][0] == newGravity[i][0] && oldGravity[i][1] == newGravity[i][1] ){ flag ++; } state ++; if(flag == k - 1){ for(int i = 0; i < k; i++){ printf("position of claster%d (x:%f y:%f)\n",i,newGravity[i][0],newGravity[i][0]); } printf("\nend %d kaime no Try de %d kaime to onajininarimsaita",state, state-1); break; } for(int i = 0; i < k; i++){ printf("position of claster%d (x:%f y:%f)\n",i,newGravity[i][0],newGravity[i][0]); }実行結果
position of claster0 (x:1.037435 y:1.037435)
position of claster1 (x:1.851784 y:1.851784)
position of claster2 (x:2.197544 y:2.197544)
position of claster3 (x:2.100875 y:2.100875)
==========1 try ========
position of claster0 (x:0.726606 y:0.726606)
position of claster1 (x:1.683313 y:1.683313)
position of claster2 (x:2.349820 y:2.349820)
position of claster3 (x:2.028122 y:2.028122)
==========2 try ========
position of claster0 (x:0.648206 y:0.648206)
position of claster1 (x:1.544349 y:1.544349)
position of claster2 (x:2.519150 y:2.519150)
position of claster3 (x:1.792414 y:1.792414)
==========3 try ========
position of claster0 (x:0.490201 y:0.490201)
position of claster1 (x:1.463269 y:1.463269)
position of claster2 (x:2.685085 y:2.685085)
position of claster3 (x:1.716067 y:1.716067)
==========4 try ========
position of claster0 (x:0.449521 y:0.449521)
position of claster1 (x:1.460840 y:1.460840)
position of claster2 (x:2.754912 y:2.754912)
position of claster3 (x:1.737333 y:1.737333)
==========5 try ========
position of claster0 (x:0.449521 y:0.449521)
position of claster1 (x:1.467678 y:1.467678)
position of claster2 (x:2.799023 y:2.799023)
position of claster3 (x:1.770294 y:1.770294)
==========6 try ========
position of claster0 (x:0.449521 y:0.449521)
position of claster1 (x:1.479317 y:1.479317)
position of claster2 (x:2.885219 y:2.885219)
position of claster3 (x:1.831822 y:1.831822)
==========7 try ========
position of claster0 (x:0.449521 y:0.449521)
position of claster1 (x:1.483220 y:1.483220)
position of claster2 (x:2.909183 y:2.909183)
position of claster3 (x:1.862579 y:1.862579)end 8 kaime no Try de 7 kaime to onajininarimsaita
C言語に触れるのが久々すぎて、コンパイル方法を調べるところからだったけど、4日前くらいに始めたJavaと非常に似てたから何とかいけた
首の痛みはひいてきた。サンキューサロンパス。
- 投稿日:2020-06-25T03:42:03+09:00
Process.ExitedをWPF(MVVM)で使えなかったこととその代替策
はじめに
私はWPFでアプリを開発していたんですが、Process.Exitedが作動しませんでした。
一応、解決策が見つかったので載せておきたいと思います。(ただし安全だとは決して思いませんが)
あと、注意点を書いているのでそちらもご覧ください。今回使うアプリの概要
TextBlock
とButton
がある単純なUIで、ボタンを押すと外部アプリケーションが起動する。
外部アプリケーション起動中はTextBlock
に「外部プログラム起動中」と表示され、終了すると「外部プログラム終了」と表示される。代替策1(コードビハインド)
まずは簡単なほうから。
コードビハインドで非同期メソッドを記述してDispacherを使うといけました。
TextBlock
はTextBlock1
、Button
はButton1
でコードを書いてます。MainWindow.xaml.csprivate async void Button1_Click(object sender, RoutedEventArgs e) { ProcessStartInfo info = new ProcessStartInfo { FileName = "Test.exe", UseShellExecute = false, CreateNoWindow = true }; using(Process process = new Process()) { process.StartInfo = info; process.EnableRaisingEvents = true; process.Exited += new EventHandler(OnExited); var task = new Task<bool>(()=> { return true; }); TextBlock1.Text = "外部プログラム起動中"; process.Start(); var ok = await task; } } private void OnExited(object sender, EventArgs e) { TextBlock1.Dispatcher.Invoke(() => TextBlock1.Text = "外部プログラム終了"); }私にはなんでこれで動いてしまったのかわからないので、有識者の方の解説をお待ちしてます。
代替策2(非同期でProcess.HasExitedを取得)
つぎに本題のMVVM(もどき)での解決法はこちら。
解説などはコードの下になります。MainWindowVM.cspublic class MainWindowVM : INotifyPropertyChanged { private string text; public string Text // TextBlockのTextへバインド { get => text; set { text = value; OnPropertyChanged(nameof(Text)); } } // Commandに登録(バインド)されたメソッド private async void ActivateExcute() { ProcessStartInfo info = new ProcessStartInfo { FileName = "Test.exe", UseShellExecute = false, CreateNoWindow = true }; using (Process process = new Process()) { process.StartInfo = info; process.EnableRaisingEvents = true; process.Exited += new EventHandler(OnExited); var task = new Task<bool>(() => { while (!process.HasExited) ; return true; }); Text = "外部プログラム起動中"; process.Start(); var ok = await task; } } private void OnExited(object sender, EventArgs e) => Text = "外部プログラム終了"; }このコードでは、
TextBlock
のText
プロパティにText
をバインドし、Button
が押されたらActivateExcute
が呼び出されるようになっています。ちなみに、なんでこんなコードになったのかというと、MicrosoftのDocsで
Process
について調べたら、Note that the Exited event is raised even if the value of EnableRaisingEvents is false when the process exits during or before the user performs a HasExited check.
という記述を発見しました。そして、私はこう考えたわけです。
「非同期で外部アプリケーションが終了するまでHasExited
を呼び続ければExited
イベントを起こせるのでは?」
ということで、実際に非同期で上のコードのように呼び出し続けてみました。
すると、成功してしまうという奇跡?が起きたわけです。注意点(必ず読んでください)
では注意点を。
解決法1はまだしも、解決法2ではTask
のエラー処理をやっていないのでこのままこれらコードを使うのはやめてください。
あと何かほかにもあればコメントお願いします。最後に
ここで紹介した代替策をもってしても外部プログラムの終了を検知できない環境があると思います。
そういう方は起動したプロセスをProcess.GetProcesses
で検索し続けるという究極の解決方法が考えられるので試してみてください。
ちなみに、私は最初こうしてました。
- 実行環境
- Visual Studio Community 2019
- .NET Framework 4.8
- C#
- 投稿日:2020-06-25T00:08:25+09:00
C#でコンソール画面で文字列出力をする際のConsole.WriteLine以外の箇所について解説します
どうも、臨床工学プログラマkenです。
初投稿です。エンジニアに転職して一年弱、身に付けた基礎知識を人に説明してより理解を深め、誰かの役に立ってくれたら幸いですし、凄腕エンジニアの方からご意見も得られたらいいなと思い、Qiitaの投稿を始めることにしました。
早速いきます。
C#で初めてコンソールアプリを動かす時、おそらく多くの人がコンソール画面に
「Hallo World!」
と出力するかと思いますが、ただHallo World!と出力するだけなのになんかコード長くない?
と思ったことないですか?
PHPやRubyを学習したことあるなら尚更そう感じるかと思います。今回は基礎の文字列出力するコードの細かい箇所を解説していきます。
C#で「Hallo World!」を出力するときの細かいところを解説
以下のコードを実行することでコンソール画面に「Hallo World!」と出力する事ができます。
test_1.csusing System; namespace test_1 { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); Console.ReadKey(); } } }一行目から解説していきます。
usingについて
test_1.csusing System; // 名前空間の指定usingは指定した名前空間に属するクラスをクラス名だけで呼び出すことを可能にするキーワードです。
ここでは指定した名前空間であるSystem名前空間に含まれるクラスを呼び出せるようにしています。usingを使わなくても呼び出すことは可能ですが、その場合は
System.Console.WriteLine
のようにクラスを呼び出すたびに名前空間名からの記述が必要になるので、複数回呼び出すクラスがあるときは基本的にはプログラムの最初にusingで記述して、以降はクラス名だけで呼び出せるようにしてます。namespaceについて
test_1.csnamespace test_1 // 新規の名前空間の宣言 { }namespaceは名前空間を宣言するキーワードです。
今回の例で言うと、{}で囲まれた箇所がtest_1という名前空間に属することになります。classについて
test_1.csclass Program // クラスの宣言 { }classはクラスを宣言するキーワードです。
[]で囲まれた箇所がProgramクラスに含まれます。static void Main(string[] args)について
test_1.csstatic void Main(string[] args) // メソッドの宣言 { Console.WriteLine("Hello World!"); Console.ReadKey(); }メソッドの宣言部です。
メソッドは簡単に言うと、プログラムを実行するための処理をひとまとめにしたコードのブロックのことです。
{}で囲まれた中がメソッド内の処理です。メソッドですが、プログラムの中に単独で記述して使うことはできず、必ずクラスの中に含める必要があります。
また、C#ではMain()という名前がついたメソッドが最初に実行される決まりになっています。
メソッドの基本的な書き方としては
アクセス修飾子 戻り値のデータ型 メソッド名(パラメーター)
のような書式で記述します。今回のコードに当てはめると
・アクセス修飾子→なし(アクセス修飾子は省略可能、メソッドの場合アクセス修飾子を省略した時はデフォルトでprivateとなります。)アクセス修飾子の後にstaticキーワードをつけると、静的メンバーとして扱うことになります。
staticな変数やメソッドは、インスタンスを作成してから呼び出す必要はなく、クラス名.メンバー名で呼び出すことが可能です。・戻り値のデータ型→なし(戻り値がないメソッドの時にはvoidと記述します)
・パラメーター→string[] args
ちなみにこのstring[] argsはコマンドライン引数と呼ばれています。
アプリケーションを起動する際に渡されるオプションの値となります。そしてこのMainメソッド内でConsole.WirteLineを呼び出して文字列を出力しています。
(System名前空間のConsoleという静的クラスのWirteLineメソッドを呼び出しています)まとめ
以上がC#のプログラムを実行する際の基本的な構成となります。
学習を始めた最初のうちはこの辺りは気にしなくても、特に支障はありませんが、開発などをする際にはこれらの細かい要素を理解している必要があります。今回はそれぞれ本当に簡単に解説しました。
最後まで読んでいただきありがとうございました。ちなみに僕は趣味で個人ブログを運営しております。
前職の臨床工学技士のことや、筋トレ初心者がベンチプレス100kg上げる方法、医療職からエンジニアに転職した方法などについて、個人ブログで発信していますのでご興味ある方は覗きに来てください。
臨床工学プログラマブログ今後もC#関連の基礎的な内容で記事作成していきたいと思います。
どうぞよろしくお願いします。参考文献リンク
C#を攻略しよう アクセス修飾子
ピーコックアンダーソン