【玩转.Net MF – 02】让PC成为MF的鼠标键盘

  1. 云栖社区>
  2. 博客>
  3. 正文

【玩转.Net MF – 02】让PC成为MF的鼠标键盘

技术小美 2017-11-08 19:17:00 浏览565
展开阅读全文

考虑一个应用场景,你设计了一个多功能带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 的操作,通过该渠道,把鼠标和按键信息发送给设备。实现代码如下:

    


  1. private void SendKey(KeyState state, Keys KeyCode, int KeyValue, bool Shift, bool Caps, bool Ins)  
  2.  
  3.        {  
  4.  
  5.            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};  
  6.  
  7.            SendData(bytData);  
  8.  
  9.        }  
  10.  
  11.        private void SendMouse(MouseState state, MouseButtons button, int x, int y)  
  12.  
  13.        {  
  14.  
  15.            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) };  
  16.  
  17.            SendData(bytData);  
  18.  
  19.        }  
  20.  
  21.        private void SendData(byte[] bytData)  
  22.  
  23.        {  
  24.  
  25.            if (bytData.Length != 8) return;  
  26.  
  27.            UInt32 addr = 0xD;  
  28.  
  29.            byte[] TempData = new byte[10] {0xAA, 0x55,0,0,0,0,0,0,0,0};  
  30.  
  31.            Array.Copy(bytData, 0, TempData, 2, 8);  
  32.  
  33.            bool ret = engine.WriteMemory(addr, TempData, 0, 10);  
  34.  
  35.        }  
  36.  
  37.        private void palScreen_MouseDown(object sender, MouseEventArgs e)  
  38.  
  39.        {  
  40.  
  41.            SendMouse(MouseState.Down, e.Button, e.X, e.Y);  
  42.  
  43.        }  
  44.  
  45.        private void palScreen_MouseMove(object sender, MouseEventArgs e)  
  46.  
  47.        {  
  48.  
  49.            SendMouse(MouseState.Move, e.Button, e.X, e.Y);  
  50.  
  51.        }  
  52.  
  53.        private void palScreen_MouseUp(object sender, MouseEventArgs e)  
  54.  
  55.        {  
  56.  
  57.            SendMouse(MouseState.Up, e.Button, e.X, e.Y);  
  58.  
  59.        }  
  60.  
  61.        private void frmVirtualInput_KeyDown(object sender, KeyEventArgs e)  
  62.  
  63.        {  
  64.  
  65.            lblKey.Text = e.KeyCode.ToString();  
  66.  
  67.            SendKey(KeyState.Down, e.KeyCode, (int)e.KeyValue, e.Shift, falsefalse);  
  68.  
  69.        }  
  70.  
  71.        private void frmVirtualInput_KeyUp(object sender, KeyEventArgs e)  
  72.  
  73.        {  
  74.  
  75.            SendKey(KeyState.Up, e.KeyCode, (int)e.KeyValue, e.Shift, falsefalse);  
  76.  
  77.        }  
  78.  
  79.        private void frmVirtualInput_KeyPress(object sender, KeyPressEventArgs e)  
  80.  
  81.        {  
  82.  
  83.            byte[] bytChar = System.Text.ASCIIEncoding.UTF8.GetBytes(e.KeyChar.ToString());  
  84.  
  85.            SendKey(KeyState.Press, Keys.Space, (int)bytChar[0], falsefalsefalse);  
  86.  
  87.   }    

窗体的大小通过获取设备的LCD的尺寸,进行自动设置,这等于虚拟出一个与LCD等大的鼠标活动区。

二、      鼠标和按键信息获取

上一步我们向设备写入了一个虚拟写Flash操作,所以我们在设备的上的代码,需做些修改,还是修改\CLR\Debugger目录下的Debugger.cpp文件,在CLR_DBG_Debugger::AccessMemory函数增添如下代码:


  1. case AccessMemory_Write:  
  2.  
  3.      if(accessAddress == 0xD  && NumOfBytes==10 && bufPtr[0] == 0xAA && bufPtr[1] == 0x55 )  
  4.  
  5.     {  
  6.  
  7.          UINT32 data1=(bufPtr[2]<<24) | (bufPtr[3]<<16) |  (bufPtr[4] <<8) | bufPtr[5];  
  8.  
  9.          UINT32 data2=(bufPtr[6]<<24) | (bufPtr[7]<<16) |  (bufPtr[8] <<8) | bufPtr[9];                 
  10.  
  11.          VI_GenerateEvent(data1,data2);  
  12.  
  13.          success = TRUE;  
  14.  
  15.     }  
  16.  
  17.    
  18.     else 
  19.  
  20.     {                  
  21.  
  22.          success = m_deploymentStorageDevice->Write( accessAddress , NumOfBytes, (BYTE *)bufPtr, FALSE );  
  23.  
  24.         }  
  25.  
  26.     break;  
  27.  

