Rust で Win32 API ことはじめ

Pocket

本記事は、 Rust Advent Calendar 2024 シリーズ3 の 14日目 の記事だ。
ついでに、 Windows Advent Calendar 2024 14日目 の記事としても登録させてもらっている。

Rust で Win32 API を叩くにあたって、最初に知っておきたい情報をまとめておく。

Win32 API を呼ぶ手段

C# の P/Invoke のように一つ一つの API をソースコード上で定義してリンクすれば、呼び出せなくはない。 1

しかしそれは流石に面倒なので、 Microsoft による windows-rs のクレートを使うのが基本となる。

windows-rs とは

windows-rs は、 Microsoft 自身が Windows API のメタデータから生成されたコードを用いて、 Rust から Windows API を呼び出しやすくするクレートを提供するプロジェクトだ。
その仕組みの通り、 Win32 API のほぼ全てに加え、 COM API や WinRT もその多くがカバーしている。

大まかな情報は、以下のページにまとまっていて基本的にはこれを読み進めればよい。

しかしそれではわざわざ記事を書く必要性が薄いので、この記事では Win32 API コードを書くにあたって、最初に知っておきたい情報をまとめておこう。

なお、後述するが windows-rs は COM や WinRT の API もカバーしており、それぞれ呼び出し方にコツがある。
すべてを解説すると分量が多いので、本記事では Win32 API の範囲に限って説明する。


ところで、↑のドキュメントのドメインである kennykerr.ca って誰のやねんって話だが、 Microsoft のエンジニア らしい。
Microsoft 公式マニュアルが個人名のドメインに置かれているってどうなのと言う気もせんでもないが、元々個人の趣味プロジェクトだったのかな…?

また、 2024年12月現在最新版が 0.58.0 と、セマンティックバージョン的にもまだ破壊的変更が入りうるものだ。
最近こそ更新は落ち着いてきているものの、以下の情報が今後変わりうることはご留意いただきたい。

windows クレート と windows-sys クレートの使い分け

windows-rs には、 windowswindows-sys というふたつの代表的なクレートが存在する。

Choosing between the windows and windows-sys crates - Kenny Kerr

What do you need? windows windows-sys
コンパイル時間の短縮が最大の主題
no_std でのビルドが必要support
COM や WinRT API のサポートが必要
Rust らしい API を使いたい
サポートされている最小 Rust バージョン 1.56 1.56

使い分けは上記のとおりだが、基本的に windows クレート で良いだろう。
以降は windows クレートを利用することを前提に説明する

Cargo.toml の設定

通常の Rust のクレートと同様に、 Cargo.tomlwindows クレート の依存関係を記述すれば使えるようになる。

例:

[dependencies.windows]
version = "0.*"
features = [
    "Win32_Foundation",
    "Win32_System_Threading",
]

windows, windows-sys クレートは規模が大きいため、全てをビルドするには少々時間がかかる。

このため、使いたい Win32 API に応じて必要な "features" を選択し、条件付きコンパイルを行う。 2

使いたい API と、その構文は、 windows - Rust ドキュメントページで検索できる。 中には、元の Win32 の C API と引数の数が異なるモノもあるので要注意だ。
一方、 API に対応する Features の対応は、 Features Search のページで検索する。

例えば、 RegisterClassExW 関数 を呼びたい場合、 RegisterClassExW in windows::Win32::UI::WindowsAndMessaging が見つかる。
更に v0.59.0 で Features Search で検索すると、 "Win32_Graphics_Gdi", "Win32_UI_WindowsAndMessaging" の2つの features の設定が必要だということがわかる。

以前は、windows - Rust ドキュメントのページにも必要な features の情報が載っていたようだが、 features の依存が windows クレートのバージョンによって変わることがあるからなのか、ページが分かれてしまった。
せめて、前者のページから直リンしてくれればよいのに。

また、ビルドに時間がかかるとはいえ、一度依存関係をビルドしてしまえばあとはキャッシュされるので、とりあえず全部の features 指定してビルドしてしまえ…と思いがちだが…
生半可な気持ちでやるととんでもないビルド時間がかかり、痛い目をみるぞ。

v0.58.0 現在、指定可能な features はこちらの通り

