Programming control point application using the UPnP Control Point API
The article describes how to use the Microsoft's UPnP Control Point API for finding and controlling UPnP devices, and includes a description of simple library to facilitate the application of Control Point API in your own programs, together with an example of MFC and WinForms applications.
Note: This is updated version of article. In original version I presented code in which I used the ATL library for simplyfying COM programming. ATL is really useful library but I thought that many programmers could not use this library because of lack one in the version of Visual Studio which they use (Express Editions). Thus I created new version of UPnPCpLib library without help of ATL (what made COM programming more difficult, of course :). Instead, I used STL library. ATL version of my library (with the same functionality) is still available for download. But this is not all. New library's version is more object oriented and easier for use, I hope :). Moreover, I created new Windows Forms "Finder" application with the help of new version (STL) of UPnPCpLib library and Visual C#. "Finder" for .net uses managed wrapper over native library - "FindManager.net". You can use this class library in applications written in any .net language like C# or Visual Basic. |
Download
- UPnPCpLib library source (STL version) - 22 kB
- UPnPCpLib library source (ATL version) - 23 kB
- Samples with STL version (VS 2003 Win32 console project) - 107 kB
- Samples with ATL version (VS 2003 Win32 console project) - 116 kB
- "Finder" MFC application source (VS 2003 MFC project) - 126 kB
- "Finder" MFC application (executable) - 96 kB
- "Finder.net" WinForms application source (VC# 2008 Express Edition WinForms project) - 87 kB
- "Finder.net" WinForms application (executable) - 205 kB
- "FindManager.net" class library source (VC++ 2008 Express Edition project) - 68 kB
- "FindManager.net" class library (dll) - 315 kB
Contents
- Introduction
- UPnP Technology
- Control Point API
- Basic objects in API
- Interfaces related to Device Finder object
- Interfaces related to Device object
- Description document
- Interfaces related to Service object
- UPnPCpLib framework
- WinForms control point application (Finder.net)
- History
- References

Introduction
The article describes how to use the Microsoft's UPnP Control Point API [1] for finding and controlling UPnP devices, available in Windows Me, CE .Net, XP and later. It also contains a description of a simple library of classes and functions aimed, at first, to make it easier to use Control Point API in your own applications, secondly, to help build a model of the structure of detected UPnP devices. At the end (for the most patient readers ;) is an example of simple MFC and WinForms applications designed to present the possibilities of Control Point API in conjunction with that library, including reading public IP address of Internet Gateway Devices and configure their "Port Forwarding" feature. It should be noted that the UPnP Control Point API can be used both, in scripts embedded in HTML pages and in applications written in C++ or Visual Basic languages. However, this article is intended for C++ developers. I assume that you mastered the basics of using COM interfaces and the STL library. When reading the article might be at hand the documentation of Control Point API [1].
Before the idea came to write this article, I studied the problems of network programming using the Winsock library. Then it interested me the problem of programmatic read of router’s public IP address on home network. In this case I had been analyzing available solutions such as through SNMP protocol (Simple Network Management Protocol) [5] or using a public server using STUN protocol (Simple Traversal of UDP through NATs) [6], or a simple analysis of packets. My exploration, however, tended towards finding a solution to a simple and independent. I excluded SNMP because I found it difficult to develop, although it allowed for direct communication with the router. In contrast, the service of dedicated server, although easy to implement, it does not meet the condition for direct communication. Then I have interested in Universal Plug and Play technology (UPnP). It turned out that UPnP offers the possibility of direct communication with network device implemented this technology so that it is possible to download various useful information about the device and controlling it by using shared services. The question arises whether the UPnP technology fulfilled my expectations and proved to be a good solution to the problem? I must say that the answer is no :), but the technology itself interested me enough that I decided to learn it more closely and so was born the idea to write this article. Why did I not approve UPnP the perfect solution? First of all, support for UPnP technology, as well as SNMP protocol, in SOHO networking equipment is not an active feature by default. To use it you must properly configure the device. However, UPnP technology gradually gaining in popularity and more and more companies decide on its implementation in their devices. Increasingly, the UPnP features can be found in the network devices of SOHO class such as routers and access points. Secondly, it is necessary to prepare the operating system. Also in this case, the elements of the operating system responsible for handling UPnP technology require configuration by the user. It appears that the easy exercise of UPnP technology faces many obstacles. In the rest of this article I will describe how to deal with these problems. After this introduction, those of you who have not yet hated UPnP ;), I invite to continue reading.
UPnP Technology
UPnP (Universal Plug and Play) is a technology which, speaking briefly and vividly, transfer well-known from the PC the Plug and Play idea to devices capable of communicating through the network. UPnP allows for direct communication of different networked devices, such as intelligent appliances, wireless devices and PCs, for exchange of data and control devices. The process of connecting to the network devices that supports UPnP technology takes place automatically, without the need for manual configuration. The device self-configures, recognizes the environment in which work and then announce its presence and willingness to work, and then detects other devices present in the network. UPnP is independent of the physical medium, the operating system and programming language. UPnP architecture is based on existing standards such as XML and IP, TCP, UDP, HTTP, SSDP protocols. A more precise and comprehensive definition of UPnP technology and a description of its architecture can be found on the website of UPnP Forum organization [3], which deals with the development of UPnP technology, develop standards for this architecture and certification of equipment. As for the usefulness of this technology, in my view, it is interesting implementation of the digital home idea.
UPnP implementation and configuration in Windows XP
UPnP technology is supported by the following systems of Windows family: Millennium Edition, CE .Net, XP and newer (read Vista). Implementation in Windows XP [8] [9] (called UPnP framework) is based on two basic components: system services and COM libraries providing programming interfaces. System services related to UPnP are “Universal Plug and Play Device Host” (upnphost) and “SSDP Discovery Service” (ssdpsrv). As you can see, in the system are two different services related to UPnP. This is because Microsoft divided the functions related to hosting UPnP devices providing the services, and functions for the discovery and control UPnP devices and, as we'll see later, a similar division was introduced in the case of APIs.
The upnphost service allows, generally, run applications performing the role of UPnP devices giving them the necessary functionality compatible with UPnP architecture. So, if you wish to run the UPnP device application, you must first check whether the upnphost service is running. In contrast, the ssdpsrv service lets you find UPnP devices on all networks available in the system. This service through the SSDP protocol (Simple Service Discovery Protocol) [7] detects UPnP devices connected to the network and notifies the client when the device is connected or disconnected. To an application that helps you discover and control UPnP devices could properly operate the ssdpsrv service must be started.
In Windows XP, there are a few programming interfaces to allow the use of UPnP technology developers in their applications. These are the “Network Address Translation Traversal API”, "UPnP Device Host API" and “UPnP Control Point API”. All these interfaces are described in the MSDN [1] [2]. Purpose of the first two of these APIs I will explain briefly, because in this article I won't be describing them, instead I will focus on Control Point API. NAT Traversal API [2] allows applications running on computers in a LAN programmatic configuration of port mapping feature (port forwarding) in equipment of type Internet Gateway Device (IGD), that support network address translation (NAT) and UPnP technology. IGD may be a hardware router in a LAN or a PC with Windows XP and Internet Connection Sharing feature enabled. Excellent article written by Mike O'Neill about NAT Traversal API, titled "Using UPnP for Programmatic Port Forwardings and NAT Traversal" [4], can be found on CodeProject.com. Two other APIs: Device Host and Control Point, as in the case of system services, are two independent interfaces designed for programming in different fields. Device Host API provides support for the creation of applications hosted on Windows and acting as UPnP devices, which are exhibiting their services to other devices or applications of type control point. Device Host API helps the developer build the basic functionality of the device including shared services, and integrate them with the rest of the necessary functions (discover, control and events), which are provided by UPnP framework and system services: upnphost and ssdpsrv.
For painless cooperation UPnP application with Windows XP, beside system services, it is also necessary to properly set up firewall application [10]. SSDP Protocol (ssdpsrv service) uses UDP port 1900 for broadcast and TCP port 2869 for event notification. Thus, in order to ssdpsrv service, and hence the application of type control point, to be able to function properly, in the firewall you should unblock ports 1900 UDP and 2869 TCP. In the version of Service Pack 1 and earlier of Windows XP instead of TCP port 2869 was used TCP port 5000.
At this point it is worth mentioning the possibility of configuring the UPnP framework through the settings in system registry (Configuration Settings) [1]. Some of them concern the SSDP protocol parameters and the rest the overall functionality of UPnP framework. There are a few interesting options among them letting change, for example, the default value of TTL parameter of packets of SSDP protocol which is responsible for the maximum number of hops. Another interesting option is acceptable range of IP addresses during the devices discovery. For detailed description of configuration settings refer to the MSDN.
Now, richer in knowledge about the configuration of UPnP in Windows XP, we can move on to discuss the Control Point API.
Control Point API
UPnP Control Point API is used to create applications capable of discovering and controlling UPnP devices. In what must be noted that the UPnP device can be dedicated hardware device (such as a router) or application running on a PC. Discovered devices can be connected to any physical network medium, to which has access a computer that is running the application. UPnP framework provides to the application all the necessary mechanisms of finding and controlling UPnP devices as well as mechanisms related to the notifying about events generated by the device and to connecting or disconnecting the devices on the network. The main element of UPnP framework that delivers these mechanisms is a system service ssdpsrv. With Control Point API it is possible to build applications that do not operate as an UPnP device but are performing exclusively tasks of so-called "control points", that is, an application discovering devices and communicating with them in order to use of services and registration of events. Of course, there is no obstacle to device application to enrich its functionality with functions of control point. Control Point interface can be used in applications written in C++ or Visual Basic for MS Windows. There is also the possibility of using this API in scripts written in VBScript scripting language, embedded in HTML files, so you can create applications for the web browser. This is perhaps the simplest and quickest way to create applications of type control point. But in this article I will focus on Control Point API, from the C++ programmer's point of view. Classes, to which the Control Point API refers, are stored in a COM library named upnp.dll. To seamlessly use this library is worth at the beginning install Platform SDK, where are needed header files and libraries. In application's source files must be included the “upnp.h” file.
Basic objects in API
Using the Control Point API we will have to deal with three basic objects closely related to UPnP device architecture:
- Device Finder,
- Device,
- Service.
Device Finder object carries out the tasks related to the discover devices in the network and provide found devices objects to the application. The Device object represents an UPnP device's model, which directly relates to the standard of UPnP device architecture developed by the UPnP Forum organization. From the perspective of a programmer the Device object is a physical UPnP device, with which an application communicates to use its services. In an autonomous Device object (root device) can be embedded member Device objects (child device). The Service object is inseparable and an essential element of a Device object and its task is to provide services to clients (devices, control points). Using the services of the device relies on invoking by control point the so-called "actions" on Service object. One Device object may contain many Service objects.
To better understand the functions of the aforementioned objects and the purpose and functions of individual COM interfaces available in Control Point API let’s consider a model (object) of UPnP device, which presents schematically graph below.

There is a picture of the overall scheme of the relationship between Finder, Device and Service objects and the very overall scheme of functioning of the application of type control point. Core functionality of this kind of application includes the implementation of the Finder object, which will provide Device objects to application, and interacting with Device objects. The illustration exposes Device object’s structure, which essential elements are: a collection of member devices – Devices, and collection of services - Services. It is necessary to note that each Device object from the Devices collection may contain their own member Device objects. This means that the structure of the Device object is a tree structure. "Black box" called Service we’ll see at the next picture, which brightens a little of its contents.

