考慮一個應用場景,你設計了一個多功能帶LCD顯示的儀器,假設為了節省成本,沒有安裝觸摸屏和擴充外接滑鼠鍵盤的接口,儀表上僅有幾個外置按鈕,但是由于功能相對複雜,需要配置很多參數,如果單單依靠外置按鈕,輸入不僅慢,還得為此設計一套輸入規則,想想看如果能通過儀表調試口,通過擴充讓我們的PC成為它的滑鼠鍵盤,則輸入工作将變的異常簡單(其實這樣的儀表并不是我憑空瞎想,以前開發ICU打點滴系統時,國外生産的打點滴裝置就是這樣的儀表,比如要輸入藥名、打點滴速度和打點滴壓力等一系列相關參數)。
通過擴充我以前為.Net MF開發的WinForm庫(參見我以前的文章《開源System.Windows.Forms庫,讓.Net Micro Framework界面開發和上位機一樣簡單》),增加一個輸入代理層,就可以實作虛拟滑鼠和鍵盤輸入。
要實作這個功能還真不那麼簡單,需要做如下四步工作:一、為MFDeploy開發一個插件,捕捉PC上的滑鼠和按鍵資訊,并把它們發送到裝置;二、修改TinyCLR核心代碼,讓它擷取PC上發送的滑鼠和按鍵資訊;三、為.Net MF添加一個事件源,當有滑鼠和按鍵時,觸發.Net MF應用程式中特定的事件;四、為WinForm庫擴充一個輸入代理層。下面我們将一一介紹上面四步的實作步驟。
一、 MFDeploy虛拟輸入插件
在上一篇同系列的文章《【玩轉.Net MF – 01】Flash遠端讀寫》,我們已經介紹了MFDeploy的實作方法,是以這裡就不熬述了。
實作思路的是這樣的,因為我們已經通過MFDeploy可以和裝置進行通信,在不額外增加代碼的情況下,我們虛拟一個寫Flash 的操作,通過該管道,把滑鼠和按鍵資訊發送給裝置。實作代碼如下:
private void SendKey(KeyState state, Keys KeyCode, int KeyValue, bool Shift, bool Caps, bool Ins)
{
byte[] bytData = new byte[8]{ 0x01,(byte)state,(byte)KeyCode, (byte)KeyValue, (byte)(Shift ? 1 : 0), (byte)(Caps ? 1 : 0), (byte)(Ins ? 1 : 0),0};
SendData(bytData);
}
private void SendMouse(MouseState state, MouseButtons button, int x, int y)
{
byte[] bytData = new byte[8] { 0x02, (byte)state, (byte)(button == MouseButtons.Left ? 1 : 0), (byte)(button == MouseButtons.Right ? 1 : 0), (byte)(x >> 8), (byte)(x & 0xFF), (byte)(y >> 8), (byte)(y & 0xFF) };
SendData(bytData);
}
private void SendData(byte[] bytData)
{
if (bytData.Length != 8) return;
UInt32 addr = 0xD;
byte[] TempData = new byte[10] {0xAA, 0x55,0,0,0,0,0,0,0,0};
Array.Copy(bytData, 0, TempData, 2, 8);
bool ret = engine.WriteMemory(addr, TempData, 0, 10);
}
private void palScreen_MouseDown(object sender, MouseEventArgs e)
{
SendMouse(MouseState.Down, e.Button, e.X, e.Y);
}
private void palScreen_MouseMove(object sender, MouseEventArgs e)
{
SendMouse(MouseState.Move, e.Button, e.X, e.Y);
}
private void palScreen_MouseUp(object sender, MouseEventArgs e)
{
SendMouse(MouseState.Up, e.Button, e.X, e.Y);
}
private void frmVirtualInput_KeyDown(object sender, KeyEventArgs e)
{
lblKey.Text = e.KeyCode.ToString();
SendKey(KeyState.Down, e.KeyCode, (int)e.KeyValue, e.Shift, false, false);
}
private void frmVirtualInput_KeyUp(object sender, KeyEventArgs e)
{
SendKey(KeyState.Up, e.KeyCode, (int)e.KeyValue, e.Shift, false, false);
}
private void frmVirtualInput_KeyPress(object sender, KeyPressEventArgs e)
{
byte[] bytChar = System.Text.ASCIIEncoding.UTF8.GetBytes(e.KeyChar.ToString());
SendKey(KeyState.Press, Keys.Space, (int)bytChar[0], false, false, false);
}
窗體的大小通過擷取裝置的LCD的尺寸,進行自動設定,這等于虛拟出一個與LCD等大的滑鼠活動區。
二、 滑鼠和按鍵資訊擷取
上一步我們向裝置寫入了一個虛拟寫Flash操作,是以我們在裝置的上的代碼,需做些修改,還是修改/CLR/Debugger目錄下的Debugger.cpp檔案,在CLR_DBG_Debugger::AccessMemory函數增添如下代碼:
case AccessMemory_Write:
if(accessAddress == 0xD && NumOfBytes==10 && bufPtr[0] == 0xAA && bufPtr[1] == 0x55 )
{
UINT32 data1=(bufPtr[2]<<24) | (bufPtr[3]<<16) | (bufPtr[4] <<8) | bufPtr[5];
UINT32 data2=(bufPtr[6]<<24) | (bufPtr[7]<<16) | (bufPtr[8] <<8) | bufPtr[9];
VI_GenerateEvent(data1,data2);
success = TRUE;
}
else
{
success = m_deploymentStorageDevice->Write( accessAddress , NumOfBytes, (BYTE *)bufPtr, FALSE );
}
break;
記得在函數頭聲明VI_GenerateEvent函數,它負責觸發消息。
extern void VI_GenerateEvent(UINT32 data1, UINT32 data2);
三、 實作事件觸發
我們在/DeviceCode/pal目錄建立一個VirtualInput子目錄,我們要為TinyCLR新添加一個feature。核心代碼如下:
void VI_GenerateEvent(UINT32 data1, UINT32 data2)
{
if(g_Context)
{
GLOBAL_LOCK(irq);
SaveNativeEventToHALQueue( g_Context,data1, data2);
}
}
static HRESULT InitializeEventDriver( CLR_RT_HeapBlock_NativeEventDispatcher *pContext, UINT64 userData )
{
g_Context = pContext;
g_UserData = userData;
return S_OK;
}
static HRESULT EnableDisableEventDriver( CLR_RT_HeapBlock_NativeEventDispatcher *pContext, bool fEnable )
{
g_InterruptEnalbed = fEnable;
return S_OK;
}
static HRESULT CleanupIestDriver( CLR_RT_HeapBlock_NativeEventDispatcher *pContext )
{
g_Context = NULL;
g_UserData = 0;
CleanupNativeEventsFromHALQueue( pContext );
return S_OK;
}
static const CLR_RT_DriverInterruptMethods g_InteropEventDriverMethods =
{
InitializeEventDriver,
EnableDisableEventDriver,
CleanupIestDriver
};
const CLR_RT_NativeAssemblyData g_CLR_AssemblyNative_Microsoft_SPOT_YFVI_Event =
{
"Microsoft_SPOT_YFVI_Event",
DRIVER_INTERRUPT_METHODS_CHECKSUM,
&g_InteropEventDriverMethods
};
以上代碼就是.Net MF實作事件觸發的典型結構,比如按鍵按下擡起、序列槽資料接收、SD卡插入等等事件通知就是如此實作的。
四、 WinForm輸入代理實作
接收事件的代碼如下,也是一個标準結構。
public class EventDispatcher : NativeEventDispatcher
{
public EventDispatcher()
: base("Microsoft_SPOT_YFVI_Event", 0)
{ }
public EventDispatcher(string EventDispatcherName, ulong userData)
: base(EventDispatcherName, userData)
{ }
}
"Microsoft_SPOT_YFVI_Event"要和上一步g_CLR_AssemblyNative_Microsoft_SPOT_YFVI_Event中的名字保持一緻。
為WinForm庫新添加一個YFSoft.InputProxy.dll庫,和虛拟輸入相關的代碼如下:
private void eventDispatcher_OnInterrupt(uint data1, uint data2, DateTime time)
{
byte[] bytData = new byte[8];
bytData[0] = (byte)(data1 >> 24 & 0xFF);
bytData[1] = (byte)(data1 >> 16 & 0xFF);
bytData[2] = (byte)(data1 >> 8 & 0xFF);
bytData[3] = (byte)(data1 >> 0 & 0xFF);
bytData[4] = (byte)(data2 >> 24 & 0xFF);
bytData[5] = (byte)(data2 >> 16 & 0xFF);
bytData[6] = (byte)(data2 >> 8 & 0xFF);
bytData[7] = (byte)(data2 >> 0 & 0xFF);
//key
if (bytData[0] == 0x01)
{
SendKey((KeyState)bytData[1], (Keys)bytData[2], bytData[3], bytData[4] == 1, bytData[5] == 1, bytData[6] == 1);
}
//mouse
if (bytData[0] == 0x02)
{
MouseButtons button=MouseButtons.None;
if(bytData[2] ==1) button= MouseButtons.Left;
if(bytData[3] ==1) button= MouseButtons.Right;
SendMouse((MouseState)bytData[1], button, bytData[4] << 8 | bytData[5], bytData[6] << 8 | bytData[7]);
}
}
public void Initialize(System.Windows.Forms.Dispatcher Dispatcher)
{
this.Dispatcher = Dispatcher;
if (KeyEnable) Key_Initialize();
if (MouseEnable) Mouse_Initialize();
if (VirtualInputEnable)
{
EventDispatcher eventDispatcher = new EventDispatcher();
eventDispatcher.OnInterrupt += new NativeEventHandler(eventDispatcher_OnInterrupt);
}
}
public void SendKey(KeyState state, Keys KeyCode, int KeyValue, bool Shift, bool Caps, bool Ins)
{
//Debug.Print(KeyValue.ToString()+" "+((int)state).ToString());
if (Key != null) Key(state, KeyCode, KeyValue, Shift, Caps, Ins);
}
public void SendMouse(MouseState state,MouseButtons button, int x, int y)
{
//Debug.Print("(" + x.ToString() + "," + y.ToString() + ")");
if (Mouse != null) Mouse(state, button, x, y);
}
我們在開發基于WinForm庫的應用程式時,隻要在Main函數中添加如下代碼,即可以啟動該功能。
Application.InputProxy.VirtualInputEnable = true;
需要說明的是,該虛拟輸入和正常的輸入裝置并沒有沖突(如觸摸屏及相關按鍵),原先的輸入裝置還是可以正常工作的,這樣做的目的,隻是額外為你的裝置擴充了一個強大的輸入裝置。
還是那句話,開源後的.Net MF放飛了我們的夢想,通過簡單的擴充,使我們和裝置的互動的能力比以往更加強大和有力。