features = [
    "AI_MachineLearning",
    "ApplicationModel",
    "ApplicationModel_Activation",
    "ApplicationModel_AppExtensions",
    "ApplicationModel_AppService",
    "ApplicationModel_Appointments",
    "ApplicationModel_Appointments_AppointmentsProvider",
    "ApplicationModel_Appointments_DataProvider",
    "ApplicationModel_Background",
    "ApplicationModel_Calls",
    "ApplicationModel_Calls_Background",
    "ApplicationModel_Calls_Provider",
    "ApplicationModel_Chat",
    "ApplicationModel_CommunicationBlocking",
    "ApplicationModel_Contacts",
    "ApplicationModel_Contacts_DataProvider",
    "ApplicationModel_Contacts_Provider",
    "ApplicationModel_ConversationalAgent",
    "ApplicationModel_Core",
    "ApplicationModel_DataTransfer",
    "ApplicationModel_DataTransfer_DragDrop",
    "ApplicationModel_DataTransfer_DragDrop_Core",
    "ApplicationModel_DataTransfer_ShareTarget",
    "ApplicationModel_Email",
    "ApplicationModel_Email_DataProvider",
    "ApplicationModel_ExtendedExecution",
    "ApplicationModel_ExtendedExecution_Foreground",
    "ApplicationModel_Holographic",
    "ApplicationModel_LockScreen",
    "ApplicationModel_PackageExtensions",
    "ApplicationModel_Payments",
    "ApplicationModel_Payments_Provider",
    "ApplicationModel_Preview_Holographic",
    "ApplicationModel_Preview_InkWorkspace",
    "ApplicationModel_Preview_Notes",
    "ApplicationModel_Resources",
    "ApplicationModel_Resources_Core",
    "Foundation_Collections",
    "ApplicationModel_Resources_Management",
    "ApplicationModel_Search",
    "ApplicationModel_Search_Core",
    "ApplicationModel_UserActivities",
    "ApplicationModel_UserActivities_Core",
    "ApplicationModel_UserDataAccounts",
    "ApplicationModel_UserDataAccounts_Provider",
    "ApplicationModel_UserDataAccounts_SystemAccess",
    "ApplicationModel_UserDataTasks",
    "ApplicationModel_UserDataTasks_DataProvider",
    "ApplicationModel_VoiceCommands",
    "ApplicationModel_Wallet",
    "ApplicationModel_Wallet_System",
    "Data_Html",
    "Data_Json",
    "Data_Pdf",
    "Data_Text",
    "Data_Xml_Dom",
    "Data_Xml_Xsl",
    "Devices",
    "Devices_Adc",
    "Devices_Adc_Provider",
    "Devices_Background",
    "Devices_Bluetooth",
    "Devices_Bluetooth_Advertisement",
    "Devices_Bluetooth_Background",
    "Devices_Bluetooth_GenericAttributeProfile",
    "Devices_Bluetooth_Rfcomm",
    "Devices_Custom",
    "Devices_Display",
    "Devices_Display_Core",
    "Foundation_Numerics",
    "Devices_Enumeration",
    "Storage_Streams",
    "Devices_Enumeration_Pnp",
    "Devices_Geolocation",
    "Devices_Geolocation_Geofencing",
    "Devices_Geolocation_Provider",
    "Devices_Gpio",
    "Foundation",
    "Devices_Gpio_Provider",
    "Devices_Haptics",
    "Devices_HumanInterfaceDevice",
    "Devices_I2c",
    "Devices_I2c_Provider",
    "Devices_Input",
    "Devices_Input_Preview",
    "Devices_Lights",
    "Devices_Lights_Effects",
    "Devices_Midi",
    "Devices_PointOfService",
    "Devices_PointOfService_Provider",
    "Devices_Portable",
    "Devices_Power",
    "Devices_Printers",
    "Devices_Printers_Extensions",
    "Devices_Pwm",
    "Devices_Pwm_Provider",
    "Devices_Radios",
    "Devices_Scanners",
    "Devices_Sensors",
    "Devices_Sensors_Custom",
    "Devices_SerialCommunication",
    "Devices_SmartCards",
    "Devices_Sms",
    "Devices_Spi",
    "Devices_Spi_Provider",
    "Devices_Usb",
    "Devices_WiFi",
    "Devices_WiFiDirect",
    "Devices_WiFiDirect_Services",
    "Embedded_DeviceLockdown",
    "Foundation_Diagnostics",
    "Foundation_Metadata",
    "Gaming_Input",
    "Gaming_Input_Custom",
    "Gaming_Input_ForceFeedback",
    "Gaming_Input_Preview",
    "Gaming_Preview",
    "Gaming_Preview_GamesEnumeration",
    "Gaming_UI",
    "Gaming_XboxLive",
    "Gaming_XboxLive_Storage",
    "Globalization",
    "Globalization_Collation",
    "Globalization_DateTimeFormatting",
    "Globalization_Fonts",
    "Globalization_NumberFormatting",
    "Globalization_PhoneNumberFormatting",
    "Graphics",
    "Graphics_Capture",
    "Graphics_DirectX",
    "Graphics_DirectX_Direct3D11",
    "Graphics_Display",
    "Graphics_Display_Core",
    "Graphics_Effects",
    "Graphics_Holographic",
    "Graphics_Imaging",
    "Graphics_Printing",
    "Graphics_Printing_OptionDetails",
    "Graphics_Printing_PrintSupport",
    "Graphics_Printing_PrintTicket",
    "Graphics_Printing_Workflow",
    "Graphics_Printing3D",
    "Management",
    "Management_Core",
    "Management_Deployment",
    "Management_Deployment_Preview",
    "Management_Policies",
    "Management_Setup",
    "Management_Update",
    "Management_Workplace",
    "Media",
    "Media_AppBroadcasting",
    "Media_AppRecording",
    "Media_Audio",
    "Media_Capture",
    "Media_Capture_Core",
    "Media_Capture_Frames",
    "Media_Casting",
    "Media_ClosedCaptioning",
    "Media_ContentRestrictions",
    "Media_Control",
    "Media_Core",
    "Media_Effects",
    "Media_Core_Preview",
    "Media_Devices",
    "Media_Devices_Core",
    "Media_DialProtocol",
    "Media_Editing",
    "Media_FaceAnalysis",
    "Media_Import",
    "Media_MediaProperties",
    "Media_Miracast",
    "Media_Ocr",
    "Media_PlayTo",
    "Media_Playback",
    "Media_Playlists",
    "Media_Protection",
    "Media_Protection_PlayReady",
    "Media_Render",
    "Media_SpeechRecognition",
    "Media_SpeechSynthesis",
    "Media_Streaming_Adaptive",
    "Media_Transcoding",
    "Networking",
    "Networking_BackgroundTransfer",
    "Networking_Connectivity",
    "Networking_NetworkOperators",
    "Networking_Proximity",
    "Networking_PushNotifications",
    "Networking_ServiceDiscovery_Dnssd",
    "Networking_Sockets",
    "Networking_Vpn",
    "Networking_XboxLive",
    "Perception",
    "Perception_Automation_Core",
    "Perception_People",
    "Perception_Spatial",
    "Perception_Spatial_Preview",
    "Perception_Spatial_Surfaces",
    "Phone",
    "Phone_ApplicationModel",
    "Phone_Devices_Notification",
    "Phone_Devices_Power",
    "Phone_Management_Deployment",
    "Phone_Media_Devices",
    "Phone_Notification_Management",
    "Phone_PersonalInformation",
    "Phone_PersonalInformation_Provisioning",
    "Phone_Speech_Recognition",
    "Phone_StartScreen",
    "Phone_System",
    "Phone_System_Power",
    "Phone_System_Profile",
    "Phone_System_UserProfile_GameServices_Core",
    "Phone_UI_Input",
    "Security_Authentication_Identity",
    "Security_Authentication_Identity_Core",
    "Security_Authentication_OnlineId",
    "Security_Authentication_Web",
    "Security_Authentication_Web_Core",
    "Security_Authentication_Web_Provider",
    "Security_Authorization_AppCapabilityAccess",
    "Security_Credentials",
    "Security_Credentials_UI",
    "Security_Cryptography",
    "Security_Cryptography_Certificates",
    "Security_Cryptography_Core",
    "Security_Cryptography_DataProtection",
    "Security_DataProtection",
    "Security_EnterpriseData",
    "Security_ExchangeActiveSyncProvisioning",
    "Security_Isolation",
    "Services_Maps",
    "Services_Maps_Guidance",
    "Services_Maps_LocalSearch",
    "Services_Maps_OfflineMaps",
    "Services_Store",
    "Services_TargetedContent",
    "Storage",
    "Storage_AccessCache",
    "Storage_BulkAccess",
    "Storage_Compression",
    "Storage_FileProperties",
    "Storage_Pickers",
    "Storage_Pickers_Provider",
    "Storage_Provider",
    "Storage_Search",
    "System",
    "System_Diagnostics",
    "System_Diagnostics_DevicePortal",
    "System_Diagnostics_Telemetry",
    "System_Diagnostics_TraceReporting",
    "System_Display",
    "System_Implementation_FileExplorer",
    "System_Inventory",
    "System_Power",
    "System_Profile",
    "System_Profile_SystemManufacturers",
    "System_RemoteDesktop",
    "System_RemoteDesktop_Input",
    "System_RemoteDesktop_Provider",
    "System_RemoteSystems",
    "System_Threading",
    "System_Threading_Core",
    "System_Update",
    "System_UserProfile",
    "UI",
    "UI_Accessibility",
    "UI_ApplicationSettings",
    "UI_Popups",
    "UI_Composition",
    "UI_Composition_Core",
    "UI_Composition_Desktop",
    "UI_Composition_Diagnostics",
    "UI_Composition_Effects",
    "UI_Composition_Interactions",
    "UI_Composition_Scenes",
    "UI_Core",
    "UI_Core_AnimationMetrics",
    "UI_Core_Preview",
    "UI_Input",
    "UI_Input_Core",
    "UI_Input_Inking",
    "UI_Input_Inking_Analysis",
    "UI_Input_Inking_Core",
    "UI_Input_Inking_Preview",
    "UI_Input_Preview",
    "UI_Input_Preview_Injection",
    "UI_Input_Spatial",
    "UI_Notifications",
    "UI_Notifications_Management",
    "UI_Notifications_Preview",
    "UI_Shell",
    "UI_StartScreen",
    "UI_Text",
    "UI_Text_Core",
    "UI_UIAutomation",
    "UI_UIAutomation_Core",
    "UI_ViewManagement",
    "UI_ViewManagement_Core",
    "UI_WebUI",
    "UI_WebUI_Core",
    "UI_WindowManagement",
    "UI_WindowManagement_Preview",
    "Wdk_Devices_Bluetooth",
    "Win32_Devices_Properties",
    "Wdk_Devices_HumanInterfaceDevice",
    "Win32_Foundation",
    "Wdk_Foundation",
    "Wdk_System_SystemServices",
    "Win32_Security",
    "Wdk_Storage_FileSystem",
    "Win32_System_IO",
    "Win32_System_Kernel",
    "Win32_System_Power",
    "Wdk_Graphics_Direct3D",
    "Win32_Graphics_Direct3D9",
    "Win32_Graphics_DirectDraw",
    "Win32_Graphics_Gdi",
    "Wdk_NetworkManagement_Ndis",
    "Win32_Networking_WinSock",
    "Win32_NetworkManagement_Ndis",
    "Wdk_NetworkManagement_WindowsFilteringPlatform",
    "Win32_NetworkManagement_WindowsFilteringPlatform",
    "Win32_System_Rpc",
    "Win32_System_Memory",
    "Win32_Storage_FileSystem",
    "Win32_System_Ioctl",
    "Win32_Security_Authentication_Identity",
    "Wdk_Storage_FileSystem_Minifilters",
    "Win32_Storage_InstallableFileSystems",
    "Wdk_System_IO",
    "Wdk_System_Memory",
    "Wdk_System_OfflineRegistry",
    "Wdk_System_Registry",
    "Wdk_System_SystemInformation",
    "Win32_System_Diagnostics_Debug",
    "Win32_System_Diagnostics_Etw",
    "Win32_System_SystemInformation",
    "Win32_Storage_IscsiDisc",
    "Win32_System_WindowsProgramming",
    "Win32_System_SystemServices",
    "Win32_System_Threading",
    "Wdk_System_Threading",
    "Web",
    "Web_AtomPub",
    "Web_Http",
    "Web_Http_Diagnostics",
    "Web_Http_Filters",
    "Web_Http_Headers",
    "Web_Syndication",
    "Web_UI",
    "Web_UI_Interop",
    "Win32_AI_MachineLearning_DirectML",
    "Win32_Graphics_Direct3D12",
    "Win32_AI_MachineLearning_WinML",
    "Win32_Data_HtmlHelp",
    "Win32_UI_Controls",
    "Win32_System_Com",
    "Win32_Data_RightsManagement",
    "Win32_Data_Xml_MsXml",
    "Win32_Data_Xml_XmlLite",
    "Win32_Devices_AllJoyn",
    "Win32_Devices_BiometricFramework",
    "Win32_Devices_Bluetooth",
    "Win32_Devices_Communication",
    "Win32_Devices_DeviceAccess",
    "Win32_Devices_DeviceAndDriverInstallation",
    "Win32_System_Registry",
    "Win32_UI_WindowsAndMessaging",
    "Win32_Devices_DeviceQuery",
    "Win32_Devices_Display",
    "Win32_System_Console",
    "Win32_Graphics_OpenGL",
    "Win32_UI_ColorSystem",
    "Win32_Devices_Enumeration_Pnp",
    "Win32_Devices_Fax",
    "Win32_Devices_FunctionDiscovery",
    "Win32_UI_Shell_PropertiesSystem",
    "Win32_Devices_Geolocation",
    "Win32_Devices_HumanInterfaceDevice",
    "Win32_Devices_ImageAcquisition",
    "Win32_System_Variant",
    "Win32_Devices_PortableDevices",
    "Win32_Devices_Pwm",
    "Win32_Devices_Sensors",
    "Win32_Devices_SerialCommunication",
    "Win32_Devices_Tapi",
    "Win32_System_AddressBook",
    "Win32_Devices_Usb",
    "Win32_Devices_WebServicesOnDevices",
    "Win32_Security_Cryptography",
    "Win32_Gaming",
    "Win32_Globalization",
    "Win32_Graphics_CompositionSwapchain",
    "Win32_Graphics_Dxgi_Common",
    "Win32_Graphics_DXCore",
    "Win32_Graphics_Direct2D",
    "Win32_Graphics_Direct2D_Common",
    "Win32_Graphics_Dxgi",
    "Win32_Graphics_Direct3D",
    "Win32_Graphics_Direct3D_Dxc",
    "Win32_Graphics_Direct3D_Fxc",
    "Win32_Graphics_Direct3D11",
    "Win32_Graphics_Direct3D10",
    "Win32_Graphics_Direct3D11on12",
    "Win32_Graphics_Direct3D9on12",
    "Win32_Graphics_DirectComposition",
    "Win32_Graphics_DirectManipulation",
    "Win32_Graphics_DirectWrite",
    "Win32_Graphics_Dwm",
    "Win32_Graphics_GdiPlus",
    "Win32_Graphics_Hlsl",
    "Win32_Graphics_Imaging",
    "Win32_Graphics_Imaging_D2D",
    "Win32_Graphics_Printing",
    "Win32_Storage_Xps",
    "Win32_System_Ole",
    "Win32_Graphics_Printing_PrintTicket",
    "Win32_Management_MobileDeviceManagementRegistration",
    "Win32_Media",
    "Win32_Media_Multimedia",
    "Win32_Media_Audio",
    "Win32_Media_Audio_Apo",
    "Win32_Media_Audio_DirectMusic",
    "Win32_Media_Audio_DirectSound",
    "Win32_Media_Audio_Endpoints",
    "Win32_Media_Audio_XAudio2",
    "Win32_Media_DeviceManager",
    "Win32_Media_DirectShow",
    "Win32_Media_MediaFoundation",
    "Win32_System_Com_StructuredStorage",
    "Win32_Media_DirectShow_Tv",
    "Win32_Media_KernelStreaming",
    "Win32_Media_DirectShow_Xml",
    "Win32_Media_DxMediaObjects",
    "Win32_Media_LibrarySharingServices",
    "Win32_Media_MediaPlayer",
    "Win32_UI_Controls_Dialogs",
    "Win32_Media_PictureAcquisition",
    "Win32_Media_Speech",
    "Win32_Media_Streaming",
    "Win32_Media_WindowsMediaFormat",
    "Win32_NetworkManagement_Dhcp",
    "Win32_NetworkManagement_Dns",
    "Win32_NetworkManagement_InternetConnectionWizard",
    "Win32_NetworkManagement_IpHelper",
    "Win32_NetworkManagement_MobileBroadband",
    "Win32_NetworkManagement_Multicast",
    "Win32_NetworkManagement_NetBios",
    "Win32_NetworkManagement_NetManagement",
    "Win32_NetworkManagement_NetShell",
    "Win32_NetworkManagement_NetworkDiagnosticsFramework",
    "Win32_NetworkManagement_NetworkPolicyServer",
    "Win32_NetworkManagement_P2P",
    "Win32_NetworkManagement_QoS",
    "Win32_NetworkManagement_Rras",
    "Win32_NetworkManagement_Snmp",
    "Win32_NetworkManagement_WNet",
    "Win32_NetworkManagement_WebDav",
    "Win32_NetworkManagement_WiFi",
    "Win32_Security_ExtensibleAuthenticationProtocol",
    "Win32_System_RemoteDesktop",
    "Win32_NetworkManagement_WindowsConnectNow",
    "Win32_NetworkManagement_WindowsConnectionManager",
    "Win32_NetworkManagement_WindowsFirewall",
    "Win32_NetworkManagement_WindowsNetworkVirtualization",
    "Win32_Networking_ActiveDirectory",
    "Win32_UI_Shell",
    "Win32_Networking_BackgroundIntelligentTransferService",
    "Win32_Networking_Clustering",
    "Win32_System_Services",
    "Win32_Networking_HttpServer",
    "Win32_Networking_Ldap",
    "Win32_Networking_NetworkListManager",
    "Win32_Networking_RemoteDifferentialCompression",
    "Win32_Networking_WebSocket",
    "Win32_Networking_WinHttp",
    "Win32_Networking_WinInet",
    "Win32_Networking_WindowsWebServices",
    "Win32_Security_AppLocker",
    "Win32_Security_Credentials",
    "Win32_System_PasswordManagement",
    "Win32_Security_Authentication_Identity_Provider",
    "Win32_Security_Authorization",
    "Win32_Security_Authorization_UI",
    "Win32_Security_ConfigurationSnapin",
    "Win32_Security_Cryptography_Catalog",
    "Win32_Security_Cryptography_Sip",
    "Win32_Security_Cryptography_Certificates",
    "Win32_Security_Cryptography_UI",
    "Win32_Security_WinTrust",
    "Win32_Security_DiagnosticDataQuery",
    "Win32_Security_DirectoryServices",
    "Win32_Security_EnterpriseData",
    "Win32_Storage_Packaging_Appx",
    "Win32_Security_Isolation",
    "Win32_Security_LicenseProtection",
    "Win32_Security_NetworkAccessProtection",
    "Win32_Security_Tpm",
    "Win32_Security_WinWlx",
    "Win32_System_StationsAndDesktops",
    "Win32_Storage_Cabinets",
    "Win32_Storage_CloudFilters",
    "Win32_System_CorrelationVector",
    "Win32_Storage_Compression",
    "Win32_Storage_DataDeduplication",
    "Win32_Storage_DistributedFileSystem",
    "Win32_Storage_EnhancedStorage",
    "Win32_Storage_FileHistory",
    "Win32_Storage_FileServerResourceManager",
    "Win32_Storage_Imapi",
    "Win32_Storage_IndexServer",
    "Win32_Storage_Jet",
    "Win32_Storage_StructuredStorage",
    "Win32_Storage_Nvme",
    "Win32_Storage_OfflineFiles",
    "Win32_Storage_OperationRecorder",
    "Win32_Storage_Packaging_Opc",
    "Win32_Storage_ProjectedFileSystem",
    "Win32_Storage_Vhd",
    "Win32_Storage_VirtualDiskService",
    "Win32_Storage_Vss",
    "Win32_Storage_Xps_Printing",
    "Win32_System_Antimalware",
    "Win32_System_ApplicationInstallationAndServicing",
    "Win32_System_ApplicationVerifier",
    "Win32_System_AssessmentTool",
    "Win32_UI_Accessibility",
    "Win32_System_ClrHosting",
    "Win32_System_Com_CallObj",
    "Win32_System_Com_ChannelCredentials",
    "Win32_System_Com_Events",
    "Win32_System_Com_Marshal",
    "Win32_System_Com_UI",
    "Win32_System_Com_Urlmon",
    "Win32_System_ComponentServices",
    "Win32_System_Contacts",
    "Win32_System_DataExchange",
    "Win32_System_DeploymentServices",
    "Win32_System_DesktopSharing",
    "Win32_System_DeveloperLicensing",
    "Win32_System_Diagnostics_Ceip",
    "Win32_System_Diagnostics_ClrProfiling",
    "Win32_System_WinRT_Metadata",
    "Win32_System_Time",
    "Win32_System_Diagnostics_Debug_ActiveScript",
    "Win32_System_Diagnostics_Debug_Extensions",
    "Win32_System_Diagnostics_ProcessSnapshotting",
    "Win32_System_Diagnostics_ToolHelp",
    "Win32_System_Diagnostics_TraceLogging",
    "Win32_System_DistributedTransactionCoordinator",
    "Win32_System_Environment",
    "Win32_System_ErrorReporting",
    "Win32_System_EventCollector",
    "Win32_System_EventLog",
    "Win32_System_EventNotificationService",
    "Win32_System_GroupPolicy",
    "Win32_System_Wmi",
    "Win32_System_HostCompute",
    "Win32_System_HostComputeNetwork",
    "Win32_System_HostComputeSystem",
    "Win32_System_Hypervisor",
    "Win32_System_Iis",
    "Win32_System_JobObjects",
    "Win32_System_Js",
    "Win32_System_LibraryLoader",
    "Win32_System_Mailslots",
    "Win32_System_Mapi",
    "Win32_System_Memory_NonVolatile",
    "Win32_System_MessageQueuing",
    "Win32_System_MixedReality",
    "Win32_System_Mmc",
    "Win32_System_ParentalControls",
    "Win32_System_Performance",
    "Win32_System_Performance_HardwareCounterProfiling",
    "Win32_System_Pipes",
    "Win32_System_ProcessStatus",
    "Win32_System_RealTimeCommunications",
    "Win32_System_Recovery",
    "Win32_System_RemoteAssistance",
    "Win32_System_RemoteManagement",
    "Win32_System_RestartManager",
    "Win32_System_Restore",
    "Win32_System_Search",
    "Win32_System_Search_Common",
    "Win32_System_SecurityCenter",
    "Win32_System_ServerBackup",
    "Win32_System_SettingsManagementInfrastructure",
    "Win32_System_SetupAndMigration",
    "Win32_System_Shutdown",
    "Win32_System_SideShow",
    "Win32_System_SubsystemForLinux",
    "Win32_System_TaskScheduler",
    "Win32_System_TpmBaseServices",
    "Win32_System_TransactionServer",
    "Win32_System_UpdateAgent",
    "Win32_System_UpdateAssessment",
    "Win32_System_UserAccessLogging",
    "Win32_System_VirtualDosMachines",
    "Win32_System_WinRT",
    "Win32_System_WinRT_AllJoyn",
    "Win32_System_WinRT_Composition",
    "Win32_System_WinRT_CoreInputView",
    "Win32_System_WinRT_Direct3D11",
    "Win32_System_WinRT_Display",
    "Win32_System_WinRT_Graphics_Capture",
    "Win32_System_WinRT_Graphics_Direct2D",
    "Win32_System_WinRT_Graphics_Imaging",
    "Win32_System_WinRT_Holographic",
    "Win32_System_WinRT_Isolation",
    "Win32_System_WinRT_ML",
    "Win32_System_WinRT_Media",
    "Win32_System_WinRT_Pdf",
    "Win32_System_WinRT_Printing",
    "Win32_System_WinRT_Shell",
    "Win32_System_WinRT_Storage",
    "Win32_System_WindowsSync",
    "Win32_UI_Animation",
    "Win32_UI_Input_Pointer",
    "Win32_UI_Controls_RichEdit",
    "Win32_UI_HiDpi",
    "Win32_UI_Input",
    "Win32_UI_Input_Ime",
    "Win32_UI_Input_KeyboardAndMouse",
    "Win32_UI_Input_Ink",
    "Win32_UI_Input_Radial",
    "Win32_UI_Input_Touch",
    "Win32_UI_Input_XboxController",
    "Win32_UI_InteractionContext",
    "Win32_UI_LegacyWindowsEnvironmentFeatures",
    "Win32_UI_Magnification",
    "Win32_UI_Notifications",
    "Win32_UI_Ribbon",
    "Win32_UI_Shell_Common",
    "Win32_UI_TabletPC",
    "Win32_UI_TextServices",
    "Win32_UI_Wpf",
    "Win32_Web_InternetExplorer",
]

