Hosting webservices on Windows Embedded Compact (Windows CE) using gSOAP

In this blog post I'd like to discuss how to host webservices on a Windows Embedded Compact device. You can certainly consume webservices on a smart device using managed code, but hosting a webservice on the device is unfortunately not possible using managed code. So it's back to good old native code again!

I will use gSOAP, a third party framework, that will do most of the nitty gritty work for us. Sounds easy right? Let's get started!

The most important part is the WSDL (Web Services Description Language) file. The WSDL file is an XML document that describes the interface and thus which methods we want to expose and host on our device. I won't spend much time explaining the WSDL syntax as there are lots of online WSDL tutorials available that explain the syntax much better than I ever could.

For now I just created a very simple WSDL file that describes one very simple webservice method: string HelloWorld(string name)

The wsdl:

<?xml version="1.0" encoding="utf-8"?>
<definitions name="HelloService"
   targetNamespace="http://www.YourServer.com/wsdl/HelloService.wsdl"
   xmlns="http://schemas.xmlsoap.org/wsdl/"
   xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:tns="http://www.YourServer.com/wsdl/HelloService.wsdl"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <message name="HelloWorldRequest">
    <part name="name" type="xsd:string"/>
  </message>
  <message name="HelloWorldResponse">
    <part name="answer" type="xsd:string"/>
  </message>

  <portType name="HelloWorld_PortType">
    <operation name="HelloWorldOperation">
      <input message="tns:HelloWorldRequest"/>
      <output message="tns:HelloWorldResponse"/>
    </operation>
  </portType>

  <binding name="HelloWorld_Binding" type="tns:HelloWorld_PortType">
    <soap:binding style="rpc"
       transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="HelloWorldOperation">
      <soap:operation soapAction="HelloWorldAction"/>
      <input>
        <soap:body
           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
           namespace="urn:examples:helloservice"
           use="encoded"/>
      </input>
      <output>
        <soap:body
           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
           namespace="urn:examples:helloservice"
           use="encoded"/>
      </output>
    </operation>
  </binding>

  <service name="HelloWorld_Service">
    <documentation>WSDL File for HelloService</documentation>
    <port binding="tns:HelloWorld_Binding" name="HelloWorld_Port">
      <soap:address
         location="http://Topaz:8080"/>
    </port>
  </service>
</definitions>

As you can see there are 6 main tags in the WSDL within the mandatory <definition></definition> tags:

types
Provides data type(s) used for the method parameters and return values.
message
Represents an abstract definition of the data being transmitted. A message consists of logical parts, each of which is associated with a definition within some type system.
portType
A set of abstract operations. Each operation refers to an input message and output messages.
binding
Specifies concrete protocol and data format specifications for the operations and messages defined by a particular portType.
port
Specifies an address for a binding, thus defining a single communication endpoint.
service
Used to aggregate a set of related ports and to set the network location of the actual service.

Basically Type, Message and portType describe the webservice methods used and binding, port and service describe how to transfer the data over the socket.

The location contains the IP address or hostname of the target that runs the server hosting the webservice. In this example I use a Topaz device (you would normally set this to the IP address or hostname assigned to your device).

After this very brief WSDL description let's set up our Visual Studio 2008 project. This will be the biggest task.

Before we can create the actual VS2008 project we need to perform a couple of steps (described in more detail below):

  1. Import the WSDL in an emtpy project
  2. Let gSOAP generate a header file via wsdl2header.exe
  3. Let gSOAP generate the corresponding implementation *.cpp file
  4. Implement our methods

Because gSOAP generates a lot files I prefer to separate the generated files from the actual implementation files so that you only have to focus on your implementation and not what is being generated by gSOAP. So let’s begin and create an empty project in Visual Studio 2008 and add the WSDL file.

Step 1: Import the WSDL in an empty project

Open Visual Studio and create a new C++ smart device project (File menu | New | Project) and enter HelloWorldWebService as the name of the solution.

