Zero Client Installation for Visual C++ Accessing Java

Java/J2EE COM Interoperability Products Page

Zero client installation is a form of late binding which does not require any installation on client machine. It provides the ability to access Java Objects running anywhere from COM clients running on unmodified Windows machines.

Summary

This example shows you how you can access Java™ objects (including EJBs) running on any Operating System from COM clients running on a standard Microsoft Windows NT (SP4 or greater) or Windows 2000 machine using J-Integra®™.

There is zero deployment overhead: you will not have to install any software at all on the Windows client machine.

Introduction

This example uses the objref moniker string generated by GetJvmMoniker, and is a complete analogue to the VB to Java (zero client installation) example. The main difference is that this sample shows how to access Java class from a Visual C++ client. For the details on how this sample works please refer to VB to Java (zero client installation) example.

The Steps Involved

  1. Display the JVM's moniker
  2. Create your Java class and start the JVM
  3. Create Windows VC COM client
  4. Access Java objects from the Windows VC client by using the moniker

Display the JVM's moniker

On any machine that has the jintegra.jar file on it, set your CLASSPATH to include jintegra.jar.

Then run the com.linar.jintegra.GetJvmMoniker Java class, specifying the full TCP/IP name or address of the machine running the JVM in which Java objects will be accessed, and a free TCP/IP port number as parameters:

java com.linar.jintegra.GetJvmMoniker mymachine.mycompany.com 1350

Note If you have already tried the VB to Java (zero client installation) example, there is no need to run GetJvmMoniker again.

You will see a long message displayed which shows the objref moniker and explains how to use it. The full text is also copied to your clipboard:

Create your Java class and start the JVM

Create a file called Simple.java with the following contents:

public class Simple {
  //  
  public static void main(String[] args) throws Exception {
    // Register the JVM with the name "ajvm"
    com.linar.jintegra.Jvm.register("ajvm");

    Thread.sleep(6000000); // Sleep for a while
  }

  public int property1 = 77;
  public static java.util.Date property2;

  public void method1(String aString) {
    System.out.println("method1 has been called with " + aString);
  }
}

Compile the file using javac Simple.java then run it using

java -DJINTEGRA_DCOM_PORT=1350 Simple

Create the Windows VC COM client

  1. Start Microsoft Visual C++

  2. Click on File>New>Projects>MFC AppWizard (exe)

  3. Name the new project MFC_Moniker and click OK

  4. Select Dialog based and click Next



  5. Click the 3D controls check box and click Finish

  6. Add two buttons to your MFC_Moniker dialogue box and call them BindToObject and CoGetObject. These buttons access both the BindToObject() and CoGetObject() APIs.

  7. Add the following static text:
    MFC Sample client using Moniker
    See J-Integra® Zero Installation sample
    Run: java -DJINTEGRA_DCOM_PORT=1350 TestJvm

    Your dialogue box should look something like this:

Access Java objects from the Windows VC client by using the moniker

Now we will add functionality to our client to make it an actual COM client and to enable it retrieve COM object through a given objref moniker string:

  1. Add dialog member values to represent moniker and pointer to the object¡¯s Dispatch interface;
  2. Add COM library;
  3. Add message handlers for the buttons;
  4. Add helper functions.

Add member variables

  1. On a Workspace panel (usually the left one) click a ClassView tab

  2. Right-click the CMFC_MonikerDlg class

  3. Select Add member variable

  4. Set the Type to IDispatch*, the Name to m_pIDJvm, and the Access to protected

  5. Add another member variable and set the Type to LPCWSTR, the Name to m_pszMoniker, and the Access to protected