API の呼び出し例

API の呼び出しサンプルは、 microsoft/windows-rs リポジトリの windows-rs/crates/samples に色々載っているので、たいへん参考になる。

試しに、メッセージボックスの呼び出しの例 (windows-rs/crates/samples/windows/message_box) を少し弄ったものを紹介しよう。

Cargo.toml:

[package]
name = "sample_message_box"
version = "0.0.0"
edition = "2021"
publish = false

[dependencies.windows]
version = "0.*"
features = [
    "Win32_UI_WindowsAndMessaging",
    "Win32_UI_Shell",
]

src/main.rs:

#![windows_subsystem = "windows"]
use windows::{core::*, Win32::UI::Shell::*, Win32::UI::WindowsAndMessaging::*};
fn main() -> Result<()> {
    unsafe {
        MessageBoxA(None, s!("Ansi"), s!("World"), MB_OK);
        let cap_t = format!("{}!", String::from("world").to_uppercase());
        MessageBoxW(None, h!("WinRT"), &HSTRING::from(cap_t), MB_OK);
        ShellMessageBoxW(None, None, w!("Wide"), w!("World"), MB_ICONERROR);
    }
    Ok(())
}

ポインタを扱う関係上、 多くの API は unsafe となっている。

Win32 API と同様、文字列を扱いうる API には *A で終わる ANSI (MBCS) 版と、 *W で終わる Unicode 版が存在する。

