虽说目前.Net Micro Framework已经支持文件系统(FAT16/FAT32),但在远程还无法直接访问,从某种意义上讲,无法和PC交互的存储介质显得有些鸡肋。我做SideShow相关开发的时候,为了向该文件系统拷贝文件,实现了UsbMassStorage功能,把设备当优盘来用,但这样做,等于独占了USB口,并且设备和PC的连接也必须为USB,对仅拥有串口或网口的设备是无效的。做过WinCE或Windows Mobile开发的人都知道,VS2008开发工具提供了些远程工具,诸如远程文件查看器、远程注册表编辑器、远程堆查看器和远程放大等等。受此启发,所以才有了MF的远程文件查看器。

该远程文件查看器,仍然作为MFDeploy的插件存在(如何做插件?请参见《玩转.Net MF–01》),最终的操作主界面如下:

 

【玩转.Net MF – 03】远程文件查看器_屏幕

该功能的实现要比《让PC成为MF的鼠标键盘》还要复杂,需要修改和添加的代码较多,下面我们一一进行讲解。

实现思路:考虑到MFDeploy已经实现了读写Flash的功能,所以最初的思路是想在PC端实现FAT文件系统(我曾实现过基于硬盘的FAT16系统),但是要支持FAT16/FAT32两种模式,还是非常复杂的,并且效率也很难保证;FTP是一种远程访问文件系统的常用办法,但这是基于TCP/IP上的协议,对USB和串口并不适合,所以考虑在PC端实现一个中间层,做一个类似串口/USB转TCP的模块,这样实现的好处是FTP客户端是现成的,不用再专门开发相关操作界面,但是FTP是基于两个TCP连接,实现起来有些难度;最终还是结合FTP的特点,实现了FTP的相关指令,如PWD、CD、CDUP、MKD和DELE指令等等,美中不足的是操作界面要自己开发。

一、      信道建立

在《让PC成为MF的鼠标键盘》中,鼠标和按键信息的传递,我们是借用了MFDeploy设备访问内存的通道,虽然简单,但极不正规,有点山寨的味道。所以彻底一点,我们自己另外新建一个访问通道。

MFDeploy和设备交互(包括VS2008部署和调试设备),都是基于WireProtocol协议,该协议是上位机MFDeploy或VS2008程序在诊断、部署、调试.Net Micro Framework设备及相关应用程序时的通信协议。该协议与具体的硬件链路无关,目前支持的物理连接有串口、网口、USB等。

该协议为点对点协议,协议中没有设备地址的概念,在同一时间同一物理通道仅能调试一台设备。协议格式分两部分,帧头和负荷(Payload)(一帧命令可以不包含Payload)。详细介绍请参见我写的文章《Micro Framework WireProtocol协议介绍》。

(1)、Native Code代码

 i、在TinyCLR_Debugging.h头文件CLR_DBG_Commands结构体中添加如下代码(57行),定义一个信道(我们的名称为Custom,意思是以后新扩充的基于通信交互的功能,就可以使用该信道,不仅仅是给远程文件查看器使用):

 

  1. static const UINT32 c_Monitor_Custom = 0x0000000F

 ii、依然在TinyCLR_Debugging.h头文件CLR_DBG_Commands结构体中添加如下代码(194行),定义我们的数据帧的通信结构(对WireProtocol协议来说就是Payload部分的数据结构):

   

  1. struct Monitor_Custom  
  2.  
  3.     {  
  4.  
  5.         UINT32 m_command;  
  6.  
  7.         UINT32 m_length;  
  8.  
  9.         UINT8  m_data[ 1 ];  
  10.  
  11.     };  
  12.  
  13. iii、在TinyCLR_Debugging.h的CLR_DBG_Debugger结构体中添加如下项(980行),声明相关信息到来时,将执行的函数:  
  14.  
  15. static bool Monitor_Custom ( WP_Message* msg, void* owner );  
  16.  
  17. iv、为在Debugger_full.cpp文件中c_Debugger_Lookup_Request数组新添一个条目(83行),声明我们的命令:  
  18.  
  19. DEFINE_CMD2(Custom)  
  20.  
  21. v、在Debugger.cpp中添加Monitor_Custom函数的具体实现:  
  22.  
  23. extern bool Monitor_Custom_Process(UINT8* data,int length,UINT8* retData,int *retLength);  
  24.  
  25. bool CLR_DBG_Debugger::Monitor_Custom( WP_Message* msg, void* owner )  
  26.  
  27. {  
  28.  
  29.     NATIVE_PROFILE_CLR_DEBUGGER();  
  30.  
  31.     bool fRet;  
  32.  
  33.    
  34.  
  35.     CLR_DBG_Debugger        * dbg = (CLR_DBG_Debugger*)owner;  
  36.  
  37.     CLR_DBG_Commands::Monitor_Custom* cmd = (CLR_DBG_Commands::Monitor_Custom*)msg->m_payload;  
  38.  
  39.    
  40.  
  41.      UINT8  retData[1024];  
  42.  
  43.      int    retLength=0;  
  44.  
  45.     fRet = Monitor_Custom_Process(cmd->m_data,cmd->m_length,retData,&retLength);     
  46.  
  47.    
  48.  
  49.     //lcd_printf("R:%d L:%d\r\n",fRet,retLength);  
  50.  
  51.      if(retLength==0)  
  52.  
  53.      {  
  54.  
  55.        dbg->m_messaging->ReplyToCommand( msg, fRet, false);  
  56.  
  57.      }  
  58.  
  59.      else 
  60.  
  61.      {  
  62.  
  63.         dbg->m_messaging->ReplyToCommand( msg, fRet, false,retData,retLength);  
  64.  
  65.      }  
  66.  
  67.     return fRet;  
  68.  
  69. }  
  70.  

