Exposing Web Services from Windows CE .NET

 

Mike Hall
Microsoft Corporation

Steve Maillet
Entelechy Consulting

December 5, 2002

Summary: Shows how to build a Microsoft Windows CE .NET operating system image that includes the core operating system components needed to support XML Web services. Also discusses how to create an ATL/COM object that exposes useful operating-system monitoring functions, as well as how to build the WSDL/WSML files for the ATL/COM object. (11 printed pages)

Download Xmlws.exe.

Wow! What an awesome Windows Embedded Developers Conference in Las Vegas (October 21–24). If you were there and attended the hands-on labs, then it was great to see you. If you didn't make it to the event, then I hope to see you at the up-coming developers conferences in Asia and Europe.

Based on your feedback from last year's developers conference, we increased the number and breadth of hands-on labs at this year's event. One of the labs covered building XML Web services for Microsoft® Windows® CE .NET. Windows CE supports XML Web services through native code, specifically ATL/COM objects hosted by the HTTPD Web server. The Web server can, of course, be included in headless or display-based devices.

In the hands-on lab, we built an operating system image that included the core operating system components needed to support XML Web services. We then built and registered an ATL/COM object using Microsoft® eMbedded Visual® C++ 4.0. The Web service simply returned an integer and a string. The lab shows the process of building an operating system image to support XML Web services, and how to use eMbedded Visual C++ 4.0 to create the ATL/COM object, which exposed the GetString( ) and GetInt( ) functions. Some of the lab attendees asked how the sample built in the lab could be extended to show some real world functionality. So this month's article "embraces and extends" the lab content from the developers conference. We will take a look at a number of subjects in this month's article, specifically:

  • Streamlining the process of including the core XML Web service components into your base operating system platform.
  • Creating an ATL/COM object that exposes some interesting operating-system monitoring functions, including current memory load, current running processes, and the ability to start additional processes on the remote device. (Pretty neat, huh?)
  • We will also take a look at the process of building the WSDL/WSML files for the ATL/COM object. Right now, the process involves using a command-line tool called wsdlstb_ce.exe. This tool takes a number of parameters, including ProgID, GUID, and so forth. I've put together a C# Microsoft® .NET Framework tool that simplifies the WSDL/WSML build process. (No more command line!)

For background reading on building XML Web services for Windows CE .NET, take a look at Building XML Web Services in Native Code for Windows CE .NET.

There are three areas for us to focus on in this month's article. The first is streamlining the process of including the core XML Web services components into your platform. We need to add 5 components, namely, ATL, COM, SOAP Server, XML HTTP, and the HTTPD Web server. In the case of display-based devices, the components can be found in the following locations in the catalog (note that the components are also available for headless devices):

  • Core OS | Display-Based Devices | Applications & Services Development | Active Template Library.
  • Core OS | Display-Based Devices | Applications & Services Development | Component Services (COM and DCOM) | Component Object Model (choose 1) | COM.
  • Core OS | Display-Based Devices | Applications & Services Development | Simple Object Access Protocol (SOAP) Toolkit | Server.
  • Core OS | Display-Based Devices | Applications & Services Development | XML | MSXML 3.0 (Choose 1) | XML Core Services and Document Object Model (DOM) | XML HTTP.
  • Core OS | Display-Based Devices | Communication Services and Networking | Servers | Web Server (HTTPD).

I'm sure that once you've read this article, you will be building a number of platforms that expose XML Web services. So the question that you're just itching to ask is, "Why do I need to add each of these features to my platform? Wouldn't it be super cool to have one single component in the Platform Builder catalog that exposed the core XML Web services features?" Yes, you are absolutely right. This would be a great feature. And guess what: You can create the "Macro Component" that wraps this functionality (or any other set of components) yourself, using the CEC Editor, which is built into Platform Builder. This is, of course, wildly innovative! So let's take a look at what we need to make this happen.

Lets take a look at a couple of components from the catalog, specifically ATL and the HTTPD Web server. (The methodology used for these components will be exactly the same for other components from the catalog.) We can right-click on any of the components in the catalog and click Properties on the menu that is displayed. This will display the properties of the specific component.

The properties dialog exposes three tabs: General, Variables, and Support. The General tab displays the vendor name, approximate size of the component, and the components version number. The Variables tab shows the environment variables used to include this component into a platform. There are two ways to include a component: Either by adding the component from the catalog, or by setting the environment variables through the Platform | Settings | Environment dialog. In our case, the ATL component uses the SYSGEN_ATL environment variable. By the way, did you know that you can search the catalog for components? It's simple. Right-click on the catalog, click Find, and then enter ATL. Viola! You've found the ATL component.