Service object, which structure shows the picture, represents the service, which Device object offers. Each service has a logically coherent set of actions that are closely associated with the "state variables". Those variables, marked on the picture by symbols "Var1" and "Var2", represent the characteristics of the service. These characteristics (that is variables) accept certain values that clearly define the status of service and also device, to which the service belongs. If the value of the state variable has changed, then this means, that the state of the device also has changed, what will cause indicating this change through notification of the event. Event notifications subscribes and receives the control point application, using a callback object, monitoring in this way, the current state of the device. Knowing the current state, the application can adequately respond to the needs. The device’s service (that is Service object) offers a set of actions possible to comply by using the service. Actions are used by control point application to retrieve information about the condition of device and to command changes in its condition or perform certain tasks that is, speaking briefly, let control device. Control point application, affecting through actions on the state variables, causing some specific reactions of device, for example, reducing the intensity of the light source. Every action may be associated with one or more state variables. This may be so, that the action is not attributed to any variable. On above picture, we have an example where action “Action1” causes a change of the variable “Var1” and action “Action3” changes variable “Var2”, while action “Action2” causes reaction of both variables “Var1” and “Var2”. In fact, Control Point API allows direct reading of the state variable’s value of using the QueryStateVariable
method, however, the UPnP Forum organization discourages the use of this function, and recommends the use of actions and event notifications.
Interfaces related to Device Finder object
Control Point API is a set of several COM interfaces, which can be divided according to type of object with which they are associated. This selection concerns the three basic objects discussed earlier. The first group is the interfaces referring to the "Device Finder" object, which as the name indicates, deals with the discovery of devices, or to be more precise, implements the tasks associated with monitoring processes of connecting and disconnecting devices in the network.
- IUPnPDeviceFinder
- IUPnPDeviceFinderCallback
IUPnPDeviceFinder
is one of the key interfaces of Control Point API and is used to create a Device Finder object. By means of it you can find the devices through synchronous or asynchronous method using the callback object. In many cases (such as GUI applications) asynchronous method will be a better choice because it does not block the user interface of the program. As a result of the discovery of devices by Device Finder object we get a Device object through the IUPnPDevice
interface. There is yet another way to creation of a Device object, which I'll cover later, on the occasion of the presentation of the IUPnPDevice
interface.
Remember that before start using the COM library and creation of any object you must initialize the library and after the completion of its work free resources. If you write application for multiple Windows platforms, should be paid attention to the way initialization of COM library. In this case, API’s documentation recommends single threaded apartment model to avoid problems that may arise in Windows Me, if we chose a multi threaded apartment model.
#define _WIN32_DCOM // for CoInitializeEx #include <objbase.h> HRESULT hr; hr = CoInitializeEx(0, COINIT_APARTMENTTHREADED); if(hr == S_OK) { // ... code using COM library } CoUninitialize();
In the case of console application or GUI application dedicated to run on Windows Me the more appropriate threading model for COM library will be single threading model rather than multi threading. To initialize COM library for use single threading model You should specify COINIT_APARTMENTTHREADED
flag in CoInitializeEx
function call. On the other hand, to use multi threading model of COM library it is neccessary to specify COINIT_MULTITHREADED
flag.
Let us return to the Device Finder object. After creating an object we can access it through the IUPnPDeviceFinder
interface. By choosing the synchronous method we have available two functions belonging to this interface. With FindByType
function you can search for a device of specific type - if you'll pass the specific name of a type (e.g. urn:schemas-upnp-org:device:BinaryLight:1), or any type if you'll pass the type common to all UPnP devices: "upnp:rootdevice". This function returns a collection of found devices in the form of IUPnPDevices
interface. Second option is the FindByUDN
function that gets as a parameter the device’s unique name UDN (Unique Device Name), which, unfortunately, you must know earlier, while returns the device object in the form of IUPnPDevice
interface. The disadvantage of the synchronous method is long time waiting for results, by which program’s execution is suspended even for a few dozen seconds. I don’t include example of synchronous method, because now come to significantly more instructive asynchronous method, which will be illustrated by example.
Asynchronous finding consists of four main steps. To better understand the whole process of asynchronous search, I will use the picture, which, I hope, is worth a thousand words ;).

