もこたんブログ@mocuLab(・ω・)

Programming & Designing

C#からdllImportでWin32 APIのEnumWindows関数を使う方法

.NET Frameworkに大抵のものはあらかた揃っていますが、それでもないものもあります。そのため、.NET以外の他所の関数を読み込んで使用する方法があります。いろいろ調べたのでメモ。

やりたかったこ

あるプログラムP1からexeキックしたプログラムP2のタイトルとプロセスIDをC#で取得したい。

経緯の履歴
同じプロセス空間にあるため(インスタンスを生成したような感じらしい)、P2はP1とプロセス名が同じになってしまい、P2のMainWindowTitleが取得できなかった。P1もP2もタスクバーに表示されているからProcess.GetProcessesメソッド(プロセス一覧取得)で取れそうなのに…

「タスクバー タイトル 取得 c#」とかでぐぐったが、直接的な方法をうまく検索できず、断念…

c# 子ウィンドウ 取得」あたりで検索した。
EnumChildWindows関数というものがあった。使ってみたが、ウィンドウというのがイメージと違ってコントロールのことのようで、画面の上に乗っかっているテキストフィールドやラベルなど、細々したコントロールまで含めて列挙するようで、ちょっと違うと思い、保留。

最終的に、EnumWindowsを使って全列挙中から必要なものだけ取得する方法で、なんとか目的を達成できた。

画面上のすべてのウィンドウとそのタイトルを列挙する DOBON.NET
https://dobon.net/vb/dotnet/process/enumwindows.html
を参考にした。
上記ページの説明
「プロセスのメインウィンドウしか探せませんので、同じプロセスが複数のウィンドウを表示している場合は、その内1つしか取得できません。」
の通り、Process.GetProcessesメソッドでは目的の画面のMainWindowTitleが取得できなかった。

dll読み込みの記述

Win32 API(WindowsシステムのAPI)のEnumWindows関数を使用します。トップレベルウィンドウを列挙する関数です。(トップレベルウィンドウとは、親を持たないウィンドウのこと)
↓EnumWindows 関数(MSDN)
https://msdn.microsoft.com/ja-jp/library/cc410851.aspx

BOOL EnumWindows(
  WNDENUMPROC lpEnumFunc,  // コールバック関数
  LPARAM lParam            // アプリケーション定義の値
);

公式リファレンスの「対応情報」の「インポートライブラリ:User32.lib を使用」より、「user32.dll」を読み込めばいいことがわかります。
基本的に.lib→.dllに変換すればOKです。
f:id:mocotanus:20171012054323j:plain

コールバック関数は、falseを返すか、全てを列挙し終わるまで、何度も呼ばれ続けます。

引数のlpEnumFuncにはコールバック関数へのポインタを指定します。デリゲートを渡せばOKです。デリゲートは、タイプセーフな関数ポインタまたはコールバック関数と等価です。

コールバック メソッドとしてのデリゲートのマーシャ リング https://msdn.microsoft.com/ja-jp/library/5zwkzwf4(v=vs.110).aspx

