C/C++ Users Journal November, 2004
In "Wrapping C++ COM Components as XML Web Services" [1] I discussed wrapping C++ COM components as XML web services. Following my own recipe, I've since wrapped a large COM component as an ATL web service. Although the wrapping went fine, performance requirements swayed me into pursuing even tighter integration. Therefore, I decided to fuse my C++ COM component with the ATL web service wrapper, thus eliminating the overhead of co-class initialization as well as serialization/deserialization of the component's state.
The resulting performance improvement was well worth the trouble of mixing and crushing together ATL and MFC code. In the process of merging the COM component with the ATL web service, I have completely eliminated the COM layer and instead created a static MFC library equivalent in functionality to the former COM component. Instead of a single co-class, the library contained several MFC classes.
As a side effect of the merger, I implemented a unique way of persisting session state because standard ISession and ISessionStateService interfaces required implementing a cumbersome (and inefficient) class serialization/deserialization mechanism. Instead, I used a simple memory allocation mechanism relying on static memory. Such memory persists as long as the ATL web service (essentially an ISAPI DLL) remains in memory.
Combining COM component with the ATL web service did not end my troubles, however. Although the code was performing reliably and quickly, ongoing improvements to the business logic resulted in periodic changes in the web service interface with each subsequent release. Even though the changes were mostly minor and incremental, the implications were severe because web service reference files (such as Visual Studio-generated Reference.cs or Reference.vb clients encapsulating client-side web service interface) had to be manually modified each time. Recall in "Wrapping C++ COM Components as XML Web Services" that manual changes to the generated web service reference files are required because of a slightalbeit crucialincompatibility between the .NET and ATL implementations of SOAP. The incompatibility exists both in the way SOAP headers are named and in the way web method parameters are represented. For example, .NET DateTime and ATL/COM DATE types are incompatible and do not map into each other as you might expect (DATE maps into .NET's float). Another minor issue exists with the ATL/COM VARIANT_BOOL type, which maps into .NET short rather than into bool. Although there is not much difference between short and bool as far as logical expressions in Managed C++ are concerned, the difference becomes critical and annoying when developing in C#: C# neither allows logical operations such as negation ("!") on shorts, nor does it interpret nonzero values as true.
The bottom line is that manual tweaking of the generated web service reference files allows mitigating differences in implementations of SOAP headers between ATL and .NET, as well as more accurate type mapping from VARIANT_BOOL to bool and from DATE to DateTime. The latter is achieved by adding the [SoapElement(DataType = "date")] formatting attribute to the affected parameter or user-defined structure field. However, the task of manual modification of several reference files, especially when such modifications are ample and releases are frequent, becomes a nightmare. Also, there is a liability involvedthe liability of breaking code, which has been working fine with the past release.
The situation with escalating changes made me search for another solution. My first idea was to rewrite the web service in .NET, which implies porting all business logic to .NET. No matter how excited I was with the idea of the port, the issue of liability associated with breaking proven code, and thus shedding doubt on the validity of the results produced by the web service (which is consumed by numerous clients), prevented the port from materializing. The issue of testing the ported code was also out of the question, as the formal process of business logic and rules verification was extremely rigorous, not to mention time- and human-resource consuming. Therefore, no matter what the solution was going to be, it had to somehow incorporate proven-good and formally verified business codewhich happened to be good old unmanaged C++ with extensive use of MFC. Consequently, I decided to merge the existing unmanaged C++ code with the managed .NET XML web service.
First of all, I created a new Managed C++ ASP.NET web service project using Visual Studio's New Project Wizard. Since, by default, managed projects do not support MFC, I had to modify the project options to allow MFC support and to ensure proper initialization of the MFC and C Run-Time Library (CRT). A detailed description of creating "mixed" projects is given in [2], but the relevant steps are:
#include <afxwin.h> // MFC core and standard components #include <afxdb.h> // MFC ODBC database classes #include <afxtempl.h> // MFC Template classes #include <afxole.h> // MFC OLE classes
Curiously, I have not found a working solution for the "Use of MFC" set to "Use MFC in a Static Library." Even when I got the code to compile, all attempts to access MFC classes resulted in memory exceptions caused by the failure of the assembly start-up code to properly initialize MFC. None of the MFC internal afx-initialization functions seemed to help the situation, even when they were called directly to force the initialization of the Foundation Classes.
Once the web service project configuration was complete, I added references to unmanaged libraries containing MFC classes (the business logic), which I used to compile my ATL web service project.
Now I could at least compile (and deploy) my project as an ASP.NET web service containing a mixture of managed and unmanaged code. The only remaining task was to provide an interface between Managed C++ code in web methods and unmanaged C++ code in MFC classes stored in the static library, plus ensure session-state persistence.
When programming in .NET, session-state persistence is trivial since any managed object can be stored in the Session collection of the WebService-derived class [4]. However, when the main business logic class is implemented in unmanaged C++ and its state is represented by an unmanaged MFC class, such an object cannot be stored in a managed collection since it can store only managed objects. Of course, it is possible to write a lengthy and tedious unmanaged-to-managed conversion routine relying on the rules of common conversion [5], but this approach is time consuming and ridiculously inefficient.
My solution was to scrap convenient session-state management provided by the Session collection and implement my own session-state management mechanism relying on unmanaged memory heap. Indeed, the Visual Studio project wizard conveniently generated a Global.asax.h file containing the declaration of the Global class derived from the HttpApplication base class. By means of HttpApplication, Global.asax provides access to such important ASP.NET web service events as Application_Start, Session_Start, Application_End, and Session_End. The events are respectively fired when the web service is first loaded (the web application is started), a new session is started (the new client connects to the web service), the web application is unloaded (through IIS when you click the Unload button to remove from memory a web application running in Medium or High isolation level, or when IIS is stopped or restarted), and a session expires. The Global class seems like a natural place for custom session handling. All you have to do is add a static unmanaged member to the Global class and override Application_Start, Session_Start, Application_End, and Session_End methods; see Listing 1.
I decided to use the CMapStringToPtr MFC class as a storage base for my session state, as this MFC collection object allows pointer lookup based on a character string key, and session state is identified by a session ID, which is a character string. When the web application is started (that is, Application_Start is called when the web service is accessed for the first time), the m_pUnmanagedStorage static member is initialized via allocation on an unmanaged heap. When the web service is unloaded, or IIS is stopped or restarted, the m_pUnmanagedStorage is disposed of to save memory.
Similarly, when a new session is created and the Session_Start is called, a new entry corresponding to the current session ID is added to the m_pUnmanagedStorage collection (in the sample code the session storage is represented by the CUltraMax MFC object). When the session expires (Session_End is called), the allocated CUltraMax object is disposed of and the corresponding entry is removed from the collection.
Last, I added a helper method to my web service class for retrieving the unmanaged session state represented by the CUltraMax object to be used with the current session; see Listing 2. Note that the Global class is indirectly accessible through the ApplicationInstance member of the current HTTP Context.
That's all. The unmanaged session-state management mechanism is done. The next step is to create web methods equivalent to the web methods that existed in the ATL version of the web service, which interfaced with the encapsulated MFC class internal methods. But prior to this, you must create .NET value types corresponding to user-defined structures used by the web methods in the ATL version of the web service, which in turn correspond to structures used by the MFC class methods.
In the code included with this article (see http://www.cuj.com/code/), the CUltraMax class operates on two user-defined typesCFileInfo and CSongInfo; see Listing 3.
The MFC CString class naturally maps to the .NET String type (also, COleDateTime maps to DateTime). As you can see in the online code, I have defined init-from constructors for each .NET value type to facilitate interfacing with unmanaged code in web methods. Each init-from constructor accepts an unmanaged MFC structure as a parameter and performs a field-by-field copying of data. Associated with each class is a convert-to function performing a .NET value type to unmanaged structure conversion, which is external to the class. It was tempting to define an implicit convert-to operator and use simple (classname*) notation to initiate the conversion [6], but managed classes do not allow functions that return unmanaged structures when those structures contain virtual methods or classes containing virtual methods. Because CString contains virtual methods, any attempt to define an implicit convert-to operator results in a C2230 compiler error. Therefore, I had to represent a convert-to operator by an external to the class function.
When the .NET value types mimic MFC unmanaged structures and the corresponding init-from and convert-to, interfacing managed and unmanaged code in web methods becomes practically a no-brainer.
The encapsulated MFC class in the online code has only four methods; see Listing 4. Therefore, the ASP.NET web service has the web methods in Listing 5 that mimic the MFC class methods.
As you can see, the MFC COleDateTime class is mapped into the DateTime type and CArray template array CSongInfoArray is mapped into a managed array SongInfo[] array. Also note the EnableSession property of the WebMethod attribute, which must be set to true to ensure that session information (session ID) is preserved between web method calls. Listing 6 is the actual web method implementation. The GetSessionStorage method is used to retrieve the appropriate CUltraMax object associated with the current session. Once a pointer to the CUltraMax is obtained, the underlying MFC method can be called.
In the case of the LogOn method, the internal LogOn call is enclosed within the try-catch block to catch the MFC CException and to throw the .NET Exception object, which is automatically mapped by the Framework into the SoapException, provided that the web service is accessed via HTTP-SOAP protocol.
Managed-to-unmanaged primitive type conversion is seamless, as String* can be implicitly converted to CString and vice versa. On the other hand, array conversion requires element-by-element conversion using the init-from constructor of the managed value type or an external convert-to helper function. COleDateTime and DateTime functions rely on their respective constructors to initialize new objects with their counterpart data.
Thus, in the end the unmanaged MFC class is completely wrapped inside the managed ASP.NET web service interface. Because the web method definitions of the new ASP.NET web service match web method definitions of the former ATL web service (save for forced mappings from DATE to DateTime and from VARIANT_BOOL to bool in the ATL web service), the two can be used interchangeably with web service clients. Nevertheless, it is a good idea to regenerate new web service reference files to be used with the .NET version of the web service just to be sure that the client is using the interface, which is correct down to minute details. All you have to do is right-click on the web service name in the Web References section of the project and select "Update Web Reference" from the context menu. Such an update was a "no-no" with the ATL version of the web service since regeneration of the web service reference file resulted in a loss of all manual modifications that had to be made to the file to ensure SOAP compatibility between ATL and .NET.
As a final note, all shared unmanaged DLLs, such as third-party components used by the mixed-mode web service project and their respective dependencies, must be deployed either in the same directory as the web service assembly (typically, bin folder under the web service virtual root) or in the system32 folder. The unexpected side effect of the .NET Framework assembly/DLL location mechanism prevents loading of dependent DLLs when some of them reside in the web service bin folder and their dependent DLLs such as mfc71.dll, mscvrt71.dll, and so on, reside elsewhere (in system32, for example). Therefore, all third-party DLLs and their dependent DLLs must be either in bin or system32, unless you create an extensive and complicated application configuration file explicitly resolving the dependency lookup issue.
This approach to mixing managed and unmanaged code in ASP.NET web services written in Managed C++ provides an easy and efficient way to wrap existing MFC code as a fully .NET-compliant XML web service without requiring port of the entire legacy code base to .NET and without compromising the existing web service interface defined by the ATL web service implementation of the component.