|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
|
OPC and Java JNI TutorialThis tutorial, originally published by Naknan, will show the high points of how to connect Java(tm) to an OPC Alarm and Event Server using JNI. The code is available for download and may be used for educational purposes. This write up assumes a familarity with Java(tm) and OPC. It attempts to help the user understand the code and why it is written the way it is. Writing the Java Code (Native Methods) Generating the JNI Header file from your class file Tips when writing the C++ side (Native Implementation) Java CodeThe first thing we will take a look at is how to define a native method in Java(tm) and how to use that method in connecting to our OPC Alarm & Event Server.
--- Snip ---
public class Opc
implements Runnable
{
public native boolean connect_p(Opc ae_callback);
public native boolean init_p(String host_name, String server_name);
public static void main(String args[])
{
Opc opc_connection = new Opc(args[0], args[1]);
(opc_connection.getThread()).start();
} // main
--- Snip ---
First we notice the use of the keyword native on lines 4 and 5. This tells the Java(tm) compiler that the implementation of this methods will be provided via an external native library (ie code not written in Java(tm)) Next we look at how to call a method declared to be native. It turns out that calling a native method is no different than calling a normal Java(tm) method, as we can see in line 6 below.
--- Snip ---
private boolean init()
{
if (inited)
return true;
return (inited = init_p(host_name, server_name));
} // init
--- Snip ---
The key is that before calling any methods declared to be native we must first load the library that contains the method body. We see on line 3 below that Java(tm) provides a simple way to load external librarys into the VM. Also notice that we don't have to put an extension on the library name. Java(tm) automatically adds the correct extension for the operating system (ie .dll for windows, .so for unix, etc).
--- Snip ---
try
{
System.loadLibrary("OPC");
}
catch(Exception e)
{
System.err.println("Unable to load library - " +
System.mapLibraryName("OPC"));
}
--- Snip ---
Java HeaderOnce you have your Java(tm) code written and compiled, it's time to generate the header file that will allow Java(tm) to call the appropriate method(s) in the native code. To do this, we run the javah utility passing our class as an argument to it. javah -jni com.naknan.example.opc.Opc Notice that we don't pass the .java source file, we pass in the .class file without the .class extension. The javah utility then generates a file based on the name of the class passed to it. In our case, it generates a file named: com_naknan_example_opc_Opc.h. The contents of this file look simular to this.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_naknan_example_opc_Opc */
#ifndef _Included_com_naknan_example_opc_Opc
#define _Included_com_naknan_example_opc_Opc
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_naknan_example_opc_Opc
* Method: connect_p
* Signature: (Lcom/naknan/example/opc/Opc;)Z
*/
JNIEXPORT jboolean JNICALL Java_com_naknan_example_opc_Opc_connect_1p
(JNIEnv *, jobject, jobject);
/*
* Class: com_naknan_example_opc_Opc
* Method: init_p
* Signature: (Ljava/lang/String;Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_com_naknan_example_opc_Opc_init_1p
(JNIEnv *, jobject, jstring, jstring);
#ifdef __cplusplus
}
#endif
#endif
C++ TipsThe Visual C++ project that comes as part of the download contains a project settings that may need to be changed in order for you to build it successfully. Under Project->Settings on the C/C++(tab) in the
Prepocessor(Catagory) Also modified were the Precompiled Headers(Catagory), to "Not using precompiled headers" and under the General(Catagory) we added _WIN32_DCOM to the Preprocessor definitions inorder to get the COM/DCOM environment. COM/DCOM InitializationIn initialization of the COM/DCOM environment on line 2 below, we use the COINIT_MULTITHREADED options which will allow multiple threads (JavaVM and Alarm & Event Server) to share data.
--- Snip ---
// Initialize the COM/DCOM environment for multi-threaded access
result = CoInitializeEx(NULL, COINIT_MULTITHREADED);
result = CoGetMalloc(MEMCTX_TASK, &pIMalloc);
if (FAILED(result))
{
CoUninitialize();
return false;
}
--- Snip ---
Connecting to an OPC Alarm & Event Server
--- Snip ---
// Retrieve the Class ID based on the Program ID
CLSIDFromProgID(WSTRFromSBCS(server_name, pIMalloc), &clsid);
// Get the Class Object
CoGetClassObject(clsid, CLSCTX_LOCAL_SERVER, NULL,
IID_IClassFactory, (void **)&factory);
// Create an Instance (attach if already created)
factory->CreateInstance(NULL, IID_IUnknown,(void **)&objserver);
// Query for the EventServer Interface
objserver->QueryInterface(IID_IOPCEventServer, (void**)&ae_server);
// Get EventSubscriptionMgt Interface
ae_server->CreateEventSubscription(true, 0, 0, handle,
IID_IOPCEventSubscriptionMgt, &objsubscription,
&revisedBufferTime, &revisedMaxSize);
// Query for the Connection Point Interface
((IOPCEventSubscriptionMgt *)objsubscription)->QueryInterface(&cpc);
// Get the Connection Point
cpc->FindConnectionPoint(IID_IOPCEventSink, &cp);
// Create an object for callback events from the A&E Server
iopces = new JNISink();
// Register Sink Object for Event Notification
result = cp->Advise(iopces, &cookie);
--- Snip ---
As events are generated in the Alarm & Event Server, the OnEvent method of the JNISink object will be called. Once the Event is received it can be handled in any way, even passed into Java(tm). Getting the events into JavaBelow is the implementation of the OnEvent method of our JNISink object. This method will be called from the OPC Alarm & Event Server when a new event has been generated. This method then gathers all the information from the ONEVENTSTRUCT and converts it the the appropriate Java(tm) types and after connecting to the Java(tm) VM and looking up the callback method, forwards the OPC Alarm/Event to the Java(tm) side for further processing.
--- Snip ---
HRESULT STDMETHODCALLTYPE JNISink::OnEvent(
/* [in] */ OPCHANDLE hClientSubscription,
/* [in] */ BOOL bRefresh,
/* [in] */ BOOL bLastRefresh,
/* [in] */ DWORD dwCount,
/* [size_is][in] */ ONEVENTSTRUCT __RPC_FAR *pEvents)
{
JNIEnv *jni_env = NULL;
// Play nice and connect
jni_vm->AttachCurrentThread((void **)&jni_env, NULL);
// Just a dummy value so that we don't send null to the java side
const char *UnKnown = "Unknown";
// Only do this once...
if (lookupMethod)
{
jclass clazz = jni_env->GetObjectClass(jni_callback);
// Use "javap -s" to generate signature
mid = jni_env->GetMethodID(clazz, "addEvent",
"(Ljava/lang/String;IIIIIIIILjava/lang/String;" +
"IIILjava/lang/String;Ljava/lang/String;IZIIIII" +
"IIIILjava/lang/String;II)V");
lookupMethod = false;
}
// Initialize some jstrings...
jstring source = jni_env->NewStringUTF(UnKnown);
jstring message = jni_env->NewStringUTF(UnKnown);
jstring condition = jni_env->NewStringUTF(UnKnown);
jstring subcondition = jni_env->NewStringUTF(UnKnown);
jstring actor = jni_env->NewStringUTF(UnKnown);
int len = 0;
if ((len = wcslen(pEvents->szSource)) > 0)
source = jni_env->NewString(pEvents->szSource, len + 1);
if ((len = wcslen(pEvents->szMessage)) > 0)
message = jni_env->NewString(pEvents->szMessage, len + 1);
if ((len = wcslen(pEvents->szConditionName)) > 0)
condition = jni_env->NewString(pEvents->szConditionName, len + 1);
if ((len = wcslen(pEvents->szSubconditionName)) > 0)
subcondition = jni_env->NewString(pEvents->szSubconditionName, len + 1);
if ((len = wcslen(pEvents->szActorID)) > 0)
actor = jni_env->NewString(pEvents->szActorID, len + 1);
SYSTEMTIME time;
FileTimeToSystemTime(&pEvents->ftTime, &time);
SYSTEMTIME activetime;
FileTimeToSystemTime(&pEvents->ftActiveTime, &activetime);
// Call the "addEvent" method which is void with the correct data
jni_env->CallVoidMethod(jni_callback, mid, source, time.wYear, time.wMonth,
time.wDay, time.wDayOfWeek, time.wHour, time.wMinute, time.wSecond,
time.wMilliseconds, message, pEvents->dwEventType,
pEvents->dwEventCategory, pEvents->dwSeverity, condition,
subcondition,
pEvents->wQuality, pEvents->bAckRequired, activetime.wYear,
activetime.wMonth, activetime.wDay, activetime.wDayOfWeek,
activetime.wHour, activetime.wMinute, activetime.wSecond,
activetime.wMilliseconds, pEvents->dwCookie, actor,
pEvents->wChangeMask, pEvents->wNewState);
// Play nice and disconnect
jni_vm->DetachCurrentThread();
return S_OK;
} // OnEvent
--- Snip ---
Pulling it togetherOnce you have the VC++ project built, copy the .dll that was created into the directory containing your .class file. The System.loadLibrary call will look for it here as well as a few other places, but I have found that it is simpler to place it in the same directory as the .class file. Sample Windows(tm) .bat file used for running the system. c: cd \ cd cvs\com\naknan\example\opc java -cp c:\cvs com.naknan.example.opc.Opc FactorySoft.Example.Alarm.1 asylum DownloadDownload the source code for this tutorial. RequirementsThe VC++ project was created and tested under VC++ 6.0. The Java(tm) code was compiled and run under the JDK 1.3 VM from Sun Microsystems. You will also need to have an OPC Alarm & Event Server installed. I use the Alarm & Event Server Demo server from FactorySoft, but any OPC Alarm & Event server will work (demo or not). DisclaimerInformation on OPC can be found at the OPC Foundation. This code is provided for educational purposes only. It is provided as-is and without support or warranty of any sort. You are free to study this code and to use the techniques in your own implementation. Parts of this example are left up to the user and it is assumed that the user will be knowledgeable in VC++ and OPC Alarm & Event terminology and data types. This code has not been reviewed, tested or approved by the OPC Foundation. You may also freely incorporate parts of this code into an implementation as long as the following appears in each source file: "(c) Copyright 2001-2007 Naknan Inc and Your_Company_Name ALL
RIGHTS RESERVED" |
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||