记得在函数头声明VI_GenerateEvent函数,它负责触发消息。

extern void VI_GenerateEvent(UINT32 data1, UINT32 data2);

三、      实现事件触发

我们在\DeviceCode\pal目录新建一个VirtualInput子目录,我们要为TinyCLR新添加一个feature。核心代码如下:


  1. void VI_GenerateEvent(UINT32 data1, UINT32 data2)  
  2.  
  3. {  
  4.  
  5.     if(g_Context)  
  6.  
  7.     {  
  8.  
  9.         GLOBAL_LOCK(irq);             
  10.  
  11.         SaveNativeEventToHALQueue( g_Context,data1, data2);  
  12.  
  13.     }  
  14.  
  15. }  
  16.  
  17. static HRESULT InitializeEventDriver( CLR_RT_HeapBlock_NativeEventDispatcher *pContext, UINT64 userData )  
  18.  
  19. {  
  20.  
  21.    g_Context  = pContext;  
  22.  
  23.    g_UserData = userData;  
  24.  
  25.    return S_OK;  
  26.  
  27. }  
  28.  
  29. static HRESULT EnableDisableEventDriver( CLR_RT_HeapBlock_NativeEventDispatcher *pContext, bool fEnable )  
  30.  
  31. {  
  32.  
  33.    g_InterruptEnalbed = fEnable;  
  34.  
  35.    return S_OK;  
  36.  
  37. }  
  38.  
  39. static HRESULT CleanupIestDriver( CLR_RT_HeapBlock_NativeEventDispatcher *pContext )  
  40.  
  41. {  
  42.  
  43.     g_Context = NULL;  
  44.  
  45.     g_UserData = 0;     
  46.  
  47.     CleanupNativeEventsFromHALQueue( pContext );  
  48.  
  49.     return S_OK;  
  50.  
  51. }  
  52.  
  53. static const CLR_RT_DriverInterruptMethods g_InteropEventDriverMethods =   
  54.  
  55. {   
  56.  
  57.     InitializeEventDriver,  
  58.  
  59.     EnableDisableEventDriver,  
  60.  
  61.     CleanupIestDriver  
  62.  
  63. };  
  64.  
  65. const CLR_RT_NativeAssemblyData g_CLR_AssemblyNative_Microsoft_SPOT_YFVI_Event  =  
  66.  
  67. {  
  68.  
  69.     "Microsoft_SPOT_YFVI_Event",   
  70.  
  71.     DRIVER_INTERRUPT_METHODS_CHECKSUM,  
  72.  
  73.     &g_InteropEventDriverMethods  
  74.  
  75. };  
  76.  
  77. 以上代码就是.Net MF实现事件触发的典型结构,比如按键按下抬起、串口数据接收、SD卡插入等等事件通知就是如此实现的。  
  78.  
  79. 四、      WinForm输入代理实现  
  80.  
  81. 接收事件的代码如下,也是一个标准结构。  
  82.  
  83. public class EventDispatcher : NativeEventDispatcher  
  84.  
  85.     {  
  86.  
  87.         public EventDispatcher()  
  88.  
  89.             : base("Microsoft_SPOT_YFVI_Event", 0)  
  90.  
  91.         { }  
  92.  
  93.         public EventDispatcher(string EventDispatcherName, ulong userData)  
  94.  
  95.             : base(EventDispatcherName, userData)  
  96.  
  97.         { }  
  98.  
  99. }  
  100.  

"Microsoft_SPOT_YFVI_Event"要和上一步g_CLR_AssemblyNative_Microsoft_SPOT_YFVI_Event中的名字保持一致。   