Add COM library

  1. Go to the CMFC_MonikerDlg::OnInitDialog() member function by clicking on OnInitDialog() in theWorkspace panel.

  2. After // TODO: Add extra initialization here add the following code:

    m_pIDJvm = NULL;
    HRESULT hRes = CoInitialize(NULL);
    
    if(FAILED(hRes))
    {
    	AfxGetApp()->ExitInstance();
    }
    
    m_pszMoniker = OLESTR("objref:TUVPVwEAAAAABAIAAAAAAMAAAAAAAABGABAAAAAAAABKaW50ZWdyYVRhbGtUb01lV2hhdHNBbGxUaGlzVGhlbhcAEAAHAGcAZgBpAGwAawBvAHYAWwAxADMANQAwAF0AAAAAAAoA//8AAAAAAAAAAAAA:");

  3. Replace the moniker value above with the actual value generated by GetJvmMoniker.

  4. Go to the CMFC_MonikerApp::InitInstance() member function.

  5. After int nResponse = dlg.DoModal(); add the line:

    CoUninitialize();

Add message handlers for the buttons

  1. Double-click the BindToObject button

  2. Click OK (accept defaults) on the message box popped up by Wizard and you¡¯ll be in the body of the empty message handler for the first button:

    void CMFC_MonikerDlg::OnButton1()
    {
    	// TODO: Add your control notification handler code here
    }
  3. In the same manner create and empty handler for the CoGetObject button.

  4. Now copy and paste the following code:

    void CMFC_MonikerDlg:: OnButton1()
    {
    	LPCTSTR ctLabel = _T("Moniker Test");
    	TCHAR tcBuf[0x20];
    	IDispatch* pIDObj = NULL;	// Java Object ref
    	long lTest = 0;
    	BSTR bsTest = SysAllocString(L"MFC Client, OnButton1()");
    	BSTR bsObj = SysAllocString(L"Simple");
    
    	// Get pointer to JVM:
    	// create a new binding context for parsing and binding the moniker
    	IBindCtx *pbc = 0;
    	HRESULT hr = CreateBindCtx(0, &pbc);
    	if(SUCCEEDED(hr))
    	{
    		ULONG cchEaten;
    		IMoniker *pmk = NULL;
    
    		// ask COM to convert the display name to a moniker object
    		hr = MkParseDisplayName(pbc, m_pszMoniker, &cchEaten, &pmk);
    		if(SUCCEEDED(hr))
    		{
    			// ask the moniker to find or create the object that it refers to
    			hr = pmk->BindToObject(pbc, 0, IID_IDispatch, (void**)&m_pIDJvm);
    			// we now have a pointer to the desired object, so release
    			// the moniker and the binding context
    			pmk->Release();
    		}
    		pbc->Release();
    	}
    	if(FAILED(hr))
    	{
    		goto out;
    	}
    
    	// Use pIDispJvm: Get Object:
    	hr = GetJavaObject(bsObj, &pIDObj);
    	if(FAILED(hr))
    	{
    		goto out;
    	}
    
    	// Test COM object:
    	if(FAILED(hr = TestCom(pIDObj, bsTest)))
    	{
    		goto out;
    	}
    
    out:
    	if(m_pIDJvm)
    	{
    		lTest = m_pIDJvm->Release();
    		m_pIDJvm = NULL;
    	}
    
    	if(SUCCEEDED(hr))
    	{
    		MessageBox(_T("Success"), ctLabel, MB_OK|MB_ICONINFORMATION);
    	}
    	else
    	{
    		_stprintf(tcBuf, _T("Failed\nHResult = %X"), hr);
    		MessageBox(tcBuf, ctLabel, MB_OK|MB_ICONSTOP);
    	}
    }
    
    void CMFC_MonikerDlg::OnButton2()
    {
    	HRESULT hr = S_OK;
    	IDispatch* pIDObj = NULL;	// Java Object ref
    	long lTest = 0;
    	LPCTSTR ctLabel = _T("Moniker Test2");
    	TCHAR tcBuf[0x20];
    	BSTR bsObj = SysAllocString(L"Simple");
    	BSTR bsTest = SysAllocString(L"MFC Client, OnButton2()");
    
    	// Get pointer to JVM:
    	hr = CoGetObject(m_pszMoniker, 0, IID_IDispatch, (void**)&m_pIDJvm);
    	if(FAILED(hr))
    	{
    		goto out;
    	}
    
    	// Use pIDispJvm: Get Object:
    	hr = GetJavaObject(bsObj, &pIDObj);
    	if(FAILED(hr))
    	{
    		goto out;
    	}
    
    	// Test COM object:
    	if(FAILED(hr = TestCom(pIDObj, bsTest)))
    	{
    		goto out;
    	}
    
    
    out:
    
    	if(m_pIDJvm)
    	{
    		lTest = m_pIDJvm->Release();
    		m_pIDJvm = NULL;
    	}
    
    	if(SUCCEEDED(hr))
    	{
    		MessageBox(_T("Success"), ctLabel, MB_OK|MB_ICONINFORMATION);
    	}
    	else
    	{
    		_stprintf(tcBuf, _T("Failed\nHResult = %X"), hr);
    		MessageBox(tcBuf, ctLabel, MB_OK|MB_ICONSTOP);
    	}
    
    }

Both handlers work the same way:

  1. Retrieve pIDJvm—pointer to JVM object¡¯s IDispatch interface

  2. Retrieve pIDObj—pointer to Java object¡¯s IDispatch interface

  3. Test-run method and property put/get on the Java object

The difference is in the method of retrieving pIDJvm:

Add helper functions

We will add two helper functions: GetJavaObject() will retrieve pointer to the Java object¡¯s IDispatch interface, and TestCom() will run complete test on the retrieved Java object.

  1. On a Workspace panel (usually the left one) click a ClassView tab.

  2. Right-click the CMFC_MonikerDlg class.

  3. Select Add member function.

  4. Set the Type to HRESULT, the Declaration to GetJavaObject(BSTR bsObj, IDispatch** ppIDObj) and the Access to public.

  5. Add another member function and set the Type to HRESULT, the Declaration to TestCom(IDispatch* pIDObj, BSTR bsMessage and the Access to public.

  6. Copy and paste the following code for those functions:

    HRESULT CMFC_MonikerDlg::TestCom(IDispatch *pIDObj, BSTR bsMessage)
    {
    	HRESULT hr = S_OK;
    	DISPID dispid;
    	DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
    	OLECHAR FAR* szMember = L"property1";
    	long nTest = 13;
    
    	// Specify method and params:
    	VARIANTARG MyArgs[1];   // Just 1 arg
    	VariantInit(&MyArgs[0]);
    
    	VARIANT vResult;
    	VariantInit(&vResult);
    
    	DISPPARAMS MyParams = {MyArgs, 0, 1, NULL};     // Just 1 not named param
    
    	if(NULL == pIDObj)
    	{
    		hr = E_INVALIDARG;
    		goto out;
    	}
    
    	// Set Simple.property1
    	hr = pIDObj->GetIDsOfNames(IID_NULL, &szMember, 1, GetUserDefaultLCID(), &dispid);
    	if(FAILED(hr))
    	{
    		goto out;
    	}
    
    	MyParams.rgvarg[0].vt = VT_I4;
    	MyParams.rgvarg[0].lVal = nTest;	// Set to something
    	MyParams.rgdispidNamedArgs = &dispid;
    	MyParams.cArgs = 1;
    	MyParams.cNamedArgs = 1;
    
    	hr = pIDObj->Invoke(
    		dispid,
    		IID_NULL,
    		LOCALE_USER_DEFAULT,
    		DISPATCH_PROPERTYPUT,
    		&MyParams, NULL, NULL, NULL);
    
    	if(FAILED(hr))
    	{
    		goto out;
    	}
    
    	// Get Simple.property1
    	hr = pIDObj->Invoke(
    		dispid,
    		IID_NULL,
    		LOCALE_USER_DEFAULT,
    		DISPATCH_PROPERTYGET,
    		&dispparamsNoArgs,
    		&vResult,
    		NULL,
    		NULL);
    
    	if(FAILED(hr))
    	{
    		goto out;
    	}
    
    	// Check property value:
    	if(VT_I4 != vResult.vt)
    	{
    		hr = E_FAIL;
    		goto out;
    	}
    	if(nTest != vResult.iVal)
    	{
    		hr = E_FAIL;
    		goto out;
    	}
    
    	szMember = L"method1";
    	hr = pIDObj->GetIDsOfNames(IID_NULL, &szMember, 1, GetUserDefaultLCID(), &dispid);
    	MyParams.rgvarg[0].vt = VT_BSTR;   // String
    	MyParams.rgvarg[0].bstrVal = bsMessage;
    
    	hr = pIDObj->Invoke(
    		dispid,
    		IID_NULL,
    		GetUserDefaultLCID(),
    		DISPATCH_METHOD,
    		&MyParams,
    		&vResult,
    		NULL,
    		NULL);
    
    	if(FAILED(hr))
    	{
    		goto out;
    	}
    
    out:
    	VariantClear(&MyArgs[0]);	// Embedded BSTR will be freed
    	VariantClear(&vResult);		// Embedded pIDisp will be released
    
    	return hr;
    }
    
    HRESULT CMFC_MonikerDlg::GetJavaObject(BSTR bsObj, IDispatch **ppIDObj)
    {
    	HRESULT hr = S_OK;
    	*ppIDObj = NULL;
    
    	OLECHAR* name1 = L"get";	// Set simple = jvm.get("Simple")
    	DISPID dispid;
    
    	// Specify method and params:
    	VARIANTARG MyArgs[1];   // Just 1 arg
    	VariantInit(&MyArgs[0]);
    	MyArgs[0].vt = VT_BSTR;   // String
    	MyArgs[0].bstrVal = bsObj;	//MyArgs[0].bstrVal = SysAllocString(L"Simple");
    
    	DISPPARAMS MyParams = {MyArgs, 0, 1, NULL};     // Just 1 not named param
    
    	VARIANT vResult;
    	VariantInit(&vResult);
    
     	hr = m_pIDJvm->GetIDsOfNames(IID_NULL, &name1, 1, GetUserDefaultLCID(), &dispid);
    	if(FAILED(hr))
    	{
    		goto out;
    	}
    
    	hr = m_pIDJvm->Invoke(
    		dispid,
    		IID_NULL,
    		GetUserDefaultLCID(),
    		DISPATCH_METHOD,
    		&MyParams,
    		&vResult,
    		NULL,
    		NULL);
    
    	if(FAILED(hr))
    	{
    		goto out;
    	}
    
    	// retrieve returned pointer to jvm.Simple object:
    	if(VT_DISPATCH != vResult.vt)
    	{
    		goto out;
    	}
    
    	*ppIDObj = vResult.pdispVal;	// if all OK
    
    out:
    	return hr;
    }

Results

Run your Visual C++ client. Click the BindToObject button. The Java object will be retrieved and tested. A success message will be displayed by client, and an output string from the Java object will appear on the Java console.

Test the CoGetObject button in the same way.

Normally you will have the following output on your Java console:

C:\JI_Support\ZeroInst>java -DJINTEGRA_DCOM_PORT=1350 Simple
method1 has been called with MFC Client, OnButton1()
method1 has been called with MFC Client, OnButton2()

If something goes wrong you will receive an error message from your client.

Troubleshooting

If you receive the error message: ¡°Failure. Hresult = 8007000E¡± (Not enough storage is available to complete this operation), you probably did not started Java with the command:

java -DJINTEGRA_DCOM_PORT=1350 Simple

If you receive error message: ¡°Failure. Hresult = 800706BA¡± (The RPC server is unavailable), you have probably started Java on the wrong port, not the one that was indicated in your GetJvmMoniker command.