具体的功能实现在函数Monitor_Custom_Process中实现,它如果为空,则什么事也不做(当你不需要该功能时,为了节省程序空间,你就可以这么做)。

(2)   、修改Microsoft.SPOT.Debugger.dll

 i、在WireProtocol.cs文件的Commands类中添加如下代码(119行),和NativeCode的代码保持一致:

  1. public const uint c_Monitor_Custom = 0x0000000F;  
  2.  

ii、仍在WireProtocol.cs文件的Commands类添加Monitor_Custom类的声明(385行):

  1. public class Monitor_Custom  
  2.  
  3.         {  
  4.  
  5.             public uint m_command = 0;  
  6.  
  7.             public uint m_length = 0;  
  8.  
  9.             public byte[] m_data = null;  
  10.  
  11.    
  12.  
  13.             public void PrepareForSend(uint command, byte[] data, int offset, int length)  
  14.  
  15.             {  
  16.  
  17.                 m_command = command;  
  18.  
  19.                 m_length = (uint)length;  
  20.  
  21.                 m_data = new byte[length];        
  22.  
  23.                 Array.Copy(data, offset, m_data, 0, length);  
  24.  
  25.             }  
  26.  
  27.             public class Reply : IConverter  
  28.  
  29.             {  
  30.  
  31.                 public byte[] m_data = null;  
  32.  
  33.                 public void PrepareForDeserialize(int size, byte[] data, Converter converter)  
  34.  
  35.                 {  
  36.  
  37.                     m_data = new byte[size];  
  38.  
  39.                 }  
  40.  
  41.             }   
  42.  
  43.       }  
  44.  

iii、在WireProtocol.cs文件的ResolveCommandToPayload的函数中添加如下项,声明返回数据的结构(1470行)。

  1. case c_Monitor_Custom: return new Monitor_Custom.Reply();  
  2.  

iv、仍是在WireProtocol.cs文件的ResolveCommandToPayload的函数中添加如下项,声明执行类(1537行)。

case c_Monitor_Custom: return new Monitor_Custom();