ANSI 版用の文字列リテラルには s! マクロが、 Unicode 版用には w! マクロか h! マクロが使える。
文字列については後ほど詳しく説明するが、基本的には Unicode 版を使うで良いだろう。

main 関数

Win32 API プログラムでのエントリーポイント関数と言えば、 WinMainwWinMain がおなじみだが、 Rust では main 関数のままだ。

戻り値も一見 Rust でおなじみの Result に見えるが、 実は ::core::result::Result ではなく ::windows::core::Result となっていて、 Error 側の型が ::windows::core::Error に固定されている。
一部の API (元々 HRESULT を返すようなもの) も、この Result を返すようになっていて、エラーが発生していた場合は、内部的に GetLastError 関数 のエラーコードを持つようになっている。
そのままこの Result を main 関数の戻り値に返すと、 Error 情報を持っていた場合は内部的に FormatMessage 関数 を呼んでメッセージを整形し、標準エラーに書き込んでくれる。
もし、 Result を返さない API であっても、エラー内容を GetLastError で取得するような Win32 API の関数であれば、自ら Err(Error::from_win32()) (::windows::core::Result::Err(::windows::core::Error::from_win32())) を返すことで、上記の機能を使うことができる。

#![windows_subsystem = "windows"]

こちらの クレートアトリビュート は、コンソールウィンドウを表示せずに Windows プログラムを起動させたり、内部的に wWinMain から main への読み替えと、 main 前に行われる Rust の std ライブラリの初期化処理を維持するといった処理を行ってくれる。 3
このアトリビュートを省略したり #![windows_subsystem = "console"] としてビルドすると、 CLI プログラムおなじみの黒いコマンドプロンプトウィンドウが表示され、標準出力や標準エラーが出力される。