The first step is to create a callback object, which will receive search results (items 1, 2, 3). In the next step we call CreateAsyncFind
function passing to it pointer to callback object (item 5). The next step is to start the actual search using StartAsyncFind
function (item 6). The last step is to return the search result from UPnP framework to callback object (item 7). Now I will explain in more detail the individual steps.
For using asynchronous method, you'll need a callback object. To create an instance of such object you must first prepare a class implementing IUPnPDeviceFinderCallback
interface (item 1). At this point, you can implement in your own class, by oneself, all the functions of the IUnknown
interface and its descendant IUPnPDeviceFinderCallback
, but… ATL library brings us easier solution. If our callback class will be inherit from CComObjectRootEx
class belonging to ATL, then we won't have to implement the functions of IUnknown
interface, if in this way prepared class we derive from CComObject
template class. Thanks to that, developer is exempt from the implementation of details common to the COM objects, relying on CComObject
class in the reference counter management and object instance creation, and can focus on the details of implementation of IUPnPDeviceFinderCallback
interface. Let's see an example of class, which I called DevFinderCallback
(version without ATL).
// IUPnPDeviceFinderCallback implementation, without ATL #include <upnp.h> class DevFinderCallback : public IUPnPDeviceFinderCallback { private: long _refcount; // object's reference counter public: DevFinderCallback() {_refcount = 0;} virtual ~DevFinderCallback() {} // IUnknown implementation virtual HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject) { HRESULT result = ppvObject == 0 ? E_POINTER : S_OK; if(result == S_OK) { if(riid == IID_IUnknown || riid == IID_IUPnPDeviceFinderCallback) { *ppvObject = static_cast<IUPnPDeviceFinderCallback*>(this); this->AddRef(); } else { *ppvObject = 0; result = E_NOINTERFACE; } } return result; } virtual unsigned long _stdcall AddRef() { return ::InterlockedIncrement(&_refcount); } virtual unsigned long _stdcall Release() { if(::InterlockedDecrement(&_refcount) == 0L) { delete this; return 0; // object deleted !!! } return _refcount; } // IUPnPDeviceFinderCallback implementation virtual HRESULT __stdcall DeviceAdded(long findid, IUPnPDevice* idev) { // process IUPnPDevice pointer when device was added return S_OK; } virtual HRESULT __stdcall DeviceRemoved(long findid, BSTR devudn) { // do something when device was removed return S_OK; } virtual HRESULT __stdcall SearchComplete(long findid) { // do something when search is complete return S_OK; } };
IUnknown
interface functions which should be implemented in your own callback class are:
- QueryInterface,
- AddRef,
- Release.
These functions are called by COM library or user in case of creating and deleting object. In QueryInterface
, at the request with specified proper interface identifier, pointer to the callback object is returned. AddRef
and Release
respectively increases or decreases object's reference counter. These operations thanks to the "Interlocked..." functions are thread safe. When counter is equal to zero, object can be deleted
IUPnPDeviceFinderCallback
interface functions which should be implemented in your own callback class are:
- DeviceAdded,
- DeviceRemoved,
- SearchComplete.
These functions are called in callback object by the UPnP framework at the moment, when searched device will be found or again connected to the network (DeviceAdded
), when previously founded device will be disconnected from the network (DeviceRemoved
) or when the search initiated by StartAsyncFind function call will be terminated (SearchComplete
). The return value of these functions is irrelevant to the UPnP framework.
The next example shows how to create a callback object of DevFinderCallback
class (item 2).
// Instantiate the callback object DevFinderCallback* cback = new DevFinderCallback(); // after object's creating reference counter is initially equal to zero // so, it is neccessary to increment it. // be sure to release on finish, // once created object should not be deleted directly (delete cback) // instead use Release function. cback->AddRef();
Thus, we have created a callback object (item 3), so now we can concern the creation of a Device Finder object. This object we create using COM library (item 4):
// Instantiate the device finder object #include <upnp.h> HRESULT hr; IUPnPDeviceFinder* _ifinder = 0; hr = CoCreateInstance(CLSID_UPnPDeviceFinder, 0, CLSCTX_SERVER, IID_IUPnPDeviceFinder, (void**)&_ifinder); if(hr == S_OK) { // use finder object here }
After creating of Device Finder (item 4) and callback (item 3) objects it is possible to start searching of devices. Before we begin the actual search, it is necessary, using CreateAsyncFind
function (item 5) of IUPnPDeviceFinder
interface, to specify the type of searched devices (as in the case of FindByType
function), pass to UPnP framework a pointer to callback object, and pass an empty pointer of type long
, which will be the session identifier. After return of CreateAsyncFind
function we get the value of session identifier, which is needed to call another function of Device Finder object - StartAsyncFind
. This function call begins the actual search (item 6). The StartAsyncFind
function immediately returns value of type HRESULT
indicating whether the call was faultlessly executed and the search request forwarded to UPnP framework. Since then, the callback object is awaiting results. Using once received session identifier we can repeat the call to StartAsyncFind
function. If we create more search sessions using CreateAsyncFind
(passing every time another session identifier), then the callback object can identify each session by using its identifier, which will be passed to callback object by the UPnP framework during returning search results. At any time after calling StartAsyncFind
function we can interrupt the search by using the CancelAsyncFind
function of Finder object passing as a parameter the identifier of session, we want to finish. Let's see an example:
// start asynchronous find for all root devices #include <upnp.h> HRESULT hr; // Device Finder object interface IUPnPDeviceFinder* _ifinder = 0; // device type to find std::wstring devtypestr(L"upnp:rootdevice"); BSTR devtype = SysAllocString(devtypestr.c_str()); // search identifier long _findhandle; hr = CoCreateInstance(CLSID_UPnPDeviceFinder, 0, CLSCTX_SERVER, IID_IUPnPDeviceFinder, (void**)&_ifinder); if(hr == S_OK) { // prepare search // cback is DevFinderCallack pointer obtained earlier hr = _ifinder->CreateAsyncFind(devtype, 0, cback, &_findhandle); if(hr == S_OK) { // start search hr = _ifinder->StartAsyncFind(_findhandle); if(hr == S_OK) { // do something if success or simply return } } } SysFreeString(devtype);
If in the application we’ll create once the Finder object and will start the search, then the UPnP framework will be for our application, until of its completion, monitoring network and relaying the results to the callback object. When a specific type of device will be found or will be connected to the network, then UPnP framework will call DeviceAdded
function of callback object passing IUPnPDevice
interface of this device’s object (item 7). The SearchComplete
function of callback object is called by the framework after the completion of the initial phase of search initiated by a Finder object by StartAsyncFind
function. The duration of this phase can amount to a few minutes. The SearchComplete
function’s call doesn’t mean the end of the process of discovering devices. We can finish this process calling Finder’s CancelAsyncFind
function.
When the application is being closed and we were used the asynchronous method of search, be sure to destroy COM objects, which were created, that is Device Finder and callback. Don't directly delete callback object, instead use Release
function.
// cancelling search and destroing objects // release DevFinderCallback object if(cback != 0) cback->Release(); // cancel search and release Device Finder object if(_ifinder != 0) { _ifinder->CancelAsyncFind(_finderhandle); _ifinder->Release(); }
Interfaces related to Device object
The next group is related to Device object:
- IUPnPDevice,
- IUPnPDevices,
- IUPnPDeviceDocumentAccess,
- IUPnPDescriptionDocument,
- IUPnPDescriptionDocumentCallback.
The most important in this group is IUPnPDevice
interface through which you can gain access to the Device object. It is not possible to direct create this interface, because it is provided by the UPnP framework as a result of searching devices available on the network. Through received pointer to the interface of device object, by using this interface’s functions, can to read a lot of information about the device (properties) such as its unique name, model name, the name of the manufacturer or URL, which will open in a browser an access to the device configuration options.
Furthermore, the IUPnPDevice
interface’s functions allow the examination of the device’s structure that is its internal logic structure:
- get_HasChildren,
- get_Children,
- get_Services.
With get_HasChildren
function we can find out whether the device contains member devices (child devices) at all. If the get_HasChildren
function returns logical value "true", then you can get a collection of member devices calling get_Children
. A collection of member devices is returned in the form of IUPnPDevices
interface. Please note, that the logical structure of a device is tree, therefore, every member device may include a collection of its own member devices. Most conveniently is to deal with the identification of the tree structure through recursion, i.e. call get_Children
function in the auxiliary recurrent function. The get_Services
function returns us a collection of device’s services - Service objects, in the form of IUPnPServices
interface. More about interfaces associated with the Service object will be later in this article.
Let’s see in example the process of identifying device’s structure.
// examination of structure of root device #include <upnp.h> // _idevice is pointer to root device void EnumerateDevices(IUPnPDevice* _idevice) { IUPnPDevices* children = 0; // collection of member devices VARIANT_BOOL bcheck = 0; // first check if device has children if(_idevice->get_HasChildren(&bcheck) == S_OK && bcheck != 0) { // if device has children then get collection interface if(_idevice->get_Children(&children) == S_OK) { // device has children and collection's pointer is valid HRESULT hr = S_OK; IUnknown* ienum = 0; // for obtain the enumerator long devscount = 0; // count of member devices // get count of member devices children->get_Count(&devscount); // enumerate and add devices // get helper interface if(children->get__NewEnum(&ienum) == S_OK) { IEnumUnknown* icol = 0; // collection enumerator hr = ienum->QueryInterface(IID_IEnumUnknown, (void**)&icol); if(hr == S_OK) { IUnknown* iitem = 0; // collection’s item IUPnPDevice* ichild = 0; // member device // enumerate collection icol->Reset(); while(icol->Next(1, &iitem, 0) == S_OK) { // get member device interface hr = iitem->QueryInterface(IID_IUPnPDevice, (void**)&ichild); if(hr == S_OK) { // here we can do something with device's pointer // continue recursive EnumerateDevices(ichild); ichild->Release(); ichild = 0; } iitem->Release(); iitem = 0; } icol->Release(); } ienum->Release(); } children->Release(); } } }
Description document
One of the most important and fundamental elements of the UPnP device architecture is the "description document". This is simply an XML document (hereafter I will be using the concept of "document"), where are written all the information about the device and its structure. The document is required element of the device and its format and structure is specified by standard of UPnP Forum organization [3]. Document in the form of an XML file is prepared by the manufacturer of the device and placed in the device.
The information about the device stored in a document is the specific properties of the device, the same that can be read using the IUPnPDevice
interface. The most essential thing in the document is that it contains a complete description of the whole logical structure of the device that is a description of the member devices tree together with their properties and collection of services. An important conclusion results from it: if the document has been downloaded, then analyzing it, you can read all the information about the device without the necessity of the access to Device objects and using the interfaces.
Let's see how to obtain access to the description document. Helpful interface in this task is IUPnPDeviceDocumentAccess
. It has only one function GetDocumentURL
which, as you may guess, returns the URL of the document. We will get the pointer to IUPnPDeviceDocumentAccess
through the IUPnPDevice
interface.
// retrieving of description document URL #include <upnp.h> // idev is pointer to root device void GetDeviceDocumentAccessURL(IUPnPDevice* idev) { HRESULT hr; IUPnPDeviceDocumentAccess* idoc = 0; // URL will be stored into this variable BSTR btmp = 0; // query for IUPnPDeviceDocumentAccess hr = idev->QueryInterface(IID_IUPnPDeviceDocumentAccess, (void**)&idoc); if(hr == S_OK) { // get URL and write to BSTR idoc->GetDocumentURL(&btmp); // do something with retrieved URL // on finish release resources SysFreeString(btmp); idoc->Release(); } }
I mentioned above that there is another way to obtain a Device object, other than that through the Device Finder object. It relies on the use of the IUPnPDescriptionDocument
interface. However, it makes sense only when we know in advance the description document’s URL of device, which the object we are interested in. To obtain a pointer to IUPnPDescriptionDocument
you need to create a new object through the COM library and CoCreateInstance
function. The IUPnPDescriptionDocument
interface has functions to load the document into memory, in synchronous or asynchronous manner, using a callback object (class implementing IUPnPDescriptionDocumentCallback
). Only after loading the document into memory, you can call functions returning a pointer to IUPnPDevice
. It is a pity that although the apparent document loading into memory by this method, IUPnPDescriptionDocument
interface does not offer function allowing direct access to the content of the document.
Interfaces related to Service object
Last group of interfaces belonging to Control Point API is related to Service object.
- IUPnPService,
- IUPnPServices,
- IUPnPServiceCallback.
Access to the Service object can be obtained only through a Device object. The primary interface of Service object is IUPnPService
. To obtain a collection of services - Service objects of given device, you should call the get_Services
function of IUPnPDevice
interface. This function returns the services collection object, to which we have access through an IUPnPServices
interface.
// retrieving of service objects #include <upnp.h> // idev is pointer to root device void EnumerateServices(IUPnPDevice* idev) { HRESULT hr = S_OK; long srvcount = 0; // count of services IUnknown* ienum = 0; // for obtain the enumerator IUPnPServices* isrvs = 0; // the collection of services // get collection interface if(idev->get_Services(&isrvs) != S_OK) return; // get number of services isrvs->get_Count(&srvcount); // get helper interface if(isrvs->get__NewEnum(&ienum) == S_OK) { IEnumUnknown *icol = 0; // the enumerator of collection hr = ienum->QueryInterface(IID_IEnumUnknown, (void**)&icol); if(hr == S_OK) { IUnknown* iitem = 0; // collection’s item IUPnPService* isrv = 0; // service object BSTR btmp = 0; // enumerate collection icol->Reset(); while(icol->Next(1, &iitem, 0) == S_OK) { // get interface to service object hr = iitem->QueryInterface(IID_IUPnPService, (void**)&isrv); if(hr == S_OK) { // do something with service object // for example get service id isrv->get_Id(&btmp); SysFreeString(btmp); isrv->Release(); } iitem->Release(); } icol->Release(); } ienum->Release(); } isrvs->Release(); }
The IUPnPService
interface has two very interesting functions that allow for interaction with the device (controlling device), that is the use of the services and read values of the state variables. The first primary function is InvokeAction
, which I will spare more space, and the second is QueryStateVariable
through which you can simply read the current value of the state variable.
Using InvokeAction
function is not too easy, so, at the beginning, I beg to quote the function’s declaration:
HRESULT InvokeAction ( BSTR bstrActionName, // [in] name of action to invoke VARIANT varInActionArgs, // [in] array of input arguments of action VARIANT* pvarOutActionArgs, // [out] array of main output values of action VARIANT* pvarRetVal // [out] one action’s output value );
You can see that the function requires four arguments. Their detailed definitions can be found in the API’s documentation on MSDN [1]. But I would like to draw attention to the fact that calling this function, usually we passing on the name of action (bstrActionName
- required argument) and an array of input arguments of action (varInActionArgs
- required argument), while the left two arguments point to empty variables of type VARIANT
. Of course, this is not only the format but the most popular.
The main problem with the InvokeAction
function is that before calling it you need to know not only the name of action to invoke but also the number and types of its arguments. Where from to take them? The answer is: from the description document of a Service object! And here another problem arises because none of the Control Point API interfaces does not have function that allows us to read the content of the document directly. You have to cope with it alone. Knowing the document’s URL you can individually, using the HTTP protocol, download from device the document file and then parse it.
Each service, like the device itself, is described in the XML file - description document, dedicated to given service. The structure of this document must also be compatible with the standard of UPnP Forum organization [3]. Document of service includes data of actions, their names and arguments and details about state variables associated with actions. Structure of the service’s document consists of two lists: actions and state variables. For each action is specified its name and list of arguments. If the action has arguments, then are given their names, direction of passing (input / output) and the name of related state variable. For the state variables are defined such data as: name, type, allowed values, minimum and maximum value, step and the default value.
The service's document file can be downloaded from the device. But where to find the URLs of these documents? The URL is saved in the relative form (usually but not necessarily), in a device’s document, in part related to given service, in tag called <SCPDURL>. The base part of URL, in accordance with the standard, should be placed in the document of device, in tag called <URLBase>.
Thus, the scenario of getting data about actions may appear as follows:
- read the URL of device’s document in a way given above in description of
IUPnPDeviceDocumentAccess
interface, - download the device’s description document file,
- examining the content of device’s document read the URL of the service’s document from SCPDURL tag (if the URL is relative then append it to the base URL read out from the URLBase tag),
- download the service’s description document file,
- examining the content of service’s document read the data of the action it interested to us.
How it results from the scenario, we meet the need for parsing XML files. Fortunately, there are many excellent and free XML parsers for C++.
In example, I show you how to, with the help of HTTP protocol, using only the Winsock library, download the document file from the device.
// downloading description document // sizes of buffers and URL are exemplary #pragma comment(lib, "ws2_32") // required linked library #include <winsock2.h> #include <iostream> using namespace std; // initialize Winsock library WSADATA wsadata; WSAStartup(MAKEWORD(2, 2), &wsadata); // path of document from its URL string path("/document.xml"); // prepare inet address from URL of document sockaddr_in addr; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // put here ip address of device addr.sin_family = AF_INET; addr.sin_port = htons(56616); // put here port for connection const int rbsize = 4096; // internal buffer size char rbuff[rbsize] = {0}; // internal temporary receive buffer int rbshift = 0; // index in internal buffer of current begin of free space int b = 0; // bytes curently received int tb = 0; // bytes totally received string respbuff; // response buffer string headertail("\r\n\r\n"); // request tail string document; // document content // prepare string of HTTP GET command ostringstream os; os << "GET " << path << " HTTP/1.1\r\nHost: " << inet_ntoa(addr.sin_addr) << ':' << ntohs(addr.sin_port) << headertail; // create TCP socket SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // connect socket connect(s, (sockaddr*)&_addr, sizeof(sockaddr_in)); // send request b = send(s, os.str().c_str(), os.str().length(), 0); // receive response data - document while((b = recv(s, rbuff + rbshift, rbsize - rbshift, 0)) != SOCKET_ERROR) { // finish loop if connection has been gracefully closed if(b == 0) break; // sum of all received chars tb += b; // sum of currently received chars rbshift += b; // temporary buffer has been filled // thus copy data to response buffer if(rbshift == rbsize) { respbuff.append(rbuff, rbshift); // reset current counter rbshift = 0; } } // close connection gracefully shutdown(s, SD_SEND); while(int bc = recv(s, rbuff, rbsize, 0)) if(bc == SOCKET_ERROR) break; closesocket(s); // analyse received data if(tb > 0) { // copy any remaining data to response buffer if(rbshift > 0) respbuff.append(rbuff, rbshift); // check response code in header if(respbuff.substr(0, respbuff.find("\r\n")).find("200 OK") != string::npos) { int cntlen_comp = 0; // computed response's content length int cntlen_get = 0; // retrieved response's content length string::size_type pos; string::size_type posdata = respbuff.find(headertail); if(posdata != string::npos) { // compute content length cntlen_comp = tb - (posdata + headertail.length()); if(cntlen_comp > 0) { if(b == 0) { // connection has been gracefully closed // thus received data should be valid cntlen_get = cntlen_comp; } else { // get content length from http header // to check if number of received data is equal to number of sent data string header = respbuff.substr(0, posdata); transform(header.begin(), header.end(), header.begin(), tolower); if((pos = header.find("content-length:")) != string::npos) istringstream(header.substr(pos, header.find("\r\n", pos)).substr(15)) >> cntlen_get; } // if number of received bytes is valid then save document content if(cntlen_comp == cntlen_get) document.assign(respbuff.begin() + posdata + headertail.length(), respbuff.end()); } } } } // cleanup Winsock library WSACleanup();
Now, we know how to obtain data of the action, which we want to invoke. Now we can go back to the InvokeAction
function. Before the call, on the basis of acquired data of the action, you need to prepare arguments to pass in. Passing the name of action is trivial. The varInActionArgs
argument is type of VARIANT
and contains an array of type SAFEARRAY
which elements are input arguments of action. If any action not require passing input arguments, then it is sufficient to pass an empty variable of type VARIANT
. Array of type SAFEARRAY
stores individual elements (action’s arguments) as variables of type VARIANT
. The pvarOutActionArgs
argument points to an empty variable of type VARIANT
, while after function’s return it contains an array of type SAFEARRAY
, whose elements (of type VARIANT
) are the output values of action. The number and the types of these elements are consistent with data of the action placed in service’s description document. The last argument pvarRetVal
also points to an empty variable of type VARIANT
, while at the end of the call it contains a single value of the return of action.
While using the InvokeAction
function we will have to deal with variables of type VARIANT
and SAFEARRAY
. Using these types is not very convenient because their variables require initialization and releasing of resources. Here comes again with the help the ATL library. Thanks to ATL types, above all, developer does not need to remember about the releasing of resources and initialization is done automatically. For the VARIANT
type we have adequate wrapper called CComVariant
and for the SAFEARRAY
corresponding type CComSafeArray
. ATL's CComBSTR
type wraps BSTR
type. Application of ATL types considerably simplifies the code and makes it clearer. But from the reason about which I wrote in the note at the beginning of article I will present the code without ATL types.
In the following example I will show you how to use the InvokeAction
function to execute sample action. Suppose that the device I want to control is a router (IGD) on a home network, and its service I want to use is the service of type urn:schemas-upnp-org:service:WANPPPConnection:1. This service provides, inter alia, an action called GetSpecificPortMappingEntry, which is used to retrieve a single, specific entry from the list of port mappings of the router. This action has three input arguments and returns five values containing data of list entry. Action’s data read from the document of service presents the table below:
Name of state variable related to argument |
Passing direction of argument |
Argument type | Allowed values |
RemoteHost | in | string | may be empty string |
ExternalPort | in | ui2 | |
PortMappingProtocol | in | string | TCP, UDP |
InternalPort | out | ui2 | |
InternalClient | out | string | |
PortMappingEnabled | out | boolean | |
PortMappingDescription | out | string | |
PortMappingLeaseDuration | out | ui4 | 0 means permanent |
The order of input arguments of action read from the service’s document is important. In the same order they should be placed in the array passed in the varInActionArgs
variable to InvokeAction
function. A little more about the types of arguments passed to the action later in this article.
I assume that earlier I received from the Device Finder object the Device object in the form of a pointer to IUPnPDevice
interface and next with its help I obtained from the Device object the pointer to IUPnPService
interface of service interesting to me.
// Invoking service’s action // _iservice is pointer to service’s interface (IUPnPService*) #include <upnp.h> // helper array for input arguments // first argument's type in pair - type of argument // second argument's type in pair - value of argument vector< pair<unsigned short, wstring> > _in; typedef vector< pair<unsigned short, wstring> >::const_iterator ArgsIterator; // helper array for output arguments vector< pair<unsigned short, wstring> > _out; // first argument of type string (VT_BSTR) – remote host _in.push_back(pair<unsigned short, wstring>(VT_BSTR, L"")); // second argument of type ui2 (VT_UI2) – external port _in.push_back(pair<unsigned short, wstring>(VT_UI2, L"8839")); // third argument of type string (VT_BSTR) – port mapping protocol _in.push_back(pair<unsigned short, wstring>(VT_BSTR, L"TCP")); // action name BSTR actionName = SysAllocString(L"GetSpecificPortMappingEntry"); HRESULT hr = S_OK; long inargscount = 0; // number of input arguments VARIANT inargs; // Invoke argument in VARIANT outargs; // Invoke argument out VARIANT retval; // Invoke argument ret VARIANT inval; // temp input value SAFEARRAY* arr_inargs = 0; // array for input arguments VARIANT outval; // temp output value SAFEARRAY* arr_outargs = 0; // array for output arguments long arr_index[1]; // current index of safe array // get number of arguments inargscount = _in.size(); // initialize variants VariantInit(&inargs); VariantInit(&outargs); VariantInit(&retval); // create input array SAFEARRAYBOUND bounds[1]; bounds[0].lLbound = 0; bounds[0].cElements = inargscount; arr_inargs = SafeArrayCreate(VT_VARIANT, 1, bounds); if(arr_inargs != 0) { // fill input safe array ArgsIterator argi = _in.begin(); for(long i = 0; i < inargscount; ++i, ++argi) { arr_index[0] = i; VariantInit(&inval); inval.vt = VT_BSTR; V_BSTR(&inval) = SysAllocString((*argi).second.c_str()); // value of arg if(V_BSTR(&inval) != 0) { hr = S_OK; VARTYPE vt = (*argi).first; // type of arg if(vt != VT_BSTR) // change type if other than VT_BSTR hr = VariantChangeType(&inval, &inval, VARIANT_NOUSEROVERRIDE, vt); if(hr == S_OK) SafeArrayPutElement(arr_inargs, arr_index, (void*)&inval); } VariantClear(&inval); } // assign input array to input argument inargs.vt = VT_ARRAY | VT_VARIANT; V_ARRAY(&inargs) = arr_inargs; // invoke action hr = _iservice->InvokeAction(actionName, inargs, &outargs, &retval); if(hr == S_OK) // invoke succeeded { // data from outargs - output arguments arr_outargs = V_ARRAY(&outargs); long arrsize = 0; VARTYPE vartype; // get size of array of output arguments if(SafeArrayGetUBound(arr_outargs, 1, &arrsize) == S_OK) { // read arguments from array for(long i = 0; i <= arrsize; ++i) { arr_index[0] = i; VariantInit(&outval); // get current argument from array hr = SafeArrayGetElement(arr_outargs, arr_index, (void*)&outval); if(hr == S_OK) { vartype = outval.vt; // change type to VT_BSTR (string) if(outval.vt != VT_BSTR) hr = VariantChangeType(&outval, &outval, VARIANT_ALPHABOOL, VT_BSTR); // put current argument to helper utput array if(hr == S_OK) _out.push_back(pair<unsigned short, wstring>(vartype, V_BSTR(&outval))); } VariantClear(&outval); } } // data from retval if(retval.vt != VT_EMPTY) { hr = S_OK; vartype = retval.vt; if(retval.vt != VT_BSTR) hr = VariantChangeType(&retval, &retval, VARIANT_ALPHABOOL, VT_BSTR); if(hr == S_OK) _out.push_back(pair<unsigned short, wstring>(vartype, V_BSTR(&retval))); } } else // invoke failed { // do something if error occured } // destroy input array SafeArrayDestroy(arr_inargs); // array has been destroyed thus set related variant to empty // to avoid destroing array again during clear of variant inargs.vt = VT_EMPTY; } // clear variants VariantClear(&inargs); VariantClear(&outargs); VariantClear(&retval); SysFreeString(actionName);
Reading information about any action from the service’s document, may be noticed that notation of the names of types of action’s arguments differs from notation that is used in C++ code operating on type VARIANT
and using Control Point API. For example, instead of notation "VT_BSTR" we meet notation "string". The reason for this is the fact that the standards of UPnP Forum are independent of the programming language and in the case of types are based on XML standard. In such situations is useful a "crib", which can prompts what type we face. In the following table I completed the list of names of XML types used in the service’s document and their equivalents used with type VARIANT
in Control Point API. It is a slightly extended (more convenient) version of the table, which can be found in the documentation on MSDN [1].
XML Data Type |
IDL Data Type |
Type of VARIANT |
bin.base64 |
SAFEARRAY |
VT_ARRAY | VT_UI1 |
bin.hex |
SAFEARRAY |
VT_ARRAY | VT_UI1 |
boolean |
VARIANT_BOOL |
VT_BOOL |
char |
wchar_t |
VT_UI2 |
date |
DATE |
VT_DATE |
dateTime |
DATE |
VT_DATE |
dateTime.tz |
DATE |
VT_DATE |
fixed.14.4 |
CY |
VT_CY |
float |
float |
VT_R4 |
i1 |
char |
VT_I1 |
i2 |
short |
VT_I2 |
i4 |
long |
VT_I4 |
int |
long |
VT_I4 |
number |
BSTR |
VT_BSTR |
r4 |
float |
VT_R4 |
r8 |
double |
VT_R8 |
string |
BSTR |
VT_BSTR |
time |
DATE |
VT_DATE |
time.tz |
DATE |
VT_DATE |
ui1 |
unsigned char |
VT_UI1 |
ui2 |
unsigned short |
VT_UI2 |
ui4 |
unsigned long |
VT_UI4 |
uri |
BSTR |
VT_BSTR |
uuid |
BSTR |
VT_BSTR |
At the end of considerations about the InvokeAction
function it’s worth to draw attention to the error codes returned by this function in the form of HRESULT
, because they may be helpful in determining the cause of error if the function call fails. The error codes can be found in the upnp.h file or in the documentation [1].
I mentioned earlier one more interesting function of IUPnPService
interface: QueryStateVariable
. It may be useful in certain cases, when is needed, at the moment, the value of the state variable, which is normally available only after the relevant event has been generated. But in general, such cases should not be much, so more effective way to read the values of the state variables is the receiving of events notifications generated at the moment of the change of variable’s value.
To receive notifications of events generated by the services is used the callback object, which should be created from class implementing IUPnPServiceCallback
interface. It has two functions, of which the most interesting is function StateVariableChanged
, called by the UPnP framework at the moment of change the service’s state (change of the value of the state variable). The second function is ServiceInstanceDied
, which is used to notify the control point application of the unavailability of service. UPnP framework calls StateVariableChanged
function to notify the application about change the value of the state variable, passing a pointer to the related service object (IUPnPService
*) as well as the name and current value of the changed variable. A class of object receiving events notifications we create like a class of callback object for Device Finder.
// IUPnPServiceCallback implementation #include <upnp.h> // IUPnPServiceCallback // IUPnPServiceCallback implementation class SrvEventCallback : public IUPnPServiceCallback { private: long _refcount; // object's reference counter public: SrvEventCallback() : _refcount(0) {} virtual ~SrvEventCallback() {} // IUnknown implementation virtual HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject) { HRESULT result = ppvObject == 0 ? E_POINTER : S_OK; if(result == S_OK) { if(riid == IID_IUnknown || riid == IID_IUPnPServiceCallback) { *ppvObject = static_cast<IUPnPServiceCallback*>(this); this->AddRef(); } else { *ppvObject = 0; result = E_NOINTERFACE; } } return result; } virtual unsigned long _stdcall AddRef() { return ::InterlockedIncrement(&_refcount); } virtual unsigned long _stdcall Release() { if(::InterlockedDecrement(&_refcount) == 0L) { delete this; return 0; // object deleted !!! } return _refcount; } // IUPnPServiceCallback implementation virtual HRESULT _stdcall StateVariableChanged(IUPnPService* isrv, LPCWSTR varname, VARIANT varvalue) { // process current value of state variable return S_OK; } virtual HRESULT _stdcall ServiceInstanceDied(IUPnPService* isrv) { // do something when service was died return S_OK; } };
The return value of the IUPnPServiceCallback
interface functions should be equal to zero (S_OK).
Before the callback object will be able to receive events notifications it is necessary to register this object in the service, i.e. pass to its (service) object the pointer to a callback object using AddCallback
function of IUPnPService
interface.
// Instantiate and registering the callback object for receiving events notifications // isrv is pointer to service’s interface (IUPnPService*) SrvEventCallback* cback = new SrvEventCallback(); if(cback != 0) { // reference counter is 0 thus increment // be sure to release on finish cback->AddRef(); // register callback with service object isrv->AddCallback(cback); }
As you can see, analyzing the above example and IUPnPService
interface functions, it is not obvious from which device (if we control more than one and we created only one callback object) comes received event notification. This doubt arises from the fact that in the function IUPnPServiceCallback::StateVariableChanged
we get (from UPnP framework) only a pointer to the interface of service object. In contrast, the service object does not have a function, which returns a pointer to the object of hosting device. Therefore, we do not have the possibility of direct reference to a device object from its service level. In the next part of this article I will propose solution to the problem of callback objects (IUPnPServiceCallback
) management for a number of controlled devices.
UPnPCpLib framework
For ease of use Control Point API I created a simple framework, which, I hope :), simplifies the creation of control point application. It contains a few simple classes that define the most important objects of UPnP architecture as well as set of utility functions, interfaces and structures that are helpful at accomplishment of the basic tasks of control point application. All of the examples presented so far come from the source files of framework, and all issues that were raised so far are applicable in functions of this library.
The main idea of this framework is to create a model of UPnP device and to provide the tools to handle this model. Framework can be used to build console or GUI applications as well as the native win32/MFC or managed WinForms applications. The main native library consists of a header file upnpcplib.h
and source file upnpcplib.cpp
. The code is commented, what should ease of use the library. For parsing XML files I used CMarkup software by First Objective Software, Inc. under the conditions of "CMarkup Evaluation License Agreement". To compile the library, you must add to the application project the markup.h
and markup.cpp
files. Library uses STL strings in CMarkup software, so You should specify preprocessor definition MARKUP_STL
in project's properties. I beg to note that UPnPCpLib framework is not a commercial product and can be used in your own applications on rules of CPOL license, under the condition of keep terms of "CMarkup Evaluation License Agreement".
I will present here shortly the key elements of library, but I encourage interested to review the source code. After a brief description I will present examples. In order to supplement the information contained in the following description, please familiarize with the comments and the implementation details in the library's source files.
UPnPCpLib library contains four main classes defining the four most important objects of UPnP architecture.
- FindManager - refers to the Device Finder object.
- Device - corresponds to UPnP device.
- Service - corresponds to service object of UPnP device.
- Action - corresponds to service's action
Besides, contains two helper classes:
- DevFinderCallback - implements
IUPnPDeviceFinderCallback
interface. - SrvEventCallback - implements
IUPnPServiceCallback
interface.
Moreover, it contains structure DocAccessData
which helps parsing of description documents.
Below they are the two general schemes of relationships between library’s objects. First scheme presents centralized model which key feature is that the collection of devices is managed by FindManager object.

