C++, Java, & Service-Oriented Architectures

C++ and Java can be integrated-and there are cases where it makes sense to do so, such as when a system needs to be language and platform independent.


September 01, 2004
URL:http://www.drdobbs.com/jvm/c-java-service-oriented-architectures/184401849

There's no doubt in my mind that I became a better software developer the day I became a C++ programmer. But while I enjoy C++, there are aspects of Java that make me more productive, such as its support for strings, threads, I/O, distributed computing, and building web services. Moreover, Java has another strength that's not always taken advantage of—its ability to integrate with C++ applications.

The Java Native Interface (JNI) lets Java interface with software written in other languages, namely C++. Understanding JNI can bring the world of web services to your C++ application, without changing a line of existing C++ code. By leveraging the tools and support Java has for building web services, even a platform-specific C++ application can be exposed to the world in a platform-agnostic fashion.

The Scenario

To understand how Java can integrate with C++, and even augment it, consider the following scenario. Assume you have a securities trading system written and deployed as a C++/COM application on Windows. The application runs well, and is in use at many customer sites. However, there have been two overwhelming customer requests.

A potential solution is to add SOAP support to your C++ trading system and integrate it with the library internally. However, some problems quickly arise. First, in this scenario, assume the library is a UNIX library, for which you do not have the source code. Second, how do you go about adding SOAP support in C++? You could port your code to .NET or find a third-party library to use. However, the downside to these choices is that they require a great deal of change to your C++ code, and probably result in two baselines of the system—one as a standalone C++/COM application (for existing customers), and the other as a C++ web service.

An alternate solution is to use Java to build a web service around your trading system and to integrate the portfolio manager within the Java code. The result would be a web service front end to your trading system, with portfolio management support, without requiring a change to your C++ code. You can leverage all that Java has to offer when building web services.

In this scenario, the C++ trading system is implemented as a COM server written in C++ as an Active Template Library (ATL) executable. This server exposes one interface—ITradeStock.

The Solution

The architecture I propose here uses JNI to provide a Java interface to the C++ application and library. The JNI code is to be exposed via a Java object that extends the java.rmi.Remote interface, making it accessible to remote Java applications via the Java Remote Method Invocation (RMI) API. Finally, a Java servlet implements the SOAP interface, and through RMI, calls the JNI layer that interfaces to the C++ code. Figure 1 illustrates the proposed system architecture.

Figure 1: System architecture.

The result is a solution that takes a C++/COM application (which must run on Windows) and a UNIX C++ library, and integrates them with Java, which can run on a variety of platforms. The integrated application is exposed via SOAP, making it platform and language independent. It is within the Java code that you solve the trading system/portfolio manager integration issue and implement the SOAP interface.

The first step in this solution is to define a Java interface that exposes methods similar to the C++ trading system and the portfolio manager, as in Listing One.

Listing One

public interface TradingSystemInterface extends Remote
{
    // From C++ Trading System (ITradeStock interface)
    public String GetQuote(String stock) 
        throws RemoteException;
    public String Buy(String stock, int shares) 
        throws RemoteException;
    public String Sell(String stock, int shares) 
        throws RemoteException;    
    // From C++ Portfolio Manager library
    public String AddHolding(String stock, int shares) 
        throws RemoteException;
    public String RemoveHolding(String stock, int shares) 
        throws RemoteException;
    public String EnumHoldings() 
        throws RemoteException;
    public String GetPosition(String stock) 
        throws RemoteException;
}

The first three methods, GetQuote, Buy, and Sell, map to the ITradeStock COM interface implemented in the C++ trading system. GetQuote returns the current quote of the given stock symbol. Buy and Sell place market orders for the stock.

The remaining methods map to those implemented in the C++ portfolio manager library. The methods AddHolding and RemoveHolding are called after you buy and sell stock, respectively, to track your positions. EnumHoldings returns a comma-delimited list of stock symbols that are in your portfolio. Finally, GetPosition returns the number of shares held for the given stock symbol. For the sake of simplicity, the portfolio manager library in this example will be implemented as a Windows DLL. This is done so that you can run all of the demo code (the COM application, the portfolio manager, and Java) on one Windows machine.

The Java Native Interface

With the interface defined, you need to generate the JNI code to integrate with the C++ trading system and the portfolio manager library. First, you define a Java class that implements the TradingSystemInterface Java interface, named TradingSystemImpl (Listing Two).

Listing Two

import java.rmi.server.*;