int __clrcall WinMain(
  [in]           HINSTANCE hInstance,
  [in, optional] HINSTANCE hPrevInstance,
  [in]           LPSTR     lpCmdLine,
  [in]           int       nShowCmd
);

一方で、 wWinMain で与えられていたインスタンスハンドルやコマンドライン文字列などは、別途取得する必要がある。

    use windows::Win32::System::LibraryLoader::GetModuleHandleW;
    use windows::Win32::System::Threading::{GetStartupInfoW, STARTUPINFOW};
    let hinstance: HINSTANCE = unsafe { GetModuleHandleW(None)? }.into();
    let args: Vec<String> = env::args().collect();
    let n_show_cmd = {
        let mut si = STARTUPINFOW {
            cb: std::mem::size_of::<STARTUPINFOW>() as u32,
            ..Default::default()
        };
        unsafe {
            GetStartupInfoW(&mut si);
        };
        si.wShowWindow as i32
    };

なお上記サンプルでは、 以下の features を Cargo.toml に追加する必要がある。

  • "Win32_System_LibraryLoader"
  • "Win32_System_Threading"

文字列

Rust では基本文字列を UTF-8 で扱うが、 Win32 では ANSI (MBCS - Shift_JIS 等) 文字列または Unicode (UTF-16LE) で扱う。
それにあたって、以下の4つ(!)の文字列型(とそれに対応する文字リテラルマクロ)が増えている。

  • ANSI 系
    • PCSTR: s!("")
  • Unicode 系
    • PCWSTR: w!("")
    • BSTR: (リテラルマクロ無し)
    • &HSTRING: h!("")