Second scheme presents diffused model which key feature is that the collection of devices is managed outside of FindManager by object of class implementing IFinderCallbackClient interface.

FindManager class
The FindManager
is the primary class and is used to manage the Device Finder object. Automatically creates and destroys the Finder object and controls the process of discovering. It's the only class which objects You can create by oneself. The rest of objects are created by framework automatically and can be accessed via other objects methods.
// ============== FindManager class ============== // // class for manage IUPnPFinder* object and devices collection. // To manage external device's collection outside this class // create your own class implementing IFinderCallbackClient interface // then call FindManager::Init passing pointer to your own object. // Else, pass pointer to client of type IFinderManagerClient // to manage collection internally and notify client about events. class FindManager : public IFinderCallbackClient, public IProcessDevice { public: FindManager(); virtual ~FindManager(); // devices collection will be managed internaly. // pass interface of client which will be notified about events related to collection bool Init(IFinderManagerClient* client, const wstring& devicetype = L"upnp:rootdevice"); // external collection. // pass pointer to your own class implementing IFinderCallbackClient. bool Init(IFinderCallbackClient* client, const wstring& devicetype = L"upnp:rootdevice"); // starts new search bool Start(); // stops current search bool Stop(); // access to devices collection // if collection is managed externally then throws exception const DeviceArray* GetCollection() const; DeviceArrayIterator GetCollectionBegin() const; DeviceArrayIterator GetCollectionEnd() const; const Device* GetDevice(unsigned int index) const; const Device* operator[] (unsigned int index) const; int GetCollectionCount() const; bool IsCollectionEmpty() const; // current search identifier long GetFindId(); // when devices collection is managed externally in your own class // then you should manually add callback to services events using Service::SetCallbackClient. // this function is used only if FindManager manages devices collection. // adds callback to services and notify given client about events related to service. // if client will be set to null pointer then callback won't be added to the collection // and client won't be notified about events. void SetServiceEventClientPtr(IServiceCallbackClient* client); // IFinderCallbackClient implementation // calls EnterCriticalSection and LeaveCriticalSection void Lock(); void UnLock(); // standard UPnP devices types static const wchar_t* device_type[]; static wstring GetRootDeviceType(); static int TypesCount(); private: bool Init(const wstring& devicetype); // releases finder callback void ReleaseCallback(); // IProcessDevice implementation // for assign callback's client to each service object in devices tree virtual void ProcessDevice(const Device* dev, void* param, int procid); // IFinderCallbackClient implementation virtual void DeviceAdded(long findid, IUPnPDevice* idev); virtual void DeviceRemoved(long findid, const wstring& devname); virtual void SearchComplete(long findid); void RemoveAllDevices(); private: DevFinderCallback* _findercallback; // IUPnPDeviceFinderCallback implementation IUPnPDeviceFinder* _ifinder; // IUPnPDeviceFinder interface DeviceArray _devs; // device's collection long _finderhandle; // IUPnPDeviceFinder find handle IFinderManagerClient* _findermanagerclient; // pointer to client receiving events related to changes // in devices collection managed internally by FindManager IFinderCallbackClient* _findercallbackclient; // pointer to client which manages devices collection IServiceCallbackClient* _srveventclient; // pointer to client receiving services events bool _externalcollection; // true if collection is managed externally CRITICAL_SECTION _cs; // for synchronize access to devices collection FindManager(const FindManager& srcobj) {} FindManager& operator= (const FindManager& srcobj) {return *this;} };
Using FindManager
is pretty simple. After creating the object just call appropriate Init
overload function and next Start
. Start
and Stop
functions respectively run and stop the process of discovering devices. FindManager
allows managing an internal collection of detected devices (objects of Device
class). You can also store a collection of devices discovered by FindManager
outside, in your own class implementing IFinderCallbackClient
interface. In the case when we want to FindManager
will manage the devices collection then we should pass pointer to IFinderManagerClient
object to FindManager::Init
function. To gain access to internal collection the GetCollection
or GetDevice
functions should be called. FindManager
managing a collection can cooperate with the client implementing IFinderManagerClient
interface, so that the client will be notified about events of beginning and finishing the process of discovering as well as adding and removing object from collection. Devices
objects in the internal collection of FindManager
receive events notifications generated in physical UPnP devices. FindManager
can pass these notifications, to an external client, which implements IServiceCallbackClient
interface. However, earlier you must use the FindManager::SetServiceEventClientPtr
function passing a pointer to client object.
In the case when we want to FindManager
will manage only the Device Finder object and we would like to put the collection of discovered devices outside, in your own class, then it should implement the IFinderCallbackClient
interface. Then, after creating the FindManager
object, just call its function Init
passing pointer to object of your own class.
FindManager
uses the DevFinderCallback
class to receive from UPnP framework (Device Finder object) the results of searching devices in the network.
Device class
Device
is the second class, in addition to FindManager
, defining the one of the basic elements of UPnP architecture – the UPnP device. This class reflects a tree structure of the device and its functionality in accordance with the standard of device architecture of the UPnP Forum organization.
// class representing UPnP device class Device { public: explicit Device(IUPnPDevice* idev, const Device* parentdev = 0); ~Device(); wstring GetUDN() const; wstring GetFriendlyName() const; wstring GetType() const; // access to list of services int GetServiceListCount() const; ServiceIterator GetServiceListBegin() const; ServiceIterator GetServiceListEnd() const; const Service* GetService(unsigned int index) const; // access to list of devices int GetDeviceListCount() const; DeviceIterator GetDeviceListBegin() const; DeviceIterator GetDeviceListEnd() const; const Device* GetDevice(unsigned int index) const; bool IsDeviceListEmpty() const; // access to list of icons int GetIconsListCount() const; IconIterator GetIconsListBegin() const; IconIterator GetIconsListEnd() const; const IconParam& GetIcon(unsigned int index) const; bool IsIconsListEmpty() const; // gets device icon URL // passing width & height equals to 0 function returns icon with standard parameters, // if it exists (mimetype = image/png). // otherwise icon is returned as specified, if any exists. // works on root device wstring GetDeviceIconURL(int iwidth, int iheight, int idepth) const; const Device* GetParentDevice() const; const Device* GetRootDevice() const; bool IsRoot() const; // with AddRef, don't forget to release interface when unused void GetInterface(IUPnPDevice** idev) const; // returns structure contains data which helps manipulate descr documents const DocAccessData* GetAccessData() const; // retrieves description document url from this UPnP device wstring GetDevDocAccessURL() const; // retrieves description document content from this UPnP device wstring GetDeviceDocument() const; // retrieves info about this UPnP device bool GetDeviceInfo(InfoData& data) const; // recursive process device object tree // see above instructions about IProcessDevice void EnumerateDevices(IProcessDevice* iproc, void* param, int procid) const; private: // enumerates member devices of this UPnP device // and stores members in collection // calls EnumSrv bool EnumDev(); // enumerates member services of this UPnP device // and stores collection in corresponding device object bool EnumSrv(); // creates new device object and add one to collection // with AddRef() void AddDevice(IUPnPDevice* idev); // creates new service object and add one to collection // with AddRef() void AddService(IUPnPService* isrv); bool SetUDN(); void GenerateFriendlyName(); wstring GetDocURL() const; bool SetType(); void RemoveAllDevices(); void RemoveAllServices(); private: IUPnPDevice* _idevice; // COM interface of UPnP device const Device* _parent; // parent device object wstring _udn; // Unique Device Name of UPnP device wstring _name; // friendly name of UPnP device wstring _type; // type of UPnP device ServiceList _services; // list of service objects representing hosted UPnP services DeviceList _devices; // list of device objects representing hosted UPnP devices DocAccessData _accessdata; // helper data to maniplulate description documents IconList _icons; // list of icon resources for UPnP device };
The structure of the object of Device
class.

Device
class contains three collections:
- DeviceList - of objects of type
Device
representing the member devices, - ServiceList - of objects of type
Service
representing the member services, - IconList - of structures of type
IconParam
containing icons parameters.
Furthermore, the Device
contains information about the name of the device and data concerning the description document in the structure DocAccessData
. Also stores a pointer to the object’s interface of appropriate device and pointer to the parent object (if not root device).
In Service
object is stored a collection of objects of actions available in the service. About Service
class more lately.
Device
has a number of functions for the manipulation of the above-mentioned collections: adding, removing and enumerating items. It has also EnumerateDevices
function, which recursively enumerate objects in the tree structure of member devices. With this function must cooperate the object of class implementing IProcessDevice
interface (client), whose function ProcessDevice
will be called while enumerating in order to pass for processing the pointer to currently enumerated device. The enumerating can be started from any node of the tree and is continued in depth of the structure. You can pass to it any object, which will be processed together with the object of the device. In addition, you can pass a parameter of integer type, of general purpose, helpful for example, to control the function’s behavior.
Service class
The Service
class represents the object of service of the UPnP device.
// class representing UPnP service class Service { friend class Device; public: ~Service(); // access to list of actions // each service may have 0 or more actions int GetActionCount() const; ActionIterator GetCollectionBegin() const; ActionIterator GetCollectionEnd() const; const Action& GetAction(unsigned int index) const; const Action& GetAction(const wstring& aname) const; wstring GetServiceID() const; wstring GetServiceTypeID() const; // if returns zero then error occurred long GetLastTransportStatus() const; const Device& GetParentDevice() const; // with AddRef, don't forget to release interface when unused void GetInterface(IUPnPService** isrv) const; // adds callback for events and sets its client bool SetCallbackClient(IServiceCallbackClient* iclient); // returns descr document url of this service wstring GetScpdURL() const; // returns descr document content of this service wstring GetScpdContent() const; // returns structure contains data which helps manipulate descr documents const DocAccessData* GetAccessData() const; // retrieves info about this UPnP service bool GetServiceInfo(InfoData& data) const; // retrieves info about service's state variables // each service must have one or more state variables bool GetServiceVariables(VarData& data) const; private: Service(IUPnPService* isrv, const Device& parentdev); // retrieves scpd info, url and document content bool SetAccessData(); // reads actions names bool EnumActions(); bool SetServiceID(); wstring _name; // service Id wstring _typeid; // service type Id ActionList _actions; // list of UPnP service actions const Device& _parent; // parent device object IUPnPService* _iservice; // COM interface of UPnP service DocAccessData _accessdata; // uri and content of document describing UPnP service SrvEventCallback* _isrvcback; // IUPnPServiceCallback object Service(const Service& srcobj) : _parent(srcobj._parent) {} Service& operator= (const Service& srcobj) {return *this;} };
Service
class contains a list of actions objects (ActionList
) belonging to the service it represents, and also stores the URL and content of service’s description document, where are stored all the data needed to perform the action.
Each Service
object creates its own instance of SrvEventCallback
class (implementing IUPnPServiceCallback
interface) to receive event notifications from services. Received notifications are sent farther to the client of callback object, i.e. to the object of class implementing IServiceCallbackClient
interface. The callback object (SrvEventCallback
), receiving a notification from UPnP framework about the event in the service, receives at the same time a pointer to service object (IUPnPService*
). But passing notification farther to the client, instead of a received pointer the pointer to Service
object (its host) is passed, to allow the client identification of device, which has generated the event. Client of SrvEventCallback
object, processing notification, can determine the source of event on the basis of received pointer to Service
object and available in it the pointer to a parent object (Device
).
Action class
Action
object contains informations about one's input arguments and has function Invoke
which invokes appropriate action on parent service.
// class representing member action of UPnP service class Action { friend class Service; public: // name of this action wstring GetName() const; const Service& GetParentService() const; // number of arguments (input & output) // returns -1 if error occured int GetArgsCount(); // number of input arguments // returns -1 if error occured int GetInArgsCount(); // retrieves this action info bool GetInfo(/*out*/InfoDataList& inflist) const; // sets array of input arguments types and values bool SetInArgs(const StrList& args); // sets value of argument at specified index in input arguments array bool SetInArgs(const wstring& arg, unsigned int index); // gets array of input arguments types and values bool GetInArgs(ArgsArray& args); // gets input argument at specified index in input arguments array bool GetInArgs(InfoDataItem& arg, unsigned int index); // invokes this action on parent UPnP service // argsout - array of values of output arguments or error message. // on error argsout contains only error messages of InvokeAction // return: -1 = error, >0 = number of output arguments + returned value (if any) // -2 = argsout contains only returned value int Invoke(ArgsArray& argsout) const; private: wstring _name; // action's name const Service* _parent; // this action's parent service int _argcount; // number of input arguments int _inargcount; // number of all arguments, input and output bool _complete; // true if number of arguments has been retrieved bool _inargscomplete; // true if number of input arguments has been retrieved ArgsArray _in; // array of input arguments (types and values) bool SetArgsCount(); bool InitInArgsList(); Action(const Service* srv, const wstring& name); };
Utility functions
CString GetErrorMessage(HRESULT hr);
Returns description of the error on the basis of retrieved COM error code.
VARTYPE GetVariantType(const wstring& vtype);
Converts the name of a type of state variable (of action’s argument) to corresponding VARIANT
type.
wstring GetTypeDescr(VARTYPE vtype);
Converts description of state variable type to variant type.
bool IsICSConnEnabled();
Checks whether the ICS feature is enabled, and whether the ICS uses public and private connections. If the ICS is on, you should not manually enable the Windows Firewall exception for the UPnP framework. This turns off firewall protection for the UPnP ports on all network interfaces, including the ICS public interface. This could expose the computer directly to the Internet.
int CheckFirewallPortState(long number, transport_protocol protocol);
Checks the state of a given port in firewall. If the function returns zero, it means that the error occurred. The value of 1 means that the port is unblocked, while the value of -1 means that the port is blocked. You should pass udp_protocol
value to protocol argument for UDP protocol and tcp_protocol
value for TCP protocol.
bool ControlUPnPPorts(bool open);
Unblock or block in firewall the ports used by UPnP framework: 2869 TCP and 1900 UDP. To function has effect, an application must work with administrator privileges.
void CheckServiceState(const wstring& srvname, /*out*/srvstate& state);
Checks and returns in the form of description the state of specified system service.
bool ControlSSDPService(bool start);
Starts or stops ssdpsrv system service. To function has effect, an application must work with administrator privileges.
Samples
It’s known that the best is to learn from examples. So now it’s time to show some examples of application of UPnPCpLib library. I will show you how to use the framework in Win32 applications for console because in this case it will be easier to analyze the code. Obviously use of the library in GUI applications (Win32 or MFC or WinForms) will be much more effective. In the next part of the article will be about example of use the library in WinForms managed application. All presented examples are available for download in the form of Visual Studio 2003 projects which may be easily converted to 2008 version.
// sample 1 // In this sample FindManager manages internal devices collection #define _WIN32_DCOM #include <iostream> #include "upnpcplib.h" using namespace UPnPCpLib; using namespace std; // This class hosts FindManager object and uses its ability to manage // devices collection, thus implements IFinderManagerClient interface // to receive notifications from FindManager about changes in collection // and start/stop events. To receive notifications about events // from device's services this class implements IServiceCallbackClient. class FinderClient : public IFinderManagerClient, public IServiceCallbackClient, public IProcessDevice { public: FinderClient() : _searching(false) { _fm = new FindManager(); // create new FindManager object // creates IUPnPDeviceFinderCallback object, // pointer to this class is passed, to manage devices internally // and receive notifications from FindManager _fm->Init(this); // sets receiver (this class) of services events _fm->SetServiceEventClientPtr(this); wcout << L"\nFinderClient was created successfully" << flush; } ~FinderClient() { if(IsSearching()) StopSearch(); // destroy FindManager object delete _fm; // when all objects were destroyed then // post WM_QUIT message to message loop, to finish it PostQuitMessage(0); wcout << L"\nFinderClient has been destroyed" << flush; } bool StartSearch() { // start finding devices // with default argument "upnp:rootdevice" return (_searching = _fm->Start()); } bool StopSearch() { // stop finding devices return !(_searching = !_fm->Stop()); } bool IsSearching() {return _searching;} void InvokeAction(unsigned int devindex, const wstring& actionName, const StrList& args) { _action = 0; // find service related to given action name // and if found then save pointer to requested action object. // see ProcessDevice function. const Device* dev = _fm->GetDevice(devindex); dev->EnumerateDevices(this, (void*)&actionName, 0); // if action found then invoke it if(_action != 0) { wcout << L"\nInvoke action: " << actionName << endl; wstring result; ArgsArray argsout; const_cast<Action*>(_action)->SetInArgs(args); _action->Invoke(argsout); for(ArgsArray::size_type i = 0; i < argsout.size(); ++i) result.append(argsout[i].second).append(L"\n"); wcout << L"action result:" << endl; // write result to console wcout << result; } else wcout << L"\nAction not found" << endl; } private: // IProcessDevice interface implementation. // This interface is implemented to use Device::EnumerateDevices function virtual void ProcessDevice(const Device* dev, void* param, int procid) { const wstring& actname = *(const wstring*)param; ServiceIterator srvi = dev->GetServiceListBegin(); int srvcount = dev->GetServiceListCount(); // enumerate member services of given device for(int i = 0; i < srvcount; ++i, ++srvi) { // check if current service contains requested action // if so, then save pointer. // btw, sorry for this "exceptional coding" style :) try { _action = &(*srvi)->GetAction(actname); break; } catch(...) { } } } // IFinderManagerClient interface implementation virtual void OnStartFindDevice(long findid) { // called by FindManager when finding was started successfully wcout << L"\nSearching started" << flush; } virtual void OnStopFindDevice(long findid, bool iscancelled) { // called by FindManager when finding was stopped successfully // display reason of calling wcout << L"\nSearching stopped because of " << (iscancelled ? L"cancel" : L"search complete") << flush; if(!iscancelled) { // when search is complete (not cancelled) // let's break loop and finish application wcout << L"\nSearching will be cancelled" << flush; this->StopSearch(); } } virtual void OnAddDevice(long findid, const Device* dev, int devindex) { // called by FindManager when device object has been added to collection // display device name wcout << L"\nDevice added: " << dev->GetFriendlyName() ; // display passed device object properties InfoData devdata; if(dev->GetDeviceInfo(devdata)) { for(InfoIterator ii = devdata.begin(); ii != devdata.end(); ++ii) wcout << endl << (*ii).first << L" = " << (*ii).second; } wcout << L"\nSearching is being continued" << flush; } virtual void OnRemoveDevice(long findid, const wstring& devudn, const wstring& friendlyname, int removedindex) { // called by FindManager when device object has been removed from collection // display removed device name wcout << L"\nDevice removed: " << friendlyname << L"\nSearching is being continued" << flush; } // IServiceCallbackClient interface implementation virtual void ServiceEventVariableChanged(const Service* srv, const wstring& varname, const wstring& varvalue) { // called by service callback when value of state variable has been changed // get pointer to parent device wstring devparent = srv->GetParentDevice().GetFriendlyName(); // display device's name, service's id, name of state variable // and its current value wcout << L"\n==> Event fired:\n\tfrom service id: " << srv->GetServiceID() << L"\n\tparent device: " << devparent << L"\n\tsource state variable name: " << varname << L"\n\tcurrent state variable value: " << varvalue << L"\nSearching is being continued" << flush; } virtual void ServiceEventInstanceDied(const Service* srv) { // called by service callback when service is not responding wcout << L"\nService died: " << srv->GetServiceID() << "\nIt was member of device: " << srv->GetParentDevice().GetFriendlyName() << "\nSearching is being continued" << flush; } private: bool _searching; // helps break message loop FindManager* _fm; // object of class from UPnPCpLib library const Action* _action; // requested action name }; int main() { // initialize COM library. // in console application, COM library is initialized // to use single threaded concurrency model if(CoInitializeEx(0, COINIT_APARTMENTTHREADED) != S_OK) { CoUninitialize(); return -1; } wcout << L"\nCOM library initialized"; // initialize Winsock library WSADATA wdata; WSAStartup(MAKEWORD(2,2), &wdata); wcout << L"\nWinsock library initialized"; wcout << L"\nCreating FinderClient object" << flush; // create FinderClient object which hosts FinderManager FinderClient* fclnt = new FinderClient(); // start finding all root devices wcout << L"\nStart finding\n" << flush; if(fclnt->StartSearch()) { // process messages MSG Message; while(GetMessage(&Message, 0, 0, 0)) { DispatchMessage(&Message); wcout << '.' << flush; // when FinderClient receives "search complete" notification // its function IsSearching returns false // and FinderClient will be destroyed and application finished if(!fclnt->IsSearching()) { // let's execute some actions wstring name; StrList args; // action without input arguments name = L"GetExternalIPAddress"; fclnt->InvokeAction(0, name, args); // it has one input argument of type ui2 name = L"GetGenericPortMappingEntry"; args.push_back(L"0"); // entry's index fclnt->InvokeAction(0, name, args); wcout << L"\nFinderClient finished work and will be destroyed\n" << flush; // in FinderClient destructor WM_QUIT message is posted // to break this loop delete fclnt; fclnt = 0; } } } delete fclnt; wcout << L"\nCleaning up Winsock library"; // free Winsock library WSACleanup(); wcout << L"\nCleaning up COM library"; // free COM library CoUninitialize(); wcout << L"\nExiting... bye" << flush; return 0; }
Above program can print out on the console the following results:
COM library initialized Winsock library initialized Creating FinderClient object FinderClient was created successfully Start finding Searching started............ Device added: Wireless-G ADSL Home Gateway (uuid:8c59ad37-1dd2-11b2-ada1-0018398b6752) Description = LINKSYS WAG200G Router Document URL = http://10.0.0.1:49152/gateway.xml Friendly name = Wireless-G ADSL Home Gateway (uuid:8c59ad37-1dd2-11b2-ada1-0018398b6752) Manufacturer = LINKSYS Manufacturer URL = http://www.linksys.com/ Model = Wireless-G ADSL Home Gateway Model URL = http://www.linksys.com/ Model number = WAG200G Presentation URL = http://10.0.0.1/index.htm Serial number = 123456789 Type = urn:schemas-upnp-org:device:InternetGatewayDevice:1 UDN = uuid:8c59ad37-1dd2-11b2-ada1-0018398b6752 UPC = WAG200G Searching is being continued......... ==> Event fired: from service id: urn:upnp-org:serviceId:L3Forwarding1 parent device: Wireless-G ADSL Home Gateway (uuid:8c59ad37-1dd2-11b2-ada1-0018398b6752) source state variable name: DefaultConnectionService current state variable value: Searching is being continued........... ==> Event fired: from service id: urn:upnp-org:serviceId:WANCommonIFC1 parent device: Internet Connection Sharing (uuid:8c59ad36-1dd2-11b2-ada1-0018398b6752) source state variable name: PhysicalLinkStatus current state variable value: Up Searching is being continued........... ==> Event fired: from service id: urn:upnp-org:serviceId:WANEthLinkC1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: EthernetLinkStatus current state variable value: Up Searching is being continued........... ==> Event fired: from service id: urn:upnp-org:serviceId:WANPPPConn1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: PossibleConnectionTypes current state variable value: IP_Routed Searching is being continued......... ==> Event fired: from service id: urn:upnp-org:serviceId:WANPPPConn1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: ConnectionStatus current state variable value: Connected Searching is being continued......... ==> Event fired: from service id: urn:upnp-org:serviceId:WANPPPConn1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: X_Name current state variable value: Local Area Connection Searching is being continued......... ==> Event fired: from service id: urn:upnp-org:serviceId:WANPPPConn1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: ExternalIPAddress current state variable value: 57.153.248.16 Searching is being continued......... ==> Event fired: from service id: urn:upnp-org:serviceId:WANPPPConn1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: PortMappingNumberOfEntries current state variable value: 2 Searching is being continued..... Searching stopped because of search complete Searching will be cancelled Searching stopped because of cancel. Invoke action: GetExternalIPAddress action result: 57.153.248.16 Invoke action: GetGenericPortMappingEntry action result: 11599 TCP 11599 10.0.0.2 True BitComet (10.0.0.2:11599) 11599 TCP 0 FinderClient finished work and will be destroyed FinderClient has been destroyed Cleaning up Winsock library Cleaning up COM library Exiting... bye
And the next sample:
// sample 2 // In this sample devices collection is managed outside of FindManager #define _WIN32_DCOM #include <iostream> #include "upnpcplib.h" using namespace UPnPCpLib; using namespace std; // This class hosts FindManager object and DON'T uses its ability to manage // devices collection. Instead, this class on its own is managing the collection. // In this case FindManager is used only for finding devices. // Thus this class implements IFinderCallbackClient interface // to receive notifications from UPnP framework about found devices. // To receive notifications about events from device's services // this class implements IServiceCallbackClient. class FinderHost : public IFinderCallbackClient, public IServiceCallbackClient, public IProcessDevice { public: FinderHost() : _searching(false) , _finish(false) { // create IUPnPDeviceFinderCallback object // and pass pointer to FinderHost which will be managing devices collection _fm = new FindManager(); _fm->Init(this); wcout << L"FinderHost was created successfully" << endl; } ~FinderHost() { if(IsSearching()) StopSearch(); // remove devices collection RemoveAllDevices(); // destroy FindManager object delete _fm; // when all objects were destroyed then // post WM_QUIT message to message loop, to finish it //PostQuitMessage(0); wcout << L"\nFinderHost has been destroyed" << endl; } bool StartSearch() { // start finding devices // with default argument "upnp:rootdevice" return (_searching = _fm->Start()); } bool StopSearch() { // stop finding devices return !(_searching = !_fm->Stop()); } bool IsSearching() {return _searching;} void SetFinish() {_finish = true;} bool IsFinished() {return _finish;} // IFinderCallbackClient implementation // in single threading model it's unneccessary to synchronize access // to devices collection, thus function bodies are empty void Lock() {} void UnLock() {} void InvokeAction(int devindex, const wstring& actionName, const StrList& args) { _action = 0; // find service related to given action name // and if found then save pointer to requested action object. // see ProcessDevice function. const Device* dev = _devs[devindex]; dev->EnumerateDevices(this, (void*)&actionName, PID_FIND_ACTION); // if action found then invoke it if(_action != 0) { wcout << L"\nInvoke action: " << actionName << flush; wstring result; ArgsArray argsout; const_cast<Action*>(_action)->SetInArgs(args); _action->Invoke(argsout); for(ArgsArray::size_type i = 0; i < argsout.size(); ++i) result.append(argsout[i].second).append(L"\n"); wcout << L"\naction result:" << endl; // write result to console wcout << result << endl; } else wcout << L"\nAction not found" << endl; } private: // identifiers used in ProcessDevice static const int PID_ADD_SERVICE_CALLBACK = 0; static const int PID_FIND_ACTION = 1; // IProcessDevice interface implementation. // This interface is implemented for use Device::EnumerateDevices function virtual void ProcessDevice(const Device* dev, void* param, int procid) { switch(procid) { // for add pointer to client of services callbacks case PID_ADD_SERVICE_CALLBACK: { ServiceIterator srvi = dev->GetServiceListBegin(); int srvcount = dev->GetServiceListCount(); for(int i = 0; i < srvcount; ++i, ++srvi) const_cast<Service*>(*srvi)->SetCallbackClient(this); } break; // for find service containing given action name case PID_FIND_ACTION: { const wstring& actname = *(const wstring*)param; ServiceIterator srvi = dev->GetServiceListBegin(); int srvcount = dev->GetServiceListCount(); // enumerate member services of given device for(int i = 0; i < srvcount; ++i, ++srvi) { // check if current service contains requested action // if so, then save pointer. // btw, sorry for this "exceptional coding" style :) try { _action = &(*srvi)->GetAction(actname); break; } catch(...) { } } } break; } } // IFinderCallbackClient interface implementation virtual void DeviceAdded(long findid, IUPnPDevice* idev) { // called by UPnP framework when the new device was found // create new root Device object and build its structure Device* dev = new Device(idev); // add callback for services events dev->EnumerateDevices(this, 0, PID_ADD_SERVICE_CALLBACK); // add Device root object to collection _devs.push_back(dev); wcout << L"\nNew device added: " << dev->GetFriendlyName() << endl; // display info about added device InfoData devdata; if(dev->GetDeviceInfo(devdata)) { for(InfoIterator ii = devdata.begin(); ii != devdata.end(); ++ii) wcout << endl << (*ii).first << L" = " << (*ii).second; } wcout << L"\nSearching is being continued" << flush; } virtual void DeviceRemoved(long findid, const wstring& devname) { // called by UPnP framework when the device was removed from collection for(DeviceArray::size_type i = 0; i < _devs.size(); ++i) { if(devname == _devs[i]->GetUDN()) { wstring friendlyname = _devs[i]->GetFriendlyName(); // remove passed device from devices collection delete _devs[i]; _devs.erase(_devs.begin() + i); wcout << L"\nDevice was removed: " << friendlyname << L"\nSearching is being continued" << flush; break; } } } virtual void SearchComplete(long findid) { // called by UPnP framework when finding was completed successfully wcout << L"\nSearch is complete" << endl; // when search is complete (not cancelled) // let's break loop and finish application wcout << L"Searching will be cancelled" << endl; this->StopSearch(); } // IServiceCallbackClient interface implementation virtual void ServiceEventVariableChanged(const Service* srv, const wstring& varname, const wstring& varvalue) { // called by service callback when value of state variable has been changed // get parent device name wstring devparent = srv->GetParentDevice().GetFriendlyName(); // display device's name, service's id, name of state variable // and its current value wcout << L"\n==> Event fired:\n\tfrom service id: " << srv->GetServiceID() << L"\n\tparent device: " << devparent << L"\n\tsource state variable name: " << varname << L"\n\tcurrent state variable value: " << varvalue << endl; if(_searching) wcout << L"Searching is being continued" << flush; // when all notifications are received then // post WM_QUIT message to message loop, to finish it if(_finish) PostQuitMessage(0); } virtual void ServiceEventInstanceDied(const Service* srv) { // called by service callback when service is not responding wcout << L"\nService died: " << srv->GetServiceID() << L"\nIt was member of device: " << srv->GetParentDevice().GetFriendlyName() << L"\nSearching is being continued" << flush; } void RemoveAllDevices() { if(!_devs.empty()) { for(vector<Device*>::iterator di = _devs.begin(); di != _devs.end(); ++di) { delete *di; *di = 0; } _devs.clear(); } } private: bool _searching; // helps break message loop bool _finish; // helps break message loop FindManager* _fm; // object of class from UPnPCpLib library DeviceArray _devs; // devices collection const Action* _action; // requested action name }; int _tmain(int argc, _TCHAR* argv[]) { // initialize COM library. // in console application, COM library is initialized // to use single threaded concurrency model if(CoInitializeEx(0, COINIT_APARTMENTTHREADED) != S_OK) { CoUninitialize(); return -1; } wcout << L"COM library initialized" << endl; // initialize Winsock library WSADATA wdata; WSAStartup(MAKEWORD(2, 2), &wdata); wcout << L"Winsock library initialized\n" L"Creating FinderHost object" << endl; // create FinderHost object which hosts FinderManager FinderHost* fhost = new FinderHost(); // start finding all root devices wcout << L"Start finding" << flush; if(fhost->StartSearch()) { // process messages MSG Message; while(GetMessage(&Message, NULL, 0, 0)) { DispatchMessage(&Message); wcout << '.' << flush; // when FinderHost receives "search complete" notification // its function IsSearching returns false // and FinderHost will be destroyed and application finished if(!fhost->IsSearching() && !fhost->IsFinished()) { wcout << L"\nSearching has been cancelled successfully" << endl; // let's execute some actions wstring name; StrList args; // this action doesn't return any output arguments // but fires event from state variable PortMappingNumberOfEntries name = L"AddPortMapping"; args.push_back(L"any"); // remote host, type: string args.push_back(L"11200"); // external port, type: ui2 args.push_back(L"UDP"); // protocol, type: string args.push_back(L"11200"); // internal port, type: ui2 args.push_back(L"192.168.0.10"); // internal client, type: string args.push_back(L"true"); // enabled, type: boolean args.push_back(L"add port mapping example"); // description, type: string args.push_back(L"0"); // lease duration (0 = permanent), type: ui4 fhost->InvokeAction(0, name, args); // this action also doesn't return any output arguments // but fires event from state variable PortMappingNumberOfEntries name = L"DeletePortMapping"; args.clear(); args.push_back(L"any"); // remote host, type: string args.push_back(L"11200"); // external port, type: ui2 args.push_back(L"UDP"); // protocol, type: string fhost->InvokeAction(0, name, args); wcout << L"\nFinderHost finished work and will be destroyed" << endl; // in FinderHost ServiceEventVariableChanged WM_QUIT message is posted // to break this loop fhost->SetFinish(); //delete fhost; //fhost = 0; } } } delete fhost; wcout << L"Cleaning up Winsock library" << endl; // free Winsock library WSACleanup(); wcout << L"Cleaning up COM library" << endl; // free COM library CoUninitialize(); wcout << L"Exiting... bye" << endl; return 0; }
Above program can print out on the console the following results:
COM library initialized Winsock library initialized Creating FinderHost object FinderHost was created successfully Start finding............ New device added: Wireless-G ADSL Home Gateway (uuid:8c59ad37-1dd2-11b2-ada1-0018398b6752) Description = LINKSYS WAG200G Router Document URL = http://10.0.0.1:49152/gateway.xml Friendly name = Wireless-G ADSL Home Gateway (uuid:8c59ad37-1dd2-11b2-ada1-0018398b6752) Manufacturer = LINKSYS Manufacturer URL = http://www.linksys.com/ Model = Wireless-G ADSL Home Gateway Model URL = http://www.linksys.com/ Model number = WAG200G Presentation URL = http://10.0.0.1/index.htm Serial number = 123456789 Type = urn:schemas-upnp-org:device:InternetGatewayDevice:1 UDN = uuid:8c59ad37-1dd2-11b2-ada1-0018398b6752 UPC = WAG200G Searching is being continued......... ==> Event fired: from service id: urn:upnp-org:serviceId:L3Forwarding1 parent device: Wireless-G ADSL Home Gateway (uuid:8c59ad37-1dd2-11b2-ada1-0018398b6752) source state variable name: DefaultConnectionService current state variable value: Searching is being continued........... ==> Event fired: from service id: urn:upnp-org:serviceId:WANCommonIFC1 parent device: Internet Connection Sharing (uuid:8c59ad36-1dd2-11b2-ada1-0018398b6752) source state variable name: PhysicalLinkStatus current state variable value: Up Searching is being continued........... ==> Event fired: from service id: urn:upnp-org:serviceId:WANEthLinkC1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: EthernetLinkStatus current state variable value: Up Searching is being continued........... ==> Event fired: from service id: urn:upnp-org:serviceId:WANPPPConn1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: PossibleConnectionTypes current state variable value: IP_Routed Searching is being continued......... ==> Event fired: from service id: urn:upnp-org:serviceId:WANPPPConn1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: ConnectionStatus current state variable value: Connected Searching is being continued......... ==> Event fired: from service id: urn:upnp-org:serviceId:WANPPPConn1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: X_Name current state variable value: Local Area Connection Searching is being continued......... ==> Event fired: from service id: urn:upnp-org:serviceId:WANPPPConn1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: ExternalIPAddress current state variable value: 57.154.192.91 Searching is being continued......... ==> Event fired: from service id: urn:upnp-org:serviceId:WANPPPConn1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: PortMappingNumberOfEntries current state variable value: 2 Searching is being continued..... Search is complete Searching will be cancelled . Searching has been cancelled successfully Invoke action: AddPortMapping action result: Invoke action: DeletePortMapping ==> Event fired: from service id: urn:upnp-org:serviceId:WANPPPConn1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: PortMappingNumberOfEntries current state variable value: 3 action result: FinderHost finished work and will be destroyed ..... ==> Event fired: from service id: urn:upnp-org:serviceId:WANPPPConn1 parent device: Internet Connection Sharing (uuid:8c59ad37-1dd2-11b2-ada0-0018398b6752) source state variable name: PortMappingNumberOfEntries current state variable value: 2 . FinderHost has been destroyed Cleaning up Winsock library Cleaning up COM library Exiting... bye
WinForms control point application (Finder.net)
Finally, I would like to present an example of control point application with GUI of type Windows Forms and written in C#. This .net application uses native UPnPCpLib library through the .net class library (FindManager.net dll) which wraps native code. However, due to significantly greater amount of code, in comparison to simple Win32 examples listed above, instead of code I will present screens from application and I'll cover its functions. The source code of application and library is available for download in the form of Visual Studio 2008 projects (Finder.net and FindManager.net). In addition, application’s executable files are attached. Beside WinForms application the MFC version is also available for download (Finder). More examples related to programming control points can be found on MSDN [1] and in Platform SDK (default in path %ProgramFiles%\Microsoft Platform SDK\Samples\NetDS\upnp\GenericUCP\cpp). Moreover, many code samples, tools and other helper stuff you can download from Intel website [11].
In the sample WinForms application I tried to cover as much as possible range of functionality of UPnP control point. My intention was also to create an application simple and useful. Did I succeeded in achieve this intention? Let the readers (users) decide.
Application’s features:
- discovering devices by type (asynchronously),
- creation of objects corresponding to structure and functionality of discovered devices,
- managing a collection of devices objects,
- presentation of objects structure,
- displaying information about elements of a device object,
- controlling devices via actions,
- receiving events notifications generated by services,
- viewing and saving the description document,
- opening URLs related to devices and services,
- management of ssdpsrv system service,
- management of ports used by ssdpsrv service,
- editing configuration entries in the system registry.
The program searches for the specified type of device. If you choose the type "upnp:rootdevice", the scope of the search will cover devices of all basic types. On picture, we can see that the program has found several devices of different types. We can see also information (displayed in the "Event Log" window) coming from the events received during detection of new device.

