Developing with Visual C 6


Developing with Visual C++ 6

Most developers who use Visual C++ 6 are looking for low-level flexibility and application execution speed. Given the flexibility this environment provides, some types of data manipulations are much simpler than with a product such as Visual Basic. The problem with Visual C++ 6 is that you pay a price in developer productivity when using it. A Google Web Services application can take two or three times longer to build than with Visual Basic, given applications of the same capability. In addition, building database support into a Visual C++ 6 application is more difficult and time consuming than when you use Visual Basic. Visual C++ 6 is the language of choice when you value flexibility and performance over ease of use and productivity.

Note  

You must have the Microsoft SOAP Toolkit installed on your system to work with the examples in this section. Learn more about this toolkit in the "Getting the Microsoft SOAP Toolkit" section of the chapter.

Adding a SOAP Toolkit Reference to Visual C++ 6

You really need to know the location and names of the SOAP Toolkit files when working with Visual C++ because the IDE doesn't perform any hand-holding. This lack of support is one reason that Table 6.1 is so important. The following steps will help you install the SOAP Toolkit, XML, and FlexGrid support required for this example.

  1. Use the Project Add to Project Components and Controls command to display the Components and Controls dialog box shown in Figure 6.3.

    click to expand
    Figure 6.3: Use the Components and Controls dialog box to add SOAP, XML, and FlexGrid support.

  2. Double-click the Registered ActiveX Controls folder. Locate the Microsoft FlexGrid Control, and then click Insert. Visual C++ will ask if you want to insert this component.

  3. Click OK. You'll see a Confirm Classes dialog box similar to the one shown in Figure 6.4. Normally, you don't have to change any of the entries on this dialog box. However, you may need to change the names if there's a conflict with another class.

    click to expand
    Figure 6.4: The Confirm Classes dialog box shows which classes the IDE adds to support the FlexGrid.

  4. Click OK. Visual C++ adds the new classes to your application and you'll see the new control in the Toolbox.

  5. Click Close to close the Components and Controls dialog box.

You add the XML and SOAP reference to the application by adding code to the file. In general, you'll use the following code to add the SOAP toolkit and XML references.

 // You must change these locations to match your setup!   #import "MSXML4.DLL"   using namespace MSXML2;   #import "E:\Program Files\Common Files\MSSoap\Binaries\MSSOAP30.DLL" \      exclude("IStream", "IErrorInfo", "ISequentialStream", \      ";_LARGE_INTEGER", "_ULARGE_INTEGER", "tagSTATSTG", "_FILETIME")   using namespace MSSOAPLib30; 

Notice that the MSSOAP30.DLL file has a specific directory attached because it doesn't appear in the \Windows\System32 folder. You must change this folder to match your system or the code won't compile. Also, notice that the #import reference excludes a number of elements from the MSSOAP30.DLL file. If you don't include these exclusions, the code probably won't compile because it will detect errors with existing files. The IDE automatically adds the support required by the #import statements when you build the application the first time.

Performing a Search with Visual C++ 6

Working with Visual C++ and Google Web Services does require a lot more patience and code than working with just about any other language in the book. In fact, this example demonstrates how to work with difficult languages ”those that don't work well with some Web services. Make sure you read the " Dealing with Difficult Languages" section for details on this issue. Listing 6.2 shows how to create a Google search application similar to the Visual Basic 6 example using Visual C++. This example requires substantially more code than shown. You'll find the complete source for this example in the \ Chapter 06 \ VC6Search folder of the source code located on the Sybex Web site.