リテラルそのまま使う場合は PCWSTR (ないし PCSTR)、それ以外は &HSTRING がオススメだ。

各文字列型の構造

PCSTR, PCWSTR はそれぞれおなじみ u8 (char) と u16 (wchar_t) のゼロ終端文字列用の、先頭ポインタだ。

PCSTR は Rust の utf-8 文字列と同じく u8 の配列であるが、 MBCS の文字コードはロケールに依存することから Rust の標準ライブラリや windows-rs クレートの範囲内では非 Ascii 文字の変換ができない。 s!("あ") マクロで非 Ascii 文字を指定してもコンパイルが通ってしまうが、そのまま Win32 API に渡すと何が起こるかわからないので、 PCSTR (PSTR) の使用は著しくオススメしない。

BSTR は古くは Visual BASIC (.NET じゃない方) の文字列型の実装に利用されている構造体で、 COM 等に用いられる。
見かけ上 PCSTR と同じく u16 のゼロ終端文字列の先頭ポインタを指しているだけのように見えるが、そのポインタから4バイト遡った部分に文字の u32 で文字の長さが格納されている独特な構造をしている。 4

HSTRING は UWP など WinRT で使われる文字列だ。
管理フラグや文字列長、参照カウント等々が含まれたややオーバーヘッドが大きめの構造となっている。 5

