もくじ
https://tera1707.com/entry/2022/02/06/144447
やりたいこと
好きなウインドウに、好きなWindowsメッセージを送りたいということがあった。
(WM_CLOSEとか、そういうやつ)
以前こちらの記事で、好きなウインドウにWM_CLOSEを送るということをしたので、 それの派生で、好きなMsgを送れるようにする、プラス、なんとなくWinUI3でやってみることにした。
C#でwin32APIを使うためにCsWin32を使った
Microsoft.Windows.CsWin32
というMS製ライブラリをつかった。
こちらは、ソースジェネレーターという仕組みで、プロジェクトに手動で追加した「NativeMethods.txt」というファイルに書いたWin32APIに対応するP/Invokeのためのコードを自動で書いてくれるというパッケージ。
それを使ってやってみる。
コード置き場
https://github.com/tera1707/WindowsMessageSenderWinui
コード中身
現時点のコード
<?xml version="1.0" encoding="utf-8"?> <Window x:Class="WindowMessageSender.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:WindowMessageSender" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid Grid.Row="0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="auto"/> </Grid.ColumnDefinitions> <Button Grid.Column="0" x:Name="myButton" Grid.Row="0" Click="myButton_Click" HorizontalAlignment="Stretch" Margin="5" Background="Blue">ウインドウを探す</Button> <ToggleSwitch Grid.Column="1" x:Name="VisibleOnly" IsOn="False" OffContent="可視ウインドウのみ表示" OnContent="不可視ウインドウも表示"/> </Grid> <Grid Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="2*"/> <ColumnDefinition Width="1*"/> </Grid.ColumnDefinitions> <HyperlinkButton Content="WMメッセージ番号を調べる" NavigateUri="https://blog.goo.ne.jp/masaki_goo_2006/e/65fe9047e5f97bde1830566766b4829e"/> <TextBlock Grid.Column="1" Text="0x" VerticalAlignment="Center" FontSize="25"/> <TextBox Grid.Column="2" x:Name="tbMsgNumber" TextAlignment="Left" Text="0010" FontSize="30"/> <Button Grid.Column="3" Content="メッセージ送信" Click="Button_Click" Background="DarkCyan" HorizontalAlignment="Stretch"/> </Grid> <ListView x:Name="WindowList" Grid.Row="2" HorizontalAlignment="Stretch" SelectionMode="Single" ShowsScrollingPlaceholders="True" BorderThickness="1" BorderBrush="{ThemeResource ControlStrongStrokeColorDefaultBrush}"> <ListView.ItemTemplate> <DataTemplate> <Grid BorderBrush="CadetBlue" BorderThickness="1" Margin="1" Padding="7,2"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ContextFlyout> <MenuFlyout> <MenuFlyoutItem Text="送信する" Click="MenuFlyoutItem_Click"/> </MenuFlyout> </Grid.ContextFlyout> <TextBlock Grid.Row="0" Text="{Binding Title}"/> <TextBlock Grid.Row="1" Text="{Binding ClassName}"/> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </Window>
using Microsoft.UI.Xaml; using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.InteropServices; using Windows.Graphics; using Windows.Win32; using Windows.Win32.Foundation; namespace WindowMessageSender { public sealed partial class MainWindow : Window, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName) => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); internal ObservableCollection<WindowData> WindowTitle { get { return _windowTitle; } set { _windowTitle = value; OnPropertyChanged(nameof(WindowTitle)); } } private ObservableCollection<WindowData> _windowTitle = new(); public MainWindow() { this.InitializeComponent(); // WinUI3のウインドウのハンドルを取得 var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); // そのウインドウのIDを取得 Microsoft.UI.WindowId windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd); // そこからAppWindowを取得する Microsoft.UI.Windowing.AppWindow appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId); appWindow.Resize(new SizeInt32(700, 900)); } private void myButton_Click(object sender, RoutedEventArgs e) { WindowTitle.Clear(); string windowName; string windoClass; PInvoke.EnumWindows(((hWnd, param) => { int bufferSize = PInvoke.GetWindowTextLength(hWnd) + 1; unsafe { var isVisible = PInvoke.IsWindowVisible(hWnd); fixed (char* windowNameChars = new char[bufferSize]) { if (PInvoke.GetWindowText(hWnd, windowNameChars, bufferSize) == 0) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode != 0) throw new Win32Exception(errorCode); return true; } windowName = new string(windowNameChars); } fixed (char* windoClassChars = new char[256]) { if (PInvoke.GetClassName(hWnd, windoClassChars, 256) == 0) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode != 0) throw new Win32Exception(errorCode); return true; } windoClass = new string(windoClassChars); } if (VisibleOnly.IsOn) { WindowTitle.Add(new WindowData(hWnd, windowName, windoClass, isVisible)); } else if (isVisible == true) { WindowTitle.Add(new WindowData(hWnd, windowName, windoClass, isVisible)); } return true; } }), (LPARAM)0); WindowList.ItemsSource = WindowTitle; } private void MenuFlyoutItem_Click(object sender, RoutedEventArgs e) { Button_Click(null, null); } // Msg送信ボタン押下 private void Button_Click(object sender, RoutedEventArgs e) { if (WindowList.SelectedItem is WindowData wd) { var msg = UInt32.Parse(tbMsgNumber.Text, System.Globalization.NumberStyles.HexNumber); PInvoke.PostMessage(wd.hWnd, msg, (WPARAM)0, (LPARAM)0); } } } internal class WindowData { public HWND hWnd { get; set; } public string Title { get; set; } public string ClassName { get; set; } public bool IsVisible { get; set; } public WindowData(HWND hwnd, string title, string className, bool isVisible) { hWnd = hwnd; Title = title; ClassName = className; IsVisible = isVisible; } } }
コードと同じ階層に作っておいておくNativeMethods.txt
EnumWindows GetWindowText GetWindowTextLength GetClassName IsWindowVisible PostMessage
PWSTRを使いたくない
こちらのページを参考にさせて頂いてやってみたのだが、 そのページに書かれている通り、
[DllImport("USER32.dll", ExactSpelling = true, EntryPoint = "GetWindowTextW", SetLastError = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] [SupportedOSPlatform("windows5.0")] internal static extern int GetWindowText(winmdroot.Foundation.HWND hWnd, winmdroot.Foundation.PWSTR lpString, int nMaxCount);
という感じで、PWSTR
という、ポインタを含む構造体が出てくる。
これを受けるためには、
fixed (char* windowNameChars = new char[bufferSize]) { PInvoke.GetWindowText(hWnd, windowNameChars, bufferSize); windowName = new string(windowNameChars); }
みたいな、charの配列へのポインタを含むコードをかかないといけなくて 、非常にうっとうしい。
ので、上のページに、span
それを参考に、コードを書きなおしてみる。
spanで書き直す
書き直し中。。。。
参考
https://github.com/microsoft/CsWin32
CsWin32 で Win32 API や COM を使ったアプリケーション開発を効率化する →Win32から文字列をC#でうけるときのうまいやり方が書かれている。
https://blog.shibayan.jp/entry/20220501/1651339430
[C#] CsWin32でWin32APIのプラットフォーム呼び出し(P/Invoke)コードを自動生成
https://qiita.com/radian-jp/items/a4509f9a44101fb2f30e
Getting the Value from PWSTR
PWSTR を使って文字列を取る方法
https://github.com/microsoft/CsWin32/discussions/181#discussioncomment-456365