Channels ▼
RSS

Web Development

Integrating XML Web Services With VB6 Applications

Source Code Accompanies This Article. Download It Now.


Feb04: Integrating XML Web Services with VB6 Applications

Max is the author of .NET Programming with Visual C++ and can be contacted at fomitchev@cox.net.


While VB .NET supports web-service integration natively with Visual Studio .NET, no such built-in support exists for VB6. Consequently, you must use the Microsoft SOAP Toolkit if VB6 clients are to consume web services. In this article, I present a method and a couple of scenarios that illustrate how legacy VB6 applications can consume web services written in C++. The complete UltraMaxService web service, which I present as an example along with a sample VB6 consumer application, is available electronically; see "Resource Center" (page 5).

The easiest way to integrate XML web services with VB6 applications involves installation of Microsoft's freely available SOAP Toolkit 3.0 (http://msdn.microsoft.com/library/default.asp?url=/downloads/ list/websrv.asp). Although the SOAP Toolkit was originally designed to provide XML web-service functionality for existing COM components (that is, to convert COM components into XML web services), it can also be used for consuming virtually any web service designed from scratch.

The first SOAP Toolkit scenario involves wrapping existing COM components as web services for replacing an existing COM-server/COM-client integration with an instantly distributed web-service provider/web-service consumer architecture. In this case, you must install the Toolkit on both the server and client machines. On the server, the SOAP Toolkit provides a SOAP wrapper for registered COM components by means of the SoapServer object. On the client, the Toolkit serves as an interface between SOAP messages and COM method calls by means of the SoapClient client. The SoapServer and SoapClient objects communicate with each other by means of SOAP messages, and perform mapping of the SOAP messages into COM method calls internally. Wrapping COM components as web services usually involves running the WSDL Generator program (included with the Toolkit) to generate Web Service Description Language (WSDL) and Web Service Meta Language (WSML) files for existing registered COM components. The resulting WSDL/WSML pair can be used by the SoapClient object to consume the COM-generated web service (see Figure 1).