Select Ok, and click next on the "Welcome to the Win32 Smart Device Project Wizard" dialog. In the "platforms" dialog select the SDK for your target device. As you can see I chose the Topaz device (http://guruce.com/topaz). Select next:

In the "project setttings" dialog select "console application" and "empty project" and click finish.

When the solution is created, go to the solution explorer (normally this would be visible by default but if not: go to View | Solution Explorer) and add a new filter "gsoap" in the "header" section (right click "Header Files", choose Add->New Filter) and do the same in the "source" section. We'll get gSOAP to generate its files there. Now create a new file with extension .wsdl in your solution's folder. Copy the contents of the sample WSDL above and add the WSDL file to your solution (right click on the solution and select “add existing item”). I've named the file HelloWsdl.wsdl. It should look something like this:

Step 2: Let gSOAP generate the header file from our wsdl.

First download gSOAP in order to get the tools needed. You can download gSOAP from this location:
http://sourceforge.net/projects/gsoap2/files/
Make sure you save the extracted files in an appropriate folder for you to remember because we need references to this folder within our visual studio project later on.

Now go back to your solution and right click on the wsdl file and select properties. In the “Custom build step“ add the following command (note that I’ve put the gSOAP tools in the $(SolutionDir); make sure you get the path right and that the solution directory doesn't contain any spaces) in the “Command Line” section:
$(SolutionDir)\gsoap-2.8\gsoap\bin\win32\wsdl2h.exe -s $(InputPath) -o $(ProjectDir)$(InputName).h
This will run the wsdl2h.exe to generate our header file. The parameters specify the input and output file. In the “Outputs” field on the property page enter:
$(InputName).h

.

You need to specify something in the “outputs” field in order to make the wsld ‘compilable’. Note that the path here contains a version number (gsoap-2.8). This can change of course so keep that in mind. Also notice that I did this "setting" for "All Configurations" and not just for the current one.

Click “Apply” and “Ok”, and then right click the wsdl file again and choose “Compile”.
Now our header (HelloWsdl.h) file is generated in the project directory and we need to add this file to the project. In the solution explorer right click on the “gsoap” folder in the header section and choose “add existing item”. Navigate to HelloWsdl.h and add it.

Let’s do the same for generating the cpp file:

Step 2: Let gSOAP generate the cpp source file.

In the solution explorer right click on the HelloWsdl.h, which we just added in the previous step, and select “properties”. In the “Custom build step“ add the following command:
$(SolutionDir)\gsoap-2.8\gsoap\bin\win32\soapcpp2.exe -S $(InputPath)

In the “Outputs” field enter the following:
$(InputName).cpp

.

Right click on the HelloWsdl.h file and choose "compile". This will generate a bunch of files as well, but we are not yet ready to build our solution... If you would try to build the solution at this time, like I did when I first started with gSOAP, you will run into a lot of compile errors so bear with me for a few more steps.

Right click the gSOAP folder in the header section in your solution and add these header files:
- soapH.h
- soapStub.h

Right click the gSOAP folder in the source section in your solution and add these cpp files:
- soapC.cpp
- soapServer.cpp

The next step is to add stdsoap2.h and stdsoap2.cpp to your solution. You can find these 2 files in “gsoap-2.8\gsoap”. Add them together by right clicking the project in the solution explorer and select “Add existing item”. They will automatically appear under the correct “header” and “source” sections.

We’ve added the stdsoap2.h to our solution but we also need to add the directory where stdsoap2.h resides to the project's "include directory list". Add “$(SolutionDir)\gsoap-2.8\gsoap” to the include directorie list by right clicking the project in the solution explorer and click "Properties". In the “Configuration Properties" | C/C++ | General” section you will find “Additional Include Directories”.

Now that we’re done generating files we can actually start to code!

Step 4: Implement our methods

First we need to create the cpp source code containing the main definition and our methods. I will use 2 separate files for this. The first file will contain the server code and will listen to incoming requests/messages. The second file will actually implement our webservice's methods.

Right click on the “Source Files” in the Solution Explorer and select “New Item”. Choose C/C++ file and name the file HelloWsdl.cpp. Do exactly the same for a file called HelloWsdlMethods.cpp.

Your complete solution should now look like this:

.

Let’s start with an easy one; open the HelloWsdlMethods.cpp and copy and paste the following code snippet into that file:

#include "soapH.h"

int ns1__HelloWorldOperation(struct soap*,
                             char*  name,       /// Request parameter
                             char*  &answer     /// Response parameter
)
{
    printf("Hello my method\r\n");

    char* myName = {"Erwin"};
    answer = myName;
    return SOAP_OK;
}

Now if you have built the project prior to adding this piece of incredible intelligent code, you would have seen an unresolved external in the error list: this method. The above function is generated (or better; declared) by gSOAP and the code snippet above is the implementation. You can find the declaration of this method in the generated soapStub.h file:

/************************************************************************\
* Server-Side Operations                                                    
\************************************************************************/

SOAP_FMAC5 int SOAP_FMAC6 ns1__HelloWorld(struct soap*, char *name, char *&answer);

This is where you will find your method's declarations when you have added your own in the WSDL.

We’re almost there! The last thing we need to do is add our server code. Code that will wait for a request from any client. Below is the code needed. It may look complicated at first but don’t let it scare you. This code is taken from the gSOAP website (section 7.2.3 How to Create a Stand-Alone Server in the documentation section: link listed below) with some minor changes that I will describe below:

/** Include the namespaces struct */
#include "HelloWorld_USCOREBinding.nsmap"

int _tmain(int argc, char* argv[])
{
   struct soap soap;
   int m, s; // master and slave sockets
   soap_init(&soap);
   soap_set_namespaces(&soap, namespaces);      //** Set the namespaces **/
   m = soap_bind(&soap, "", 8080, 100);         //** leave the string empty and gSOAP will figure out what our "localhost" is **/
   if (m < 0)
      soap_print_fault(&soap, stderr);
   else
   {
      fprintf(stderr, "Socket connection successful: master socket = %d\n", m);
      for (int i = 1; ; i++)
      {
         s = soap_accept(&soap);
         if (s < 0)
          {
            soap_print_fault(&soap, stderr);
            break;
         }
      fprintf(stderr, "%d: accepted connection from IP=%d.%d.%d.%d socket=%d", i,
            (soap.ip >> 24)&0xFF, (soap.ip >> 16)&0xFF, (soap.ip >> 8)&0xFF, soap.ip&0xFF, s);
      if (soap_serve(&soap) != SOAP_OK) // process RPC request
      soap_print_fault(&soap, stderr); // print error
      fprintf(stderr, "request served\n");
      soap_destroy(&soap);      // clean up class instances
      soap_end(&soap)// clean up everything and close socket
      }
   }
   soap_done(&soap); // close master socket and detach context

   return 0;
}

Copy and paste the code above into HelloWsld.cpp.

I've listed my changes with some comments (/** */). What is added is that we include a namespaces struct to explicitly set the correct namespaces. gSOAP (soapcpp2.exe) will not only generate code files but also a *.nsmap file which generates a static struct containing the correct namespace to use. The soap_set_namespaces() method will set this struct to use it. That’s it!

All of this may seem like a lot of work but when you are finished setting up the project every change you make in your interface (wsdl) will automatically drill down into your implementation. After setting up the project you most likely will only need to work on the wsdl file and HelloWsdlMethods.cpp to add your own methods.

Now that we've created the server on Windows CE it is time to create a client that will consume our service:

The C# Managed Client

Create a C# .NET (desktop) application with just one Form (I'm not going through each single step of how to create a C# .NET application as this is outside of the scope of this article and assumed known). Add just one button to the form and give it a name. After that we need add our webservice reference to the solution (our wsdl file). In the solution explorer right click on "References" and select "Add Service Reference". In the address field enter the path and the name of the wsdl file on your local machine. In my case this is: C:\Data\Development\HelloWorldWebService\HelloWorldWebService\HelloWsdl.wsdl. I named the reference HelloWsdlMethods and this name will appear in the solution explorer. Click "Go":

.

On the main form Double click on the button and paste the following code into the button handler:

private void btnTest_Click(object sender, EventArgs e)
{
    try
    {
        HelloWsdlMethods.HelloWorld_PortTypeClient server = new HelloWsdlMethods.HelloWorld_PortTypeClient();
        string name = server.HelloWorldOperation("Dummy");
        MessageBox.Show(name);
    }
    catch (Exception error)
    {
        MessageBox.Show("Request failed: " + error.Message);
    }
}

The code behind the button will access our server and call the webservices method. As you can see the naming is probably not the best but for this example it will do.
Run the server program (HelloWorldWebService.exe) on the target and run the client on the desktop. If you've done everything correct you should see the following after pressing the "Test" button:
The C# Client running on the desktop:

Application console on the Windows Embedded Compact device:

Things to lookout for

As you have seen there is a bit of work involved to get gSOAP to integrate nicely with Visual Studio 2008. The process is error prone and gSOAP's logging doesn't help much in most cases. In my experience there are basically three areas where things are most likely to go wrong:

  • Location
  • This tag in the wsdl file specifies the network address on which the webservices are running. Make sure it is set right.

  • Namespaces
  • Make sure that the namespaces used in the wsdl file match the ones in the source code (use the nsmap file) and don't forget to call soap_set_namespaces().

  • Hostname
  • Make sure that you leave the hostname empty: soap_bind(&soap, "", 8080, 100). Specifying the hostname does not work on CE. Also the tools wsdl2h.exe and soapcpp2.exe have a lot of options that I did not discuss in this article. However, getting to know the different gSOAP options is definitely worth the time. The gSOAP website contains a wealth of information and the documentation is comprehensive.

Of course this article only shows a very simple example of how to use Webservices on Windows CE with gSOAP but it should be enough to get you going on much more complex webservices hosted on smart devices.

Please let us know if you'd like to see a more complex example (for instance transferring a file through webservices).

Good luck!

AttachmentSize
File gSoap_WebService_Sample.7z32.66 KB

Comments

Hi Vikram,

Yes I have. There where some minor modifications needed for WEC2013 and WEC7. I am about to release a new post which explains what to do. If you can wait a little longer, it will be this week,
thanks

Hi Vikram,

the post is here: https://guruce.com/blogpost/hosting-webservices-on-windows-embedded-comp... let me know if you have any issues...

Hello, 3 things
1. How is this deployed in WCE7? This may be a beginner question, but what files after building do I copy to WCE7?
2. After following all the instructions, I go to build the project and I get a "error C3861: 'time': identifier not found" in the stdsoap2.cpp file. Have you seen this?
3. Do you have an example with sending a file?

Thanks

Hi Mark,

1) Both the client application and the server (HelloWorldWebService.exe) are executable's which you can run. So you need to deploy HelloWorldWebService.exe via visual studio. You can also upload the executable via FTP or copy it to a mass storage device an run the application manually. It depends a bit on your target and what debugging tools are available. What is your target device?
2) I remember redoing the exercise for WEC7 and it all worked, but that has been a while ago. I need to test whether it still works. Let me get back to you on this.
3) I never did the file transfer proof of concept due to the lack of requests, so unfortunately no.

