Toaster Device Sample

This sample provides a step by step guidance on how to integrate code between the Microsoft Windows Vista DDK and Microsoft Robotics Developer Studio using C++/CLI programming language.

This tutorial is provided in the language. You can find the project files for this tutorial at the following location under the Microsoft Robotics Developer Studio installation folder:

 Samples\Technologies\Interop\ToasterList

Here are the steps we have to do:

  • Create a new DSS C++ service
  • Add toaster application code
  • Configure build
  • Fix toast.cpp
  • Integrating code
  • Try it out

Prerequisites

Software

This tutorial requires the Windows Vista DDK also called WDK, which is available online

The actual work with the WDK is outside the scope of this tutorial, instead we are going to take an existing part of the Toaster sample from WDK and turn it into a DSS service.

The Toaster sample that we are going to use is the "toaster" application, which lists the available toaster devices and their information, which is located in the “WinDDK\6000\src\general\toaster\exe\toast” directory of your Vista WDK installation. Refer to that location on documentation about the toaster application itself.

This sample is designed for use with Microsoft Visual C++/CLI (managed C++). You can use:

  • Microsoft Visual C++ Express Edition
  • Microsoft Visual Studio Standard, Professional, or Team Edition.

You will also need Microsoft Internet Explorer or another conventional web browser.

Getting Started

To create the service you have to use Microsoft Visual Studio 2005, and the DSS Command Prompt. Due to this we will have to do some changes to reuse the source out of the WDK. Let’s take it step by step:

Step 1: Create a new DSS C++ service

Open a DSS Command Prompt. Create a new C++ DSS service with the following command:

 >bin\dssnewservice.exe /service:myToasterSvc /language:cpp

This will create a folder with the service code in it. Open the solution from there with you Visual Studio 2005 C++ editor.

Step 2: Add toaster application code

From WinDDK\6000\src\general\toaster\exe\toast location in your WDK installation copy the toast.c file to your myToasterSvc service directory, and rename it to have a cpp extension. In Visual Studio right click on Source Files and select Add->Existing Item.. and point to the toast.cpp file that you just created. Since the whole project is managed, we need to explicitly say that this file will contain unmanaged code, by putting #pragma unmanaged in the beginning of toast.cpp.

Step 3: Configuring build

To build the project we need to add the build settings from the DDK sample in our project. They are configured in the sources file in WinDDK\6000\src\general\toaster\exe\toast directory and in the build environment command shell settings. Right click on you project in Visual Studio and select Properties. Here is the list of items you need to change:

  • Under C\C++ -> General add to Additional Include Directories the following folders: WinDDK\6000\src\general\toaster\inc and WinDDK\6000\inc\api from your WDK installation.
  • Under Linker -> General add to Additional Library Directories the following folder from WDK: WinDDK\6000\lib\wnet\i386
  • Under Linker -> Input add setupapi.lib to Additional Dependencies
  • Under Configuration Properties -> General change Character Set property from Use Unicode Character Set to Use Multi-Byte Character Set.

After you add these setting you should be able to compile your project, and get a couple of code errors due to syntax change from c to c++ for the toast.cpp file.

Step 4: Fixing toast.cpp

From the previous step after compilation you should see the following 2 errors:

  • error C2440: '=' : cannot convert from 'void *' to 'PSP_DEVICE_INTERFACE_DETAIL_DATA'
    C:\Microsoft RDS\myToasterSvc\toast.cpp 155
  • error C2664: 'SetupDiGetDeviceInstanceIdW' : cannot convert parameter 3 from 'BYTE *' to 'PSTR'
    C:\Microsoft RDS\myToasterSvc\toast.cpp 306

The first problem can be fixed by just adding a cast:

 deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) malloc (predictedLength);

The second problem just needs the right cast:

 fSuccess = SetupDiGetDeviceInstanceId(hdi, &deid,
                                      (PSTR)currentToaster->szCompInstanceId,
                                      MAX_PATH, NULL);

After these errors are fixed you should be able to compile your service without errors. The toaster code that we added does not do anything useful, because we need to add the integration between it and the service code.

Step 5: Integrating code

The service that we are going to create will have as it’s state a list of toaster devices that are present in the system. To get the toaster device we will reuse the PrintToasterDeviceInfo() function, but change slightly how it works. We will want to gather the toaster device information, but instead of printing it to the console we will populate our service state with it. Here are the steps needed to make that integration happen:

Drop main method

