Implementing the Native Functions
Implementing the native functions in your C/C++ source code typically depends on your .NET control and how you exposed it as a COM component. Two of the native functions
initialize and
destroyare required to initialize the component and attach it to the
HWND of the Java control.
Create a new C/C++ project. You'll need to
#include the
jni.h header file, so make sure you include both the
include and
include/win32 directories from the JDK installation directory. Import the
jni.h header and the
JNIBridge.h header from this sample in a C source file.
To begin implementing the native functions, copy the declarations from the header file you generated into the source file. Add parameter names and curly braces to each, setting up each function for a definition. You'll define the
initialize function first (see
Listing 1 or the sample source for the complete implementation).
This
initialize function gets the
HWND from the Canvas. This window handle is provided by the JVM. After getting the
HWND, execution is passed to a thread so that the method can return and allow the Java control to continuing initializing and handling events. The new thread creates the .NET control you exposed as a COM component and attaches it to the
HWND. It then initializes properties of the control when necessary and starts the message loop to process messages and to keep the control running (see
Listing 2 or the sample source for implementation).
The destroy function destroys the COM component window resources, decrements the reference count, and deletes the pointer. This makes sure that the .NET control is destroyed and memory is reclaimed (see
Listing 3 or sample source for implementation).
Implementing the
getUsername native function requires that you get a string from the .NET control and return it to the Java control:
JNIEXPORT jstring JNICALL Java_COMControl_getUsername
(JNIEnv *env, jobject obj)
{
HRESULT hr = S_OK;
CComBSTR username;
jstring jstr;
if (g_spControl != NULL)
{
username.Empty();
hr = g_spControl->get_Username(&username);
if (SUCCEEDED(hr))
{
jstr = env->NewStringUTF(CW2A(username.m_str));
return jstr;
}
}
return NULL;
}
The Common Language Runtime (CLR)the runtime for the .NET Frameworkcreates a COM-Callable Wrapper (CCW) that marshals System.String values as
BSTR values. The Microsoft Active Template Library (ATL) provides a handy wrapper class,
CComBSTR, to make this easier and to automatically free space allocated to the
BSTR. You should first make sure that you have a valid reference to the .NET control. Then call
get_Usernamethe
get accessor of the
Username propertypassing a reference to the
CComBSTR for the username. If the method returns a successful
HRESULT code, use the
NewStringUTF method of the
JNIEnv object to convert the Unicode string to a
jstring and return it.
You'll encounter an interesting problem when implementing the
getBirthday native function: there is no intrinsic type for the
java.util.Date class provided by JNI. You must create an object of type Date in the native implementation as shown in the following code fragment:
// Get the java.util.Date class and its ctor.
cls = env->FindClass("java/util/Date");
mid = env->GetMethodID(cls, "<init>", "(J)V");
if (mid != 0)
{
// Allocate a new Date object.
jdate = env->AllocObject(cls);
// Adjust for differences in whole days.
ldate = ((LONG64)date - 25568) * 86400000;
// Instantiate the Date object.
env->CallVoidMethod(jdate, mid, ldate);
// Return the Date object.
return jdate;
}
Use the
FindClass and
GetMethodID methods from the
JNIEnv object to get the
jclass reference and find the method ID for the appropriate constructor. The JNI specifications state that you should use the string
"" to find the constructor. The third parameter,
"(J)V", dictates that the constructor should take a
long valuea 64-bit integer in Javaand should return
void, as all constructors do.
Another problem arises when dealing with dates. The
DateTime structure in .NET stores the number of ticksor 100 nanosecondsfrom January 1, 0001 A.D. using the local time zone, as described in the
NET Framework SDK documentation.
The CLR marshals the
DateTime structure as a
DATE, which is a
typedef for a
double. The
DATE data type documentation describes that the CLR stores time as the number of milliseconds since December 30, 1899, also using the local time zone.
Unfortunately, Java stores the number of milliseconds since January 1, 1970 in Universal Coordinate Time (UTC). Not only do you have to worry about adjusting the base time, you must also take the time zone difference into account. The
java.util.Calendar class can assist with the time zone problem.
The
size and
location methods in the Java class are straight-forward. Any method calls to change the position or size of the Java
COMControl class will call the native functions which marshal the values to the .NET control. All three environments use 32-bit integers to represent the top and left coordinates, and the width and height of the control.
Assigning the background and foreground can be difficult, but there is a workaround to simplify your implementation. Both environments use a structure to hold color values similarly, but each uses a slightly different structure. Marshaling these two types would add complexity to your code. To make marshaling easier, the Java control instead passes the RGB value of a
java.awt.Color object as a
long, which is then marshaled as an
OLE_COLOR to the .NET controlwhich converts the value to a System.Drawing.Color structure.
Finish implementing your native functions. Just remember that passing complex types can lead to marshaling problems so try to use basic types as much as possible. Understanding the marshaling services provided by the .NET Framework in the System.Runtime.InteropServices namespace and understanding the JNI specifications can help solve these problems if you must use complex types.
When you finish implementing the native functions, configure the project to link against the
jawt.lib static library. I've done this for you in the sample project. You should make sure to add the
lib directory in the JDK to your
LIB environment variable. Compile the project.