摘要:比较 Microsoft Windows CE .NET 应用程序开发人员目前可以使用的三种开发选择(即 Win32、MFC 和 Microsoft .NET Framework 精简版)的优势。学习如何使用每个运行时创建类似于 MFC Scribble 示例的应用程序。
前不久,我们分别在中国台湾和韩国举办了“Microsoft® Windows® Embedded 开发人员研讨会”(日本和中国大陆将在二月举行,欧洲将在三月举行)。在汉城的时候,有一次我与其他演讲者在我们下榻的旅馆附近的 Outback Steakhouse 等待就餐时听到有人在谈论“与服务器对话”的话题,当时首先映入我脑海的是一台采用了语音识别和文字到语音转换的大型的多处理器计算机。(我想它一定很酷。)不一会儿,我们这张桌子上的人也开始谈论起这个话题,但大家的看法似乎大同小异。我想,这就是一群倍受时差反应折磨的 Windows 嵌入技术开发人员/演讲者聚在一个饭店里时可能会发生的事情吧。
鉴于 Microsoft Windows CE .NET 4.1(英文)的最后一部分最近已经在 Web 上发布,所以现在为 Windows CE .NET 设备应用程序开发人员介绍各种可以使用的选择应该是一个不错的时机。
Microsoft® Windows® CE .NET 应用程序开发人员目前有三种选择:它们分别是 Win32、Microsoft 基础类(以及 ATL,它主要用于创建 COM 组件、Web 服务和 Microsoft® ActiveX® 控件)和 Microsoft® .NET Framework 精简版。这三种选择各有优势。对应用程序开发人员来说,您可以自行决定使用哪一种选择来构建您的应用程序。
选择的过程中可能需要考虑多方面的因素。本文将着重讨论最重要的三个因素:应用程序文件的大小、运行时占用的资源以及应用程序开发的速度。其他要考虑的因素可能包括安全性、稳定性、工作集需要、实时支持、性能、现有代码库等。我们将通过开发一个类似于 Scribble 的应用程序来了解各个运行时的开发过程。
如果您想知道运行时的相对大小,可以参考下表列出的各运行时的总体大小:
为了说明包括 MFC 和 Framework 精简版时的大小差异,我在 Windows CE .NET Emulator 中构建了一个“Internet Appliance”平台。下面是各个发行版本的大小比较。
运行时 | 大小(以字节为单位) | 基本 Win32 平台增加的大小 |
---|---|---|
Win32 | 9,805,231 | 0 |
MFC | 10,234,415 | 429,184 |
Framework 精简版 | 11,201,459 | 1,396,228 |
在平台中添加对 MFC 或 Framework 精简版的支持非常简单,只需右击 Platform Builder 目录中的组件,然后单击 Add to Platform(添加到平台)即可。如果您只关心操作系统的大小,那么您可以跳到本文的结尾,看看我们下个月要讨论的话题。对大多数读者来说,有关运行时的决策并非易事。
为了帮助您了解每个运行时涉及的工作量,下面我们分别用 Win32、MFC 和 .NET Framework 精简版编写一个 Scribble 式的应用程序(与 MFC Scribble 示例相似)。该应用程序将包括所有常用的功能(文件保存和恢复、菜单以及图形输出)。每个应用程序都需要处理同一组事件中的大多数事件:按下鼠标、移动鼠标、释放鼠标,以及构建一个可以在相应的画图/绘图功能中回放的鼠标点数组。它还需要处理文件的保存和恢复操作。听起来十分简单,对不对?好吧,现在我们打开一瓶 Jolt Cola,放松一下,然后开始编码。
为 Windows CE .NET 平台编写代码时,可以创建应用程序、驱动程序、控制面板小程序或 DLL。要创建某些底层代码,如设备驱动程序、实时代码、控制面板小程序等,本机 Win32 开发是唯一的选择。编写用户应用程序时,可以使用 Win32、MFC 或 Framework 精简版。在某些方面,您可能会考虑对某个设备进行分层,最底层为驱动程序和实时代码,用 Win32/本机代码编写;然后是一些中间层、数据分析层,或许还有一个 DLL 或 COM 对象,可以用本机代码、MFC 或 ATL 编写;最高层可能是一个提供用户界面的应用程序,可以用 Win32、MFC 或 Framework 精简版编写。
Platform Builder 和 Microsoft® eMbedded Visual C++® 都包括一个应用程序向导,可以通过它为应用程序创建 Win32 框架代码。框架代码包括对绘图(应用程序工作区中心的“Hello World”)、菜单(支持 File [文件]/Exit [退出] 和 Help [帮助]/About [关于] 的支持,还包括 About(关于)框的代码。
图 1:初具规模的 Win32 框架应用程序
对我们的任务来说,从框架代码入手是最好的选择,而且只有在这个时候,这些工具才会在我们开发 Win 32 应用程序的过程中发挥其作用(当然,除此以外我们还会用到 eMbedded Visual C++ 附带的优秀联机帮助功能)。有了框架代码之后,我们需要手动编写其他所有内容。我们的示例应用程序要支持鼠标事件,因为没有向导可以帮助我们实现这个过程,所以我们要在 Windows 过程 (WndProc) 中插入相应的 WM_MOUSEMOVE、WM_LBUTTONDOWN 和 WM_LBUTTONUP 处理程序。下面,我们就要用到大家都喜欢用的 switch 语句。
我喜欢调用独立的函数,而不喜欢使用大的 switch 语句来处理 WNDPROC 中的内联代码,因为这样可以使代码更容易阅读和调试。请记住,您可以通过在各个函数的入口点和出口点添加调试区域信息来动态跟踪应用程序的流程,从而借助“调试区域”执行调试进程。“调试区域”的一个优点是,您可以决定调试信息的级别以及何时需要信息。如果使用过多的 OutputDebugString,则会产生大量的调试信息,以至淹没了真正需要的信息。下面是我的 WNDPROC 的核心:
switch (message) { case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // 分析菜单选择:+ switch (wmId) { case IDM_HELP_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, DLGPROC)About); break; case ID_FILE_OPEN: OpenScribbleFile(hWnd); break; case ID_FILE_SAVE: SaveScribbleFile(hWnd); break; case IDM_FILE_EXIT: CleanUp( ); DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_CREATE: hwndCB = CommandBar_Create(hInst, hWnd, 1); CommandBar_InsertMenubar(hwndCB, hInst, IDM_MENU, 0); CommandBar_AddAdornments(hwndCB, 0, 0); Initialize( ); // 设置最初的数组元素和鼠标标志 break; case WM_LBUTTONDOWN: HandleLButtondown(hWnd,LOWORD(lParam),HIWORD(lParam)); break; case WM_LBUTTONUP: HandleLButtonUp(hWnd,LOWORD(lParam),HIWORD(lParam)); break; case WM_MOUSEMOVE: HandleMouseMove(hWnd,LOWORD(lParam),HIWORD(lParam)); break; case WM_PAINT: RECT rt; hdc = BeginPaint(hWnd, &ps); GetClientRect(hWnd, &rt); LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING); DrawText(hdc, szHello, _tcslen(szHello), &rt, DT_SINGLELINE | DT_VCENTER | DT_CENTER); DrawArray(hWnd,ps); EndPaint(hWnd, &ps); break; case WM_DESTROY: CommandBar_Destroy(hwndCB); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
我们还需要构建一个点数组,以保存 Scribble 数据。Win32 不提供处理数组的函数,因此我们需要自己创建链接表(因为我们希望动态地增加点的数量),或者在 Web 上找一个合适的数组类。这正是生活的乐趣所在。您可能会认为创建一个动态的点数组很简单,当然您可能是正确的。但我的画图处理程序代码却出了差错,结果超过了数组末尾,直接导致了访问冲突。我花了大约两个小时进行调试,最后终于查出并改正了问题(在一个包括 Win32 模板代码的 400 行的应用程序中 - 真是痛苦!)。使用 Win32,您可以随意地调用任意的 API、分配对象的生存期以及安排内存的使用方式。在我的示例中,我实在是太自由了。
下面是 POINT 结构。我保留上次鼠标移动的 x 和 y 位置,还保留了指向数组中下一项的指针。我还计算了数组中的点数。因此,我可以逐个地查看点列表,并画出各个项或把它们写到一个文件中。
typedef struct tag_ptArray { POINT pt; LPVOID ptrNext; } PTARRAY,*LPPTARRAY;
下面的代码将一个点添加到有关鼠标移动的数组中。请注意,在完成该应用程序之后或者在加载 Scribble 文件时,我们需要逐个查看列表中的各个元素并手动删除它们。否则,将导致内存泄漏。
void AddElement(int X, int Y) { Current_Point->ptrNext=(LPPTARRAY)LocalAlloc(LPTR,sizeof(PTARRAY)); Current_Point=(LPPTARRAY)Current_Point->ptrNext; Current_Point->pt.x=X; Current_Point->pt.y=Y; Current_Point->ptrNext=NULL; iPointCount++; }
我们还需要显示一个用于打开(和保存)文件的对话框,以便获取和保存我们的 Scribble 数据。由于使用的是 Win32,因此需要用相应的元素填充一个 OPENFILENAME 结构,并调用 GetOpenFilename 来显示该对话框。之后,我们可以使用 CreateFile、WriteFile(或 ReadFile)和(一个 API)CloseHandle(为什么不是 CloseFile 呢?)。
void OpenScribbleFile(HWND hWnd) { OPENFILENAME ofd; TCHAR tcFileName[MAX_PATH]; TCHAR tcDefaultName[MAX_PATH]; wcscpy(tcDefaultName,L"Scribble.scr"); memset(&ofd,0x00,sizeof(ofd)); ofd.lStructSize=sizeof(ofd); ofd.hwndOwner=hWnd; ofd.hInstance=hInst; ofd.lpstrFile=tcFileName; ofd.nMaxFile=MAX_PATH; ofd.lpstrDefExt=L"scr"; ofd.lpstrFilter=L"Scribble Files\0*.scr\0\0"; ofd.lpstrTitle=L"Scribble 文件"; ofd.Flags=OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; BOOL bRet=GetOpenFileName(&ofd); if (TRUE == bRet) { CleanUp( ); // 清除现有的 Scribble 数组 pt_Array=(LPPTARRAY)LocalAlloc(LPTR,sizeof(PTARRAY)); Current_Point=pt_Array; HANDLE hFile=CreateFile(ofd.lpstrFile,GENERIC_READ,0,NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); POINT pt; DWORD dwRead; BOOL bRead=FALSE; iPointCount=0; while(TRUE) { bRead=ReadFile(hFile,&pt,sizeof(pt),&dwRead,0); if (dwRead) { AddElement(pt.x,pt.y); } else break; } CloseHandle(hFile); } InvalidateRect(hWnd,NULL,TRUE); }
编写(和调试)Win32 应用程序共需大约四个小时。最终生成一个 7 KB 大小的可执行文件,加载和执行该文件不需要任何额外的运行时。
Win32 API 使用起来非常有意思。有些函数接受多个参数,有些函数接受一个结构,有些函数返回一个指针,有些则返回一个操作系统在内部跟踪对象的句柄。而且,当我们使用对象之后,根据对象的具体类型,我们需要删除、关闭或释放该对象。(如果不这样做,就会导致内存泄漏。)对于 4 字节的句柄,这可能不是太大的问题,但如果数百万次地泄漏该句柄,则很快就会耗尽设备的资源。如果您的设备要运行数小时、数周、数月甚至数年,这就会成为一个问题。所幸,我们有像 LMEMDEBUG、Memalyzer 和远程性能监视器这样的工具,能够帮助我们跟踪泄漏,还有像来自 Entrek 的 CodeSnitch 等其他好工具。所有这些工具在查找有漏洞的代码时都能助我们一臂之力。
MFC 对创建用于 Windows CE 的应用程序(或用于同一目的的桌面应用程序)有何作用呢?很简单。顾名思义,Microsoft 基础类提供了一组有用的类,这些类(大多数)隐藏了 Win32 开发的复杂性。很多时候,您只需要直接调用 Win32 API。基于 Microsoft® .NET Framework 精简版的应用程序也是如此,这就是使用平台调用 (pInvoke) 的原因。
从 MFC 调用本机 API 非常简单。您只需要直接调用本机 Win32 API,就像在编写本机 Win32 应用程序一样。如果看一下 MFC 源代码,您就会注意到 MFC 基本上就是在 Win32 API 之上加了一层薄薄的包装。MFC 包含完整的源代码,对于调试以及理解其内部运行情况非常有用。在 Windows CE .NET 4.1 上,如果默认安装了 eMbedded Visual C++,则可以在 C:\Program Files\Windows CE Tools\wce410\STANDARDSDK_410\Mfc\Src 中找到 MFC 源代码。
MFC 支持大约 160 个类、包装窗口、视图、文档、时间、数组、字符串、套接字以及笔、画笔等 GDI 对象。您可以在 Web 上或 eMbedded Visual C++ 产品文档(英文)中查看受支持的类列表。
桌面 MFC 与 Windows CE MFC 在实现方面存在一些差异。Differences from Desktop(英文)中重点介绍了这些差异。
基于 MFC 的应用程序是使用 eMbedded Visual C++ 4.0 创建的,您可以从此处(英文)免费下载 eMbedded Visual C++ 4.0。用于 Windows CE 的基于 MFC 的应用程序只能使用此工具创建。Platform Builder 只能创建 Win32 应用程序和 DLL。
eMbedded Visual C++ 通过 eMbedded Visual C++ 工具和支持的 MFC 类,协助您进行应用程序的开发。如果我们回顾一下 Win32 应用程序,为诸如 WM_LBUTTONDOWN(鼠标按钮按下,此操作是由单击鼠标或用笔尖触击触摸屏生成)这样的 Windows 消息添加支持,需要通过在 WNDPROC switch 中添加更多的 case 语句来修改应用程序的 WNDPROC。我们还需要知道如何将 WPARAM 和 LPARAM 参数转换为设备坐标。通过 MFC,我们可以使用 MFC 类向导(Ctrl+W 或从菜单中调用)创建 Windows 消息处理程序(无需更多的 switch 语句)。消息处理程序分解消息参数,然后将某些更适用的参数传递给 MFC 消息处理程序。下面是 MFC 类向导的外观。
图 2:MFC 类向导
您也可以在类上右击并选择 Windows Message Handler(Windows 消息处理程序)。
图 3:Windows 消息处理程序
那么,典型的 MFC Windows 消息处理程序是什么样子的呢?我们来看类向导生成的一个用于 WM_LBUTTONDOWN 消息的函数。您可以清楚地看到我们传递了鼠标状态标志(表明按下了哪个鼠标按钮),以及一个包含鼠标按下时的 x 和 y 位置的 CPoint。
void CMFCScribbleView::OnLButtonDown(UINT nFlags, CPoint point) { m_InDraw=true; m_CurrentPoint=point; CView::OnLButtonDown(nFlags, point); } void CMFCScribbleView::OnLButtonUp(UINT nFlags, CPoint point) { m_InDraw=false; m_CurrentPoint=CPoint(0,0); CView::OnLButtonUp(nFlags, point); } void CMFCScribbleView::OnMouseMove(UINT nFlags, CPoint point) { if (m_InDraw) { if (point != m_CurrentPoint) { CPoint line[2]; line[0]=m_CurrentPoint; line[1]=point; m_CurrentPoint=point; CDC *pDC=GetDC( ); pDC->Polyline(line,2); ReleaseDC(pDC); } } else CView::OnMouseMove(nFlags, point); }
上面的代码是绘制鼠标实际移动情况时所需的全部代码。我们获取鼠标移动处理程序中的设备上下文(使用 GetDC),绘制一条从前一点到当前位置的线条(使用 Polyline)。这段代码运行一切正常,但如果我们的工作区由于某种原因而无效,那么就会丢失其中的内容。理想情况下,我们应该保留一个点列表。我们可以使用 Win32 示例中的链接列表代码。如果 MFC 能为我们处理这个问题就好了。MFC 支持标准模板库,这样我们就可以使用像 CArray 这样的类,用来存储我们的点列表。
下面是创建 CArray 并将点添加到数组中的一个示例。数组随着点的增加而动态扩大。在加载 Scribble 文件时只需调用 myArray.RemoveAll( ); 即可清除数组,这比使用 Win32 应用程序要容易得多。编写 Win32 应用程序时利用 CArray 可以节省近一个小时的时间。
CArray<CPoint,CPoint> myArray; // 将元素添加到数组中。 for (int i=0;i < 10;i++) myArray.Add( CPoint(i, 2*i) );
这样,我们就只需要将一个 CArray <CPoint, CPoint> myArray
成员变量添加到文档类中,并修改 OnMouseMove 处理程序从而将新的点添加到类中。
void CMFCScribbleView::OnMouseMove(UINT nFlags, CPoint point) { if (m_InDraw) { if (point != m_CurrentPoint) { CPoint line[2]; line[0]=m_CurrentPoint; line[1]=point; // 将新点添加到文档中。 GetDocument( )->myArray.Add(point); // 将新点添加到文档中。 m_CurrentPoint=point; CDC *pDC=GetDC( ); pDC->Polyline(line,2); ReleaseDC(pDC); } } else CView::OnMouseMove(nFlags, point); }
然后就可以在 OnDraw 处理程序中回放这些点,如下所示:
void CMFCScribbleView::OnDraw(CDC* pDC) { CMFCScribbleDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); int iCount=pDoc->myArray.GetSize( ); POINT *pt=new POINT[iCount]; for (int x=0;x < iCount;x++) { pt[x].x=pDoc->myArray.GetAt(x).x; pt[x].y=pDoc->myArray.GetAt(x).y; } pDC->Polyline(pt,iCount); delete [] pt; }
现在,我们可以在应用程序的工作区随意涂写,并在需要重画工作区时回放 Scribble。最后一步是存储和加载 Scribble 数据。使用 CArray 保存 CPoint 数组有一个弊端:CArray 支持一个称为 Serialize 的方法,而且我们的 Scribble 应用程序的 CDocument 类包括一个 Serialize 函数。此函数传递一个 CArchive 对象,用于以永久性二进制格式保存一个复杂的对象网络(通常保存在磁盘上),删除对象后,这种复杂的对象网络依然存在。我们只需要在 Serialize 函数中添加两行代码,如下所示。
void CMFCScribbleDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // 存储线条数组 myArray.Serialize(ar); } else { // 还原线条数组 myArray.Serialize(ar); } }
编写、测试和调试 MFC 应用程序总共需要大约两个小时。发布的应用程序大小约为 18 KB。不难看出,MFC 提供了许多有用的类,使您可以集中精力编写应用程序代码,尽管您仍然需要经常调用本机 Win32 API,但不必在底层操作系统提供的 API 上花费太多的时间。
接下来,让我们看看使用 Microsoft .NET Framework 精简版编写 Scribble 应用程序的情况。我决定将其作为桌面应用程序进行编写和测试,然后再将代码移植到 Windows CE 中。这是一种有意思的练习,我会在编写代码的过程中进行解释。
构建 Framework 精简版应用程序的开发环境有些类似于 Microsoft® Visual Basic® 开发环境。如果您需要菜单、常用的文件对话框或其他控件,只需要从工具箱中将这些内容拖放到窗体中,然后设置控件的属性,编写控件的附加代码。
图 4:设备控件工具箱
我们需要添加用于按下鼠标、移动鼠标和释放鼠标的处理程序。这很简单。使用窗体属性对话框(参见下图)可以为典型的基于窗体的消息添加处理程序:按下/释放键、按下/释放/移动鼠标,等等。然后,我们编写处理程序的代码。
图 5:应用程序窗体属性
图 6:Scribble 应用程序中使用的控件
请注意,这些控件不具备设计时 UI。但是,大多数 Framework 精简版控件都有一种设计时调整机制,使您可以在一个近似于“所见即所得”的环境中排列窗体上的控件并调整控件的大小。
将三个变量添加到类中:
...public bool bMouseDown=false; ...public ArrayList m_myAL; ...public Point m_CurrentPoint;
下面是用于按下鼠标、移动鼠标和释放鼠标的处理程序。请注意,我们在鼠标移动中创建了一个 System.Drawing.Graphics 对象。这样,我们就可以在上一点与当前点之间绘制一条线,而不用关闭工作区。
private void Form1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) { bMouseDown=false; } private void Form1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) { if (true == bMouseDown) { m_myAL.Add(new Point(e.X,e.Y)); System.Drawing.Graphics gr=this.CreateGraphics( ); Pen myPen=new Pen(System.Drawing.Color.Black); gr.DrawLine(myPen,m_CurrentPoint.X,m_CurrentPoint.Y,e.X,e.Y); m_CurrentPoint.X=e.X; m_CurrentPoint.Y=e.Y; } } private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) { bMouseDown=true; m_CurrentPoint.X=e.X; m_CurrentPoint.Y=e.Y; }
我们需要更新(创建一个新的对象实例)ArrayList。我是在加载窗体的过程中实现此操作的。
private void Form1_Load(object sender, System.EventArgs e) { m_myAL = new ArrayList(); }
接下来是用于从文件系统读回 Scribble 文件的代码。这正是编写应用程序的乐趣所在。我起初使用了 BinaryFormatter 类,它可以利用 BinarayFormatter.Serialize( ) 函数序列化整个 ArrayList。从文件读回 ArrayList 与使用 BinaryFormatter.DeSerialize( ) 函数一样简单。在我将代码移植到 Framework 精简版之前,应用程序的构建、测试和运行都非常顺利。但结果还是构建失败:Framework 精简版不支持 BinaryFormatter。现在只好重新编写代码。
在这种情况下,我决定使用 BinaryReader/Writer 将各个点写出到流。我先写入项数,然后写入各个项本身。下面是将 ArrayList 写入文件中的代码。注意,我不需要指定我所写入的对象类型。我直接调用 Foo.Write(m_myAL.Count) 将元素的数目写到文件中。
...BinaryWriter Foo=new BinaryWriter(myStream); ...Foo.Write(m_myAL.Count); foreach (Point p in m_myAL) { Foo.Write(p.X); Foo.Write(p.Y); } Foo.Close( ); myStream.Close();
一切都运行正常。现在,从文件读回信息时,我需要调用 Foo.Read<%Type%> 来从文件读回相应的大小。(假定您知道文件中包含什么内容,因为您很可能首先创建了该文件。)我可以使用代码 Console.WriteLine ("X is a {0}",p.x.GetType);
来了解对象类型,在此示例中为 Int32。因此,我的反序列化代码将调用 Foo.ReadInt32( ); 来读取项数以及各个 x 和 y 元素。
private void FileOpen_Click(object sender, System.EventArgs e) { openFileDialog1.Filter = "Scribble files (*.scr)|*.scr"; openFileDialog1.FilterIndex = 1; if (openFileDialog1.ShowDialog( ) == DialogResult.OK) { Stream myStream = File.OpenRead(openFileDialog1.FileName); if (myStream != null) { BinaryReader Foo=new BinaryReader(myStream); int iCount=Foo.ReadInt32( ); Point p=new Point(0,0); for (int x=0; x < iCount;x++) { p.X=Foo.ReadInt32( ); p.Y=Foo.ReadInt32( ); m_myAL.Add(p); } Foo.Close( ); myStream.Close(); this.Refresh(); } } }
那么,编写和测试要花费多长时间呢?整个应用程序在 20 分钟内编写完毕并运行良好,再用 5 分钟左右将 BinaryFormatter 代码更改为 BinaryReader,编写整个应用程序从头到尾共需要不到 30 分钟。桌面应用程序的大小为 28 KB,在设备上为 7 KB。(不要忘记应用程序还需要 Framework 的支持,而它的大小为 1.5 MB。)
也许您希望尽快知道使用哪个运行时好。下面是您做决定时要考虑的因素:是否具有 Win32/MFC/C#/Visual Basic 代码、所编写的代码类型(驱动程序、实时、应用程序)、包括应用程序和运行时在内的最终操作系统映像的总大小、开发应用程序的速度以及其他诸如安全性和可移植性等因素。好在您可以自行选择,还可以根据所执行的项目层有针对性地利用代码:驱动程序使用 Win32,最终用户应用程序使用 Win32、MFC 或 C#/Visual Basic。本文主要向应用程序开发人员说明利用桌面编程知识可以快速提高 Windows CE 的开发效率。
Get Embedded
Steve Maillet 是 Entelechy Consulting 公司的创始人兼资深顾问。自 1997 年 CE 问世以来,Steve 一直在为客户提供培训以及开发 Windows CE 解决方案。Steve 是 Microsoft Windows CE 开发新闻组的热心参与者。除了在计算机前狂热地工作以外,他还喜欢到最近的极限活动中心参加跳伞活动。