天天看點

【玩轉.Net MF – 02】讓PC成為MF的滑鼠鍵盤

考慮一個應用場景,你設計了一個多功能帶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放飛了我們的夢想,通過簡單的擴充,使我們和裝置的互動的能力比以往更加強大和有力。

繼續閱讀