Taking a look at the five components we need to support XML Web services, the environment variables are as follows:

Component Environment Variable
ATL SYSGEN_ATL
HTTPD SYSGEN_HTTPD
XML HTTP SYSGEN_MSXML_HTTP
COM SYSGEN_OLE
SOAP Server SYSGEN_SOAPTK_SERVER

Now that we have the environment information, we simply need to build a CEC file that will be the host for our macro component.

First we need to insert a Feature Group. This will provide the name of the component as it appears in the catalog, and the location of the component in the catalog. For this example, I used the following:

Name: XML Web Services Core
Description: Core XML Web Services Support
Vendor: Microsoft Corporation

We then need to insert a feature (on the Insert menu, click Feature). I used the following information on the Feature dialog box:

Name: XML Web Services Support
Version: 1.0.0.0
Vendor: Microsoft

You will notice that the XML Web Services Support dialog box has two tabs: General and Advanced. We use the Advanced tab to set the environment variables for the macro component. Click the Advanced tab, and then set the following environment variables (use the Variables Name, and Value fields):

SYSGEN_ATL = 1
SYSGEN_HTTPD = 1
SYSGEN_MSXML_HTTP = 1
SYSGEN_OLE = 1
SYSGEN_SOAPTK_SERVER = 1

Next, we insert a build method. This gives us the ability to select which CPUs and platforms will be supported (IABASE, HLBASE). Select the following:

Step: PreCESYSGEN
CPUs: Select ALL
Supported Core OS: Select ALL

We're done with our Macro component. Save this to a suitably named CEC file and then use Platform Builder to import the component. This is achieved by, on the File menu, clicking Manage Catalog Features; then import the CEC file. If you now need to include XML Web services support to your platform, you can simply add the "XML Web Services Core" component to your platform workspace and build your platform. This process can be easily extended to create macro components that "wrap up" any functionality from the Platform Builder Catalog. We then build our platform (release build is fine), and create an SDK so that eMbedded Visual C++ 4.0 understands the API is exposed from our platform. Information on building the SDK can be found in the XML Web services document referenced above, or in the Get Embedded article, Creating a Software Development Kit for Windows CE .NET.

Okay, so that's step one. We now have an easy mechanism for adding XML Web services support to our platform. The next step is building an ATL/COM object to expose operating system functionality from the platform. The example I'm using below exposes three functions, GetMemoryLoad, GetRunningProcesses, and StartProcess. XML Web services can of course be used for a number of purposes. In this case, we will monitor a running device and start new processes. Note that we could also expose a function to stop processes on the device.

The ATL/COM object we're building is hosted by the HTTPD Web server. This is running in user mode. Therefore, we have access to all Win32 API's exposed from the Windows CE .NET platform. The only mechanism we have for exposing XML Web services from Windows CE .NET is to use native code developed using eMbedded Visual C++ 4.0. Let's leap right in and build an ATL/COM object that will become our Web services host.

