65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 VC++ 以编程方式更改 Windows 音频设备。

starIconstarIconstarIconstarIconstarIcon

5.00/5 (9投票s)

2008 年 12 月 17 日

CPOL

6分钟阅读

viewsIcon

138322

downloadIcon

2969

如何以编程方式启动和操作小程序。

引言

您是否曾经想过通过编程方式更改 Windows 计算机上的活动音频输出设备(声音播放)?您是否注意到 Microsoft(有意)让这项操作几乎变得不可能?好吧,这里有一个解决方案。这是一个托管 C++(.NET)解决方案,但这里使用的方法可以轻松地改编到其他语言。

背景

在继续深入之前,有几点需要说明:

  1. 正是因为 Microsoft **不**希望您这样做,所以这个解决方案本质上是一种临时解决方案,一种权宜之计。
  2. 任何输出到声音播放音频设备(如 Windows Media Player)的第三方应用程序,在更改音频设备后都必须重新启动,因为它们通常只在应用程序启动时才查阅注册表并进行一次配置。您自己的应用程序不必模仿这种限制(眨眼)。
  3. 我只在 XP 上测试过。我不知道有任何理由该解决方案在 Vista 下无效,但验证解决方案在 Vista 下的可操作性留给读者作为练习。
  4. 由于我们要切换音频设备,并且您的计算机上可用的实际音频设备几乎肯定与我的不同,因此您必须提供一些依赖于计算机的信息才能使解决方案在您自己的计算机上正常工作。

好了,消除了这四点注意事项,下面是基本思路。我们以编程方式启动“声音和音频设备属性”小程序,并使用消息系统操作该面板。该面板实际上会在屏幕上短暂出现,但这不成问题。我认为通过这种方式更改音频设备比直接修改注册表要好,因为直接修改注册表总是容易出错的,而且不清楚具体涉及哪些注册表项。

这种通用方法当然可以扩展到操作**任何**小程序或应用程序,它不限于“声音和音频设备属性”小程序。该解决方案足够小,可以轻松地改编到其他小程序(.cpl)和应用程序(.exe),这应该是显而易见的。

Using the Code

让我们开始吧。您首先需要确定要切换的两个音频设备。从控制面板启动“声音和音频设备属性”小程序,转到“音频”选项卡,然后单击“声音播放”组合框。会出现一个下拉列表,显示您计算机上可用的声音播放音频输出设备。记下您希望操作的两个设备(**准确**,所有大写和标点符号都必须正确)。

在我的机器上,我选择了“Realtek HD Audio output”和“CreamWare Play/Rec 1”。

在文件 ACMDefines.h 的顶部,用您自己的选择替换我的选择。

#define AUDIO_DEVICE_1 _T("Realtek HD Audio output")
#define AUDIO_DEVICE_2 _T("CreamWare Play/Rec 1")

现在,构建解决方案,它应该可以在您的机器上工作。

好的,让我们解释一下这是怎么回事。

从托管代码启动小程序(.cpl)足够简单,只需一行代码即可完成:

Process::Start("rundll32.exe","shell32.dll,Control_RunDLL mmsys.cpl");

小程序“mmsys.cpl”位于 system32 目录中,与其他在控制面板中找到的小程序一样,这并不奇怪。我特意没有直接启动显示“音频”选项卡的那个小程序,以便在示例代码中演示如何以编程方式选择选项卡。但是,我们可以将上面的行更改为:

Process::Start("rundll32.exe","shell32.dll,Control_RunDLL mmsys.cpl,,2");

结果将是小程序窗口出现时,“音频”选项卡已选中,因此在代码中就不需要进行选择选项卡的过程了。但是,如果您希望将我的解决方案作为模板来适应您自己的特定需求,最好显示所有涉及的步骤。

一旦小程序成功启动,我们就可以获得小程序窗口的句柄:

appletWindow = FindWindow(NULL,APPLET_WINDOW_TITLE);

然后,只需通过发送消息来模拟键盘活动来操作小程序。这个概念很简单。困难之处在于获取小程序窗口中各个控件的句柄。首先,我们需要使用 Spy++(位于 Visual Studio 中)在小程序窗口中进行侦察,以发现我们要操作的控件的类型和标签。所以,启动小程序,打开 Spy++,然后查找小程序窗口。您应该会看到类似以下内容:

Window xxxxxxxx "Sounds and Audio Devices Properties" #xxxxx (Dialog)

