考慮一個應用場景,你設計了一個多功能帶LCD顯示的儀器,假設為了節省成 本,沒有安裝觸摸屏和擴展外接鼠標鍵盤的接口,儀表上僅有幾個外置按鈕,但 是由於功能相對復雜,需要配置很多參數,如果單單依靠外置按鈕,輸入不僅慢 ,還得為此設計一套輸入規則,想想看如果能通過儀表調試口,通過擴展讓我們 的PC成為它的鼠標鍵盤,則輸入工作將變的異常簡單(其實這樣的儀表並不是我 憑空瞎想,以前開發ICU輸液系統時,國外生產的輸液裝置就是這樣的儀表,比如 要輸入藥名、輸液速度和輸液壓力等一系列相關參數)。
通過擴展我以前為.Net MF開發的WinForm庫(參見我以前的文章《開源 System.Windows.Forms庫,讓.Net Micro Framework界面開發和上位機一樣簡單 》),增加一個輸入代理層,就可以實現虛擬鼠標和鍵盤輸入。
先看看最終的成果(如下圖),然後我們再細說是如何實現的。
(虛擬鼠標輸入,設備上的鼠標將和PC上的鼠標同步移動)
(虛擬鍵盤輸入,千萬不要以為上面的字符為軟鍵盤所輸入,細心的讀者會發 現,軟鍵盤上根本沒有 @#¥%等按鍵)
要實現這個功能還真不那麼簡單,需要做如下四步工作:一、為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放飛了我們的夢想,通過簡單的擴展,使我們 和設備的交互的能力比以往更加強大和有力。