「アプリケーション定義の値」については調べたけど結局よくわかっておらず…IntPtr.Zeroを指定するサンプルばかりだったのでそうしています(´・ω・`)

C#から呼び出すコードの書き方

using System.Runtime.InteropServices;// DllImport属性用

(略)

        // トップレベルウィンドウを列挙する
        [DllImport("user32.dll")]
        private static extern bool EnumWindows(EnumWindowsDelegate lpEnumFunc, IntPtr lParam);

        // EnumWindowsから呼び出されるコールバック関数のデリゲート
        private delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lParam);

using System.Runtime.InteropServices;
 DllImport 属性(DllImportAttribute)の定義場所の名前空間。usingで省略すると便利。
DllImport 属性
 呼び出したい関数を含むDLLを文字列で指定する。
 Win32 API以外でも同様の記述になる。
static修飾子
 必須
extern修飾子
 必須
 関数がC#コードの外部で実装されていることを宣言する。
 一般に、アンマネージ コードを呼び出すときにDllImport 属性と共に使用される。

・publicやprivateは自由

フォームアプリケーションのフォームのコンストラクタから呼んでみる

        private static int cnt = 0;

        // コンストラクタ
        public Form1()
        {
            InitializeComponent();

            // ウィンドウの列挙を開始
            EnumWindows(EnumerateWindows, IntPtr.Zero);

            Console.WriteLine(cnt.ToString());// 2840とかすごい数だった
        } 

        // ウィンドウを列挙するコールバックメソッド
        private static bool EnumerateWindows(IntPtr hWnd, IntPtr lParam)
        {
            cnt++;

            // 何か処理

            // 途中で列挙をやめるときは、return false;となる

            // ウィンドウの列挙を継続する
            return true;            
        }

コード全体のサンプル

Windowsフォームアプリケーションを新規に作成して試しに動かしてみました。タスクバーに表示されているプログラム一覧と非常に近い一覧が取得できました。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Diagnostics;// プロセス用
using System.Runtime.InteropServices;// DllImport用

namespace Sample03
{
    public partial class Form1 : Form
    {
        #region DllImport
        // トップレベルウィンドウを列挙する
        [DllImport("user32.dll")]
        private static extern bool EnumWindows(EnumWindowsDelegate lpEnumFunc, IntPtr lParam);

        // ウィンドウの表示状態を調べる(WS_VISIBLEスタイルを持つかを調べる)
        [DllImport("user32.dll")]
        private static extern bool IsWindowVisible(IntPtr hWnd);

        //ウィンドウのタイトルの長さを取得する
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern int GetWindowTextLength(IntPtr hWnd);

        // ウィンドウのタイトルバーのテキストを取得
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

        // ウィンドウを作成したプロセスIDを取得
        //[DllImport("user32")]// 「.dll」なくても動いてた
        [DllImport("user32.dll")]
        private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); 
        #endregion

        // EnumWindowsから呼び出されるコールバック関数のデリゲート
        private delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lParam);

        #region 【クラス】WindowProp:ウィンドウの情報
        /// <summary>
        /// ウィンドウの情報
        /// </summary>
        public class WindowProp
        {
            public int ProcessId { get; set; }
            public string ProcessName { get; set; }
            public string Title { get; set; }

            public WindowProp()
            {
                this.ProcessId = 0;
                this.ProcessName = "";
                this.Title = "";
            }
        }
        #endregion

        // staticなメソッド内で使用したいプロパティにも、staticが必要
        // 「静的でないフィールド、メソッド、またはプロパティ'~(略)'  で、オブジェクト参照が必要です」なエラーが出る
        /// <summary>ウィンドウ情報のリスト</summary>
        private static List<WindowProp> WindowPropList { get; set; }

        private static int cnt = 0;// 2840とかすごい数だった
        private static int cntIsWindowVisible = 0;// 23
        private static int cntGetWindowTextLength = 0;// 12(IsWindowVisibleで除外しない場合、218)

        // コンストラクタ
        public Form1()
        {
            InitializeComponent();

            WindowPropList = new List<WindowProp>();
            // ウィンドウの列挙を開始
            // ここで渡したIntPtr.Zeroは、EnumerateWindowsの引数lParamに入ってくる
            EnumWindows(EnumerateWindows, IntPtr.Zero);

            foreach (var p in WindowPropList)
            {
                Console.WriteLine(p.ProcessName + " : " + p.Title);
            }
        }

        // ウィンドウを列挙するコールバックメソッド
        private static bool EnumerateWindows(IntPtr hWnd, IntPtr lParam)
        {
            cnt++;
            // すごい大量に列挙するので、条件つけてたくさんはじくといいようです

            // ウィンドウが可視かどうか調べて、表示してないのものを除外する
            if (IsWindowVisible(hWnd) == false) return true;
            cntIsWindowVisible++;

            //ウィンドウのタイトルの長さを取得する
            int textLen = GetWindowTextLength(hWnd);
            if (textLen == 0) return true;
            cntGetWindowTextLength++;

            //ウィンドウのタイトルを取得する
            var title = new StringBuilder(textLen + 1);
            GetWindowText(hWnd, title, title.Capacity);
            // ウィンドウハンドルからプロセスIDを取得
            int processId;
            GetWindowThreadProcessId(hWnd, out processId);
            // プロセスIDからProcessクラスのインスタンスを取得
            Process p = Process.GetProcessById(processId);

            Form1.WindowPropList.Add(new WindowProp()
            {
                ProcessId = processId,
                ProcessName = p.ProcessName,
                Title = title.ToString(),
            });

            // 途中で列挙をやめるときは、return false;にする

            // ウィンドウの列挙を継続する
            return true;
        }
    }
}

Console.WriteLineメソッドでコンソールに出力した文字列は、VisualStudioの「出力」ウィンドウにも表示されます。

C#の拡張メソッドの作り方。拡張メソッドを定義する方法

Microsoft公式 拡張メソッド (C# プログラミング ガイド)
拡張メソッド (C# プログラミング ガイド) | Microsoft Docs

大抵のものは標準で揃っているけれども、そこにない処理を個別に追加できます。

拡張メソッドとは…

既存の型(クラスなど)にメソッドを追加できます。その追加されたメソッドのことです。

拡張メソッドを使うと、StringクラスやDatetimeクラスなど、変更できないクラスにもメソッドを追加できます。クラス自体には変更を加えないでメソッドの追加だけをできます。
→逆に言うと、変えられないクラスにメソッドを追加できる!

構文

拡張メソッドは、静的クラスの静的メソッドとして定義します。また、追加するメソッドの最初のパラメータには、thisキーワードで拡張するクラスを指定します。

public static 拡張メソッド名( this 拡張する型, パラメータリスト )
{
    // 処理
}
拡張メソッドを定義するクラスの条件

入れ子になってない
ジェネリックじゃない※ジェネリッククラスの例:Listとか
・静的クラス(static)
・public
クラス名はなんでもいいですが、わかりやすく「~Extend」「~Ex」「~Extensions」などを付けることが多そうです。  

拡張メソッドの条件

・静的メソッド(static)
・public
・引数の最初に「this 拡張する型名」で拡張したい型を指定

拡張メソッドを定義する例

例として、「00、01、02、03…」など、左が0埋めされている文字列を「0、1、2、3…」という形に変換するメソッドを、stringの拡張メソッドとして作るとします(標準ではなかったはず…あるっけ?)。左の0をトリムするので、メソッド名はTrimLeftZeroとしてみました。

コンソールアプリを新規作成して、Mainから呼んでみます。 f:id:mocotanus:20171006125317p:plain

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Util;

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            var list = new List<string>()
            {
                "00",// "0"にする
                "01",// "1"にする
                "02",// "2"にする
            };

            foreach (var s in list)
            {
                string value = s.TrimLeftZero();
                Console.WriteLine(value);
            }

            var today = DateTime.Today;
            var lastDate = today.GetLastDateOfMonth();
            Console.WriteLine(today.ToString("yyyy.MM.dd") + " の月末は " + lastDate.ToString("yyyy.MM.dd"));

            Console.ReadLine();
        }
    }
}

同じプロジェクト内に、拡張メソッド用の別ファイルを作った場合。別ファイル(StringExtensions.cs)>StringExtensionsクラス>TrimLeftZeroメソッド
f:id:mocotanus:20171006125322p:plain

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication3
{
    public static class StringExtensions
    {
        #region TrimLeftZero:左にある連続した0を削除する
        /// <summary>
        /// 左にある連続した0を削除する
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string TrimLeftZero(this string value)
        {
            if (value == null) return "";
            if (value == "") return "";

            int i = 0;
            bool res = int.TryParse(value, out i);

            // 数字に変換できる場合は文字列にして返す
            if (res) return i.ToString();
            // 数字に変換できない場合はそのまま返す
            return value;
        }
        #endregion
    }
}

別プロジェクトにライブラリクラスとして作成した場合。プロジェクト名Util>ExtensionMethods.cs>DateTimeExtendクラス>GetLastDateOfMonthメソッド 「last day of month」は聞くけど、「last date of month」って言うのかはわかりません(´・ω・`)ある日付の月末の日付をDateTime型で取得するメソッドです。標準でありそうだけど。
f:id:mocotanus:20171006125326p:plain

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Util
{
    /// <summary>
    /// DateTimeの拡張メソッド
    /// </summary>
    public static class DateTimeExtend
    {
        #region GetLastDateOfMonth:日付の最終日の日付を取得する
        /// <summary>
        /// 日付の最終日の日付を取得する
        /// 例)「2017/10/6」から「2017/10/31」を取得する
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static DateTime GetLastDateOfMonth(this DateTime value)
        {
            DateTime tempDate = value.AddMonths(1);
            DateTime lastDate = new DateTime(tempDate.Year, tempDate.Month, 1);
            lastDate = lastDate.AddDays(-1);
            return lastDate;
        } 
        #endregion
    }
}

