使用 P/Invoke 和 C# 与 Windows Media Player 互操作
本文档演示了如何使用 Visual C# 中的 Platform Invoke Services 与 Windows Media Player 进行互操作。
引言
本文旨在演示
- 如何使用 P/Invoke 调用非托管代码。
- 如何使用 Spy++ 记录 Windows 消息并获取
wParam
和lParam
的值。 - 如何在 C# 中实现
FindWindow()
和SendMessage()
。 - 如何与 Windows Media Player 进行互操作。
Win32 API 简介
MSDN 对 Windows API 的定义是
Windows API(应用程序编程接口)包含您在创建运行于 Microsoft Windows 上的应用程序时可以使用到的函数、消息、数据结构、数据类型和语句。您最常使用的 API 部分是通过代码元素来从 Windows 调用 API 函数。这些包括过程声明(用于 Windows 函数)、用户定义类型定义(用于传递给这些函数的数据结构)以及常量声明(用于传递给这些函数以及从这些函数返回的值)。
简而言之,Windows API 是嵌入在 Microsoft Windows 操作系统中的 DLL。在代码中使用 Windows API 的优点是,由于它们已包含数百个有用的函数,可以减少开发时间和复杂性。缺点是 Windows API 不使用托管代码,并且其数据类型与 Visual Studio .NET 使用的数据类型不同。虽然 .NET Framework 已将大部分 Win32 API 封装到托管代码中,但那些仍然是非托管的且没有 .NET 等效项的 API 可以使用平台调用服务从托管代码调用。
平台调用服务简介
平台调用服务 (P/Invoke) 是一种托管代码调用实现于 DLL 中的非托管函数的机制。
在调用非托管函数时,平台调用必须知道 DLL 文件名和函数或序号。DLLImport
属性用于指定包含外部方法实现的 DLL 位置,其参数用于指定特定的行为,例如 EntryPoint
和 CharSet
。有关 DllImport
属性语法的更多信息,请参阅 MSDN 上的 DllImportAttribute 类。
表 1:Win32 API 中常用的 DLL
DLL |
其内容描述 |
Kernel32.dll |
用于内存管理和资源处理的低级操作系统函数。 |
GDI32.dll |
图形设备接口 (GDI) 函数,用于设备输出,例如绘图和字体管理。 |
User32.dll |
用于消息处理、计时器、菜单和通信的 Windows 管理函数。 |
使用 C# 调用 Win32 API 的步骤
- 导入
System.Runtime.InteropServices
命名空间。 - 使用
DLLImport
定义函数。 - 添加代码以调用 Win32 API。
我们将实现 User32.dll 中找到的 FindWindow()
和 SendMessage()
相关的 Win32 API 函数,如下面的 Win32 类所示。
public class Win32
{
// The WM_COMMAND message is sent when the user
// selects a command item from a menu,
// when a control sends a notification message
// to its parent window, or when an
// accelerator keystroke is translated.
public const int WM_COMMAND = 0x111;
// The FindWindow function retrieves a handle
// to the top-level window whose class name
// and window name match the specified strings.
// This function does not search child windows.
// This function does not perform a case-sensitive search.
[DllImport("User32.dll")]
public static extern int FindWindow(string strClassName,
string strWindowName);
// The FindWindowEx function retrieves
// a handle to a window whose class name
// and window name match the specified strings.
// The function searches child windows, beginning
// with the one following the specified child window.
// This function does not perform a case-sensitive search.
[DllImport("User32.dll")]
public static extern int FindWindowEx(int hwndParent,
int hwndChildAfter, string strClassName, string strWindowName);
// The SendMessage function sends the specified message to a
// window or windows. It calls the window procedure for the specified
// window and does not return until the window procedure
// has processed the message.
[DllImport("User32.dll")]
public static extern Int32 SendMessage(
int hWnd, // handle to destination window
int Msg, // message
int wParam, // first message parameter
[MarshalAs(UnmanagedType.LPStr)] string lParam);
// second message parameter
[DllImport("User32.dll")]
public static extern Int32 SendMessage(
int hWnd, // handle to destination window
int Msg, // message
int wParam, // first message parameter
int lParam); // second message parameter
public Win32()
{
}
~Win32()
{
}
}
Windows Media Player 互操作概念图
要求
- Microsoft Visual Studio .NET
- Microsoft Spy++
- Windows Media Player
- 打开 Spy++ 并按下“日志消息”按钮,或按下 Ctrl + M。
- 打开 Microsoft Windows Media Player,调整窗口布局,使 Spy++ 和 Windows Media Player 都可见。
- 确保 Windows Media Player 未使用“自动隐藏菜单栏”功能,并将查找工具拖到标题为“Windows Media Player”的外部 Windows Media Player 窗口上。
- 或 -
如果您知道 Windows Media Player 窗口的句柄,可以在“句柄”框中输入。
接下来,转到“消息”选项卡并清除所有选中的消息。在“要查看的消息”列表框中,向下滚动并选择 WM_COMMAND
,然后单击“确定”。
此时,只选中 WM_COMMAND
将使下一步更容易。
接下来,从 Windows Media Player 主菜单中选择“播放”->“停止”,或者直接按下 Ctrl+S,以便 Spy++ 记录 WM_COMMAND
消息。
<00001> 0023017A P WM_COMMAND wNotifyCode:0 (sent from a menu) wID:18809
消息视图如下所示。请注意,第一列包含窗口句柄,第二列包含消息代码。消息参数和返回值在右侧。
双击此条目以查看额外的消息属性,例如 wParam
和 lParam
的十六进制值。
对“开始”按钮重复上述操作得到
<00001> 0023017A P WM_COMMAND wNotifyCode:0 (sent from a menu) wID:18808
使用 P/Invoke 和 Spy++,可以轻松扩展功能以包含弹出、重复或音量控制等其他选项,但出于演示目的,我们将仅实现开始/暂停和停止。
private System.Int32 iHandle;
private void btnStop_Click(object sender, System.EventArgs e)
{
Win32.SendMessage(iHandle, Win32.WM_COMMAND, 0x00004979, 0x00000000);
}
private void btnPlayPause_Click(object sender, System.EventArgs e)
{
Win32.SendMessage(iHandle, Win32.WM_COMMAND, 0x00004978, 0x00000000);
}
private void MainForm_Load(object sender, System.EventArgs e)
{
// get Window handle
iHandle = Win32.FindWindow("WMPlayerApp", "Windows Media Player");
}
结论
我写这篇文章很快乐,即使它不是一个原创的想法,但希望有人会觉得它很有用。最初,我曾想深入研究 Windows Media Player,使用 FindWindowEx()
并开始使用“当前播放列表”的 ATL:SysListView32
,通过 WMI 在选定的远程计算机上操作 Windows Media Player 播放列表。如果有人有其他想法或功能请求,请告诉我。
历史
- 版本 1.0 - 2004 年 2 月 8 日 - 原始提交。