Querying Bing Using the New Windows 7 Web Services C++ API

Introduction

According to MSDN, WWSAPI is a native-code implementation of SOAP which provides core network communication functionality by supporting a set of the WS-* and .NET-* family of protocols. WWSAPI is designed to be used by components/applications which fall into one of the following categories:

  • Native code mandate
  • Require minimal dependencies
  • Require minimal startup time
  • Memory constrained environments

Using this brand new API, it is possible to make native-code SOAP based web services and clients for SOAP based web services. This article will explain how to build client applications that use SOAP based web services. The web service that will be used in the examples is the Microsoft Bing SOAP API which allows you to search for text, images and so on. You will need to have the Windows 7 SDK installed to follow the steps in this article. Get the SDK. Code using the WWSAPI will run out-of-the-box on Windows 7 and Windows Server 2008 R2. If you want to run it on Windows XP, Windows Vista, Windows Server 2003 or Windows Server 2008, you need to install a redistributable. Unfortunately, it's impossible to get this redistributable at the time of this writing except by getting a Microsoft Services Partner Advantage agreement, which is quite expensive.

More information about the Windows Web Services API can be found at http://msdn.microsoft.com/en-us/library/dd430435(VS.85).aspx

The Bing API

Before you can start using the Bing API, you need to get your Bing API AppID. Get one now. Once you have your AppID, which you will need in your C++ code, you can download the Bing SDK from here. This SDK contains some documentation on the SOAP Bing API and it includes the SOAP WSDL file which we will need for our examples.

The Console Bing Text Search Application

Let's start with a simple console application that will allow you to search for a specific string on www.bing.com. The following steps are necessary:

  1. Start Visual Studio.
  2. Create a new Console application using the project wizard.
  3. Copy the search.wsdl which was installed by the Bing SDK to your source folder of your new console application. If you used the default installation folder during the Bing SDK setup, the search.wsdl file will be located in the following folder "My Documents\Bing API 2.0 SDK\Samples\CSharp\SOAP\Web References\net.bing.api".
  4. Open a Visual Studio command line window. You should be able to find a shortcut to this in your start menu under "Microsoft Visual Studio > Visual Studio Tools > Visual Studio Command Prompt".
  5. Navigate to the folder containing the source code for your new console application.
  6. Run the following command:
  7. wsutil search.wsdl
  8. This command will generate 2 files search.wsdl.c and search.wsdl.h which are proxy files that we will use in our code.
  9. Close the command line.
  10. In Visual Studio, add the search.wsdl, search.wsdl.c and search.wsdl.h files to your console project.
  11. Right click on the search.wsdl.c file, select "Properties", go to "C/C++ > Precompiled Headers" and set "Precompiled Header" to "Not Using Precompiled Headers".

Initializing the WWSAPI

Now we are ready to start writing our WWSAPI code. All WWSAPI code will be encapsulated in a CBingSearchService class. The first thing this class will need to do is initialize the WWSAPI. This can be done as follows:

  void CBingSearchService::Initialize()
  {
      // If Initialize was called multiple times, make sure
      // to cleanup first.
      Cleanup();
   
      // Create an error object
      HRESULT hr = WsCreateError(NULL, 0, &m_error);
      if (FAILED(hr))
      {
          ShowError(hr);
          return;
      }
   
      // Create a heap to store deserialized data
      hr = WsCreateHeap(2048000, 512, NULL, 0, &m_heap, m_error);
      if (FAILED(hr))
      {
          ShowError(hr);
          return;
      }
   
      WS_CHANNEL_PROPERTY channelProperties[2]; // hold up to 2 channel properties
      ULONG channelPropertyCount = 0;
   
      WS_ENVELOPE_VERSION soapVersion = WS_ENVELOPE_VERSION_SOAP_1_1;
      channelProperties[channelPropertyCount].id = WS_CHANNEL_PROPERTY_ENVELOPE_VERSION;
      channelProperties[channelPropertyCount].value = &soapVersion;
      channelProperties[channelPropertyCount].valueSize = sizeof(soapVersion);
      channelPropertyCount++;
   
      WS_ADDRESSING_VERSION addressingVersion = WS_ADDRESSING_VERSION_TRANSPORT;
      channelProperties[channelPropertyCount].id = WS_CHANNEL_PROPERTY_ADDRESSING_VERSION;
      channelProperties[channelPropertyCount].value = &addressingVersion ;
      channelProperties[channelPropertyCount].valueSize = sizeof(addressingVersion);
      channelPropertyCount++;
   
      // Create the proxy
      hr = WsCreateServiceProxy(WS_CHANNEL_TYPE_REQUEST, WS_HTTP_CHANNEL_BINDING,
          NULL, NULL, 0, channelProperties, channelPropertyCount, &m_proxy, m_error);
      if (FAILED(hr))
          ShowError(hr);
      else
          m_bInitialized = true;
  }