じゃぁ、 HSTRING より BSTR 使った方が良いのでは…? と思われるかもしれないが、 BSTR は必ず Windows 側にメモリをアロケーションさせなくてはならないのに対して、 HSTRING の場合は文字バッファを Rust 管理のヒープに置いたり、スタックに置いても OK (fast string と呼ばれる) という特徴がある。
HSTRING のほうが Rust で処理がおさまる範囲が広いので、その分効率が良いのだ。
h! リテラルはあるのに、 BSTR にはリテラルがないのもそのため。

文字の渡し方

Win32 API で文字列を受け取る引数の型の大半は、 windows::core::Param<windows::core::PCWSTR> のジェネリクスとなっている。
この型は、 windows-core クレートの windows.rs

  • &BSTR, &HSTRING, PWSTR に対して PCWSTR に変換する Param<PCWSTR> トレイトの実装
  • PSTR の対して PCSTR に変換する Param<PCSTR> トレイトの実装

がそれぞれ存在するため、これらの型をそのまま引数に与えられる。

HSTRINGBSTRFrom<&str>From<String> などを実装していてこれらから変換できるので、一旦 Rust の utf-8 文字列の範囲で文字処理を行い、 &HSTRING を介して Win32 API に渡すのが基本の使い方となる。

文字の受け取り方

バッファを作ってそのポインタを API に渡すのが基本だ。
vec! マクロ 等で作成したバッファで受け取るのが基本となる。

Win32 API の GetFullPathNameW 関数 を例に考えてみよう。
C API の構文は以下の通りだ。

DWORD GetFullPathNameW(
  [in]  LPCWSTR lpFileName,
  [in]  DWORD   nBufferLength,
  [out] LPWSTR  lpBuffer,
  [out] LPWSTR  *lpFilePart
);

一方、 windows-rs の windows::Win32::Storage::FileSystem::GetFullPathNameW は、以下のようになっていて、 nBufferLength を受け取らなくなる替わりに lpBufferOption<&mut [u16]> で受け取るようになった。
スライス &mut [u16] に長さの情報も入っているので、バッファ長を別途渡す必要がなくなった為だ。

pub unsafe fn GetFullPathNameW<P0>(
    lpfilename: P0,
    lpbuffer: Option<&mut [u16]>,
    lpfilepart: Option<*mut PWSTR>,
) -> u32
where
    P0: Param<PCWSTR>,

一方、 GetFullPathNameW 関数 は lpBuffer が不十分の場合に、戻り値はにパスと終端の null 文字を保持するために必要なバッファーのサイズを返す仕組みなので、 バッファーサイズだけ調べたい場合は lpbufferNone を与えることができる。(Win32 API で文字やデータを受け取るものはこの構造のものが多い)

バッファサイズを調べてバッファをアロケーションして再度呼び出し、ゼロ終端 を取り除いたスライスから HSTRING を経由して rust の utf-8 文字列を取得する…とすると、処理の全貌は以下の通りになる。

let filename_str = format!("{}.{}", "hoge", "ext");
let hfilename = HSTRING::from(filename_str);
let bret = GetFullPathNameW(&hfilename, None, None);
let mut buf = vec![0u16; bret as usize];
GetFullPathNameW(&hfilename, Some(&mut buf), None);
let result = HSTRING::from_wide(&buf[0..buf.len()-1]).unwrap().to_string();

必要な using ...:

use windows::{core::HSTRING, Win32::Storage::FileSystem::GetFullPathNameW};

--

なお、 FormatMessageW 関数 の第一引数 (dwflags) に FORMAT_MESSAGE_ALLOCATE_BUFFER を指定した場合のように、 型が引数で定義されているものと実際に与えるもので一致しない場合、以下のようにかなり無理やりな型変換を行う必要がある。 6

let last_error = GetLastError();
let mut lpbuffer = PWSTR::null();
let size = FormatMessageW(
    FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,    // dwflags
    None,           // lpsource
    last_error.0,   // dwmessageid
    0,              // dwlanguageid
    *(&&mut lpbuffer as *const &mut PWSTR as *const PWSTR), // lpbuffer
    0,              // nsize
    None,           // arguments
);
let err_string = lpbuf.to_string().unwrap();
LocalFree(HLOCAL(lpbuf.as_ptr() as *mut c_void));

必要な using ...:

use std::ffi::c_void;
use windows::{core::PWSTR, Win32::{Foundation::{GetLastError, LocalFree, HLOCAL}, System::Diagnostics::Debug::{FormatMessageW, FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS}}};

