|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Contents
IntroductionThe 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 application 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 ATL 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 TechnologyUPnP (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 XPUPnP 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 APIUPnP 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 APIUsing the Control Point API we will have to deal with three basic objects closely related to UPnP device architecture:
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 Interfaces related to Device Finder objectControl 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.
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(NULL, COINIT_APARTMENTTHREADED); if(SUCCEEDED(hr)) { // ... code using COM library } CoUninitialize(); Let us return to the Device Finder object. After creating an object we can access it through the 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 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 implementation #include <atlbase.h> #include <atlcom.h> // CComObjectRootEx #include <upnp.h> // IUPnPDeviceFinderCallback class DevFinderCallback : public CComObjectRootEx<CComSingleThreadModel>, public IUPnPDeviceFinderCallback { public: DevFinderCallback() {}; ~DevFinderCallback() {}; BEGIN_COM_MAP(DevFinderCallback) COM_INTERFACE_ENTRY(IUPnPDeviceFinderCallback) END_COM_MAP() virtual HRESULT __stdcall DeviceAdded(long finddata, IUPnPDevice* idev) { // process IUPnPDevice pointer when device was added return S_OK; } virtual HRESULT __stdcall DeviceRemoved(long finddata, BSTR devudn) { // do something when device was removed return S_OK; } virtual HRESULT __stdcall SearchComplete(long finddata) { // do something when search is complete return S_OK; } };
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 ( The next example shows how to create a callback object of // Instantiate the callback object // for CComObject::CreateInstance // declare this variable as global in your source file CComModule _Module; CComObject<DevFinderCallback> *cback = NULL; // if returned pointer is equal to NULL // it means then error occured in function CreateInstance CComObject<DevFinderCallback>::CreateInstance(&cback); if(cback != NULL) // ref count is 0 thus increment, be sure to release on finish 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 = NULL; hr = CoCreateInstance(CLSID_UPnPDeviceFinder, NULL, CLSCTX_SERVER, IID_IUPnPDeviceFinder, (LPVOID*)&_ifinder); if(SUCCEEDED(hr)) { // 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 // start asynchronous find for all root devices #include <atlbase.h> // CComBSTR #include <upnp.h> HRESULT hr; // Device Finder object interface IUPnPDeviceFinder* _ifinder = NULL; // device type to find CComBSTR devtype(L"upnp:rootdevice"); // search identifier long _findhandle; hr = CoCreateInstance(CLSID_UPnPDeviceFinder, NULL, CLSCTX_SERVER, IID_IUPnPDeviceFinder, (LPVOID*)&_ifinder); if(SUCCEEDED(hr)) { // prepare search // cback is DevFinderCallack pointer obtained earlier hr = _ifinder->CreateAsyncFind(devtype, NULL, cback, &_findhandle); if(SUCCEEDED(hr)) { // start search hr = _ifinder->StartAsyncFind(_findhandle); if(SUCCEEDED(hr)) { // do something if success or simply return } else // if failed then cancel search _ifinder->CancelAsyncFind(_finderhandle); } } 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 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: // destroing objects // release DevFinderCallback object if(cback != NULL) cback->Release(); // release Device Finder object if(_ifinder != NULL) { _ifinder->CancelAsyncFind(_finderhandle); _ifinder->Release(); } Interfaces related to Device objectThe next group is related to Device object:
The most important in this group is Furthermore, the
With Let’s see in example the process of identifying device’s structure. // examination of structure of root device #include <atlbase.h> // CComBSTR #include <upnp.h> // idev is pointer to root device void EnumerateDevices(IUPnPDevice* idev) { HRESULT hr = S_OK; IUPnPDevices* children = NULL; // collection of member devices VARIANT_BOOL bcheck = 0; // enumerate devices // first check if device has children idev->get_HasChildren(&bcheck); // if device has children then get collection interface if(bcheck && idev->get_Children(&children) == S_OK) { IUnknown* ienum = NULL; // for obtain the enumerator long devscount = 0; // count of member devices // get count of member devices children->get_Count(&devscount); // get helper interface if(children->get__NewEnum(&ienum) == S_OK) { IEnumUnknown* icol = NULL; // collection enumerator // get enumerator interface hr = ienum->QueryInterface(IID_IEnumUnknown, (void**)&icol); if(SUCCEEDED(hr)) { IUnknown* iitem = NULL; // collection’s item IUPnPDevice* ichild = NULL; // member device CComBSTR btmp; // enumerate collection icol->Reset(); while(icol->Next(1, &iitem, NULL) == S_OK) { // get member device interface hr = iitem->QueryInterface(IID_IUPnPDevice, (void**)&ichild); if(SUCCEEDED(hr)) { // get some member device’s properties btmp.Empty(); ichild->get_UniqueDeviceName(&btmp); btmp.Empty(); ichild->get_PresentationURL(&btmp); // continue recursive EnumerateDevices(ichild); ichild->Release(); } iitem->Release(); } icol->Release(); } ienum->Release(); } children->Release(); } } Description documentOne 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 Let's see how to obtain access to the description document. Helpful interface in this task is // retrieving of description document URL #include <atlbase.h> // CComBSTR #include <upnp.h> // idev is pointer to root device void GetDeviceDocumentAccessURL(IUPnPDevice* idev) { HRESULT hr = S_OK; IUPnPDeviceDocumentAccess* idoc = NULL; // URL will be stored into this variable CComBSTR btmp; // query for IUPnPDeviceDocumentAccess hr = idev->QueryInterface(IID_IUPnPDeviceDocumentAccess, (void**)&idoc); if(SUCCEEDED(hr)) { // get URL and write to CComBSTR idoc->GetDocumentURL(&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 consists in the use of the Interfaces related to Service objectLast group of interfaces belonging to Control Point API is related to Service object.
Access to the Service object can be obtained only through a Device object. The primary interface of Service object is // retrieving of service objects #include <atlbase.h> // CComBSTR #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 = NULL; // for obtain the enumerator IUPnPServices *isrvs = NULL; // the collection of services // get collection interface if(idev->get_Services(&isrvs) != S_OK) return; // get count of services isrvs->get_Count(&srvcount); // get helper interface if(isrvs->get__NewEnum(&ienum) == S_OK) { IEnumUnknown *icol = NULL; // the enumerator of collection hr = ienum->QueryInterface(IID_IEnumUnknown, (void**)&icol); if(SUCCEEDED(hr)) { IUnknown *iitem = NULL; // collection’s item IUPnPService *isrv = NULL; // service object CComBSTR btmp; // enumerate collection icol->Reset(); while(icol->Next(1, &iitem, NULL) == S_OK) { // get interface to service object hr = iitem->QueryInterface(IID_IUPnPService, (void**)&isrv); if(SUCCEEDED(hr)) { // do something with service object // for example get service id btmp.Empty(); isrv->get_Id(&btmp); isrv->Release(); } iitem->Release(); } icol->Release(); } ienum->Release(); } isrvs->Release(); } The Using 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 ( The main problem with the 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 document files 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:
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> // initialize Winsock library WSADATA wsadata; WSAStartup(MAKEWORD(2, 2), &wsadata); // path of document from its URL char 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 char rbuff[16384] = {0}; // receive buffer int recvbufflen = sizeof(rbuff); // receive buffer length char reqbuff[4096] = {0}; // request buffer char host[1024] = {0}; // Host part of HTTP GET command int b = 0; // bytes currently received int tb = 0; // bytes totally received int datalen = 0; // pure requested data length // prepare Host part of HTTP GET command u_short port = ntohs(addr.sin_port); if(port > 0 && port < 65535) sprintf(host, "%s:%d", inet_ntoa(addr.sin_addr), port); else strcpy(host, inet_ntoa(addr.sin_addr)); // prepare string of HTTP GET command sprintf(reqbuff, "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", path, host); // create TCP socket SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // connect socket connect(s, (sockaddr*)&addr, sizeof(sockaddr_in)); // send request data send(s, reqbuff, strlen(reqbuff), 0); // receive response data - document while(b = recv(s, rbuff + tb, recvbufflen - tb, 0)) { if(b == SOCKET_ERROR) break; tb += b; } // check if response data has correct format if(tb > 0) // reveive succeeded { char header[4096] = {0}; // copy first line of header response to header variable strncpy(header, rbuff, strcspn(rbuff, "\r\n")); // check if response code is 200 if(strstr(header, "200 OK") != NULL) { char *databegin = NULL; // get pointer to begining of actual document databegin = strstr(rbuff, "\r\n\r\n") + strlen("\r\n\r\n"); if(databegin != NULL) { // length of document datalen = tb - (databegin - rbuff); if(datalen > 0) { // do something with document in buffer (rbuff) // databegin points to begining of document in rbuff // and datalen is length of document in bytes } } } } // close connection shutdown(s, SD_SEND); while(b = recv(s, rbuff, recvbufflen, 0)) if(b == SOCKET_ERROR) break; closesocket(s); // 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 While using the In the following example I will show you how to use the
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 I assume that earlier I received from the Device Finder object the Device object in the form of a pointer to // Invoking service’s action // isrv is pointer to service’s interface (IUPnPService *) #include <atlbase.h> // CComBSTR #include <atlcomcli.h> // CComVariant #include <atlsafe.h> // CComSafeArray #include <upnp.h> HRESULT hr = S_OK; CComVariant vaInArgs; // Invoke argument in // set type of VARIANT, must be array of VARIANTs vaInArgs.vt = VT_ARRAY | VT_VARIANT | VT_BYREF; CComVariant vaOutArgs; // Invoke argument out CComVariant vaRetVal; // Invoke argument ret CComVariant vaInVal; // temp input value CComSafeArray<VARIANT> psaInArgs; // array for input arguments of action CComVariant vaOutVal; // temp output value CComSafeArray<VARIANT> psaOutArgs; // array for output values of action // create input array, default elements=0 and lbound=0 // will be destroyed automatically when goes out of scope hr = psaInArgs.Create(); if(SUCCEEDED(hr)) { // fill input array, there is three arguments // first argument of type string (VT_BSTR) – remote host vaInVal.vt = VT_BSTR; // set type vaInVal.bstrVal = ::SysAllocString(L””); // may be empty string psaInArgs.Add(vaInVal); // add argument to input array vaInVal.Clear(); // second argument of type ui2 (VT_UI2) – external port vaInVal.vt = VT_UI2; // set type vaInVal.uiVal = 8839; // external port number psaInArgs.Add(vaInVal); vaInVal.Clear(); // third argument of type string (VT_BSTR) – port mapping protocol vaInVal.vt = VT_BSTR; // set type vaInVal.bstrVal = ::SysAllocString(L”TCP”); // protocol, one of allowed values psaInArgs.Add(vaInVal); // add argument to input array // assign input array to input argument of InvokeAction vaInArgs.pparray = psaInArgs.GetSafeArrayPtr(); // action name CComBSTR actionName(L”GetSpecificPortMappingEntry”); // invoke action hr = isrv->InvokeAction(actionName, vaInArgs, &vaOutArgs, &vaRetVal); if(SUCCEEDED(hr)) { // process data from vaOutArgs – action’s output values // get array of output values of action hr = psaOutArgs.Attach(vaOutArgs.parray); if(SUCCEEDED(hr)) { // count of output values int arrlen = psaOutArgs.GetCount(); Cstring strTemp; // temporary string for returned values // read successive returned values of action for(int i = 0; i < arrlen; ++i) { vaOutVal.Clear(); vaOutVal = psaOutArgs[i]; // get array item (value) if(vaOutVal.vt != VT_EMPTY) { hr = vaOutVal.ChangeType(VT_BSTR); if(SUCCEEDED(hr)) { // write value to temp string strTemp = vaOutVal; // do something with returned value } } } psaOutArgs.Detach(); } // data from vaRetVal if(vaRetVal.vt != VT_EMPTY) { hr = vaRetVal.ChangeType(VT_BSTR); if(SUCCEEDED(hr)) { Cstring strRetVal(vaRetVal); // do something with returned value } } } else { // read error code from InvokeAction } } 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
At the end of considerations about the I mentioned earlier one more interesting function of To receive notifications of events generated by the services is used the callback object, which should be created from class implementing // IUPnPServiceCallback implementation #include <atlbase.h> #include <atlcom.h> // CComObjectRootEx #include <upnp.h> // IUPnPServiceCallback class ServiceEventCallback : public CComObjectRootEx<CComSingleThreadModel>, public IUPnPServiceCallback { public: ServiceEventCallback() {}; ~ServiceEventCallback() {}; BEGIN_COM_MAP(ServiceEventCallback) COM_INTERFACE_ENTRY(IUPnPServiceCallback) END_COM_MAP() 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 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 // Instantiate and registering the callback object for receiving events notifications // isrv is pointer to service’s interface (IUPnPService *) // for CComObject::CreateInstance // declare this variable as global in your source file CComModule _Module; CComObject<ServiceEventCallback> *cback = NULL; // if returned pointer is equal to NULL // it means then error occured in function CreateInstance CComObject<ServiceEventCallback>::CreateInstance(&cback); if(cback != NULL) { // ref count 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 UPnPCpLib frameworkFor 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 Win32 applications as well as the MFC applications. The library consists of a header file 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 source files. UPnPCpLib library contains three main classes defining the three most important objects of UPnP architecture.
Besides, contains two helper classes:
The general scheme of relationships and functions of library’s objects.
FindManager classThe class FindManager : public IFinderCallbackClient, IProcessDevice { public: FindManager(); ~FindManager(); void CreateCallback(IFinderCallbackClient* client); void ReleaseCallback(); bool IsReady(); bool Start(CComBSTR devicetype = L"upnp:rootdevice"); bool Stop(); DevicesArray* GetCollection(); void SetClientPtr(IFinderManagerClient* client); void SetServiceEventClientPtr(IServiceCallbackClient* client); // IProcessDevice implementation virtual void ProcessDevice(Device* dev, void* param, int procid); // IFinderCallbackClient implementation virtual void DeviceAdded(long finddata, IUPnPDevice* idev); virtual void DeviceRemoved(long finddata, BSTR devname); virtual void SearchComplete(long finddata); private: DevFinderCallback* _findercallback; // IUPnPDeviceFinderCallback * IUPnPDeviceFinder* _ifinder; // Device Finder long _finderhandle; // Device Finder’s find handle DevicesArray _devs; // devices collection IFinderManagerClient* _findermanagerclient; IServiceCallbackClient* _srveventclient; };
In the case when we want to
Device class
class Device { public: Device(CComBSTR udname = L"root", Device* parentdev = 0); ~Device(); void GetUDN(BSTR* udname); void GenerateFriendlyName(); CString GetFriendlyName(); Service* AddService(CComBSTR srvname); int GetServiceListCount(); POSITION GetFirstServicePosition(); Service* GetNextService(POSITION& pos); Device* AddDevice(CComBSTR devname); int GetDeviceListCount(); POSITION GetFirstDevicePosition(); Device* GetNextDevice(POSITION& pos); Device* GetParentDevice(); Device* GetRootDevice(); bool IsRoot(); void RemoveAllDevices(); void RemoveAllServices(); void AddInterface(IUPnPDevice* idev); HRESULT GetInterface(IUPnPDevice** idev); void SetIconsList(IconsList* ilist); IconsList* GetIconsList(); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||