This function first creates a WS_ERROR object. This object is passed to all WWSAPI functions and allows us to get detailed error messages when something goes wrong. After this, a heap is created that the WWSAPI will use for all its memory management. The last thing this function does is setting up the service proxy. For this we need to configure two channel properties, specifying the envelope version of the SOAP protocol used by the web service (Bing in this example) and the addressing version used for the transport of data between the client and the web service. Both of these pieces of information depend on the target SOAP server that you want to use. That's all the initialization that is necessary to get started.

Connecting to the Bing Servers

The next thing the class needs to do is to actually connect to the remote web service endpoint. This is handled by the following function:

  void CBingSearchService::Connect()
  {
      if (!m_bInitialized)
          Initialize();
   
      WS_ENDPOINT_ADDRESS address = {};
      WS_STRING url = WS_STRING_VALUE(L"http://api.bing.net/soap.asmx");
      address.url = url;
   
      // Opens the connection to the service endpoint.
      HRESULT hr = WsOpenServiceProxy(m_proxy, &address, NULL, m_error);
      if (FAILED(hr))
          ShowError(hr);
      else
          m_bConnected = true;
  }

It first checks whether the WWSAPI is initialized and if not will call our earlier function to initialize it. After this, it sets up a WS_ENDPOINT_ADDRESS that contains the URL of the endpoint of our web service and then calls WsOpenServiceProxy to make the connection with the remote server.



Querying Bing Using the New Windows 7 Web Services C++ API

Sending Search Requests

Now the WWSAPI is initialized and we are connected to the remote web service, so we can start sending SOAP requests to it. All of the previous code is pretty generic and can be used for different web services, except that you need to make sure you initialize the channel properties correctly for the web service that you target. However, sending actual requests to the web service is highly dependent on the web service, which in my examples is Bing. Sending a request to the Bing web service can be done as follows.

  SearchResponse* CBingSearchService::Search(CString& strSearchTerm, SourceType sourceType)
  {
      if (strSearchTerm.Trim().IsEmpty())
          return NULL;
   
      if (!m_bInitialized)
          Initialize();
   
      if (!m_bConnected)
          Connect();
   
      SearchRequest bingReq = {0};
   
      bingReq.Query = strSearchTerm.GetBuffer();
      bingReq.AppId = L"<Your Bing AppID>";    // FIXME: Add your AppID!
      bingReq.Version = L"2.0";
      bingReq.Market = L"en-us";
      SourceType aSourceTypes[1];
      aSourceTypes[0] = sourceType;
      bingReq.Sources = aSourceTypes;
      bingReq.SourcesCount = 1;
   
      WebRequest webReq = {0};
      webReq.Count = m_iNumOfResults;
      webReq.Offset = m_iOffset;
   
      ImageRequest imageReq = {0};
      imageReq.Count = m_iNumOfResults;
      imageReq.Offset = m_iOffset;
   
      bingReq.Web = &webReq;    
      bingReq.Image = &imageReq;
   
      SearchResponse* bingRes = NULL;
   
      HRESULT hr = BingPortBinding_Search(m_proxy, &bingReq, &bingRes, m_heap, NULL, 0, NULL, m_error);
      if (FAILED(hr))
      {
          ShowError(hr);
          return NULL;
      }
      else
          return bingRes;
  }

The function first checks whether the WWSAPI is properly initialized and connected to the remote web service. Then it creates a SearchRequest object. The definition of this structure is automatically generated by the wsutil.exe tool based on the Bing search.wsdl file. You can look up its definition in the search.wsdl.h header file. The SearchRequest needs a couple of parameters. The most important one is the actual search query which is a string stored in the Query member of the structure. Before the Bing servers will accept your search request, you need a valid AppId. I explained how to get an AppId in the beginning of this article. The Version field should be set to the version of the Bing API that you want to use. Market can be set to tailor the results to that specific market. Sources specifies what type of results should be returned. The list of sources can be found in the search.wsdl.h header file. Some examples are images, news, translations, videos and of course webpages. At the moment, the CBingSearchService only supports searching for webpages and for images. For this, the function initializes a WebRequest and an ImageRequest object to specify additional information for both search types. I'm only specifying Count and Offset, but you can check the search.wsdl.h header file and the Bing API documentation to learn about the other possibilities like applying filters to your image results and so on. The Count parameter specifies the number of results that Bing will return with a single call. This should be a number between 0 and 50. If it's outside this range, the Bing API will return an error. The Offset parameter can be used to implement pagination. More information about the different fields of the SearchRequest can be found here.

The function then creates a pointer to a SearchResponse object and calls BingPortBinding_Search which is responsible for sending the SOAP request to the Bing web service endpoint, retrieve the results and store the results in the SearchResponse pointer. Note that you do not need to free this SearchResponse pointer as it will automatically be freed when the CBingSearchService object gets destroyed.

Using the CBingSearchService Class

Using the WWSAPI requires that you link your program with WebServices.lib. You can either specify this library in your project settings or by adding the following line:

  #pragma comment(lib, "WebServices.lib")

Now that we have a class CBingSearchService wrapping the Bing search web service, we can start using it. This is actually pretty simple. Start by adding the following 2 includes:

  #include "BingSearchService.h"
  #include "search.wsdl.h"