Type of devices which will be searched can be specified with the help of the filter.

Let's see how to invoke actions on the example of device of type Internet Gateway Device (router). Actions concern configuration of "Port Forwarding" feature. First, I add a new entry to the port mapping table.

Sequence of steps is marked on illustration. (1) Select the name of action, (2) write values of input arguments (names and types of arguments are displayed in both marked windows. (3) After execution of the action, in the "Event Log" window, check the status of action (200 OK) and the current number of entries in the table - "Variable value: 3". When determining the type of argument might be useful the table above.
Now check the content of new entry in the mapping table specifying the number of entry. Previously we read the number of entries (3 entries), thus last will be the number 2 (the first is the number 0).

In action’s response we receive content of the desired entry - in the "Properties" window under "Output arguments" caategory or in the "Action's arguments" window.
And finally, action of remove previously added entry.

The scenario is similar for case of adding an entry. (1) After selecting the appropriate action, (2) write values of input arguments in "Properties" window. (3) After action invoking, in events window, check the status of action invoke (200 OK) and the current number of entries – "Variable value: 2".
In the options window, you can change the configuration parameters of UPnP framework stored in the system registry.

Managed wrapper over UPnPCpLib library (FindManager.net)
FindManager.net is a class library which wraps native UPnPCpLib library. This class library can be used with any .net application. To do this just add reference to FindManager.net.dll and declare "FindManagerWrapper" namespace name. Class library consists of four main classes:
- FindManagerNet,
- DeviceNet,
- ServiceNet,
- ActionNet.
These classes corresponds to their native "brothers", respectively FindManager, Device, Service and Action. The basic class is FindManagerNet which serves as devices finder and manages collection of devices objects. Detailed informations about general functionality of these classes are contained above in description of UPnPCpLib framework.
FindManagerNet class
- constructors
FindManagerNet() | Default constructor. Appropriate for console application when COM library was initialized for use single threaded model. It is recommended to avoid using this constructor in GUI applications. |
FindManagerNet(SynchronizationContext) | Use this constructor in GUI applications when COM library was initialized for use multi threaded model. In this case library's functions called by UPnP framework will be executed in separate threads. Thanks to that constructor events handlers executed in separate threads can safely access controls created in main GUI thread without the need to use InvokeRequired. |
- properties
Device[int] | Returns device object from collection at specified index. |
CollectionSize | Gets number of devices in collection. |
IsCollectionEmpty | Checks if there are any devices in collection. |
FindId | Gets search session identifier. |
DeviceTypeCount | Gets number of names of devices types in array. (static) |
DeviceTypes | Returns array of names of devices types. (static) |
RootDeviceType | Gets name of basic type of device. (static) |
DeviceType[int] | Gets name of type at specified index in array. (static) |
- methods
Init(String) | Initializes FindManager object and uses specified type of device while searching for devices. Returns true if succeeded. |
Init() | Initializes FindManager object and uses "upnp:rootdevice" type of device while searching for devices. All basic types will be searched. Returns true if succeeded. |
Start | Starts searching. Returns true if succeeded. |
Stop | Cancels searching. Returns true if succeeded. |
GetEnumerator | Returns enumerator for devices collection. |
GetCollection | Returns collection of detected devices. |
- events
OnStartFind | Occurs when search for devices has been started. Passes to handler argument of type StartFindArgs which contains info about identifier of search session. |
OnStopFind | Occurs when search for devices has been cancelled. Passes to handler argument of type StopFindArgs which contains info about identifier of search session and the reason of cancelling the search. |
OnAddDevice | Occurs when new device is added to collection. Passes to handler argument of type AddDeviceArgs which contains identifier of search session, device object and index of this object in collection. |
OnRemoveDevice | Occurs when device is removed from collection. Passes to handler argument of type RemoveDeviceArgs which contains identifier of search session, unique and friendly name of device object and last index of this object in collection. |
OnServiceVariableChanged | Occurs when state variable's value has been changed. Passes to handler argument of type ServiceVariableChangedArgs which contains service object and state variable's name and value. |
OnServiceInstanceDied | Occurs when service is not responding. Passes to handler argument of type ServiceInstanceDiedArgs which contains service object. |
- static helper methods
GetErrorMessage | Converts hresult of action's invokation to message string. (HRESULT to string) |
GetVariantType | Converts description of state variable type to variant type. (string to VARTYPE) |
GetTypeDescription | Converts variant type to description of state variable type. (VARTYPE to string) |
IsICSConnectionEnabled | Checks for Internet Connection Sharing enabled connections. |
CheckSystemServiceState | Checks specified system service state. |
ControlSSDPService | Starts or stops ssdp system service. |
CheckFirewallPortState | Checks specified firewall port state. Returns: 0 - error, 1 - enabled, -1 - disabled. |
ControlUPnPPorts | Opens or closes UPnP ports (2869 TCP, 1900 UDP). |
DeviceNet class
- properties
UniqueName | Returns Unique Device Name (UDN) of device. |
FriendlyName | Returns descriptive name of device. |
Type | Gets type of device. |
ParentDevice | Returns object of parent device. Null for root device. |
RootDevice | Returns parent of all devices in hierarchy. |
IsRoot | Checks if device is root device. |
DocumentURL | Gets URL of description document. |
Info | Gets properties of device. |
Device[int] | Returns device at specified index in devices collection. |
DeviceCount | Gets number of devices in collection. |
IsDeviceCollectionEmpty | Checks if there are any devices in collection. |
Service[int] | Returns service at specified index in collection of services. |
ServiceCount | Gets number of services in collection. |
Icon[int] | Returns icon at specified index in collection of icons. |
IconCount | Gets number of icons in collection. |
IsIconCollectionEmpty | Checks if there are any icons in collection. |
EnumeratorSwitch | Gets or sets the value of enum EnumSwitch (one of: Devices, Services, Icons) indicating which collection (respectively devices or services or icons) will be enumerated with the help of enumerator obtained by GetEnumerator method. |
- methods
GetEnumerator | Returns enumerator for collection which is indicated by the current value of EnumeratorSwitch property. Which collection is currently enumerated depends on enumerator switch. |
GetDevices | Returns array of devices objects. |
GetServices | Returns array of services objects. |
GetIcons | Returns array of icons objects. |
GetDocumentContent | Returns content of description document. |
GetIconURL | Returns URL of icon with specified parameters (width, height, depth). |
EnumerateDevices | Processes device structure and for each member device OnProcessDevice event is raised. You can pass to this method two helper arguments of general purpose which will be passed as arguments to handler of OnProcessDevice event. Both arguments can be null. |
- events
OnProcessDevice | Raised for each member device while processing structure of root device with the help of EnumerateDevices method. Passes to handler argument of type ProcessDeviceArgs which contains device object and two arguments of general purpose. |
ServiceNet class
- properties
Action[int] | Gets action object at specified index in array of actions. |
Action[String] | Gets action object about specified name in array of actions. |
ActionsCount | Gets number of actions. |
StateVariables | Gets collection of state variables. Element of collection contains variable's name and boolean value indicating that variable sends events. |
Info | Gets service's properties. |
ServiceID | Gets service's name. |
ServiceTypeID | Gets service's type. |
LastTransportStatus | Gets status code of last action. |
ParentDevice | Gets parent device. |
ScpdURL | Gets URL of description document. |
- methods
GetEnumerator | Returns enumerator for actions collection. |
GetActions | Returns array of actions objects. |
GetScpdContent | Returns content of description document. |
ActionNet class
- properties
Name | Gets name of action. |
ArgumentCount | Gets number of action's input and output arguments. |
ArgumentInCount | Gets number of action's input arguments. |
ArgumentsIn | Sets input arguments. |
Argument | Sets input argument at specified index in array of input arguments. |
Arguments | Gets input arguments. |
Info | Gets action's properties. |
ParentService | Gets parent service object. |
- methods
Invoke | Invokes this action on parent UPnP service. argsout - array of values of output arguments and returned value or error message. On error argsout contains only error messages. Returns: -1 on error, >0 means number of output arguments + returned value (if any), -2 if argsout contains only returned value. |
Using the FindManagerNet
First of all I would like to notice that FindManagerNet
object uses asynchronous method of searching for devices what means that events raised from system UPnP framework and received by FindManagerNet
object can be executed in separate threads. Thus, it is better to use FindManagerNet
when COM library is configured to use multi threaded model of concurrency. Default, COM library used by .net applications is initialized to use single threaded concurrency model. Which model will be used, it depends from attribute applied to entry point of application. This entry point is Main
method in C# and Visual Basic. For use multithreaded model the attribute should be set to [MTAThread]
whereas default singlethreaded model is used when the attribute is set to [STAThread]
.
Default setting of COM threading model for C# application - singlethreaded (in file Program.cs).
static class Program { // The main entry [STAThread] static void Main() { // ... body of Main } }
Setting of COM threading model for C# application - multithreaded (in file Program.cs).
static class Program { // The main entry [MTAThread] static void Main() { // ... body of Main } }
In GUI application when COM threading model is set to multithreaded, to create FindManagerNet
object You should use constructor taking argument of type SynchronizationContext
. Thanks to that constructor events handlers executed in separate threads can safely access controls created in main GUI thread without the need to use InvokeRequired
. FindManagerNet
object should be created inside of main form's thread where this object is used. The point is that FindManagerNet
object can't be static when constructor FindManagerNet(SynchronizationContext)
is used. The property System.Threading.SynchronizationContext.Current should be called in main GUI thread where handlers of events from FindManagerNet
object (like OnAddDevice) can access controls on form.
To create FindManagerNet
object, first, define reference of FindManagerNet
inside class of form:
public partial class Form1 : Form { FindManagerNet finder; // ... rest of class body }
Next, instantiate FindManagerNet
object with the help of constructor which takes argument of type SynchronizationContext
- in constructor of form:
public Form1() { InitializeComponent(); finder = new FindManagerNet(SynchronizationContext.Current); // ... remaining instructions }
After completion of above basics steps You can set up handlers for events and initialize FindManagerNet
object as shown in following example in which will be invoked actions of adding and removing entries from port mapping table of Internet Gateway Device.
using FindManagerWrapper; namespace test_finder { public partial class Form1 : Form { // define finder object's reference FindManagerNet _fm; // service which contains needed actions // for adding and deleting port entries ServiceNet _srv; public Form1() { InitializeComponent(); // create finder and set up needed handlers _fm = new FindManagerNet(SynchronizationContext.Current); _fm.OnServiceVariableChanged += new ServiceVariableChangedEvent(_fm_OnServiceVariableChanged); _fm.OnAddDevice += new AddDeviceEvent(_fm_OnAddDevice); } void _fm_OnAddDevice(object sender, AddDeviceArgs e) { // when first device has been found // then stop searching and find appropriate service _fm.Stop(); // get found device object DeviceNet dev = e.Device; string requestedService = "urn:upnp-org:serviceId:WANPPPConn1"; // enumerate member devices for find requested service dev.OnProcessDevice += new ProcessDeviceEvent(dev_OnProcessDevice); dev.EnumerateDevices(requestedService, 0); dev.OnProcessDevice -= new ProcessDeviceEvent(dev_OnProcessDevice); } void dev_OnProcessDevice(object sender, ProcessDeviceArgs e) { // if requested service has been found // then save reference string requestedService = (string)e.Param; // switch collection which will be enumerated // default collection of devices is enumerated e.Device.EnumeratorSwitch = EnumSwitch.Services; foreach(ServiceNet srv in e.Device) if (srv.ServiceID == requestedService) { _srv = srv; break; } } void _fm_OnServiceVariableChanged(object sender, ServiceVariableChangedArgs e) { // display current number of port mappings if(e.VarName == "PortMappingNumberOfEntries") labelNumber.Text = e.VarValue; } // start private void button1_Click(object sender, EventArgs e) { _fm.Init(FindManagerNet.DeviceTypes[4]); // InternetGatewayDevice _fm.Start(); } // stop private void button2_Click(object sender, EventArgs e) { _fm.Stop(); } // add entry private void button3_Click(object sender, EventArgs e) { if (_srv != null) { ActionNet act = _srv.get_Action("AddPortMapping"); // required input arguments act.ArgumentsIn = new List<string> {"any", "11200", "UDP", "11200", "10.0.0.2", "true", "test add entry", "0"}; // this action doesn't returns any output arguments // this action causes change of state variable PortMappingNumberOfEntries // and raise of event, thus OnServiceVariableChanged will be called List<KeyValuePair<string, string>> outs = null; act.Invoke(ref outs); } } // delete entry private void button4_Click(object sender, EventArgs e) { if (_srv != null) { ActionNet act = _srv.get_Action("DeletePortMapping"); // required input arguments act.ArgumentsIn = new List<string> { "any", "11200", "UDP" }; // this action doesn't returns any output arguments // this action causes change of state variable PortMappingNumberOfEntries // and raise of event, thus OnServiceVariableChanged will be called List<KeyValuePair<string, string>> outs = null; act.Invoke(ref outs); } } } }
History
- 07 Jul 2009
Fixed issue with long time finding device when found device is "software" device on the same computer where control point application using UPnPCpLib library is running. Added description of using FindManagerNet class. - 17 Jun 2009
Rewrited library's code. Added new STL version of library. Added managed wrapper over native library. Added managed version of "Finder" application and new version of MFC application. - 28 Jun 2008
Initial release.
References
- UPnP APIs, MSDN, http://msdn.microsoft.com/en-us/library/aa382303(VS.85).aspx[^]
- NAT API, MSDN, http://msdn.microsoft.com/en-us/library/aa366187(VS.85).aspx[^]
- UPnP Forum, http://www.upnp.org/[^]
- Using UPnP for Programmatic Port Forwardings and NAT Traversal, Mike O'Neill, PortForward.aspx[^]
- RFC 1067 (SNMP), IETF, http://tools.ietf.org/html/rfc1067[^]
- RFC 3489 (STUN), IETF, http://tools.ietf.org/html/rfc3489[^]
- SSDP, IETF, http://tools.ietf.org/html/draft-cai-ssdp-v1-03[^]
- Description of Universal Plug and Play Features in Windows XP, Microsoft, http://support.microsoft.com/kb/323713[^]
- Universal Plug and Play in Windows XP, MS TechNet, http://technet.microsoft.com/en-us/library/bb457049.aspx[^]
- How Windows Firewall affects the UPnP framework in Windows XP Service Pack 2, Microsoft, http://support.microsoft.com/kb/886257[^]
- Intel Software for UPnP Technology, Intel, http://www.intel.com/cd/ids/developer/asmo-na/eng/downloads/upnp/index.htm[^]
- CMarkup software, First Objective Software, Inc., http://www.firstobject.com/dn_markup.htm[^]