单击此条目左侧的“+”以展开子窗口列表。其中一个子条目是“Window xxxxxxxx “&Apply” Button”。这里有两个重要信息。一、它是一个 BUTTON 类型的控件。二、按钮的文本(或标签)是“&Apply”(注意不实际显示在 GUI 中的 ampersand)。

在示例代码中,我们通过调用 findChildWindow() 来获取此控件(以及所有后续控件)的句柄:

applyButton = findChildWindow(appletWindow,BUTTON,APPLY_BUTTON);

BUTTONACMDefines.h 中定义为:

#define BUTTON _T("Button")

APPLY_BUTTON 定义为:

#define APPLY_BUTTON _T("&Apply")

好的,现在我们有了“Apply”按钮的句柄。接下来我们需要选择“音频”选项卡。在 Spy++ 中,您会看到一个条目,如下所示:

Window xxxxxxxx '"' SysTabControl32

类型是 SysTabControl,但它没有标签。没有标签不是问题,因为小程序窗口中只有一个类型为“SysTabControl32”的控件。我们以获取“Apply”按钮句柄的方式来获取此控件的句柄:

tabControl = findChildWindow(appletWindow,SYS_TAB_CTRL,NULL);

获得句柄后,我们模拟键盘活动(Ctrl-上/下/右/左)来选择“音频”选项卡:

SendMessage(tabControl,WM_KEYDOWN,VK_CONTROL,0); //ctrl key down
SendMessage(tabControl,WM_KEYDOWN,VK_RIGHT,0); //right arrow key down
SendMessage(tabControl,WM_KEYUP,VK_RIGHT,0); //right arrow key up
SendMessage(tabControl,WM_KEYDOWN,VK_RIGHT,0); //right arrow key down
SendMessage(tabControl,WM_KEYUP,VK_RIGHT,0); //right arrow key up
SendMessage(tabControl,WM_KEYUP,VK_CONTROL,0); //ctrl key up

一旦我们显示了音频选项卡,我们就查找我们要使用的音频设备(AUDIO_DEVICE_1)的句柄:

comboBox = findChildWindow(appletWindow,COMBO_BOX,AUDIO_DEVICE_1);

如果找不到句柄,我们就查找 AUDIO_DEVICE_2 的句柄。示例代码中假设这两个音频设备中的一个始终被选中,因此始终是组合框中的选定项。如果您的计算机有多个音频设备,并且您选择的两个设备都不是默认选定的项,那么您必须首先查找默认选定项的句柄,然后从中选择一个您感兴趣的设备。示例代码不执行此操作,它假定这两个设备之一已在选择中。

一旦我们获得了组合框中当前选定项的句柄,我们就简单地向其发送消息以更改为新的音频设备:

SendMessage(comboBox,CB_SELECTSTRING,(WPARAM)NONE,(LPARAM)&AUDIO_DEVICE_2);

然后,我们向“Apply”按钮发送消息以应用更改:

SendMessage(applyButton,BM_CLICK,0,0); //apply the change

并关闭小程序窗口:

SendMessage(appletWindow,WM_CLOSE,0,0); //close the applet

关注点

如果您希望启动一个常规应用程序(.exe)并使用模拟键盘输入来操作它,有几种方法可以做到。以下只是一种可能性:

protected: bool launchApplication()
{
    HINSTANCE returnCode;
    returnCode = ShellExecute((HWND)errorBox->getWindowHandle(), //window
                "open", //verb - action
                "C:\\Program Files\\SomeProgram.exe", //program file specification
                "", //parameters (none)
                "C:\\Program Files", //directory where opened
                SW_SHOWMINNOACTIVE); //window minimized, no focus
    return((returnCode > (HINSTANCE)32 && returnCode != 
        (HINSTANCE SE_ERR_NOASSOC) ? true : false);
}

对于 .exe 文件,只有启动方法不同,其他所有内容与 .cpl 文件相同。

正如您所见,这个概念极其简单。这个编码示例的价值在于它展示了一种紧凑且通用的方法,用于从托管 C++ 获取小程序中各种控件的句柄。

显然,这个例子是针对特定任务和特定小程序。因此,我没有试图将所有内容都抽象化,而是提供了一个清晰的示例方法,可以作为设计您自己的类似问题的特定解决方案的指南。

历史

  • 2008 年 12 月 23 日:更新了源代码。
© . All rights reserved.