Angel Hernández

WWSAPI (Windows Web Services API) + Interop + WPF… How cool is that!!!

A couple of nights ago I was reading about some of the new features in Windows 7 SDK and I can honestly say “They’re a lot”, as a matter of fact, I mentioned some of them in one of my previous Tech-Ed session this year. Despite of all these new features, there’s one that got my attention from the beginning and it was WWSAPI, because there wasn’t any support for Web Service from native code, except for a couple of existing toolkits including gSOAP that can be implemented  on Windows, Linux and Mac OSX.  WWSAPI was first introduced at  PDC 2008 and it will be formally released along Windows 7, although previous versions of the operating system starting from XP SP2 can get it via Windows Update when becomes available. I first tested it when Windows 7 was still RC but I didn’t post about it because I was expecting for Windows 7 to be released or about to be released.

Having said that, last weekend I created a solution to demo  WWSAPI in conjunction with .NET. The solution structure is depicted below

Solution 

  • NativeTester: Console application (C++) which calls our Dynamic Link Library (DLL)
  • RSSFeedService: Web Service (C#) which retrieves RSS feeds, parse them and returns a well-formed XML
  •  RSSViewer: WPF application (C#) which consumes the Web Service plus invokes our Dynamic Link Library (DLL)
  • Tester: WinForm application (C#) which consumes the Web Service plus invokes our Dynamic Link Library (DLL)
  • WWSAPIDemo: Dynamic Link Library (C++) which implements  WWSAPI and it’s invoked from .NET via Interop

Along with the code you can find MSDN.xml which contains the RSS feeds from MSDN Australia, even when the code references  http://localhost/MSDN.xml, you can change the Url and retrieve any given RSS, in my case I deployed the previously mentioned file to my local IIS.

Many of you might be wondering about, how can I generate a proxy based on the Web Service via C++?  and the answer is quite simple, the new Windows 7 SDK provides us with an utility that does this for us, WSUTIL.exe. After executing it, we have as a result two files (an .H and a .CPP). The strings are treated as  WCHAR* by default which in turn is Unicode and every single string in .NET are Unicode as well, I’ll comment a bit more about this later.  

wsutil

Having the Web Service published already on IIS, we better start working on the Dynamic Link Library which implements WWSAPI. Below we can see its header file

#include "stdafx.h"
 
#define EXPORT extern "C" __declspec(dllexport)
 
#define MAX_SIZE  128000 //128Kb
#define TRIM_SIZE 512
 
EXPORT WCHAR* GetFeeds(WCHAR* szUrl, int cbResults);
 
EXPORT void GetItem(WCHAR* szUrl, WCHAR* szItemName);
And this is the method responsible for connecting against the Web Service and retrieves the RSS feeds 
// Get feeds from a given Url through WWSAPI
EXPORT WCHAR* GetFeeds(WCHAR* szUrl, int cbResults) {
    WCHAR* temp = NULL;
    WS_HEAP* heap = NULL;
    WCHAR retval[MAX_SIZE]; // 128Kb (It should be enough to avoid WS_E_QUOTA_EXCEEDED error)
    WS_ERROR* error = NULL;
    ULONG propertiesCount = 0;
    WS_SERVICE_PROXY* proxy = NULL;
    WS_CHANNEL_PROPERTY channelProps[2]; 
    WS_STRING serviceUrl = WS_STRING_VALUE(L"http://localhost/DemoSvc/RSSFeedService.asmx");
    WS_ENDPOINT_ADDRESS endpoint = { serviceUrl}; 
    WS_ENVELOPE_VERSION soapVersion = WS_ENVELOPE_VERSION_SOAP_1_1; // Our Webservice is WsiProfiles.BasicProfile1_1 compliant
    WS_ADDRESSING_VERSION addressingVersion = WS_ADDRESSING_VERSION_TRANSPORT;
 
    // Set channel's properties
    channelProps[propertiesCount].id = WS_CHANNEL_PROPERTY_ENVELOPE_VERSION;
    channelProps[propertiesCount].value = &soapVersion;
    channelProps[propertiesCount].valueSize = sizeof(soapVersion);
    propertiesCount++;
 
    // Set addressing's properties
    channelProps[propertiesCount].id = WS_CHANNEL_PROPERTY_ADDRESSING_VERSION;
    channelProps[propertiesCount].value = &addressingVersion;
    channelProps[propertiesCount].valueSize = sizeof(addressingVersion);
    propertiesCount++;
 
    // Can we create an WsError and WsHeap objects?
    if (SUCCEEDED(WsCreateError(NULL, NULL, &error)) && SUCCEEDED(WsCreateHeap(MAX_SIZE, TRIM_SIZE, NULL, NULL, &heap, error))) {
        // Can we create a proxy based on the service?
        if  (SUCCEEDED(WsCreateServiceProxy(WS_CHANNEL_TYPE_REQUEST, WS_HTTP_CHANNEL_BINDING, NULL, NULL, NULL, 
            channelProps, propertiesCount, &proxy, error))) {
                // Can we open the proxy object?
                if  (SUCCEEDED(WsOpenServiceProxy(proxy, &endpoint, NULL, error))) {
                    // If we're able to invoke the service then copy the results to another variable because if
                    // we don't we lose the response after freeing the heap
                    if (SUCCEEDED(RSSFeedServiceSoap12_RetrieveFeeds(proxy, szUrl, cbResults, &temp, heap, NULL, NULL, NULL, error))) 
                        wcscpy_s(retval, temp);
                }
        }
    }
 
    // Deallocate and free resources
    if (error != NULL)
        WsFreeError(error);
 
    if (proxy != NULL) {
        WsCloseServiceProxy(proxy, NULL, error);
        WsFreeServiceProxy(proxy);
    }
 
    if (heap != NULL)
        WsFreeHeap(heap);
 
    return retval;
}

Please note the following:

  • We must specify the version of SOAP to use (otherwise, the client is going to complain about it, because it will use 1.2, as evidence check the RSSFeedServiceSoap12 method name) 
  • I return a WCHAR* and it works without issues, even when the right way to do it is returning an HRESULT or an integer, accept a pointer as an argument which at the same time it’s an output parameter. There’s a document about best practices for creating DLLs which can be downloaded from here

Our implementation from the console application is shown below

#include "stdafx.h"
 
typedef WCHAR* (*myCallback) (WCHAR*, int);
 
int _tmain(int argc, _TCHAR* argv[]) {
    HINSTANCE hInstance;
    myCallback ptrToFunc;
    WCHAR* results = NULL;
 
    if ((hInstance = LoadLibrary(L"C:\\Users\\angel.hernandez\\Desktop\\WWSAPI\\WWSAPIDemo\\x64\\Debug\\WWSAPIDemo.dll")) != NULL) {
        if ((ptrToFunc = (myCallback) GetProcAddress(hInstance, "GetFeeds")) != NULL) {
            results = ptrToFunc(L"http://localhost/MSDN.xml", -1);
            wprintf(L"\n%ls\n", results);
            FreeLibrary(hInstance);
            printf("\n\nPress any key to exit...\n");
            _getch();
        }
    }
    return 0;
}

After compiling, linking and executing our NativeTester application, we can see how the RSS feeds are displayed on the console.

NativeTester

GetItem is another exported function in our DLL which displays a found element (if any) after executing an  XPath query and using the starts-with function (as if it was the Like % operator). GetItem retrieves the RSS feeds by previously calling the GetFeeds function.

// Execute XPATH Query based on feeds retrieved through WWSAPI
EXPORT void GetItem(WCHAR* szUrl, WCHAR* szItemName) {
    WCHAR retval[TRIM_SIZE];
    WCHAR* results = NULL;
    BSTR nodeContent = NULL;
    WCHAR xPathQueryBuffer[TRIM_SIZE];
    IXMLDOMDocumentPtr docPtr = NULL;
    IXMLDOMNodePtr selectedNode = NULL;
 
    if ((results = GetFeeds(szUrl, -1)) != NULL &&  wcslen(results) > 0) {
        CoInitialize(NULL);
 
        docPtr.CreateInstance("Msxml2.DOMDocument.6.0");
 
        wsprintf(xPathQueryBuffer, L"/rss/items/item[starts-with(@title,'%ls')]", szItemName);
 
        if (SUCCEEDED(docPtr->loadXML(_bstr_t(results), NULL))) {
            if (SUCCEEDED(docPtr->selectSingleNode(_bstr_t(xPathQueryBuffer), &selectedNode))) {
                nodeContent = SysAllocString(retval);
                selectedNode->get_xml(&nodeContent);
                MessageBox(NULL, nodeContent, L"XPath Query Results", NULL);
                SysFreeString(nodeContent);
            }
        }
    }
}

So far we’ve been able to implement and use WWSAPI from native code, however we haven’t tested our Dll’s  functionality  from .NET, in that case the first thing to do is, “to import” the function we’re interested on by specifying the DllImport attribute.

[DllImport(@"C:\Users\angel.hernandez\Desktop\WWSAPI\WWSAPIDemo\x64\Debug\WWSAPIDemo.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr GetFeeds(IntPtr szUrl, int cbResults);

The GetFeeds function returns a  WCHAR* which in turn is interpreted by .NET as an IntPtr. We also need to use the Marshal class to convert it from  WCHAR* to an Unicode string and to pass a  WCHAR* as parameter to the function

private void btnTestWWSAPI_Click(object sender, EventArgs e) {
    IntPtr szUrl = Marshal.StringToBSTR("http://localhost/MSDN.xml");
    MessageBox.Show(Marshal.PtrToStringUni(GetFeeds(szUrl, -1)));
    Marshal.FreeBSTR(szUrl);
}

and Voila!!! I have my WWSAPI implementation being consumed and used from .NET, well.. from a WinForm application, but what about a WPF application? The answer is… the implementation remains the same except for the  XAML code required plus the databinding  process is quite simple if an  XmlDataProvider is used because the data is extracted and assigned based on an XPath syntax.

<!-- Data Provider-->
<Window.Resources>
    <XmlDataProvider x:Key="xmlFeeds" IsAsynchronous="True" XPath="/rss/items/item"/>
</Window.Resources>
 
<Grid Name="grdFeeds" Margin="0,75,0,12">
    <!-- Binding Source-->
    <ListView Margin="11.432,6" Name="lstFeeds"  
             ItemsSource="{Binding Source={StaticResource xmlFeeds} }" MouseDoubleClick="lstFeeds_MouseDoubleClick">
        <ListView.View>
            <GridView x:Name="grdFeedItems">
                <GridViewColumn Header="Title" x:Name="grcTitle" Width="100" DisplayMemberBinding="{Binding XPath=@title}"/>
                <GridViewColumn Header="Publishing Date" x:Name="grcPublishingDate" Width="100" DisplayMemberBinding="{Binding XPath=@publishingDate}"/>
                <GridViewColumn Header="Url" x:Name="grcUrl" Width="100" DisplayMemberBinding="{Binding XPath=@url}" />
            </GridView>
        </ListView.View>
    </ListView>
</Grid>
 

The Button Click event handler is shown below

private void btnExecuteOperation_Click(object sender, RoutedEventArgs e) {
    XmlDocument feeds = new XmlDocument();
    IntPtr szUrl = IntPtr.Zero, szItemName = IntPtr.Zero;
    XmlDataProvider xmlFeeds = TryFindResource("xmlFeeds") as XmlDataProvider;
 
    try {
        switch (cboOperations.SelectedIndex) {
            case 0:
                using (RSSFeedService proxy = new RSSFeedService())
                    feeds.LoadXml(proxy.RetrieveFeeds("http://localhost/MSDN.xml", -1));
                xmlFeeds.Document = feeds;
                break;
            case 1:
                szUrl = Marshal.StringToBSTR("http://localhost/MSDN.xml");
                feeds.LoadXml(Marshal.PtrToStringUni(GetFeeds(szUrl, -1)));
                Marshal.FreeBSTR(szUrl);
                xmlFeeds.Document = feeds;
                break;
            case 2:
                szUrl = Marshal.StringToBSTR("http://localhost/MSDN.xml");
                szItemName = Marshal.StringToBSTR(!string.IsNullOrEmpty(txtItemTitle.Text) ? txtItemTitle.Text : "Windows");
                GetItem(szUrl, szItemName);
                Marshal.FreeBSTR(szUrl);
                Marshal.FreeBSTR(szItemName);
                break;
        }
    } catch (Exception ex) {
        MessageBox.Show(string.Format("Oops! Something wrong just occurred\n{0}", ex.Message),
            "Exception caught", MessageBoxButton.OK, MessageBoxImage.Information);
    }
}

Attached to this post you can find the code, please feel free to download it, modify it and play with it. I hope you consider this information useful and always remember one thing… The only possible way to learn is by playing with the technology and feel confident about taking risks.

God bless you

Regards,

Angel

Leave a Comment

(required) 

(required) 

(optional)

(required)