1. It seems my build error is what's causing me not to generate the *.exe files. My target device is a Toradex T20
2. Do you still have the source files and the gSOAP version that you used to do the exercise for WEC7

Your build error is probably due to time.h not being supported on CE. Read this blog https://blogs.msdn.microsoft.com/cenet/2006/04/29/time-h-on-windows-ce/
And this https://blogs.msdn.microsoft.com/cenet/2007/03/22/time-h-for-wince-at-op...
The OpenNETCF time replacement for CE can be found here: https://crosswire.org/svn/swordreader/trunk/src/SlideBible/platform/winc...

Mark,

I just redid the scenario and indeed there is a compile error regarding time. However you can make it work by adding your own implementation of time() and make it compileable. as far as I can tell the code is not being called in my sample code. The only thing which you need to do is modify stdsoap2.cpp that time is external. Something like:

#ifdef WINCE
extern time_t time (time_t* timer);
#endif WINCE

and in the web service something like:

extern "C" time_t time (time_t* timer)
{
// return the time_t
return time_t;
}

The content of the time function, ** in this case**, can be 'anything' AFAICS, as it seems that the function need to return a random UUID where the time is a partial member. So you can either port the time() function to make it work on CE or your can simply return rand().

I tested this approach and the sample code, with the little modifications noted, is still working,