香りで集中力アップ!アロマの活用

Amazonプライム会員ならAmazonオーディブル3ヶ月無料ということで試していました。その中で「自分を操る超集中力」という本があったので聞いてみました。自分を操りたい(´・ω・`)! 

自分を操る超集中力

自分を操る超集中力

 

 

ウィルパワーとは?

オーディオブックだとどこに書いてあったか探しにくいので、うろ覚えの記憶なんですけど、本書では集中力の源となる力のことをウィルパワーと呼んでいます。たぶんwillは意志とか決めようとするとかいう意味だと思います。魔力(MP)のようなイメージで、何かをしたり、決めたり…という行動や思考で減っていきます。寝ることで回復します。総量は個人差がありますが、訓練でじわじわと総量をアップさせることができます。 

 

嗅覚を刺激してウィルパワーを回復させる

その中でアロマを活用して、集中力を回復させる3つの香りが紹介されていました。

ローズマリー

f:id:mocotanus:20171011061852j:plain

・脳への血流を良くする
 ウィルパワーが回復する。
・記憶の改善
 認知症の症状改善を目的として医療でも使われている。

使い方
エッセンシャルオイル(アロマオイルじゃないほうがおすすめ)をティッシュやアロマストーンなどに数滴たらして使う。(手に付かないように注意)

 

■ペパーミント

f:id:mocotanus:20171011061907j:plain

・リフレッシュ効果
 敏捷性、集中力アップ、覚醒効果。(様々な実験で効果が認められている)
 仕事や勉強による疲労・眠気を改善する。

使い方

ローズマリーと同じく、エッセンシャルオイルを使う。
他には、温かいミントティーを入れて飲む→ウィルパワー回復、リラックス効果も得られる。

 

■シナモン

f:id:mocotanus:20171011061918j:plain

・脳の認識機能、記憶力を高める

使い方

シナモンスティック(スーパーのスパイスコーナーにある)を集中したいときに嗅ぐ。
または、食事や休憩時間に、シリアルやコーヒーなどにシナモンスパウダーを振りかけて、香りづけするのもいい。

 

アロマストーン買ってみた!

はい、影響されやすいです。エッセンシャルオイルやアロマオイル自体は家にあったので、オイルストーンを買ってみました!


2個セットなので、家と会社で使えそうです。エッセンシャルオイルが1種類選べたので、ペパーミントを選択。

f:id:mocotanus:20171007075347j:plain

 

おまけでサシェ付いてた。

f:id:mocotanus:20171007075350j:plain

 

↓2滴落とした様子。自分の周りだけ香るので良さげです。

f:id:mocotanus:20171007075354j:plain

 

 

Amazonオーディブルを使ってみた感想。Amazonプライム会員は3ヶ月無料!

Amazonプライム会員は3ヶ月無料ということで、オーディブルを使ってみて2ヶ月半経ちました。

オーディブルの会員費は高い…

やっぱりプライム会員費と比較してしまいます。プライム会員費が年間3900円に対して、オーディブルは月額1500円。さすがに高いと感じますね…。3ヶ月無料期間があったので試しましたが、費用の問題で継続には勇気が必要です(´・ω・`)