第五引数 (lpbuffer) には &mut lpbuffer を与えるのだが、 PWSTR にキャストしなくてはならない。

Rust に於いてキャストができるのは、お互いにプリミティブ型か生ポインタ型の間でだけ 7 なので、一旦変換先の型 (PWSTR) よりひとつ参照度が高いポインタ (*const PWSTR) を作成してから、参照外しする必要がある。

なお、上記の例の PWSTR に限っては、 PWSTR 構造体を *mut u16 でインスタンス化できるので、

*(&&mut lpbuffer as *const &mut PWSTR as *const PWSTR)

の替わりに

PWSTR(&mut lpbuffer as *mut PWSTR as *mut u16)

ともできる。こちらの方がわかりやすいかもしれない。
結果は同じだ。

DLL の作成

DLL を作成する場合、 Cargo.tomllib.crate-type"cdylib" を指定しよう。 ソースコード側に #![crate_type = "cdylib"] を指定しても良いぞ。 8

Win32 DLL のエントリポイントは C 言語等で呼び出す場合と同様 DllMain を使う。

Cargo.toml:

[package]
name = "hello-dll"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]

[dependencies.windows]
version = "0.*"
features = [
    "Win32_System_SystemServices",
]

src/lib.rs:

use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};
use windows::Win32::{Foundation::*, System::SystemServices};
static CACHE: LazyLock<Mutex<HashMap<String, String>>> = 
    LazyLock::new(|| Mutex::new(HashMap::new()));

#[no_mangle]
#[allow(non_snake_case, unused_variables)]
extern "system" fn DllMain(
    hinstDLL: HINSTANCE,
    fdwReason: u32,
    lpvReserved : *const ()
) -> bool {
    match fdwReason {
        SystemServices::DLL_PROCESS_ATTACH => attach(hinstDLL),
        SystemServices::DLL_THREAD_ATTACH => (),
        SystemServices::DLL_THREAD_DETACH => (),
        SystemServices::DLL_PROCESS_DETACH => {
            if lpvReserved == std::ptr::null() {
                detach()
            }
        },
        _ => ()
    };
    return true;
}

#[no_mangle]
extern fn DllTest() -> i32 { 334 }

fn attach(hinstdll: HINSTANCE) {
    if let Ok(mut cache) = CACHE.lock() {
        cache.insert(String::from("hello"), String::from("world"));
    }
}

fn detach() { }

DllMain のように Win32 からのコールバックを受ける関数には、 extern "system" の ABI を指定しよう。
それ以外の Rust や C 同士については、 extern "C" とするか、そもそも Rust の extern ブロックのデフォルト ABI は "C" となっている 9 ため、 単に extern とすれば OKだ。

なお、同一プロセスから DLL を呼び出す場合はグローバル変数を使ってデータを共有できるものの、 異なるプロセスの場合は (C 言語等で data_seg pragma を使うような方法では) 変数を共有することはできない。
名前付き共有メモリの作成 (メモリマップドファイル) などを使ってプロセス間通信行う必要がある。

DLL の呼び出し

呼び出し側のサンプルは windows-rs/crates/samples/windows-sys/delay_load : microsoft/windows-rs - GitHub にもある。
ただ、 Rust っぽく (?) Drop 時に FreeLibrary 呼ばれるよう、以下のように書いてみた。

なお、 GetProcAddress 関数 には Unicode 版が存在しない (lpProcName に指定できるのは ASCII だけ) なので、ここだけ文字リテラルのマクロに s!("") を使う。

Cargo.toml:

[package]
name = "hello-exe"
version = "0.1.0"
edition = "2021"

[dependencies]

[dependencies.windows]
version = "0.*"
features = [
    "Win32_System_LibraryLoader",
]

src/main.rs:

use windows::{core::*, Win32::{Foundation::{FreeLibrary, HMODULE}, System::LibraryLoader::{GetProcAddress, LoadLibraryW}}};

fn main() -> Result<()> {
    let loader = HelloLibraryLoader::new()?;
    println!("{}", (loader.DllTest)());

    Ok(())
}

type DllTest = extern "C" fn() -> i32;
#[allow(non_snake_case)]
struct HelloLibraryLoader {
    hdll: HMODULE,
    DllTest: DllTest
}
impl HelloLibraryLoader {
    pub fn new() -> Result<Self> {
        let hdll = unsafe { LoadLibraryW(w!(r"hello_dll.dll"))? };
        if hdll.is_invalid() {
            return Err(Error::from_win32());
        }
        match Self::get_proc::<DllTest>(hdll, s!("DllTest")) {
            Ok(dll_test) => Ok(Self { hdll, DllTest: dll_test }),
            Err(e) => {
                _ = unsafe { FreeLibrary(hdll) };
                Err(e)
            },
        }
    }
    fn get_proc<T>(hdll: HMODULE, lpprocname: PCSTR) -> Result<T> {
        unsafe {
            let address = match GetProcAddress(hdll, lpprocname) {
                None => return Err(Error::from_win32()),
                Some(v) => v,
            };
            Ok(std::mem::transmute_copy(&address))
        }
    }
}
impl Drop for HelloLibraryLoader {
    fn drop(&mut self) {
        if !self.hdll.is_invalid() {
            _ = unsafe { FreeLibrary(self.hdll) };
            self.hdll = HMODULE::default();
        }
    }
}

おわりに

いかがだったろうか?

unsafe まみれで一見 「Rust で記述する意味とは???」となるかもしれないが、バッファで受け取ったデータをあちこちで使う Win32 API プログラミングでは、 Rust の厳密な生存期間の管理がかなり役立つ。

ぜひ、快適でメモリリークの少ないプログラミングライフを!

Rust で Win32 API ことはじめ」への1件のフィードバック

  1. ピンバック: Rust と Win32 API RawInput で HID の TouchPad を深堀り | Aqua Ware つぶやきブログ

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください