为了用电脑看电影时方便控制,我就突发其想,作一个手机app来经过无线网络远程调节电脑上的音量。后来进行尝试成功后,我就想,光是调音量彷佛单调了些,就把播放/暂停,上一首,下一首,等多媒体控制功能也加上,这样好玩一点。服务器
下面向你们简单介绍一下原理,整个解决方案的源代码我会共享给你们,以做参考。网络
先说服务器,由于控制命令比较简单,我直接用一个WPF应用程序来完成,这样方便运行,用socket来通讯比较麻烦,我就用WCF来作服务,使用WebServiceHoset,让WP手机客户端用HTTP-POST的方式来调用。app
这个相信你们都会,还有一个核心,就是如何控制系统的多媒体功能? 其实你们应该发如今你的笔记本键盘上,有一排功能按钮,能够经过按这些键来调整音量,控制播放、上一首歌曲等,还有各类功能开关,好比打开/关闭无线功能等。socket
也就是说,只要代码可以模拟发出这些按键就能够实现控制了,这就要用到Win32 API中的SendInput函数。在最初尝试时,我将SendInput函数导进托管代码中,但调用没有反应,不知道是否是我Import不对。async
后来,我干脆用C++来写一个dll,把各个控制操做都用独立的导出函数来包装,再把本身写的dll中的导出函数在托管项目中DllImport。函数
本身编写的dll的头文件以下:post
#pragma once #include "stdafx.h" /* 增大音量 */ extern "C" __declspec(dllexport) void volume_up(); /* 减少音量 */ extern "C" __declspec(dllexport) void volume_down(); /* 静音/恢复 */ extern "C" __declspec(dllexport) void volume_mute(); /* 下一首 */ extern "C" __declspec(dllexport) void media_next_track(); /* 上一首 */ extern "C" __declspec(dllexport) void media_prev_track(); /* 播放/暂停 */ extern "C" __declspec(dllexport) void media_play_pause(); /* 中止 */ extern "C" __declspec(dllexport) void media_stop(); void send_input_core(WORD vkey);
__declspec(dllexport)注明的函数为导出函数,便可以在其余代码中使用,而最后的send_input_core函数只供dll内部使用,就再也不导出了。spa
注意要加上extern "C",让函数以C语言的规范进行导出,因为C语言不支持函数重载,在编译时编译器不会改变函数的名字,因此加上extern "C"让托管代码在Dll Import时能够直接使用函数的原名,这样就不须要编写复杂的模块定义文件来重命名符号了,这种方法写dll是最方便的。code
下面在cpp文件中实现send_input_core函数:orm
void send_input_core(WORD vkey) { INPUT input; input.type = INPUT_KEYBOARD; input.ki.dwFlags = NULL; input.ki.wVk = vkey; SendInput(1, &input, sizeof(INPUT)); }
用vkey参数来接收要模拟的按键,这样就不用重复写SendInput的调用代码了,其他的函数能够直接调用该函数,而后传递按键值就能够了。
void volume_up() { send_input_core(VK_VOLUME_UP); } void volume_down() { send_input_core(VK_VOLUME_DOWN); } void volume_mute() { send_input_core(VK_VOLUME_MUTE); } void media_next_track() { send_input_core(VK_MEDIA_NEXT_TRACK); } void media_prev_track() { send_input_core(VK_MEDIA_PREV_TRACK); } void media_play_pause() { send_input_core(VK_MEDIA_PLAY_PAUSE); } void media_stop() { send_input_core(VK_MEDIA_STOP); }
完成dll后,就导入到C#托管代码中:
namespace DllAPIs { using System; using System.Runtime.InteropServices; public sealed class MediaControlAPIs { // dll文件的名字 const string DLL_NAME = "mediactrllib.dll"; #region 从dll导入的API [DllImport(DLL_NAME)] extern static void volume_up(); [DllImport(DLL_NAME)] extern static void volume_down(); [DllImport(DLL_NAME)] extern static void volume_mute(); [DllImport(DLL_NAME)] extern static void media_next_track(); [DllImport(DLL_NAME)] extern static void media_prev_track(); [DllImport(DLL_NAME)] extern static void media_play_pause(); [DllImport(DLL_NAME)] extern static void media_stop(); #endregion #region 公共方法 /// <summary> /// 增大音量 /// </summary> public static void VolumeUp() { volume_up(); } /// <summary> /// 减少音量 /// </summary> public static void VolumeDown() { volume_down(); } /// <summary> /// 静音/恢复 /// </summary> public static void VolumeMute() { volume_mute(); } /// <summary> /// 下一曲目 /// </summary> public static void MediaNextTrack() { media_next_track(); } /// <summary> /// 上一曲目 /// </summary> public static void MediaPrevTrack() { media_prev_track(); } /// <summary> /// 播放/暂停 /// </summary> public static void MediaPlayPause() { media_play_pause(); } /// <summary> /// 中止播放 /// </summary> public static void MediaStop() { media_stop(); } #endregion } }
我想,这样导入要比直接导入Win32 API要简便得多。
接着,完成WCF服务。
[ServiceContract] public interface IService { [OperationContract] [WebInvoke(UriTemplate = "invoke?action={act}")] void Invoke(string act); } public class WcfService : IService { public void Invoke(string act) { switch (act) { case "vu": //增大音量 DllAPIs.MediaControlAPIs.VolumeUp(); break; case "vd": //减少音量 DllAPIs.MediaControlAPIs.VolumeDown(); break; case "vm": //静音/恢复 DllAPIs.MediaControlAPIs.VolumeMute(); break; case "mn": //下一首 DllAPIs.MediaControlAPIs.MediaNextTrack(); break; case "mp": //上一首 DllAPIs.MediaControlAPIs.MediaPrevTrack(); break; case "mpp": //播放/暂停 DllAPIs.MediaControlAPIs.MediaPlayPause(); break; case "ms": //中止 DllAPIs.MediaControlAPIs.MediaStop(); break; } } }
其余代码就不介绍了,你们看源码吧。
最后是实现手机客户端,其实就是用HTTP-POST向刚才的WCF服务发数据便可。
private async Task PostActionAsync(string action) { string postUri = string.Format("http://{0}:{1}/invoke?action={2}", HostName, Port, action); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(postUri); request.Method = "POST"; var rep = await request.GetResponseAsync(); }
最后看看结果:
源代码下载:http://files.cnblogs.com/tcjiaan/RemoteMediaControlSlsn.zip