Let me know if you need more info,

Thank you, I utilized the time_ce source implementation by OpenNETCF. I was able to make it work by also adding

#ifdef __cplusplus
extern "C"
{
#endif

and

#ifdef __cplusplus
}
#endif

to time_ce.h in order for it to function correctly.

Additionally, I am getting a "Request failed: Could not find default endpoint element that references contract 'HelloWsdlMethods.HelloWorld_PortType' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this contract could be found in the client element."

Any hints about this? I was looking at my HelloWSDL.wsdl file, and what exactly is the targetNamespace? Is this something arbitrary I just name? For my soap:address location, I set it to the ip address of my device with port 8080 "http://192.168.0.100:8080" is this correct?

Thanks,
Mark

The IP address is correct, that is it should contain the IP address of the Windows Embedded target device on which the webservice is running. The client application is a .NET desktop application (and not a Windows Embedded .NET CF application).

So is it all running but the error you mention is when the client tries to access the webservice? Or do you get build errors?

Yes, the error occurs when I use my Client to access the webserver.
I have my Client running on my desktop and my webservice (with the webserver code) on my Windows embedded target.

When I try to access the target just through a web-browser, I see communication occurring on the target side (HelloWorldWebService.exe) and on my web browser (of course I get an error since HTTP GET method is not implemented).