public class TradingSystemImpl 
    extends UnicastRemoteObject 
    implements TradingSystemInterface
{
    public TradeStockImpl() throws RemoteException {
    }
    // From C++ Trading System (ITradeStock)
    public String GetQuote(String stock) 
      throws RemoteException {
        return doGetQuote(stock);
    }
    public String Buy(String stock, int shares) 
      throws RemoteException {
        return doBuy(stock, shares);
    }
    public String Sell(String stock, int shares) 
      throws RemoteException {
        return doSell(stock, shares);
    }
    // From C++ Portfolio Manager
    public String AddHolding(String stock, int shares) 
      throws RemoteException {
        return doAddHolding(stock, shares);
    }
    public String RemoveHolding(String stock, int shares) 
      throws RemoteException {
        return doRemoveHolding(stock, shares);
    }
    public String EnumHoldings() 
      throws RemoteException {
        return doEnumHoldings(stock, shares);
    }
    public String GetPosition(String stock) 
      throws RemoteException {
        return doGetPosition(stock, shares);
    }
    /////////////////////////////////////////////////////
    // Native methods 
    // Calls the actual C++ Trading System (ITradeStock)
    native protected String doGetQuote(String stock);
    native protected String doBuy(String stock, int shares);
    native protected String doSell(String stock, int shares);
    // Calls the actual C++ Portfolio Manager
    native protected String doAddHolding(String stock, int shares);
    native protected String doRemoveHolding(String stock, int shares)
    native protected String doEnumHoldings()
    native protected String doGetPosition(String stock)
}

It's within the definition of the methods marked native that you add the code to call the C++ software. However, these methods are not implemented in Java, but are instead implemented in a special layer of C++ code partially generated by javah—a tool that comes with the Java Development Kit (JDK) and resides within the bin subdirectory of your JDK installation.

You compile the TradingSystemImpl class using the command:

>javac TradingSystemImpl.java

Next, use javah to generate the header files for your JNI implementation, providing the Java class file as a parameter:

>javah -jni TradingSystemImpl

If all goes well, the tool outputs the file TradingSystemImpl.h in the current directory. This file contains the function declarations for the methods that were marked "native" in the TradingSystemImpl class. An external header file, jni.h, is included at the top of the file and can be located in the include subdirectory of your JDK installation.

The final step is to implement the C++ functions. In this example, the implementation is a Windows DLL that includes the TradingSystemImpl.h file, where each function calls into the ITradeStock interface as well as the portfolio manager library. Listing Three shows a portion of the C++ JNI code. The complete implementation can be downloaded electronically, along with all of the code for this solution.

Listing Three

#include "TradingSystemImpl.h" // includes jni.h

#define INITGUIDS
#include "stdafx.h"
#include <ole2.h>
#include <ole2ver.h>
#include "stdlib.h"

// The GUIDs for using the ITradeStock interface 
const IID IID_ITradeStock = {0x326E68F3,0x6376,0x4A32,
                            {0xA0,0xA5,0xDD,0x37,0x02,0x6C,0xCC,0x3C}};
const IID LIBID_TRADINGSYSTEMLib = {0x81C64ABD,0xEDBB,0x492D,
                                   {0xB6,0x22,0xC6,0x2E,0x6F,0x68,0x0F,0xC3}};
const CLSID CLSID_TradeStock = {0x0D46CA8E,0x90A1,0x4F1F,
                                   {0xAE,0xBD,0x08,0xAB,0x32,0x10,0x93,0xF6}};
BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, 
                                                       LPVOID lpReserved)
{
    if ( FAILED ( CoInitialize(NULL) ) )
        return FALSE;
    return TRUE;
}
ITradeStock* GetTradeStockRef() 
{
    ITradeStock* pITradeStock = NULL;
    CoCreateInstance(CLSID_TradeStock, NULL, CLSCTX_LOCAL_SERVER, 
                     IID_ITradeStock, (VOID**)&pITradeStock);
    return pITradeStock; 
}
void ReleaseTradeStockRef(ITradeStock* pITradeStock) 
{
    if (pITradeStock ) pITradeStock->Release();
}
/* Class:     TradeStockImpl
 * Method:    doBuy
 * Signature: (Ljava/lang/String;I)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL 
    Java_TradeStockImpl_doBuy(JNIEnv * env, jobject me, jstring Stock, 
                              jint Shares) 
{
    // ...
}
 ...

JNI provides a C++ utility class, JNIEnv, which contains methods to help deal with conversions between C++ types and Java types. This class is defined in the header file jni.h. As an example of how to use this class, examine the JNI method Java_TradingSystemImpl_doGetQuote, in Listing Four.

Listing Four

JNIEXPORT jstring JNICALL 
   Java_TradingSystemImpl_doGetQuote(JNIEnv * env, jobject me, jstring Stock)
{
    double quote = 0.0000;
    ITradeStock* pITradeStock = GetTradeStockRef();
    if ( pITradeStock ) 
    {
        // Convert the Java String, Stock, to a C++ char*
        const char* stockStr = (*env).GetStringUTFChars(Stock, 0);

        // Calling a COM interface, convert to a BSTR
        wchar_t stockWStr[32];
        mbstowcs(stockWStr, stockStr, strlen(stockStr));
        BSTR stock = SysAllocString(stockWStr);

        // Call the actual C++ implementation of this method
        pITradeStock->GetQuote(stock, "e);
        SysFreeString(stock);
    }
    ReleaseTradeStockRef(pITradeStock);
    // Convert the quote returned from a double to a string 
    int  decimal, sign;
    char* pszQuote = _ecvt( quote, 4, &decimal, &sign );
    char szQuote[12];
    // We need to add the decimal point since _ecvt doen't
    strncpy( szQuote, pszQuote, decimal );
    szQuote[decimal] = NULL;
    strcat( szQuote, "." );
    strcat( szQuote, &pszQuote[decimal] );
    // Since Java is expecting a Java String object as the return to this 
    // method, we use the JNIEnv class to convert char* to 
    // a jstring object and return it
    return (*env).NewStringUTF(szQuote);
}

This method calls the ITradeStock::GetQuote method within the C++ trading system, using the parameters passed in from the Java code. First, the parameter, Stock, is converted from a Java string to a C++ char* by calling JNIEnv::GetStringUTFChars. The resulting C++ string is converted to a COM BSTR before calling into the COM interface. The reverse occurs when the returned quote value is converted from a C++ double to a char*, and then to a Java string by calling JNIEnv::NewStringUTF.

In addition to the conversion routines, the JNIEnv class contains methods to throw Java exceptions, create Java objects, and even use Java reflection.

Java Remote Method Invocation

The JNI code needs to be accessible to other Java applications, potentially remote ones (those running on other computers). This lets you run the C++ trading system and its JNI code on a Windows PC, the portfolio manager and its JNI code on a UNIX machine, and the Java web service on a separate machine running either Windows or UNIX. Adding support for Java Remote Method Invocation (RMI) accomplishes this.

To support RMI, the interface TradingSystemInterface extends the java.rmi.Remote interface, and all of the methods throw java.rmi.RemoteExceptions. Also, the implementation class, TradingSystemImpl, extends the java.rmi.UnicastRemoteObject class. This makes the JNI code accessible to remote Java applications. The TradingSystemImpl class is then used to generate a client stub and a server skeleton for RMI. Java's stub/skeleton compiler, rmic, is called with the class name for which it is to generate the stub and skeleton classes:

>rmic TradingSystemImpl

After running successfully, you will find two new class files in the current directory, TradingSystemImpl_Stub.class and TradingSystemImpl_Skel.class. The stub class is loaded by Java for each client process that requests a reference to the TradingSystemImpl remote class. The skeleton class is loaded by Java when the RMI server process (which creates the TradingSystemImpl object) is started. The TradingSystemImpl object is only available to clients when the server process is running.

In this example, the TradingSystemImpl RMI server is defined as a Java application that performs the following tasks. First, the JNI Windows DLL is loaded via a call to System.loadLibrary. Second, an object of the TradingSystemImpl class is created; and third, a reference to this object is registered with the RMI registry running on the host machine. The code for this application, implemented in the Java class RMITradingSystem, is in Listing Five.

Listing Five

import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;

// This class runs as server process for the remotable  // TradingSystemImpl class

public class RMITradingSystem
{
    protected TradingSystemImpl tradeStock = null;
    public static void main(String [] args) {
        try {
            // Load the JNI C++ native code
            System.loadLibrary("JNITradingSystem"); 
            // Remaining work done in the constructor below
            RMITradingSystem rmiTS = new RMITradingSystem();
        }
        catch ( Exception e ) { 
            e.printStackTrace();
        }
    }    
    public RMITradingSystem() {
        try {
            // Create a reference the Remotable class
            tradeStock = new TradingSystemImpl();
            // Register this class with the local RMI registry
            Registry localRegistry = 
                LocateRegistry.getRegistry();
            try {
                localRegistry.bind( "TradingSystem", tradeStock );
            }
            catch ( Exception e ) {
                e.printStackTrace();
            }
        }
        catch ( Exception e ) { 
            e.printStackTrace();
        }
    }
}

Prior to running the TradingSystemImpl server process, the RMI registry must be started. Executing the rmiregistry command in the background and specifying the port as an optional parameter does this. The default RMI port is 1099.

When the RMI registry is started, the TradingSystemImpl server process should be started via the command:

> java TradingSystemImpl

Now, the JNI code that integrates with the C++ trading system and portfolio manager will be available to other Java applications via RMI.

The Java Web Service

Java web services are simple to build and use. To start, download the Java Web Service Developer Pack, available on Sun's Java web site at http://java.sun.com/ webservices/downloads. This package includes a complete version of the Apache Tomcat Servlet engine, along with the Apache SOAP classes and deployment tools. The Java trading system web service for this article is implemented as a Java servlet that can run within any Java servlet engine. However, the Tomcat Servlet engine serves as an excellent environment.

The TradingSystemServlet class handles each incoming HTTP SOAP request, maps the request to RMI calls into the TradingSystemImpl class, and returns the result as a SOAP response. Example 1 shows the portion of the servlet that parses the request in the SOAP message, and ultimately calls the appropriate trading system and portfolio manager routines.

Example 1: Parsing the request in the SOAP message.

String requestName = elem.getNodeName();
if ( requestName.equalsIgnoreCase("GetQuote")) {    
    ret = getQuote( nodes );
}
else if ( requestName.equalsIgnoreCase("Buy")) {    
    ret = buy( nodes );
    if ( ret.equalsIgnoreCase("success"))
        ret = addHolding( nodes );
}
else if ( requestName.equalsIgnoreCase("Sell")) {    
    ret = sell( nodes );
    if ( ret.equalsIgnoreCase("success"))
        ret = removeHolding( nodes );
}
else if ( requestName.equalsIgnoreCase("GetPosition")) {    
    ret = getPosition( nodes );
}

Example 2 shows how the servlet uses RMI to call into the TradingSystemImpl class. First, an attempt is made to find an object within the RMI Registry that implements the TradingSystemInterface. If successful, method calls can be made on the object.

Example 2: Servlet using RMI to call into the TradingSystemImpl class.

TradingSystemInterface tradingSystem;
try {
  Registry rmiRegistry = LocateRegistry.getRegistry();
  tradingSystem = (TradingSystemInterface)
      rmiRegistry.lookup("TradingSystem");
} 
catch ( Exception e ) { 
}
 ...
// buy 10 shares of IBM stock
tradingSystem.Buy("IBM", 10); 

// add the new holding to the portfolio
tradingSystem.AddHolding("IBM", 10);

The code to read an incoming SOAP message is implemented within the servlet's doPost method (Listing Six). First, an instance of a DocumentBuilder class—implemented in the org.apache.soap.xml.XMLParserUtils library—is used to parse the incoming request XML into a DOM object. Next, the SOAP Envelope, which contains the SOAP message header and body, is extracted from the DOM. Finally, the SOAP body itself is extracted via a call to env.getBody.

Listing Six

public void doPost( HttpServletRequest request, HttpServletResponse response )
    throws IOException, ServletException
{
    try {
        String ret = "";
        // Read the request in as an XML string
        BufferedReader br = request.getReader() ;
        DocumentBuilder xdb = XMLParserUtils.getXMLDocBuilder();
        Document doc = xdb.parse( new InputSource(br) );

        // Get the SOAP envelope, which  contains the header and body
        Envelope env = Envelope.unmarshall( doc.getDocumentElement() );

        // Get the SOAP body, and create a Vector of body entries
        Body body = env.getBody();
        Vector bodyEntries = body.getBodyEntries();
		        
        // For the XML we're expecting, the body's 
        // root element should be the method name
        Enumeration e = bodyEntries.elements();
        Element elem = (Element)e.nextElement();

        // The child nodes of the root are the parameters
        NodeList nodes = elem.getChildNodes();
				
        // Get the method request name
        String requestName = elem.getNodeName();

        // Handle request
        ...

        // Send the response to the call
        sendSOAPResponse(requestName, ret, response);
    }
    catch ( Exception e ) {
        e.printStackTrace();
    }
}

The body contains the actual method name to call, and its parameters, which are extracted from the SOAP body by iterating through the DOM structure. Once the SOAP request is processed and the internal method calls are made, a SOAP response containing the method's return code is sent back to the caller.

A Java SOAP Client

With the C++ trading system and portfolio manager exposed as a web service, any SOAP-enabled client can use them, regardless of platform or language. The SOAP client included with the demo code for this article is a small Java application. The Java Web Services Developer Pack makes it as simple to build a SOAP client as it was to build our web service.

The sendSOAPMessage routine in Listing Seven shows how little code it takes to create and send an HTTP SOAP request and process a SOAP response.

Listing Seven

public void sendSOAPMessage(String hostURL, String filename)
{
    try {
        // Read and parse the XML to send 
        FileReader fr = new FileReader(filename);
        DocumentBuilder xdb = XMLParserUtils.getXMLDocBuilder();
        Document doc = xdb.parse(new InputSource(fr));
            
        // create the SOAP Envelope and Body
        Envelope env = new Envelope();
        Vector bodyElems = new Vector();
        bodyElems.add( doc.getDocumentElement() );
        Body body = new Body();
        body.setBodyEntries( bodyElems );
        env.setBody( body );
            
        // create SOAP message and send it
        org.apache.soap.messaging.Message msg = 
            new org.apache.soap.messaging.Message();
        msg.send(new java.net.URL(hostURL), URI, env);

        // get SOAP response
        SOAPTransport st = msg.getSOAPTransport();
        BufferedReader br = st.receive();
            
        System.out.println("\r\nSOAP Server response:");
        String line = br.readLine();
        while ( line != null ) {
            System.out.println(line);
            line = br.readLine();
        }
    }
    catch ( Exception e ) {
        e.printStackTrace();
    }
}

The client code resembles the SOAP code from the servlet, and takes the following steps:

  1. Creates an XML DOM object by reading and parsing the XML from a file.
  2. Creates a SOAP Envelope and Body, and fills the Body with the entries from the DOM.
  3. Creates a SOAP Message, and sends the Envelope—with the Body—to the server.
  4. Receives the response by calling the receive method on the SOAPTransport object, which blocks until the server sends a response.

Other potential SOAP clients include those written in C#, C++, VB.NET, Python, Perl, or any language that supports parsing and assembling SOAP messages in some way.

Running the Sample Code

The sample code for this article is comprehensive, and is available at http://www.cuj.com/code/. Table 1 lists the components of the sample, along with the directory for each component.

Table 1: Sample application components.

Component Directory
C++/COM Trading System SOATrading\TradingSystem
C++ Portfolio Manager DLL SOATrading\PortfolioManager
Java Native Interface/C++ Code SOATrading\JNITradingSystem
Java RMI Code SOATrading\RMITradingSystem
Java Servlet/Web Service SOATrading\TradingSystemServlet
Java SOAP Client SOATrading\SOAPClient

The C++ trading system is implemented as an ATL/COM executable. The COM server must be registered on your computer by running the following command:

>TradingSystem -regserver

For the Java-based components, you need the following software downloads:

The JNI code for the trading system is implemented as a C++ Windows DLL. This DLL, and the portfolio manager DLL, must both be placed in the SOATrading\JNITradingSystem directory when built.

The Java RMI server application comes with two batch files, one to compile the source code (build.bat) and another to run it (runrmi.bat). When runrmi.bat is executed, it starts the Java RMIRegistry tool and then launches the RMITradingSystem application. The application registers its TradingSystemImpl object with the RMI registry. Both batch files contain file paths that will most likely need to change to match the locations on your computer.

The Java servlet must be deployed on a servlet-enabled web server, such as Apache Tomcat, which can be downloaded at http:// www.apache.org/. The directions for deploying the trading system servlet can be found in the file Readme.txt, in the SOATrading\TradingSystemServlet directory.

Finally, executing getquote.bat, buy.bat, or sell.bat runs the Java SOAP client. Each batch file launches the Java application, passing the web server URL and the appropriate XML file as parameters. Each XML file contains the XML body for the SOAP request. You can change the stock name, and the quantities traded, by modifying the XML files.

Conclusion

Java and C++ can be integrated, and there are cases where it is beneficial to do so. In the scenario I presented here, the trading system and the portfolio manager have been integrated via a Java web service and are now language and platform independent. Prior to this, the C++ application would not have been available to clients other than COM clients on Windows. This is a big achievement, and no existing C++ code needed to be changed.


Eric Bruno has worked extensively in Java and C++ developing real-time trading and financial applications. He can be contacted at [email protected] or http://www.ericbruno .com/.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.