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 functionsinitialize
are 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
header file, so make sure you include both the include
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).
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;
if (g_spControl != NULL)
hr = g_spControl->get_Username(&username);
jstr = env->NewStringUTF(CW2A(username.m_str));
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_Username
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.
Use the FindClass
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.
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.