これは「Friendly Advent Calendar 2014 - Qiita」の記事です。
昨日は森理麟さんの自動化の話をしようじゃないか - 森理 麟(moririring)のプログラマブログでした。
今日は最終日ですねー。
なので、お楽しみ的なことをやってみようかなーと。
世界が驚いたVisualStudioへの介入デモの種明かしです。
まずは一回動画みてください。
このデモアプリのコードはこちらからダウンロードできます。
Codeer-Software/Demo_FriendlyAttachVisualStudio · GitHub
ボタンが移動した!
はい。まあ、これは手品ですw。
Friendlyはプロセスの壁を突破できるのですが、さすがにヒープ領域繋げれるわけではないので、直接インスタンスのやり取りはできません。
では、どうやっているかというと、
①相手プロセスに新しくインスタンスを生成してビジュアルツリーに追加する。
②自分のプロセスのボタンを隠す。
そうすると、インスタンスが別プロセスに移動したようにみえますよねw。
(さすがに、それくらいはわかってましたか?)
WindowControl mainWindow; using (var app = GetVS(out mainWindow)) { WindowsAppExpander.LoadAssembly(app, GetType().Assembly); WindowsAppExpander.LoadAssembly(app, typeof(DynamicAppVar).Assembly); WindowsAppExpander.LoadAssembly(app, typeof(WPFMenuBase).Assembly); var meue = mainWindow.IdentifyFromTypeFullName("Microsoft.VisualStudio.PlatformUI.VsMenu"); var ctrl = app.Type<InsertControl>()(new WindowInteropHelper(this).Handle, meue); mainWindow.GetFromTypeFullName("System.Windows.Controls.Grid")[0].Dynamic().Children.Add(ctrl); }
最初にDLLインジェクションしてますねー。
いつもより大目に差し込んでます。
これは相手プロセスでもFriendlyを使うためですね。
で作成しているのは、自分のアセンブリに定義したクラスInsertControlですね。
これを見ると結構ガッツリ書かれています。
何やっているかと言うと、VisualStudioでボタンを押されたときに実行する動作ですね。
using Codeer.Friendly; using Codeer.Friendly.Dynamic; using Codeer.Friendly.Windows; using Codeer.Friendly.Windows.Grasp; using RM.Friendly.WPFStandardControls.Inside; using System; using System.Collections.Generic; using System.Diagnostics; using System.Windows; using System.Windows.Automation.Peers; using System.Windows.Automation.Provider; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; using System.Windows.Threading; namespace AttachVS { public partial class InsertControl : UserControl { Menu _menu; IntPtr _targetHandle; static List<MenuItem> _executeItems; public InsertControl(IntPtr targetHandle, Menu menu) { _targetHandle = targetHandle; _menu = menu; InitializeComponent(); } void ButtonClick(object sender, RoutedEventArgs e) { try { MenuItems items = new MenuItems(); _executeItems = new List<MenuItem>(); GetAllItems(_menu, items, _executeItems); using (var app = new WindowsAppFriend(_targetHandle)) { WindowsAppExpander.LoadAssembly(app, _menu.GetType().Assembly); var targetWindow = new WindowControl(app, _targetHandle); targetWindow.Dynamic().AddMenu(items); } } catch { return; } finally { ((Panel)this.Parent).Children.Remove(this); } _menu.Visibility = Visibility.Hidden; } static void Execute(int index) { IInvokeProvider invoker = new MenuItemAutomationPeer(_executeItems[index]); invoker.Invoke(); } static void GetAllItems(Visual visual, MenuItems parent, List<MenuItem> executeItems) { foreach (var element in VisualTreeUtility.GetChildren(visual)) { Visual o = element; var item = o as MenuItem; MenuItems nextParent = parent; if (item != null && item.Visibility == Visibility.Visible) { var nextItem = new MenuItems() { ExecuteIndex = executeItems.Count, Text = (item.Header != null) ? item.Header.ToString() : string.Empty, IsEnabled = item.IsEnabled }; if (string.IsNullOrEmpty(nextItem.Text)) { continue; } parent.Items.Add(nextItem); executeItems.Add(item); nextParent = nextItem; if (0 < item.Items.Count) { IInvokeProvider invoker = new MenuItemAutomationPeer(item); invoker.Invoke(); DoEvents(); } } var next = element; var popup = next as Popup; if (popup != null) { next = popup.Child as Visual; } GetAllItems(next, nextParent, executeItems); } } static void DoEvents() { DispatcherFrame frame = new DispatcherFrame(); Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrames), frame); Dispatcher.PushFrame(frame); } static object ExitFrames(object f) { ((DispatcherFrame)f).Continue = false; return null; } } }
メニューが移動した!
これも同じですね。
元プロセスにメニュー項目を送り込んで、それを使ってにメニュー作成して、VisualStudioのは隠しているのでした。
上のコードのButtonClickの処理です。
これ、新しいですねw
他の怖い人の作品でも、送り込んだプロセスから、さらにFriendlyを使うのはなかったでしょ?
さすが作者w
ここでは一つポイントがありますね。
それは、メニューを開いてリストの中に格納しているのです。
これはなぜでしょうか?
WPFではメニューは一回開かないと生成されないんですね。
だから、隠してしまうともう操作できません。
なので、隠す前に一回開いて、それを保持しておくわけです。
そうすると、このアイテムをクリックすることで処理が実行されるのですね。
だから、最初のバーッとメニューが開いているのは演出ではなく、必然性に迫られてだったんですねー。
別プロセスからメニューが使える!
はい。で元プロセスでメニュー作成のために呼び出されるコードはコレ。
MainWindowに定義されていたのでした。
void AddMenu(MenuItems root) { var menu = new Menu(); Height = _originalHeight; _buttonInsert.Visibility = Visibility.Visible; menu.Width = _panel.Width - 1; menu.Height = _panel.Height - 1; Set(menu.Items, root.Items); //return menu MenuItem item = new MenuItem(); item.Header = "return"; item.Click += delegate { ReturnList(); }; menu.Items.Add(item); _panel.Children.Add(menu); } void Set(ItemCollection parent, List<MenuItems> src) { foreach (var e in src) { MenuItem item = new MenuItem(); item.Header = e.Text; item.IsEnabled = e.IsEnabled; int index = e.ExecuteIndex; item.Click += delegate { Execute(index); }; parent.Add(item); Set(item.Items, e.Items); } } void Execute(int index) { try { WindowControl mainWindow; using (var app = GetVS(out mainWindow)) { app.Type<InsertControl>().Execute(index); } } catch { } } void ReturnList() { if (_panel.Children.Count == 0) { return; } try { _panel.Children.Clear(); Height = _firstHeight; WindowControl mainWindow; using (var app = GetVS(out mainWindow)) { mainWindow.IdentifyFromTypeFullName("Microsoft.VisualStudio.PlatformUI.VsMenu").Dynamic().Visibility = Visibility.Visible; } } catch { } }
メニューが押されると、相手プロセスに送り込んだInsertControlのExecuteが呼び出され、先ほど格納したメニューの処理が実行されるのでしたー。
種明かし終了
タネがわかっても意味わかりませんか?
はい、この辺は慣れなんですね。
複数のプロセスを同時に操作して、あっちへ行ったり、こっちへ行ったりする感じ。
慣れたら難しくないんですよー。ホントに。
それにヤミツキになります。
楽しいんですって!
皆さんも是非面白アプリも作ってみてくださいねー。
(でも、当然テストにもつかってください。)
アドベントカレンダー完走
いやー、完走しました!
書いていただいた皆様本当にありがとうございました。
去年と比べると格段にFriendlyも広まりましたが、本当に周りの方々に助けていただいたおかげです。ひたすら感謝ですねー。
アドベントカレンダーは今日でひと段落ですが、Friendlyはまだまだ続きます。(当然)
来年こそはキャズムを越える!<言いたかったw
何はともあれ、メリークリスマス(*⌒ー⌒)ο∠☆:
皆さん、本当にありがとうございました!
で、最終回と思わせといて・・・
しばらくすると、MS-MVP界のマドンナ@hr_saoさんの番外編がアップされます!
こうご期待!