Since we are going to reuse only the PrintToasterDeviceInfo() function we will not need the main function in the toast.cpp file. Our service will be started by the DSS runtime, so we don’t need this entry point.

Define new header file

Let’s create a header file for the toast.cpp file, by right clicking on your project and selecting Add -> New Item… -> Header File (.h) and call it toast.h.

Specify header files in source files

The header file that we created will be used in toast.cpp file and in myToasterSvc.cpp.

Define new data structure

In toast.h file, move the #include <wtypes.h> from the toast.cpp file to it and let’s define a structure that we will use to share the data between our managed and native functions. We will want to get the data between managed and native functions in as few calls as possible due to the performance hit of switching between managed and native code. Since we don’t know the length of the data that we want to get from the PrintToasterDeviceInfo() function, we will use a linked list so that it’s easy to expand. Here is what the structure should look like:

 typedef struct TOASTER_INFO {
    TOASTER_INFO(){nextToasterInfo= NULL;}
    TOASTER_INFO* nextToasterInfo;
    CHAR           szCompInstanceId[MAX_PATH];
    CHAR           szCompDescription[MAX_PATH];
    CHAR           szFriendlyName[MAX_PATH];
} *P_TOASTER_INFO ;

Change function signature

PrintToasterDeviceInfo() function with its current signature does not provide any information to the callers other than if it has found anything or not. Let’s change it so that it will also provide the new structure that we created. Make sure it’s declaration is in the toast.h file, here is what it’s new signature should look like:

 BOOL PrintToasterDeviceInfo(P_TOASTER_INFO* pFirstToaster);

Add allocation logic

Now let’s change how this function works in the toast.cpp so that it allocates and populates the TOASTER_INFO structure correctly. Since we don’t know up front how many devices we will find, we will allocate memory inside the loop where we enumerate over each toaster device. Since we don’t know if we will find even one, but our function will return the pointer to the first item in the linked list, we will need a custom logic to create the first element, and then any next element. Here is what that code should look like:

 if(*ppFirstToaster == NULL)
{
    *ppFirstToaster = new TOASTER_INFO;
    currentToaster = *ppFirstToaster;
}
else
{
    currentToaster->nextToasterInfo = new TOASTER_INFO;
    currentToaster = currentToaster->nextToasterInfo;
}

Populate the structure

Add the local variable that we will point to the current Toaster that we are working with: P_TOASTER_INFO currentToaster;. Next remove the szCompInstanceId, szCompDescription and szFriendlyName local variables and change the code that used them to point to their versions in the currentToaster local variable. Finally remove the calls to the printf function we will no longer need them.

Create new state

Now let’s change the service part of our code. Since we want the toaster information represented in the state of our service we need to change our current state definition. In the myToasterSvcTypes.h file add the following new class that will represent a single toaster:

 [DataContract]
public ref class myToasterInfo
{
public :
    [DataMember]
    String^ FriendlyName;
    [DataMember]
    String^ CompDescription;
    [DataMember]
    String^ CompInstanceId;
};

Now change the myToasterSvcState class so that it will only have a list of our toaster devices in it:

 [DataContract]
public ref class myToasterSvcState
{
public :
    myToasterSvcState();

    [DataMember]
    List<myToasterInfo^>^ Toasters;
};

Don’t forget to change the constructor of this class in myToasterSvcTypes.cpp so that it properly initializes that list:

 myToasterSvcState::myToasterSvcState()
{
    Toasters = gcnew List<myToasterInfo^>();
}

This should be enough to have the service state ready to be used for our needs.

Create new state renew method

We’ll need to repopulate our state to reflect the accurate data each time someone requests it using a Get operation. Since we have 2 operations defined – HttpGet and standard DSS Get, we will create a helper function that we will call from both of them. The function signature should look like this:

 myToasterSvcState^ myToasterSvc::RenewState()

Don’t forget to add it’s declaration to the myToasterSvc.h file, and change the two operations that we talked about above to use this function:

 void myToasterSvc::GetHandler(Get^ get)
{
    get->ResponsePort->Post(RenewState());
}

void myToasterSvc::HttpGetHandler(HttpGet^ httpGet)
{
    httpGet->ResponsePort->Post(gcnew HttpResponseType(RenewState()));
}

The way our service works we do not need to support the Replace operation. Remove it from service operations in myToasterSvcTypes.h file:

 public ref class Replace : Microsoft::Dss::ServiceModel::Dssp::Replace<myToasterSvcState^, PortSet<DefaultReplaceResponseType^, Fault^>^>
{
};