Then create an instance of the CBingSearchService class and set the number of results and the offset to appropriate values:

  CBingSearchService bingSearch;
  bingSearch.SetNumberOfResults(25);
  bingSearch.SetOffset(0);

Now we need to specify the type of search we want to perform. As mentioned above, the CBingSearchService currently supports image and web page searches. Let's start with a web page search:

  SearchResponse* response = bingSearch.Search(str, SourceTypeWeb);

Now we can start parsing the response. The SearchResponse structure is a structure containing a lot of other structures. It depends on the search source type which of these structures contains the information we are searching for. For a web page search (source type = SourceTypeWeb), the results will be in response->Web which is a WebResponse structure. Once you know that, parsing the response is pretty straightforward:

  wcout << L"Found " << response->Web->Total << L" results:" << endl;
  for (unsigned int i=0; i<response->Web->ResultsCount; ++i)
  {
      wcout << (i+1) << L": " << response->Web->Results[i].Title << endl;
      if (response->Web->Results[i].Description)
          wcout << response->Web->Results[i].Description << endl;
      wcout << L"    [ " << response->Web->Results[i].Url << L" ]" << endl << endl;
  }

Of course you need to check for errors. The response structure can contain a list of errors that occurred during the search request. Check the demos that come with this article to see how to handle those errors.

The MFC Bing Image Search Application

The second demo attached to this article will do an image search on Bing. It uses the same CBingSearchService class as before as follows:

  void CBingPictureSearchDlg::PerformSearch(CString strSearch)
  {
      CWaitCursor waitcursor;
   
      CBingSearchService bingSearch;
      bingSearch.SetNumberOfResults(m_iNumResultsPerQuery);
      bingSearch.SetOffset(m_iOffset);
      // We want to search images, so we use SourceTypeImage.
      SearchResponse* response = bingSearch.Search(strSearch, SourceTypeImage);
   
      wstringstream wss;
      if (!response)
          wss << L"Error occured: response == NULL" << endl;
      else
      {
          // Process the response.
          if (response->ErrorsCount > 0)
          {
              // Output any errors that occurred.
              wss << L"Errors: " << endl;
              for (unsigned int i=0; i<response->ErrorsCount; ++i)
                  wss << L"Error " << i << L": " << response->Errors[i].Message << endl;
          }
          // Show results.
          wss << L"<p><b>Showing results " << (m_iOffset+1) << L" to " << (m_iOffset+m_iNumResultsPerQuery) << L" of "
              << response->Image->Total << ":</b></p>\r\n";
          for (unsigned int i=0; i<response->Image->ResultsCount; ++i)
          {
              if (response->Image->Results[i].Thumbnail)
              {
                  // Replace all double quotes in the image title with single quotes
                  wstring strTitle = response->Image->Results[i].Title;
                  replace_if(strTitle.begin(), strTitle.end(), IsQuote , '\'');
                  // Generate HTML code to display thumbnail
                  wss << L"<a href=\"" << response->Image->Results[i].Url << L"\" target=\"_blank\">"
                      << L"<img width=" << response->Image->Results[i].Thumbnail->Width
                      << L" height=" << response->Image->Results[i].Thumbnail->Height << L" src=\"" << response->Image->Results[i].Thumbnail->Url << L"\" "
                      << L"alt=\"" << strTitle << L"\" hspace=10 vspace=10 border=0/></a>\r\n";
              }
          }
          wss << L"<p><center><a href=\"app://prev\">Previous</a> | <a href=\"app://next\">Next</a></center></p>";
      }
      WriteHTML(wss.str().c_str());
  }

It uses the same bingSearch.Search function but with source type set to SourceTypeImage. After the search call, all errors and thumbnails are shown. The demo uses a Microsoft WebBrowser control to display HTML from memory. The above function creates a HTML stream of the results and feeds it into the WebBrowser control to display it. The code also uses custom URL protocols ("app://") to handle the next and previous buttons on the results. Learn more about how to handle custom URL protocols with the Microsoft WebBrowser control.

Note that the above code is performing the Bing search query in the same thread as the user interface. Because of this, the user interface might freeze a few seconds depending on the speed of the query. Obviously, in a real-world application querying internet resources should be done in a background thread to keep the user interface responsive.

Conclusion

Have a look at the demo applications to see it all working together. Before compiling the demos, remember to open the BingSearchService.cpp file, go to the CBingSearchService::Search(...) function and put your Bing AppID in there.

Special thanks to Marian Luparu from Microsoft for helping me with the WWSAPI.



About the Author

Marc Gregoire

Marc graduated from the Catholic University Leuven, Belgium, with a degree in "Burgerlijk ingenieur in de computer wetenschappen" (equivalent to Master of Science in Engineering in Computer Science) in 2003. In 2004 he got the cum laude degree of Master In Artificial Intelligence at the same university. In 2005 he started working for a big software consultancy company. His main expertise is C/C++ and specifically Microsoft VC++ and the MFC framework. Next to C/C++, he also likes C# and uses PHP for creating webpages. Besides his main interest for Windows development, he also has experience in developing C++ programs running 24x7 on Linux platforms and in developing critical 2G,3G software running on Solaris for big telecom operators.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds