2017/8/18 追記:
当記事のコードは現在動作しなくなっているため、新しくコードを書き直しました。
これまで当ブログでは、C#でMicrosoft Edgeを操作するコードについていくつか記事を書いてきました。
- Microsoft Edgeを起動するC#コード
- //www.ka-net.org/blog/?p=6161
- Selenium(C#)でEdgeをいろいろ操作してみた。
- //www.ka-net.org/blog/?p=6165
- 起動中のMicrosoft EdgeからタイトルとURLを取得するC#コード(UI Automation編)
- //www.ka-net.org/blog/?p=6145
- 続・起動中のMicrosoft EdgeからタイトルとURLを取得するC#コード(UI Automation編)
- //www.ka-net.org/blog/?p=6148
- WebDriverを使わずMicrosoft Edgeを制御するC#コード
- //www.ka-net.org/blog/?p=6170
UI Automationを使ったりInternet Explorer_ServerクラスのウィンドウからIHTMLDocument2を取得したりしていたわけですが、MSDNフォーラムにあった質問「Get URLs of pages opened in MS-Edge Browser is not working in Windows 10 Home Edition(Upgraded from Windows 8.1) using C#」を発見し、改めてWindows 10 Home環境で上記記事のコードが使えるか確認したところ、意外なところで引っ掛かりました。
“Microsoft.mshtml が参照できない。”
これまで普通に使えていたので何とも思っていなかったのですが、どうやら開発環境かOffice用PIAが入っていないと使えないらしいのです。
「WebDriverを使わずMicrosoft Edgeを制御するC#コード」のコードをそのまま使うことができなくなったわけですが、キャストできないならdynamicで受ければ良いじゃない?、というわけでコードを書き直すことにしました。
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace Sample
{
class Program
{
[Flags]
private enum SendMessageTimeoutFlags : uint
{
SMTO_NORMAL = 0x0000,
SMTO_BLOCK = 0x0001,
SMTO_ABORTIFHUNG = 0x0002,
SMTO_NOTIMEOUTIFNOTHUNG = 0x0008,
SMTO_ERRORONEXIT = 0x0020
}
private delegate bool Win32Callback(IntPtr hWnd, ref IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumWindows(Win32Callback lpEnumFunc, ref IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumChildWindows(IntPtr hWndParent, Win32Callback lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("oleacc.dll", PreserveSig=false)]
[return: MarshalAs(UnmanagedType.Interface)]
private static extern object ObjectFromLresult(UIntPtr lResult, [MarshalAs(UnmanagedType.LPStruct)] Guid refiid, IntPtr wParam);
[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
private static extern uint RegisterWindowMessage(string lpString);
[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
private static extern IntPtr SendMessageTimeout(IntPtr windowHandle, uint Msg, IntPtr wParam, IntPtr lParam, SendMessageTimeoutFlags flags, uint timeout, out UIntPtr result);
public static void Main(string[] args)
{
IntPtr hEdge = IntPtr.Zero;
Win32Callback proc = new Win32Callback(EnumWindowsProc);
EnumWindows(proc, ref hEdge);
if (hEdge != IntPtr.Zero) {
Win32Callback childProc = new Win32Callback(EnumChildProc);
EnumChildWindows(hEdge, childProc, IntPtr.Zero);
}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
//get a Edge window('Windows.UI.Core.CoreWindow' Class)
private static bool EnumWindowsProc(IntPtr hWnd, ref IntPtr lParam)
{
IntPtr hChild = IntPtr.Zero;
StringBuilder buf = new StringBuilder(1024);
StringBuilder buf2 = new StringBuilder(1024);
GetClassName(hWnd, buf, buf.Capacity);
switch (buf.ToString()) {
case "ApplicationFrameWindow": //normal window
hChild = FindWindowEx(hWnd, IntPtr.Zero, "Windows.UI.Core.CoreWindow", "Microsoft Edge");
if (hChild != IntPtr.Zero) {
lParam = hChild;
return false;
}
break;
case "Windows.UI.Core.CoreWindow": //minimized window
hChild = FindWindowEx(hWnd, IntPtr.Zero, "Spartan XAML-To-Trident Input Routing Window", "");
GetWindowText(hWnd, buf2, buf2.Capacity);
if (hChild != IntPtr.Zero && buf2.ToString() == "Microsoft Edge") {
lParam = hWnd;
return false;
}
break;
}
return true;
}
//get 'Internet Explorer_Server' window
private static bool EnumChildProc(IntPtr hWnd, ref IntPtr lParam)
{
dynamic doc = null;
StringBuilder buf = new StringBuilder(1024);
GetClassName(hWnd, buf, buf.Capacity);
if (buf.ToString() == "Internet Explorer_Server") {
doc = GetHTMLDocumentFromWindow(hWnd);
if (doc != null) {
Console.WriteLine(doc.Title + ", " + doc.url); //get document title & url
}
}
return true;
}
//get HTMLDocument object
private static object GetHTMLDocumentFromWindow(IntPtr hWnd)
{
UIntPtr lRes;
object doc = null;
Guid IID_IHTMLDocument = new Guid("626FC520-A41E-11CF-A731-00A0C9082637");
uint nMsg = RegisterWindowMessage("WM_HTML_GETOBJECT");
if (nMsg != 0) {
SendMessageTimeout(hWnd, nMsg, IntPtr.Zero, IntPtr.Zero, SendMessageTimeoutFlags.SMTO_ABORTIFHUNG, 1000, out lRes);
if (lRes != UIntPtr.Zero) {
doc = ObjectFromLresult(lRes, IID_IHTMLDocument, IntPtr.Zero);
}
}
return doc;
}
}
}
書き直すついでにUI Automationを使わないように変更もしています。
コードの解説
上記コードは「Microsoft Edgeを操作するVBAマクロ(DOM編)」でも書いている通り、Microsoft Edgeに含まれる「Internet Explorer_Server」クラスのウィンドウからIHTMLDocument2を取得し、DOM操作を行っています。
ここでEdgeのウィンドウがどのような構造になっているのかをSpy++で見てみると、
・非最小化時
(タイトル)- Microsoft Edge : ApplicationFrameWindow
― Microsoft Edge : Windows.UI.Core.CoreWindow
― “” : Spartan XAML-To-Trident Input Routing Window
― “” : Spartan Tab XAML-To-Trident Input Routing Window
― (タイトル) : TabWindowClass
― “” : Internet Explorer_Server
上図のようになっています。
ApplicationFrameWindowクラスのウィンドウの下にWindows.UI.Core.CoreWindowクラスのウィンドウがあって…、といった感じですが、Edgeが最小化されているときは下記のようになります。
・最小化時
(タイトル)- Microsoft Edge : ApplicationFrameWindow
― “” : ApplicationFrameTitleBarWindow
― “” : ApplicationFrameInputSinkWindow
上図の通り、ApplicationFrameWindowクラスのウィンドウの下にWindows.UI.Core.CoreWindowクラスのウィンドウはありません。
では、最小化時にはInternet Explorer_Serverクラスのウィンドウが取得できないかというとそうではなく、下記のようにEdgeのWindows.UI.Core.CoreWindowクラスのウィンドウはApplicationFrameWindowクラスとは別に存在しています。
Microsoft Edge : Windows.UI.Core.CoreWindow
― “” : Spartan XAML-To-Trident Input Routing Window
― “” : Spartan Tab XAML-To-Trident Input Routing Window
― (タイトル) : TabWindowClass
― “” : Internet Explorer_Server
したがって、上記コードではEnumWindowsでウィンドウを調べていき、ApplicationFrameWindowクラスのウィンドウだったらFindWindowExを使って子ウィンドウにWindows.UI.Core.CoreWindowクラスのウィンドウ(ウィンドウ名:Microsoft Edge)があるかどうかを判断(= 非最小化時)し、Windows.UI.Core.CoreWindowクラスのウィンドウだったらウィンドウ名が「Microsoft Edge」であるかどうか、また、子ウィンドウにSpartan XAML-To-Trident Input Routing Windowウィンドウがあるかどうかを判断(= 最小化時)して、Internet Explorer_Serverウィンドウの上位ウィンドウであるWindows.UI.Core.CoreWindowウィンドウを取得するようにしています。
Windows.UI.Core.CoreWindowウィンドウが取得できれば、あとはEnumChildWindowsでInternet Explorer_Serverクラスのウィンドウを取得していくだけです。
なぜEdgeにまだ「Internet Explorer_Server」が残っているのかは謎ですが、WebDriverのインストール不要でEdgeの操作ができるのは便利だと思います。





















この記事へのコメントはありません。