Use eMbedded Visual C++ to create an ATL/COM object using the WCE ATL COM AppWizard project type. Ensure that you are building for the appropriate processor types. (I'm using an SH4 reference board to test out my code, but you could easily use the emulator or any other supported reference board.) I'm using **RemoteMonitor****for my project name. We don't need to support MFC, so we can use the default options.

We now need to insert an ATL/COM object (on the Insert menu, click New ATL Object). There are a number of object types we can add to the project, in our case a Simple Object is fine (and is also the default option). Click Next to display the ATL Object Wizard Properties dialog box, and enter a Short Name of CERemoteMonitor. The rest of the dialog box will be filled out automatically. We also need to change the threading model from Single to Free threaded. This can be found on the Attributes tab of the ATL Object Wizard Properties dialog box.

At this point, we could build our ATL/COM object. It may be helpful to expose the appropriate functions first, however. This is simple. Right-click on ICERemoteMonitor in the Class View. Click Add Method from the popup menu. We will add three functions, in each case we will need the method name and parameters. These are as follows:

Method Name: GetMemoryLoad
Properties: [out, retval] int *piMemoryLoad

Method Name: GetRunningProcesses
Properties: [out, retval] BSTR *pStr

Method Name: StartProcess
Properties: [in] BSTR pProcessName

GetMemoryLoad uses the GlobalMemoryStatus API to get the current memory load. GetRunningProcesses uses the Toolhelp APIs to walk the list of running processes. (Note that the list is returned in the order the processes were started, not alphabetically.) StartProcess uses the CreateProcess API to start a running process. Note that the call to CreateProcess takes a TCHAR * as its first parameter. The function receives a BSTR as its only parameter. We need some way to convert from BSTR to TCHAR *. Thankfully, a function called OLE2T is exposed through tchar.h; simply #include <tchar.h> and convert away. We also need to #include <tlhelp32.h>, and link with Toolhelp.lib. Okay, here's the code:

STDMETHODIMP CCERemoteMonitor::GetMemoryLoad(int *piMemoryLoad)
{
MEMORYSTATUS memStatus;

       memset(&memStatus,0x00,sizeof(memStatus));
       memStatus.dwLength=sizeof(memStatus);

       GlobalMemoryStatus(&memStatus);

       *piMemoryLoad=memStatus.dwMemoryLoad;

       return S_OK;
}

STDMETHODIMP CCERemoteMonitor::GetRunningProcesses(BSTR *pStr)
{
TCHAR tcProcessList[4000];
PROCESSENTRY32 pEntry;
HANDLE hSnapshot;
CComBSTR m_ReturnString;
BOOL bLoop=FALSE;

memset(&pEntry,0x00,sizeof(pEntry));

hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
pEntry.dwSize=sizeof(pEntry);
bLoop=Process32First(hSnapshot,&pEntry);

memset(tcProcessList,0x00,sizeof(TCHAR)*4000);

while(bLoop) {
       wcscat(tcProcessList,pEntry.szExeFile);
       bLoop=Process32Next(hSnapshot,&pEntry);
       if (bLoop) {
              wcscat(tcProcessList,L", ");
       }
}

CloseToolhelp32Snapshot(hSnapshot);
OutputDebugString(L"Processes\n");
OutputDebugString(tcProcessList);
OutputDebugString(L"\n");

m_ReturnString=tcProcessList;
*pStr=m_ReturnString.Copy( );

return S_OK;
}

STDMETHODIMP CCERemoteMonitor::StartProcess(BSTR pProcessName)
{
       TCHAR *tcProcessName;
       PROCESS_INFORMATION pi;

       USES_CONVERSION;
       tcProcessName = OLE2T(pProcessName);
       OutputDebugString(L"Starting Process - ");
       OutputDebugString(tcProcessName);
       OutputDebugString(L"\n");
       CreateProcess((TCHAR*)tcProcessName,
              L"",NULL,NULL,FALSE,
              0,NULL,NULL,NULL,&pi);
       return S_OK;
}

That's our code written for the ATL/COM object. We now need to build and test. Building the code is easy. F7 inside eMbedded Visual C++ will build the code; hopefully we don't get any build errors. We now need to build the WSDL and WSML files. This is achieved through a command-line tool called wsdlstb_ce.exe, which is located in C:\WINCE410\PUBLIC\COMMON\OAK\BIN\I386. wsdlstb_ce takes a number of command-line parameters, most of which can be extracted from the eMbedded Visual C++ RGS file. Here's how the command-line parameters look:

wsdlstb_ce -I <Exposed Class> -P <Class GUID> <ProgID> <Dll Name> <WebServer address and location of WSDL on the server> <name of WSDL file>

The parameters for generating the appropriate WSDL and WSML for "RemoteMonitor" would be as follows. (Note that the GUID will be different for your build of "RemoteMonitor".)

  • -I CERemoteMonitor
  • -P {8403AD0A-3719-4E4B-896E-0AC0AF844BDC}
  • RemoteMonitor.CERemoteMonitor.1
  • RemoteMonitor.dll
  • http://IPAddress_or_Device_Name/RemoteMonitor.wsdl
  • RemoteMonitor.wsdl

Using the command line is an interesting experience, especially if you mistype something. We need to make sure that the path to the Windows CE tools folder is correctly configured, and that we're in the build folder that contains the ATL/COM object DLL. The easiest way to achieve this is to get to a command prompt from inside Platform Builder (on the Build menu, click Open Build Release Folder), change the directory to the eMbedded Visual C++ ATL/COM object project build folder, and then type our command line. The fun doesn't end there: Once we've built and tested our Web services DLL, we may then want to include the finished DLL in our Platform Builder image.

This is where life gets very interesting. Somehow, we need to get the registry information for our COM object into our platforms registry. We could have a process run at boot time and register our DLL, but this would consume time and take up space in our NK.BIN file. It would be so much easier if we had a mechanism for extracting or building the REG information—and while we're on the subject, for also building the appropriate DAT and BIB files.

Introducing "CEWsdlGen", a C# .NET Framework application that simplifies the building of WSDL/WSML files. Simply point this application at your project's .RGS file, and whammo!—the WSDL/WSML, REG, BIB, and DAT files are generated for you in a thrice. (Actually slightly less than a thrice, but who's counting?) [Crowd goes wild!!!] The CEWSDLGen tool also generates a CEC file to include your XML Web services component into the Platform Builder Catalog. How very cool is that!

Now building and including XML Web services into your Windows CE .NET device is extremely simple. We simply need to include our XML Web service component into the catalog. We could then add this component to our workspace and simply build the image.

I've included a copy of the CEWsdlGen application into the download that accompanies this article. Here's how the application looks when it's running:

Figure 1. CEWsdlGen.exe

The application has been written in C# and requires the .NET Framework to run. The .NET Framework is a Windows Update component, which you can download/install from http://windowsupdate.com or https://www.microsoft.com/downloads/search.aspx.

CEWSDLGen enumerates the ATL/COM project folders to determine which builds of the DLL are available. You will notice the Build Folders Combo Box. This lists the available builds. The WSDL/WSML, REG, DAT, BIB, and CEC files are dropped into your selected build folder.

We have two options for testing our XML Web service. The first is to use eMbedded Visual C++ 4.0 to download/register the ATL/COM object. We could then use the Remote File Viewer to upload the WSDL/WSML files to the \Windows\www\wwwpub folder on our running device, and then test the exposed services from a Microsoft® Visual Studio® .NET desktop application. The second option is to include the XML Web service in our platform by adding the project's CEC file into Platform Builder, and then add the component to your platform.

The final part of the puzzle is building a desktop application to consume these services. I've used Visual Studio .NET to build a sample application that consumes the XML Web services exposed from the Windows CE .NET device. Consuming XML Web services, whether exposed from a PC or from Windows CE .NET, is exactly the same experience. We point Visual Studio .NET at the WSDL file for our project, which will create a Web Reference. We then simply call functions "exposed" from this Web reference. I've included a sample C# application in the download that accompanies this article. The sample works against the RemoteMonitor Web service. Note that you will need to delete the existing Web reference from the project and add a new Web reference based on your platform's WSDL file. Here's how the application looks when running:

Figure 2. The desktop application consuming XML Web Services exposed from our Windows CE .NET platform.

Here's how the code looks for extracting the current list of running processes. The call to GetRunningProcesses returns a comma-delimited string. We can use Split to extract the items from the returned list. The form has a ListBox (listBox1), to which we add the list of running processes. It would be simple to select one of the processes from the listBox and call another exposed XML Web service function on the device to stop the running process. Now we're getting close to having remote control/manageability of the device.

private void button3_Click(object sender, System.EventArgs e)
{
       char [] mySplit= {','};
       string pList=Foo.GetRunningProcesses( );       
       listBox1.Items.Clear( );
       
       string [] SplitString=pList.Split(mySplit);
       foreach (string s in SplitString) 
       {
              if (s.Trim() != "")
                     listBox1.Items.Add(s.Trim( ));
       }
}

Note that including a debug XML Web services DLL, generated using the eMbedded Visual C++ 4.0 ATL/COM AppWizard in your NK.BIN, will fail to load, even if you have the ATL component added from the catalog. This is because the debug DLL will try to load ATLCE400D.DLL, which is not part of your platform image. You will need to include a release DLL in your platform. Actually, this makes sense. You build and download/test a debug DLL from eMbedded Visual C++ (and this will download ATLCE400D.DLL to RAM on your platform). Once your Web Service is built and tested, you will then include the retail DLL in your platform. (Why would you want to ship debug code in your final NK image?)

Now it's your turn. Let us know what cool/interesting XML Web services you build for Windows CE .NET.

 

Get Embedded

Mike Hall is a Product Manager in the Microsoft Embedded and Appliance Platform Group (EAPG). Mike has been working with Windows CE since 1996—in developer support, Embedded System Engineering, and the Embedded product group. When not at the office, Mike can be found with his family, working on Skunk projects, or riding a Honda ST1100.

Steve Maillet is the Founder and Senior Consultant for Entelechy Consulting. Steve has provided training and has developed Windows CE solutions for clients since 1997, when CE was first introduced. Steve is a frequent contributor to the Microsoft Windows CE development newsgroups. When he's not at his computer burning up the keys, Steve can be found jumping out of airplanes at the nearest drop zone.