public ref class myToasterSvcOperations : PortSet<DsspDefaultLookup^, DsspDefaultDrop^, Get^, HttpGet^, Replace^>
{
};

And remove the handlers for it in the myToasterSvc.h and myToasterSvc.cpp files as well:

 void ReplaceHandler(Replace^ replace);

Arbiter::Receive<Replace^>(true, _mainPort, gcnew Handler<Replace^>(this, &myToasterSvc::ReplaceHandler))

void myToasterSvc::ReplaceHandler(Replace^ replace)
{
  _state = replace->Body;
  replace->ResponsePort->Post(DefaultReplaceResponseType::Instance);
}

Now everything is ready for the final step.

Add marshaling and cleanup logic to state renew method

in our RenewState method we need to create a new clean state instance, make a call to our native helper function, properly marshal and clean up the data that it returns. Here is the full code for that function, the comments show the particular parts that were mentioned:

 myToasterSvcState^ myToasterSvc::RenewState()
{
    // create a new empty state instance
    myToasterSvcState^ _state = gcnew myToasterSvcState();
    P_TOASTER_INFO currentToaster = NULL;
    P_TOASTER_INFO previousToaster = NULL;

    // call the native helper function
    if(PrintToasterDeviceInfo(&currentToaster))
    {
        if(currentToaster == NULL)
        {
            LogInfo(LogGroups::Console,"Current Toaster empty!");
        }

        while(currentToaster != NULL)
        {
            // populate the state, correctly marshal the strings 
            myToasterInfo^ toaster = gcnew myToasterInfo();
            toaster->FriendlyName = Marshal::PtrToStringAnsi(
                    (System::IntPtr)currentToaster->szFriendlyName);
            toaster->CompDescription = Marshal::PtrToStringAnsi(
                    (System::IntPtr)currentToaster->szCompDescription);
            toaster->CompInstanceId = Marshal::PtrToStringAnsi(
                    (System::IntPtr)currentToaster->szCompInstanceId);
            _state->Toasters->Add(toaster);

            //clean up the allocated memory
            previousToaster = currentToaster;
            currentToaster = currentToaster->nextToasterInfo;
            delete previousToaster;
        }
    }
    else
    {
        LogInfo(LogGroups::Console,"No toaster devices found!");
    }
    return _state;
}

Now that this is done you should be able to successfully compile your project.

Step 6: Try it out

Your toaster service should be fully compiled and the proxy assemblies generated in the bin folder of your Robotics Developer Studio installation location. Before you run it, you will need to add several Toaster devices to your computer. Please refer to the documentation located in the WinDDK\6000\src\general\toaster directory of your Vista WDR installation. It would provide you with the steps necessary to compile and run the tools and drivers needed to add Toaster devices to your system. Once done you should be able to run a command similar to this:

 >Enum.exe -p 8

to add a toaster device to you machine. When you have some Toaster devices you can run a command similar to this one:

 DssHost.exe /p:50000 /t:50001 /m:"myToasterSvc\myToasterSvc.manifest.xml"

out of your DSS Command Prompt. Once the DSS node starts up you can access the state of the service by going to the following URL in your browser:

 https://localhost:50000/mytoastersvc 

If you used a different name for your Toaster service, you URL will be different. The state you see will be similar to this:

 <?xml version="1.0" encoding="utf-8" ?> 
<myToasterSvcState xmlns:s="https://www.w3.org/2003/05/soap-envelope" xmlns:wsa="https://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:d="https://schemas.microsoft.com/xw/2004/10/dssp.html" xmlns="https://schemas.microsoft.com/2007/08/mytoastersvc.html">
  <Toasters>
  <myToasterInfo>
    <FriendlyName>ToasterDevice08</FriendlyName> 
    <CompDescription>Microsoft Toaster With Coinstaller</CompDescription> 
    <CompInstanceId>{B85B7C50-6A01-11D2-B841-00C04FAD5171}\MSTOASTER\1&431A56F&0&04</CompInstanceId> 
  </myToasterInfo>
  </Toasters>
</myToasterSvcState>

Summary

In this tutorial you have learned how to do basic integration between Windows DDK and Robotics Developer Studio.

Here are the steps that we took:

  • Create a new DSS C++ service
  • Add toaster application code
  • Configure build
  • Fix toast.cpp
  • Integrating code
  • Try it out
See Also 

Windows Driver Kit (WDK): OverviewDownload

 

 

© 2009 Microsoft Corporation. All Rights Reserved.