为WinForm库新添加一个YFSoft.InputProxy.dll库,和虚拟输入相关的代码如下:


  1. private void eventDispatcher_OnInterrupt(uint data1, uint data2, DateTime time)  
  2.  
  3.         {  
  4.  
  5.             byte[] bytData = new byte[8];  
  6.  
  7.             bytData[0] = (byte)(data1 >> 24 & 0xFF);  
  8.  
  9.             bytData[1] = (byte)(data1 >> 16 & 0xFF);  
  10.  
  11.             bytData[2] = (byte)(data1 >> 8 & 0xFF);  
  12.  
  13.             bytData[3] = (byte)(data1 >> 0 & 0xFF);  
  14.  
  15.             bytData[4] = (byte)(data2 >> 24 & 0xFF);  
  16.  
  17.             bytData[5] = (byte)(data2 >> 16 & 0xFF);  
  18.  
  19.             bytData[6] = (byte)(data2 >> 8 & 0xFF);  
  20.  
  21.             bytData[7] = (byte)(data2 >> 0 & 0xFF);  
  22.  
  23.             //key  
  24.  
  25.             if (bytData[0] == 0x01)  
  26.  
  27.             {  
  28.  
  29.                 SendKey((KeyState)bytData[1], (Keys)bytData[2], bytData[3], bytData[4] == 1, bytData[5] == 1, bytData[6] == 1);  
  30.  
  31.             }  
  32.  
  33.             //mouse  
  34.  
  35.             if (bytData[0] == 0x02)  
  36.  
  37.             {                  
  38.  
  39.                 MouseButtons button=MouseButtons.None;  
  40.  
  41.                 if(bytData[2] ==1) button= MouseButtons.Left;  
  42.  
  43.                 if(bytData[3] ==1) button= MouseButtons.Right;  
  44.  
  45.    
  46.  
  47.                 SendMouse((MouseState)bytData[1], button, bytData[4] << 8 | bytData[5], bytData[6] << 8 | bytData[7]);  
  48.  
  49.             }  
  50.  
  51.         }  
  52.  
  53.         public void Initialize(System.Windows.Forms.Dispatcher Dispatcher)  
  54.  
  55.         {  
  56.  
  57.             this.Dispatcher = Dispatcher;  
  58.  
  59.             if (KeyEnable) Key_Initialize();  
  60.  
  61.             if (MouseEnable) Mouse_Initialize();  
  62.  
  63.             if (VirtualInputEnable)  
  64.  
  65.             {  
  66.  
  67.                 EventDispatcher eventDispatcher = new EventDispatcher();  
  68.  
  69.                 eventDispatcher.OnInterrupt += new NativeEventHandler(eventDispatcher_OnInterrupt);  
  70.  
  71.             }  
  72.  
  73.         }  
  74.  
  75.         public void SendKey(KeyState state, Keys KeyCode, int KeyValue, bool Shift, bool Caps, bool Ins)  
  76.  
  77.         {  
  78.  
  79.             //Debug.Print(KeyValue.ToString()+" "+((int)state).ToString());  
  80.  
  81.             if (Key != null) Key(state, KeyCode, KeyValue, Shift, Caps, Ins);  
  82.  
  83.         }  
  84.  
  85.         public void SendMouse(MouseState state,MouseButtons button, int x, int y)  
  86.  
  87.         {  
  88.  
  89.             //Debug.Print("(" + x.ToString() + "," + y.ToString() + ")");  
  90.  
  91.             if (Mouse != null) Mouse(state, button, x, y);  
  92.  
  93.     }  
  94.  

我们在开发基于WinForm库的应用程序时,只要在Main函数中添加如下代码,即可以启动该功能。

Application.InputProxy.VirtualInputEnable = true;

需要说明的是,该虚拟输入和正常的输入设备并没有冲突(如触摸屏及相关按键),原先的输入设备还是可以正常工作的,这样做的目的,只是额外为你的设备扩展了一个强大的输入装置。

还是那句话,开源后的.Net MF放飞了我们的梦想,通过简单的扩展,使我们和设备的交互的能力比以往更加强大和有力。

 

 








本文转自yefanqiu51CTO博客,原文链接:http://blog.51cto.com/yfsoft/321216,如需转载请自行联系原作者

网友评论

登录后评论
0/500
评论
技术小美
+ 关注