プライム会員特典がけっこう豪華なんですよね。。

  • 送料無料
  • 時間指定無料
  • プライム会員割引
  • 動画見放題サービス(プライムビデオ)
  • 音楽聴き放題サービス(Amazonミュージック)
  • 電子書籍読み放題サービス(Prime Reading)(←2017.10.05確認)
  • Kindle月1冊無料 
  • 写真ストレージ無料で無制限
  • などなど…

オーディブルがプライム会員特典に追加されたらいいな!(´・ω・`)

 

コンテンツ量は着々と増えている

コンテンツはどんどん増えている印象です!英語、ビジネス、自己啓発、瞑想が人気みたいですね。他のオーディオブックサービスを知らないのですが、今はコンテンツ不足ではないと思いますよ。

IEnumerableの読み方

会社でenum型をみんな「イナムがた」って読んでるんですが、「IEnumerable」はアイイナメラブル?自信ない…と思い調べてみました。

結論→たぶん「アイ イニューメラブル」(´・ω・`)!
で、ネイティブが発音すると速くなって「アイニューメラブル」?

 

C#ではインターフェース名は先頭に「I」をつけるのが慣習となっているので、
「I」「Enumerable」と分けられますね。

電子辞書(英和)で調べて、読みを発音してくれるので聞いてみました。
自分の辞書では「enumerable」はなかったのですが、似た語がありました。
enumerate(動詞)(イニューメレイト)
 列挙する、数え上げる
enumeration(名詞)(イニューメレイション)
 数え上げること、列挙

ということなので、
たぶんEnumerableはイニューメラブルと読むにちがいない(´・ω・`)!

eはアルファベットで「イー」だし。
(ローマ字だと「え」なのでごっちゃになるが…)