So it seems it's my client side not being able to connect to my target webserver.

Ok not sure happens there and it sounds like connection issue somehow. So a telnet session to 8080 works as well? The only way to figure out what is going on is to take a look are your code and compare it to my environment.

Did you recompiled the wsdl and header file after you changed the IP address in the wsdl file?

Yes, I recompiled the WSDL and the header file as well.

My source code, you can download at <link removed>
Thank you

Hi Mark,

Your code looks good, and it matches exactly with mine (except for the IP address of course), so you followed the correct steps. This leaves only the network connection and the target which differ. So, but you probably already did that, double check all IP settings (netmask and gateway etc). Also check on your dev machine if whether the firewall is intercepting. If all is correct than the only thing I can think of, is that there is a firewall is running on the target or the target blocks something. You could add code to see whether the server side code accepts the socket connection...

Let me know if I need to perform some test for you. If you find and fix the error please ping back so it can help others...
Thanks

Just one thing that popped up to me is; did you update the service in the Client application after you modified the IP address in the wsdl file?

I have a few additional questions
1. In the WSDL file, what is the importance of "targetNamespace". How is it used? Is it supposed to have my target device IP? How about the xmlns:tns?
2. In my target device, the only file I deploy is the executable service file? Do i also place the .wsdl file in the target device?

First to answer your questions:
- I think the namespace get's important the moment you will split up the wsdl into separate files, but here is where my wsdl knowledge stops. This seems like a nice article about the importance: https://msdn.microsoft.com/en-us/library/aa480511.aspx. For this simple tutorial all definitions are in the wsdl so it does not matter.
- Yes the only file to deploy to the target should be HelloWorldWebService.exe. The wsdl, for this sample doesn't have to be on the target. The actual intention for the wsdl file (might be an interesting new blog post as well) is that a wsdl is on the target. That way 'any' client application can import the wsdl via the link pointing to the wsdl on the target and can write there application without the need of the physical wsdl file.

I am almost done finishing my blog post about running the sample for WEC7 and WEC2013. While testing everything seemed to work. When I only started the client application I got the error "Request failed: Could not find default endpoint element that references contract.......This might be because no configuration file was found!

It seems that next to HelloWebServiceClient.exe on the desktop, you also need HelloWebServiceClient.exe.config next to the executable....But is regarding the client application running locally on the desktop.

The blogpost will have my solutions as an attachment,

Hi Erwin,
i have a question... how can implement a multi WSDL in the same solution. Your example works perfectly but i need to insert 2 or more WDSL. Could you give me a suggest? Thanks

Pages