v、最关键的一步,消息发送接口的实现,在Engine.cs文件的Engine类中添加如下代码(2323行):

    

  1. public WireProtocol.IncomingMessage CustomCommand(uint command, byte[] buf, int offset, int length)  
  2.  
  3.       {  
  4.  
  5.           WireProtocol.Commands.Monitor_Custom cmd = new WireProtocol.Commands.Monitor_Custom();  
  6.  
  7.           cmd.PrepareForSend(command, buf, offset, length);     
  8.  
  9.           return SyncMessage(WireProtocol.Commands.c_Monitor_Custom, 0, cmd);  
  10.  
  11.  

以上代码仅仅扩展了一个信道,实际上什么事也没干(路修好了,还没有车在跑)。

二、功能实现

(1)   、Native Code代码

     在\DviceCode\Pal新建CustomProcess目录,我们将完成类FTP服务端的实现。

     新建CustomProcess.cpp文件,文件起始先做如下声明:

    

  1. #define   Custom_Command_MouseKey      0x00  
  2.  
  3.     #define   Custom_Command_FileSystem    0x01  
  4.  
  5.  
  6.  
  7.     #define   FileSystem_Start             0x00  
  8.  
  9.     #define   FileSystem_End               0x01  
  10.  
  11.     #define   FileSystem_FORMAT            0x02  
  12.  
  13.     #define   FileSystem_PWD               0x03  
  14.  
  15.     #define   FileSystem_DIR_FindOpen      0x04  
  16.  
  17.     #define   FileSystem_DIR_FindNext      0x05  
  18.  
  19.     #define   FileSystem_DIR_FindClose     0x06  
  20.  
  21.     #define   FileSystem_CD                0x07  
  22.  
  23.     #define   FileSystem_CDUP              0x08  
  24.  
  25.     #define   FileSystem_MKD               0x09  
  26.  
  27.     #define   FileSystem_DELE              0x0A  
  28.  
  29.     #define   FileSystem_UPLOAD            0x0B  
  30.  
  31.     #define   FileSystem_DOWNLOAD          0x0C  
  32.  
  33.  

     Monitor_Custom_Process函数的实现如下:

    

  1.  bool Monitor_Custom_Process(UINT8* inData,int inLength,UINT8* outData,int *outLength)  
  2.  
  3. {     
  4.  
  5.     *outLength=0;  
  6.  
  7.     if(inLength==0) return true;  
  8.  
  9.    
  10.  
  11.       bool ret=true;  
  12.  
  13.     switch(inData[0])  
  14.  
  15.     {  
  16.  
  17.        case Custom_Command_MouseKey:       
  18.  
  19.           if(inLength == 9)  
  20.  
  21.            {  
  22.  
  23.              UINT32 data1=(inData[1]<<24) | (inData[2]<<16) |  (inData[3] <<8) | inData[4];  
  24.  
  25.                 UINT32 data2=(inData[5]<<24) | (inData[6]<<16) |  (inData[7] <<8) | inData[8];              
  26.  
  27.              VI_GenerateEvent(data1,data2);  
  28.  
  29.            }  
  30.  
  31.           break;  
  32.  
  33.          case Custom_Command_FileSystem:  
  34.  
  35.            ret = FileSystem_Process(inData[1],&inData[2],inLength-2,outData,outLength);  
  36.  
  37.            break;  
  38.  
  39.     }  
  40.  
  41.       
  42.  
  43.     return ret;  
  44.  
  45. }  
  46.  

你看,我们顺便也把鼠标和键盘的信道也合并到这里来了。

FileSystem_Process就是具体实现类FTP服务端功能的函数。

  1. bool  FileSystem_Process(UINT8 command,UINT8* inData,int inLength,UINT8* outData,int *outLength)  
  2.  
  3. {     
  4.  
  5.    static WCHAR current_dir[256];              //当前工作目录     
  6.  
  7.     static WCHAR current_file[256];             //当前操作文件  
  8.  
  9.     static UINT32 findHandle=NULL;  
  10.  
  11.    
  12.  
  13.     static FileSystemVolume* volume = NULL;  
  14.  
  15.     static STREAM_DRIVER_INTERFACE*  streamDriver=NULL;  
  16.  
  17.    
  18.  
  19.    //debug_printf("[%x:%d]\r\n",command,inLength);  
  20.  
  21.    if( volume == NULL && command != FileSystem_Start) return false;  
  22.  
  23.    
  24.  
  25.     HRESULT ret=S_OK;  
  26.  
  27.    *outLength=0;  
  28.  
  29.     switch(command)  
  30.  
  31.     {  
  32.  
  33.        case  FileSystem_Start:  
  34.  
  35.         volume = FileSystemVolumeList::GetFirstVolume();     
  36.  
  37.           streamDriver = volume->m_streamDriver;  
  38.  
  39.           memcpy(current_dir, L"\\", 2);   
  40.  
  41.           current_dir[1]=0;  
  42.  
  43.         break;              
  44.  
  45.        case  FileSystem_End:  
  46.  
  47.         volume->FlushAll();  
  48.  
  49.           volume = NULL;  
  50.  
  51.           streamDriver = NULL;  
  52.  
  53.         break;              
  54.  
  55.        case  FileSystem_FORMAT:  
  56.  
  57.         volume->Format(0);  
  58.  
  59.           volume->FlushAll();  
  60.  
  61.         break;              
  62.  
  63.        case  FileSystem_PWD:  
  64.  
  65.           *outLength = MF_wcslen(current_dir)*2;   
  66.  
  67.           memcpy(outData,current_dir, *outLength);  
  68.  
  69.         break;              
  70.  
  71.         case  FileSystem_DIR_FindOpen:         
  72.  
  73.         case  FileSystem_DIR_FindNext:    
  74.  
  75.       case  FileSystem_DIR_FindClose:  
  76.  
  77.         case  FileSystem_CDUP:  
  78.  
  79. case  FileSystem_MKD:        
  80.  
  81. case  FileSystem_DELE:           
  82.  
  83. case  FileSystem_UPLOAD:  
  84.  
  85.          case  FileSystem_DOWNLOAD:  
  86.  
  87.         //略  
  88.  
  89.         break;              
  90.  
  91.     }  
  92.  
  93.     return true;  
  94.  
  95. }  
  96.  

在TinyCLR.proj文件中添加如下项,以启用我们新实现的功能。

  

  1. <ItemGroup> 
  2.  
  3.     <RequiredProjects Include="$(SPOCLIENT)\DeviceCode\PAL\CustomProcess\dotNetMF.proj" /> 
  4.  
  5.     <DriverLibs Include="CustomProcess.$(LIB_EXT)" /> 
  6.  
  7.   </ItemGroup>    
  8.  

   编译下载,这时候,我们的设备已经可支持文件系统远程访问了。下一步我们将实现上位机的相关代码。

(2)、远程文件查看器插件

新建FileViewerHandle插件类。

      public class FileViewerHandle : MFPlugInMenuItem

     {

        public override string Name { get { return "File Viewer"; } }

        public override void OnAction(IMFDeployForm form, MFDevice device)

        {

            if (form == null || device == null) return;

            (new frmFileViewer(form, device)).ShowDialog();

        }

}

   核心函数Send的代码如下,该函数与设备进行通信:

     private bool Send(byte command, byte[] bytInput, out byte[] bytOutput)

      {

            byte[] bytTemp;

            bytOutput = null;

            if (bytInput != null)

            {

                bytTemp = new byte[bytInput.Length + 2];

                Array.Copy(bytInput, 0, bytTemp, 2, bytInput.Length);

            }

            else

            {

                bytTemp = new byte[2];

            }

            bytTemp[0] = Custom_Command_FileSystem;

            bytTemp[1] = command;

            _WP.IncomingMessage reply = engine.CustomCommand(_WP.Commands.c_Monitor_Custom, bytTemp, 0, bytTemp.Length);

 

            if (!_WP.IncomingMessage.IsPositiveAcknowledge(reply)) return false;

            _WP.Commands.Monitor_Custom.Reply CustomReply = reply.Payload as _WP.Commands.Monitor_Custom.Reply;

            if (CustomReply != null) bytOutput = CustomReply.m_data;                

            return true;

    }

   限于篇幅,下面仅截取几个命令操作的片段

        private void tsbUP_Click(object sender, EventArgs e)

        {

            ShowInfo("ready.");

            byte[] outData;

            if (Send(FileSystem_CDUP, out outData))

            {

                txtDir.Text = System.Text.Encoding.Unicode.GetString(outData);

            }

            else

            {

                ShowInfo("up failed!", InfoType.Error);

            }

            list();

        }

        private void tsbNew_Click(object sender, EventArgs e)

        {

            ShowInfo("ready.");

            frmInputBox fb = new frmInputBox("Input directory", "");

            if (fb.ShowDialog() == DialogResult.OK)

            {

                string strInfo = fb.Info;

                byte[] inData = System.Text.Encoding.Unicode.GetBytes(BuildPath(strInfo));

                if (!Send(FileSystem_MKD, inData)) ShowInfo("mkd failed!", InfoType.Error);

                list();

            } 

        }

        private void tsbDelete_Click(object sender, EventArgs e)

        {

            ShowInfo("ready.");

            if (lvFile.SelectedItems.Count == 0) return;

            if (MessageBox.Show("Really implement operation for Delete?", this.Text, MessageBoxButtons.OKCancel,MessageBoxIcon.Question) == DialogResult.OK)

            {

                byte[] inData = System.Text.Encoding.Unicode.GetBytes(BuildPath(lvFile.SelectedItems[0].Text));

                if (!Send(FileSystem_DELE, inData)) ShowInfo("dele failed!", InfoType.Error);  

                lstFile.Remove(lvFile.SelectedItems[0].Text);

                list();

            }

      }

     实现.Net MF的远程文件查看器意义深远,我们不仅可以向文件系统拷贝我们的图片、字体等资源,更有意义的事,我们可以把.Net MF应用的程序,作为可执行文件拷贝到文件系统,然后让TinyCLR去加载执行。甚而.Net MF的系统库也可以放入文件系统,根据需要,我们可以非源码级别的裁剪系统的大小。