While the WSDL file adheres to a standardized web-service description (http://www.w3.org/TR/wsdl12/), the WSML file is a pure Microsoft extension for mapping the web-service methods (SOAP actions) described in the WSDL into COM method calls.

A second SOAP Toolkit scenario involves consuming existing XML web services. In this case, you only need to install the Toolkit on a client machine and rely on the SoapClient object for parsing, generating SOAP messages, and masquerading web-method invocations as ordinary COM method calls. Here, you should rely on the WSDL file provided by the web service itself, and a WSML file is generally not required.

Consuming Web Services in VB6

To consume web services in VB6, you must create an instance of the SoapClient object and initialize the object by invoking the mssoapinit method and specifying the URL of the web-service WSDL file. Assuming that a reference to the Microsoft Soap Type Library (mssoap1.dll) has been added to the VB6 project, Listing One consumes a C++/ATL web service with dynamically generated WSDL. The URL of the web-service WSDL file can correspond to a local file—UltraMax.wsdl or C:\Projects\UltraMax.wsdl, for instance. Once the SoapClient object is initialized, you can invoke web-service methods directly on the SoapClient object instance (Listing Two) where the LogOn is one of the UltraMax web-service methods.

Because web-service methods are resolved dynamically by parsing the web-service WSDL, these methods are not available in the dropdown list of object methods provided by the VB6 editor. Therefore, you must examine the web-service WSDL (or any other pertinent web-service documentation) for information on the available web methods and their parameters. Table 1 summarizes the SoapClient object properties and methods. Note that the SoapClient object provides data properties such as detail, faultactor, faultcode, and faultstring for conveying SOAP fault information. These properties are set when a SOAP fault occurs, which typically corresponds to a web-service or communication error. If in response to invoking a web-service method the SoapClient object receives a SOAP fault message, it raises an error, which can be caught using On Error GoTo (see Example 1). You usually get SOAP faults when calling nonexistent web-service methods or when supplying incorrect web-service parameters.

SOAP Type Mapping

Although the SOAP Toolkit enables invoking web-service methods with minimal effort, additional code is required to handle complex user-defined types. The SoapClient object automatically parses web-service method parameters corresponding to primitive types, such as String, Long, Single, Date, and so on (in VB6 terms), but represents output parameters corresponding to hierarchical data structures as XML DOM objects. For automatic parsing of complex user-defined types (UDTs), you must develop a custom type-mapper object and register it in the WSML file used to initialize a SoapClient instance.

To create a type-mapper class, you create a new VB6 COM DLL containing a:

  • Simple class corresponding to a UDT being mapped.

  • Type-mapper class implementing the ISoapTypeMapper interface.

The created VB6 project should reference the Microsoft Soap Type Library (mssoap1.dll) and Microsoft XML 3.0 (msxml3.dll). The ISoapTypeMapper interface allows specifying how an arbitrary data type is serialized to or deserialized from XML. Table 2 summarizes the ISoapTypeMapper interface methods.

To illustrate complex type mapping, consider the simple complex type FileInfo defined as in Listing Three(a), and the corresponding type-mapper class FileInfoMapper in Listing Three(b).

When implementing the custom type-mapper class, you must override all four ISoapTypeMapper methods that by default perform serialization/deserialization of a single String value.

The mStringMapper member corresponding to a default ISoapTypeMapper implementation is used to serialize/deserialize individual properties of the FileInfo class, which must be serialized and deserialized in the same order.

A more sophisticated example involves a situation when a complex type contains other complex types as members. Consider the SongInfo type in Listing Four(a), which contains a property of the FileInfo type. Listing Four(b) is required for the SongInfoMapper object to serialize/deserialize SongInfo to/from XML. The SongInfoMapper class contains an additional member mFileInfoMapper of type FileInfoMapper to serialize/deserialize FileInfo structure independently. The mFileInfoMapper member must be initialized in the ISoapTypeMapper_Init method and its corresponding ISoapTypeMapper_ Init must be invoked with the same parameters as the parent ISoapTypeMapper_Init call.

When it comes to serializing the MP3Info member, the mFileInfoMapper.ISoapTypeMapper_write method is invoked to serialize the nested complex type, and the mFileInfoMapper.ISoapTypeMapper_read method is invoked for deserialization. Since the FileInfoMapper class is used explicitly in the SongInfoMapper class its methods must be declared as Public, whereas SongInfoMapper methods can be declared as Private.

When the type-mapper classes are ready, you next create a WSML file to associate the type-mapper classes with the UDTs in SOAP messages.

WSML Files

The main purpose of WSML files is to define how COM object methods are mapped into web methods, which is necessary only for SOAP-wrapped COM components. An additional function of a WSML file, which comes in handy for both SOAP-wrapped and generic web services, is to specify type-mapping handler classes for UDTs.

To enact type mapping for the FileInfo and SongInfo types for the UltraMaxService web service, the contents of the associated WSML file must be like Listing Five. The <servicemapping> element wraps the <service> element, which defines operation and type mapping for a particular web service. Therefore, the name attribute of the <service> element must be set to the name of a web service for which the mapping is created (that is, the same as the name in the <service> element specified in the web-service WSDL file).

The series of <using> elements describes COM objects involved in type mapping and web-method (that is, SOAP operation) mapping. The PROGID attribute of the <using> element specifies the COM object textual name, while the ID attribute specifies the object's alias, which is going to be used throughout the WSML file. The cachable attribute of the <using> element specifies whether the COM component is kept in memory as long as the corresponding SoapServer instance is in memory. The cachable attribute is ignored for apartment and single-threaded COM components, however.

The <types> section is responsible for type mapping. The section contains a series of <type> elements, with each element specifying a concrete type-mapper handler for a particular UDT. The name attribute of a <type> element specifies the type name the way it is spelled in the web-service WSDL file, while the targetNamespace attribute specifies the namespace in which the complex type is defined. The value of the targetNamespace attribute should be extracted from the web-service WSDL file and, if the complex type is defined in the web- service WSDL itself, it is usually formatted as urn:<WebServiceName>. Lastly, the uses attribute specifies the ID of a COM component used for type mapping defined in one of the preceding <using> elements.

To enact the type mapping when invoking web-service methods, the URL of the resulting WSML file must be specified as a parameter to the mssoapinit call for initializing of the SoapClient object:

Dim WebService As New MSSOAPLib.SoapClient

WebService.mssoapinit _

http://www.UltraMax-Music.com/

UltraMax.dll?Handler=GenUltraMaxWSDL, , , _

http://www.UltraMax-Music.com/UltraMax.wsml

Again, the actual location of the WSML file is not important: The file can be stored either on the server along with the corresponding web-services files, or reside locally on the client side.

Session State with SOAP Headers

A principle difference between COM components and web services is that the state of a web service is not preserved between web-method calls automatically. To enable state persistence, a web service must contain some kind of built-in session-state support. Furthermore, to ensure proper discrimination between sessions originating from different clients, session identifiers must be communicated between a web service and its clients.

When programming with ASP the session ID is usually communicated via a cookie. With web services, however, it is possible to communicate session ID in the SOAP header, which is easier in our case.

To make sure that the web-service client can receive and submit session ID with each web-method call, the SoapClient interface provides the HeaderHandler property, which can be initialized with a user-defined class for generating/parsing SOAP headers implementing the IHeaderHandler interface. Table 3 lists members of the IHeaderHandler interface.

When implementing the IHeaderHandler interface, you must override all IHeaderHandler interface methods. If the session ID is communicated in a SOAP header element called m_SessionID, Listing Six is the SessionHeader class implementing the IHeaderHandler interface.

The IHeaderHandler_readHeader function is called for each data element found in the received SOAP header. Therefore, it is necessary to examine the baseName attribute of the IXMLDOMNode to make sure that the currently processed SOAP header element is the one you are interested in. It is possible to read several SOAP header elements in this function by adding more case comparisons on element baseName.

The IHeaderHandler_willWriteHeaders method should return True because it is necessary to transmit session ID back to web service for proper session identification.

The IHeaderHandler_readHeader method simply serializes the SOAP header element called m_SessionID into an appropriate section of a transmitted SOAP message—provided the IHeaderHandler_willWriteHeaders method returns True. It is possible to serialize more than one SOAP header element in the IHeaderHandler_readHeader method by repeating the pSerializer.startHeaderElement,pSerializer.writeString, and pSerializer.endHeaderElement statements for each header element that needs to be written.

Finally, to hook up the SOAP header handler to a particular SoapClient instance one must set the HeaderHandler property of the SoapClient object to an instance of a user-defined SOAP header handler class; for instance:

Dim SOAPHeader As New SessionHeader

Set ws.HeaderHandler = SOAPHeader

At this point, any subsequent call web-method invocation enacted through the SoapClient object results in transmission of a value stored in the SessionID property of the SOAPHeader object, as well as parsing the returned SOAP message header to update the SessionID property with a new value returned by the web service (if any). Remember, that it is up to web service to initialize and maintain the session ID value on its side.

Conclusion

The SOAP Toolkit provides sufficient means for consuming generic web services in VB6 clients. Although the amount of work associated with implementing custom type mapping and SOAP header handling is somewhat greater than in VB.NET, which supports web services natively, the SOAP Toolkit provides a well-defined interface for tapping into the wealth of XML web services from legacy VB6 projects.

DDJ

Listing One

Dim WebService As New MSSOAPLib.SoapClient
WebService.mssoapinit _
  "http://www.UltraMax-Music.com/UltraMax.dll?Handler=GenUltraMaxWSDL"

Back to Article

Listing Two

' Log on...
WebService.LogOn "MyUserName", "MyPassword"

Back to Article

Listing Three

<b>(a)</b>
Public FileName As String
Public Quality As Integer

<b>(b)</b>
Implements ISoapTypeMapper
Private mStringMapper As ISoapTypeMapper
Public Sub ISoapTypeMapper_Init( _
    ByVal pFactory As MSSOAPLib.ISoapTypeMapperFactory, _
    ByVal pSchema As MSXML2.IXMLDOMNode, _
    ByVal xsdType As MSSOAPLib.enXSDType)
    
    Set mStringMapper = pFactory.getMapper(enXSDstring, Nothing)
End Sub
Public Function ISoapTypeMapper_read(ByVal pNode As MSXML2.IXMLDOMNode, _
    ByVal bstrEncoding As String, ByVal encodingMode As _
    MSSOAPLib.enEncodingStyle, ByVal lFlags As Long) As Variant
    Dim fi As New FileInfo
    Dim Node As IXMLDOMNode
    fi.FileName = mStringMapper.read(pNode.selectSingleNode("FileName"), _
        bstrEncoding, encodingMode, lFlags)
    fi.Quality = mStringMapper.read(pNode.selectSingleNode("Quality"), _
        bstrEncoding, encodingMode, lFlags)
    Set ISoapTypeMapper_read = fi
End Function
Private Function ISoapTypeMapper_varType() As Long
    ISoapTypeMapper_varType = vbObject
End Function
Public Sub ISoapTypeMapper_write(ByVal pSoapSerializer As _
    MSSOAPLib.ISoapSerializer, ByVal bstrEncoding As String, _
    ByVal encodingMode As MSSOAPLib.enEncodingStyle, _
    ByVal lFlags As Long, pvar As Variant)
    Dim fi As New FileInfo
    Set fi = pvar
    pSoapSerializer.startElement "FileName"
    mStringMapper.write pSoapSerializer, bstrEncoding, encodingMode, _
        lFlags, fi.FileName
    pSoapSerializer.endElement
    pSoapSerializer.startElement "Quality"
    mStringMapper.write pSoapSerializer, bstrEncoding, encodingMode, _
        lFlags, fi.Quality
    pSoapSerializer.endElement
End Sub

Back to Article

Listing Four

<b>(a)</b>
Public SongTitle As String
Public AlbumTitle As String
Public Duration As Single
Public MP3Info As FileInfo

<b>(b)</b>
Implements ISoapTypeMapper
Private mStringMapper As ISoapTypeMapper
Private mFileInfoMapper As FileInfoMapper
Private Sub ISoapTypeMapper_Init( _
    ByVal pFactory As MSSOAPLib.ISoapTypeMapperFactory, _
    ByVal pSchema As MSXML2.IXMLDOMNode, _
    ByVal xsdType As MSSOAPLib.enXSDType)
    Set mStringMapper = pFactory.getMapper(enXSDstring, Nothing)
    Set mFileInfoMapper = New FileInfoMapper
    mFileInfoMapper.ISoapTypeMapper_Init pFactory, pSchema, xsdType
End Sub

Private Function ISoapTypeMapper_read(ByVal pNode As MSXML2.IXMLDOMNode, _
    ByVal bstrEncoding As String, ByVal encodingMode As _
    MSSOAPLib.enEncodingStyle, ByVal lFlags As Long) As Variant
    Dim si As New SongInfo
    Dim Node As IXMLDOMNode
    si.SongTitle = mStringMapper.read(pNode.selectSingleNode("SongTitle"), _
        bstrEncoding, encodingMode, lFlags)
    si.AlbumTitle = mStringMapper.read(pNode.selectSingleNode("AlbumTitle"), _
        bstrEncoding, encodingMode, lFlags)
    si.Duration = mStringMapper.read(pNode.selectSingleNode("Duration"), _
        bstrEncoding, encodingMode, lFlags)
    Set si.MP3Info = 
     mFileInfoMapper.ISoapTypeMapper_read(pNode.selectSingleNode("MP3Info"), _
     bstrEncoding, encodingMode, lFlags)
    Set ISoapTypeMapper_read = si
End Function
Private Function ISoapTypeMapper_varType() As Long
    ISoapTypeMapper_varType = vbObject
End Function

Private Sub ISoapTypeMapper_write(ByVal pSoapSerializer As _
    MSSOAPLib.ISoapSerializer, ByVal bstrEncoding As String, _
    ByVal encodingMode As MSSOAPLib.enEncodingStyle, _
    ByVal lFlags As Long, pvar As Variant)
    Dim si As New SongInfo
    Set si = pvar
    pSoapSerializer.startElement "SongTitle"
    mStringMapper.write pSoapSerializer, bstrEncoding, encodingMode, _
        lFlags, si.SongTitle
    pSoapSerializer.endElement
    pSoapSerializer.startElement "AlbumTitle"
    mStringMapper.write pSoapSerializer, bstrEncoding, encodingMode, _
        lFlags, si.AlbumTitle
    pSoapSerializer.endElement
    pSoapSerializer.startElement "Duration"
    mStringMapper.write pSoapSerializer, bstrEncoding, encodingMode, _
        lFlags, si.Duration
    pSoapSerializer.endElement

    pSoapSerializer.startElement "MP3Info"
    mFileInfoMapper.ISoapTypeMapper_write pSoapSerializer, _ 
                        bstrEncoding, encodingMode, lFlags, si.MP3Info
    pSoapSerializer.endElement
End Sub

Back to Article

Listing Five

<?xml version='1.0' ?> 
<servicemapping name='UltraMaxService'>
    <service name='UltraMaxService'>
        <using PROGID='UltraMaxMapper.FileInfoMapper' cachable='0' 
            ID='UltraMaxFileInfoMapperObject' />
        <using PROGID='UltraMaxMapper.SongInfoMapper' cachable='0'
            ID='UltraMaxSongInfoMapperObject' />
        <types>
            <type name='FileInfo' targetNamespace='urn:UltraMaxService'
                uses='UltraMaxFileInfoMapperObject'/>
            <type name='SongInfo' targetNamespace='urn:UltraMaxService'
                uses='UltraMaxSongInfoMapperObject'/>
        </types>
    </service>
</servicemapping>

Back to Article

Listing Six

Option Explicit
Implements IHeaderHandler
Public SessionID As String
Private Const NAMESPACE As String = "urn:UltraMaxService"
Private Const SESSIONID_NAME As String = "m_SessionID"
Private Function IHeaderHandler_readHeader(ByVal pHeaderNode _
    s MSXML2.IXMLDOMNode, ByVal pObject As Object) As Boolean
    If pHeaderNode.baseName = SESSIONID_NAME And _
        pHeaderNode.namespaceURI = NAMESPACE Then
        ' Read session ID
        SessionID = pHeaderNode.Text
        IHeaderHandler_readHeader = True
    Else
        IHeaderHandler_readHeader = False
    End If
End Function

Private Function IHeaderHandler_willWriteHeaders() As Boolean
    IHeaderHandler_willWriteHeaders = True
End Function
Private Sub IHeaderHandler_writeHeaders(ByVal pSerializer As _
                    MSSOAPLib.ISoapSerializer, ByVal pObject As Object)
    ' Write session ID
    pSerializer.startHeaderElement SESSIONID_NAME, NAMESPACE
    pSerializer.writeString SessionID
    pSerializer.endHeaderElement
End Sub

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 
Dr. Dobb's TV