Listing 6.2: Performing a Google Search with Visual C++ 6
start example
 void CVC6SearchDlg::OnTest()   {      // SOAP variables.      ISoapConnectorPtr Connector; // Connection to Web Service.      ISoapSerializerPtr DataSend; // Sends data to Google.      ISoapReaderPtr DataReceive;  // Receives data from Google.      // XML variables      IXMLDOMElement *RpcElement;  // Holds entire RPC result.      IXMLDOMNodeList *Results;    // All the results.      IXMLDOMNode *MainNode;       // High-level nodes.      ... Other XML Variables ...      // Variables used to hold the form data.      CString txtLicense; // Developer license value.      int lSafeSearch;    // Perform a safe search?      ... Other Form Data Variables ...      // Other variables.      int MainCount; // Main loop counter.      ... A Number of Miscellaneous Variables ...      // Get the data from the window.      m_License.GetWindowText(txtLicense);      lSafeSearch = m_SafeSearch.GetCheck();      ... Other Window Data Statements ...      // Initialize the COM environment.      CoInitialize(NULL);      // Create a connection to Google.      Connector.CreateInstance(__uuidof(HttpConnector30));      Connector->Property["EndPointURL"] =         "http://api.google.com/search/beta2";      Connector->Connect();      // Tell Google that the application is sending a request.      Connector->Property["SoapAction"] =         "urn:GoogleSearchAction";      Connector->BeginMessage();      // Associate the data serializer with the connection.      DataSend.CreateInstance(__uuidof(SoapSerializer30));      DataSend->Init(_variant_t((IUnknown*)Connector->InputStream));      // Create the envelope and associated namespaces. Don't      // include namespaces that Visual C++ already includes.      DataSend->StartEnvelope("", "NONE", "UTF-8");      //DataSend->SoapAttribute("xmlns:SOAP-ENV", "",      //                  "http://schemas.xmlsoap.org/soap/envelope/", "");      //DataSend->SoapAttribute("xmlns:xsi", "",      //                  "http://www.w3.org/1999/XMLSchema-instance", "");      //DataSend->SoapAttribute("xmlns:xsd", "",      //                  "http://www.w3.org/1999/XMLSchema", "");      // Create the SOAP body.      DataSend->StartBody("NONE");      // Start creating the request.      DataSend->StartElement("ns1:doGoogleSearch", "", "STANDARD", "");      DataSend->SoapAttribute("xmlns:ns1", "", "urn:GoogleSearch", "");      //DataSend->SoapAttribute("SOAP-ENV:encodingStyle", "",      //                  "http://schemas.xmlsoap.org/soap/encoding/", "");      // Add the developer key information.      DataSend->StartElement("key", "", "NONE", "");      DataSend->SoapAttribute("SOAPSDK1:type", "", "xsd:string", "");      DataSend->WriteString(txtLicense.AllocSysString());      DataSend->EndElement();      ... Other Standard Inputs ...      // Add the country restriction.      DataSend->StartElement("restrict", "", "NONE", "");      DataSend->SoapAttribute("SOAPSDK1:type", "", "xsd:string", "");      if (txtOrigin.GetLength() > 0)         DataSend->WriteString(txtOrigin.AllocSysString());      DataSend->EndElement();      // Add the safe search requirement.      DataSend->StartElement("safeSearch", "", "NONE", "");      DataSend->SoapAttribute("SOAPSDK1:type", "", "xsd:boolean", "");      if (lSafeSearch)         DataSend->WriteString("true");      else         DataSend->WriteString("false");      DataSend->EndElement();      // Add the language restriction.      DataSend->StartElement("lr", "", "NONE", "");      DataSend->SoapAttribute("SOAPSDK1:type", "", "xsd:string", "");      if (txtLanguage.GetLength() > 0)         DataSend->WriteString(txtLanguage.AllocSysString());      DataSend->EndElement();      // Not used, but you must send the empty element.      DataSend->StartElement("ie", "", "NONE", "");      DataSend->SoapAttribute("SOAPSDK1:type", "", "xsd:string", "");      //DataSend->WriteString("");      DataSend->EndElement();      // Not used, but you must send the empty element.      DataSend->StartElement("oe", "", "NONE", "");      DataSend->SoapAttribute("SOAPSDK1:type", "", "xsd:string", "");      //DataSend->WriteString("");      DataSend->EndElement();      // Close all of the open tags.      DataSend->EndElement();      DataSend->EndBody();      DataSend->EndEnvelope();      // Tell Google the request is complete.      Connector->EndMessage();      // Receive the response from Google.      DataReceive.CreateInstance(__uuidof(SoapReader30));      DataReceive->Load(_variant_t((IUnknown*)Connector->OutputStream), "");      // Build the output value.      DataReceive->get_RpcResult(&RpcElement);      RpcElement->get_childNodes(&Results);      // Set the data grid row.      CurrDGRow = 1;      // Search through the main results.      for (MainCount = 0; MainCount < Results->length; MainCount++)      {         // Get the current node.         Results->get_item(MainCount, &MainNode);         // Get the node's name.         BaseName = (const char*)MainNode->baseName;         // Update the starting index.         if (BaseName == "endIndex")            m_Index.SetWindowText((const char*)MainNode->text);         ... Other Main Node Data Items ...         // Get all of the result items.         if (BaseName == "resultElements")         {      MainNode->get_childNodes(&ItemList);      // Process each item in turn.      for (ItemCount = 0; ItemCount < ItemList->length; ItemCount++)      {         ItemList->get_item(ItemCount, &ItemNode);         // Make sure the element is an item.         ItemName = (const char*)ItemNode->baseName;         if (ItemName == "item")         {            // Enter each of the column values.            ItemNode->get_childNodes(&DataList);            for (DataCount = 0; DataCount < DataList->length; DataCount++)            {                // Process each data item.                DataList->get_item(DataCount, &DataNode);                DataName = (const char*)DataNode->baseName;                // Get the title.                if (DataName == "title")                {                   m_Output.SetCol(0);                   m_Output.SetText((const char*)DataNode->text);                }                ... Other Cells ...                // Get the cached size.                if (DataName == "cachedSize")                {                   m_Output.SetCol(3);                   m_Output.SetText((const char*)DataNode->text);                }            }            // Add a new row.            CurrDGRow++;            itoa(CurrDGRow,            DGRowTxt.GetBuffer(10), 10); DGRowTxt.ReleaseBuffer(-1);            m_Output.AddItem("", _variant_t(DGRowTxt));            m_Output.SetRow(CurrDGRow);         }      }   }   // Release the COM objects.      Connector.Release();      DataSend.Release();      DataReceive.Release();      // Uninitialize the COM environment.      CoUninitialize();   } 
end example
 

The code includes a lot of variables ”I've shortened the list to display the main types that you need to consider when working with the low-level API. Notice that instead of a client, you now need to create a connection using ISoapConnectorPtr , a method of sending a request using ISoapSerializerPtr , and a way to read the response using ISoapReaderPtr . The data handling mechanism is also different. You now receive a single element ”the root node of the response in the form of an IXMLDOMElement object. However, at this point, the processing takes a familiar turn. You still use a combination of node lists ( IXMLDOMNodeList objects) and nodes ( IXMLDOMNode objects) to perform the task.

Before the code can do anything else, it must perform some initialization tasks . It begins by retrieving the input arguments from the form and also initializing the COM environment using the CoInitialize(NULL) function.

Creating a connection to Google Web Services is a two-part process. First, the code creates a connection. Notice the method used to define the EndPointURL property. You define most connector properties using this technique. The connection is complete when the code calls Connector->Connect() . The next step is to tell Google Web Services to expect a message. You must define the SoapAction property and call Connector->BeginMessage() to perform this task.

The presence of a connection doesn't mean you can send data. To create a serializer ”an object used to send the data ”you must create an instance of the SoapSerializer30 object and initialize it to use the Connector->InputStream . This two-step process will associate the serializer with the connection so Google Web Services actually receives the message you build.

Remember that you must build the message from scratch, which means defining the SOAP envelope and body, in addition to creating the content located in the body. The only problem is that the Microsoft SOAP Library makes some strange assumptions about what you do and don't want. You can control these assumptions by providing the correct input arguments, but it takes time to figure out which ones to use in some cases. For example, Google Web Services doesn't want you to provide encoding style information as part of the envelope, and you must specifically tell Google Web Services that the message is formatted using UTF-8. Consequently, the code creates the envelope using the DataSend->StartEnvelope( , NONE , UTF-8 ) method call with the arguments shown. The envelope should include several attributes according to the Google examples. However, the SOAP library provides these arguments for you, so the listing shows these attributes as commented out.

The next step is to create the body and its content. The DataSend->StartBody( NONE ) call tells the SOAP Library to create a body that doesn't include encoding information. Notice that the encoding information does finally appear as part of the root node declaration for the body content. In this case, the code uses the DataSend->StartElement( ns1:doGoogle-Search , , STANDARD , ) method.

Because most of the arguments you must send to Google Web Services are straight text, all you need to do is create an element, define a data type using the SoapAttribute() method, write the data using the WriteString() method, and finally end the element using the EndElement() method. However, some arguments, such as the <safeSearch> element, require special handling. In this case, you must test the input argument value and write true or false as needed. It's also important to test whether optional arguments include any data. In many cases, they don't, so you'll just write the beginning and ending of the element. The message ends by closing all of the open tags and then sending the message using Connector->EndMessage() .

At this point, you can receive the response from Google Web Services. To perform this task, the code creates a parser ”an object that retrieves the data from the Web service. Again, this is a two-step process where the code creates DataReceive and then associates it with the connection using the Connector->OutputStream property. The code actually loads the output stream into the parser.

Building the data output begins by placing the XML into a node using the get_RpcResult() method. This is the root node of the data, so all you need is a single element.

Like the Visual Basic example in Listing 6.1, this example displays the main output using simple controls such as a textbox and the individual results (including title and URL) using a MSFlexGrid object. Unlike Visual Basic, Visual C++ views the incoming data as a series of pointers, not a collection. In addition, you can't track the position of the cursor on the MSFlexGrid object very easily. Consequently, you end up writing a lot more code to track all of this information. However, once you get past these differences, the code works conceptually the same as the Visual Basic example. In fact, the output looks the same as the Visual Basic example shown in Figure 6.2.

The code must perform two additional tasks before the application exits. First, the code must release the three objects used to communicate with Google Web Services or the application will incur a memory leak. Second, the application must uninitialize the COM environment.

Dealing with Difficult Languages

Sometimes, a language won't work with Google Web Services in the way that you originally anticipated. The precise cause of the problem isn't important ”all you know is that you need some working code. Visual C++ is such a language. While I was able to use the high-level interface provided with the Microsoft SOAP Toolkit with Visual Basic 6 and even JavaScript (see the examples in Chapters 3 and 4 for details), Visual C++ defied all attempts to make life simple. Consequently, you'll notice the example in this section uses the low-level interface. Yes, it's harder to build the example this way, but you gain considerable flexibility and control.

Despite my best efforts, Visual C++ still proved stubborn. Code that worked fine with other Web services failed to work with Visual C++. However, this is where you can begin using a few tricks of the trade to gain an appreciation for SOAP. Many of the tools you use will create the message one piece at a time. Consequently, you need to know how the message looks, what actually works, and how to use tools that make it easier to troubleshoot the actual message. The Microsoft SOAP Toolkit comes with the Trace Utility, but you can also use tools such as the TpcTrace tool found on the PocketSOAP site at http://www.pocketsoap.com/tcptrace/.

To begin whittling this problem down to size, you need to start the SOAP interception tool (such as the Trace Utility) and configure it for use. I'll use the Trace Utility in this section because it comes with Microsoft SOAP Toolkit, but the same techniques work with any other SOAP interception tool on the market. Create a session using the File New Formatted Trace command. You'll see a Trace Setup dialog box. Type api.google.com in the Destination Host field and click OK. The Trace Utility is now listening for SOAP requests .

Even though the Trace Utility is listening for requests, it doesn't mean it will actually receive any. You must change your code slightly to ensure the Trace Utility receives requests. The change is simple. All you need to do is use localhost as an endpoint as shown here.

 // Create a connection to Google.   Connector.CreateInstance(__uuidof(HttpConnector30));   Connector->Property[";EndPointURL"] =      "http://localhost:8080/search/beta2";   Connector->Connect(); 

Notice that the EndPointURL property now points to localhost using port 8080 ”the same settings as the input to the Trace Utility. The Trace Utility receives the request, displays the information on screen, changes the domain, and passes it to Google Web Services. When Google Web Services sends a response, the Trace Utility intercepts it, displays the information on screen, and passes the information to the application. Figure 6.5 shows a typical message session.

click to expand
Figure 6.5: Use the Trace Utility to discover the true form of messages passed between your application and Google.

Now that you can see the real message traffic, you know that you need to make a few changes to the way Visual C++ uses the SOAP Toolkit to create the message. For example, this display points out the need to remove some namespaces that the SOAP library creates automatically. It also shows the need to modify a few namespace settings for the variables. You don't use xsi:type as shown in the Google Web Services Kit examples ”you use SOAPSDK1:type instead because the SOAP library automatically generates this namespace. All of these adjustments help make a difficult language more manageable.

Not every language relies on the low-level API. Sometimes, you need to tweak the output of a high-level API application as well. In this case, you must modify the application to point to a copy of the WSDL file on your local hard drive. For example, here is how I modified the XSLT example in Chapter 3 (see Listing 3.4 in the "Performing a Simple SOAP Call" section for details). You'll find the complete source for this example in the \ Chapter 06 \ Trace Utility folder of the source code located on the Sybex Web site.

 // Initialize the SOAP client so it can access Google   // Web Services.   SoapClient.MSSoapInit("D:\\Temp\\GoogleSearch.wsdl",                         "GoogleSearchService",                         "GoogleSearchPort"); 

Once you point MSSoapInit() to the local copy, you can modify the WSDL file to work with the Trace Utility. You need to make a single change to the endpoint information in the WSDL file as shown here.

 <!-- Endpoint for Google Web APIs -->   <service name="GoogleSearchService">        <port name="GoogleSearchPort" binding="typens:GoogleSearchBinding">             <soap:address location="http://localhost/search/beta2"/>        </port>   </service> 

This entry appears near the end of the file. Once you make this change, you can use the Trace Utility with a high-level API application, just as you did with the low-level API application. The example code folder includes the resulting request in the WorkingRequest.XML file. Figure 6.6 shows a typical high-level API request. Notice that the high-level API uses unique namespaces and it also doesn't include type information for the input arguments. The omission of type information could cause problems with future releases of Google Web Services, but doesn't appear to make a difference now.

click to expand
Figure 6.6: The high-level API automatically generates a request very similar to low-level API request you can create manually.



Mining Google Web Services
Mining Google Web Services: Building Applications with the Google API
ISBN: 0782143334
EAN: 2147483647
Year: 2004
Pages: 157

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net