為了用電腦看電影時友善控制,我就突發其想,做一個手機app來通過無線網絡遠端調節電腦上的音量。後來進行嘗試成功後,我就想,光是調音量似乎單調了些,就把播放/暫停,上一首,下一首,等多媒體控制功能也加上,這樣好玩一點。
下面向大家簡單介紹一下原理,整個解決方案的源代碼我會共享給大家,以作參考。
先說伺服器,因為控制指令比較簡單,我直接用一個WPF應用程式來完成,這樣友善運作,用socket來通信比較麻煩,我就用WCF來做服務,使用WebServiceHoset,讓WP手機用戶端用HTTP-POST的方式來調用。
這個相信大家都會,還有一個核心,就是如何控制系統的多媒體功能? 其實大家應該發現在你的筆記本鍵盤上,有一排功能按鈕,可以通過按這些鍵來調整音量,控制播放、上一首歌曲等,還有各種功能開關,比如打開/關閉無線功能等。
也就是說,隻要代碼能夠模拟發出這些按鍵就可以實作控制了,這就要用到Win32 API中的SendInput函數。在最初嘗試時,我将SendInput函數導進托管代碼中,但調用沒有反應,不知道是不是我Import不對。
後來,我幹脆用C++來寫一個dll,把各個控制操作都用獨立的導出函數來包裝,再把自己寫的dll中的導出函數在托管項目中DllImport。
自己編寫的dll的頭檔案如下:
#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内部使用,就不再導出了。
注意要加上extern "C",讓函數以C語言的規範進行導出,由于C語言不支援函數重載,在編譯時編譯器不會改變函數的名字,是以加上extern "C"讓托管代碼在Dll Import時可以直接使用函數的原名,這樣就不需要編寫複雜的子產品定義檔案來重命名符号了,這種方法寫dll是最友善的。
下面在cpp檔案中實作send_input_core函數:
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();
}
最後看看結果:
源代碼下載下傳:https://files.cnblogs.com/tcjiaan/RemoteMediaControlSlsn.zip