Coroutine for Java class library provides the mechanism for Java to call external functions exported from Windows dynamic link libraries. Coroutine also enables you to dynamically build callable wrappers around Java methods so external functions can call Java methods as if they are native functions.
Coroutine shields you from dealing with Java-To-Native-Interface (JNI,RNI, JRI) details. With Coroutine you don't need to write intermediate C++ wrappers. Coroutine for Java is the only tool that you need to integrate access to Win32 and custom APIs into your Java code. Coroutine was designed to help developers who want to incorporate Windows features and functions into Java code.
Coroutine for Java supports all existing Win32-based Java Virtual Machines.
Here is Coroutine for Java API reference.
Using Coroutine for Java

An instance of Coroutine represents a function exported from a DLL. The Coroutine contains the name of the shared dynamic link library where the function can be found and the function’s name.
To access an external function using Coroutine, perform the following four steps:
- Instantiate a Coroutine and
initialize it with the proper library and function names:
Coroutine api=new Coroutine ("KERNEL32", "Beep"); - Build function’s parameter
list using various addArg() methods:
api.addArg(1770); api.addArg(999);
- Invoke the Coroutine using
the invoke() method:
if(0 !== api.invoke()) { // Error System.out.println("Error in invoke "+api.lastError()); } - Retrieve function's return
value and/or values returned via function’s outbound parameters:
if(api.answerAsBoolean()) { ... }
Selecting between ANSI/Unicode version of Win32 API
In Win32, function’s exported name may include an uppercase A or W at the end to indicate ANSI or Unicode version of an API.
Parameter offset
Several methods require zero relative parameter offset. When calculating parameter offset count each decimal or long parameter as two.
Passing primitive data types to an external function.
You can pass primitive data types - integer, long, Boolean, float and double - into an external function using appropriate addArg() methods.
Receiving an integer from an external function.
Normally, Win32 functions return integers via the pointer to a four byte memory block supplied by the caller.
When you expect the function to return an integer, simply pass a four byte array. In a case of an [in,out] parameter, you should use the setDWORDAtOffset(byte [] arr, int value, int offset) method to pass an integer value to the function. Finally, to access the value returned by the function, use intFromParameterAt(int offset).
Here is an example that invokes RegQueryValue API:
String subkey;
int bufferSize=10;
boolean success=false;
int ERROR_MORE_DATA=234;
int HKEY_LOCAL_MACHINE=0x80000002;
Coroutine api;
while(true) {
api=new Coroutine ("advapi32", "RegQueryValueA");
api.addArg( HKEY_LOCAL_MACHINE);
api.addArg(subkey);
api.addArg(new byte[bufferSize]);
byte [] sz=new byte[4];
Coroutine.setDWORDAtOffset(sz,bufferSize,0);
api.addArg(sz);
if(0 != api.invoke())
break;
int ret=api.answerAsInteger();
if(ret==0) {
success=true;
break;
} else if(ERROR_MORE_DATA==api.answerAsInteger())
//try again using correct buffer size
bufferSize=api.intFromParameterAt(3)+1;
else
break;
}
if(success)
System.out.println(" value="+api.StringFromParameterAt(2));
|
Receiving data via the function’s return value.
To convert function’s return value to a Java object, use answerAs...() methods.
Passing a Java String or a byte array to an external function.
To pass an 8-bit null-terminated ANSI string to an external function, use either addArg(String str) or addArgANSIString(String str) . To pass a 16-bit Unicode string to an external function, use addArgUnicodeString(String str) .
To pass a byte array to an external function, use either addArg(byte [] bytes) or addArg(byte [] bytes, int size) .
Strings and byte arrays are passed indirectly by copying them to the external heap, that is, Coroutine allocates a buffer big enough to accommodate all data, copies data to the buffer , and passes the address of the buffer to the function. Please note that the memory allocated during the function call is automatically released when the instance of Coroutine is destroyed.
For example, to create Win32 auto-reset event object, you invoke CreateEvent API from KERNEL32.exe, which has the following prototype:
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpsa, BOOL fManualReset,
BOOL fInitialState, LPTSTR lpszEventName);
|
Here is Java code that invokes the CreateEvent API:
public static int CreateWin32Event(String name, boolean state) throws SecurityException {
Coroutine coro=new Coroutine("KERNEL32","CreateEventA");
coro.addArgNull();
coro.addArg(false);
coro.addArg(state);
if(name == null)
coro.addArgNull();
else
coro.addArg(name);
if(0 != coro.invoke())
return -1;
return coro.answerAsInteger();
}
|
Receiving a String or a byte array from an external function.
There are two different scenarios for receiving strings or byte arrays from external functions: either the caller provides a buffer that is modified by the function, or an external function allocates the buffer and returns its pointer to the caller
When an external function modifies the buffer allocated by the caller, you pass a String or a byte array. Coroutine allocates the buffer on external heap, copies String’s/array’s bytes, and passes the address of the buffer to the function.
To retrieve the result back as an 8-bit ANSI string, use either StringFromParameterAt(int offset) or ANSIStringFromParameterAt(int offset) . For 16-bit Unicode strings use String UnicodeStringFromParameterAt(int offset)
In a case when an array of bytes is expected, use byteArrayFromParameterAt(int offset,int sz) .
Here is an example that invokes GetEnvironmentVariable API with the following prototype:
DWORD GetEnvironmentVariable(LPCTSTR lpszName, LPTSTR lpszValue, DWORD nSize); |
First parameter lpszName specifies the environment variable; second parameter lpszValue is a buffer to receive the value; third parameter nSize specifies the size of the buffer. The API answers zero when the environment variable name was not found, otherwise answers the number of characters stored in the buffer.
public static String GetEnvironmentVariable(String env) throws SecurityException {
int sz=100; //initial buffer size
while(true) {
Coroutine coro=new Coroutine("KERNEL32","GetEnvironmentVariableA");
String ret;
coro.addArg(env);
byte [] buf=new byte[sz];
coro.addArg(buf);
coro.addArg(buf.length);
if(0 != coro.invoke())
return null;
int rsz=coro.answerAsInteger();
if(rsz==0)
ret=(String)null; //variable does not exist
if(rsz>sz) {
//buffer is too small
sz=rsz; //increase the buffer size
continue;
}
return coro.StringFromParameterAt(1);
}
}
|
In a case when an external function allocates the buffer and returns its pointer to the caller, you should pass a four byte array to receive the pointer. You then use intFromParameterAt(int offset) to retrieve the pointer as an integer. Finally, you invoke either StringFromPtr(int ptr) or ANSIStringFromPtr(int ptr) when an 8-bit ANSI string is expected. When a Unicode string is expected, use UnicodeStringFromPtr(int ptr). In a case when an array of bytes is returned, use byteArrayFromPtr(int sz). For example, the following function returns a string to the caller.
__declspec(dllexport) void GetString(char **str) {
...
}
|
Here is Java code that invokes above function
public String GetString() throws SecurityException, NullPointerException {
Coroutine coro=new Coroutine("FICTIONAL_LIBRARY","GetString");
coro.addArg(new byte[4]);
if(0 != coro.invoke())
return null;
return coro.StringFromPtr(coro.ptrFromParameterAt(0));
}
|
Passing an integer array to an external function.
To pass an integer array to a native API, use either addArg(int [] byes, int size) or addArg(int [] iarr) .
Receiving an integer array from an external function.
When the function modifies the integer array and you want to retrieve the result back, use intArrayFromParameterAt(int offset, int arraySize) .
Passing and receiving a B[asic]STR[ing] from an external function.
To pass a String to a native API as a length-prefixed 16-bit Unicode string (an OLE’s BSTR), use addArgBSTR(String arg) .
Normally, OLE APIs allocate BSTR and pass the pointer back to the caller. Therefore to receive a BSTR from an external function your should pass a four byte array to receive BSTR*. On return, you invoke ptrFromParameterAt(int offset) and then BSTRFromPtr(int ptr).
Passing an array of Strings to an external function.
Here we will pass an array of Strings to an external function:
String [] parameters;
ExternalObject [] eos=new ExternalObject[parameters.length];
byte [] params=new byte[4*parameters.length];
for(int j=0;j < parameters.length; j++) {
eos[j]=new ExternalObject(parameters[j]);
Coroutine.setDWORDAtOffset(params,eos[j].getValue(),j*4);
}
Coroutine co=new Coroutine(...,...); co.addArg(params); co.invoke(); |
Please note that the reason we store ExternalObjects in array is to prevent GC from destroying those objects and subsequently freeing the memory they are pointed to.
Working with structures.
Since Java does not support structures directly, use byte arrays instead.
To set/get structure’s members, use setXXXAtOffset() and getXXXAtOffset().
To access fixed-size strings embedded within structures, use XXXStringFromByteArray(byte [], int offset)
Here is an example that invokes GetVersionEx API with the following prototype:
BOOL GetVersionEx( OSVERSIONINFO *lpVersionInformation ); |
Structure OSVERSIONINFO has the following definition:
typedef struct _OSVERSIONINFO{
DWORD dwOSVersionInfoSize;
DWORD dwMajorVersion;
DWORD dwMinorVersion;
DWORD dwBuildNumber;
DWORD dwPlatformId;
TCHAR szCSDVersion[ 128 ];
} OSVERSIONINFO;
|
The caller must store the size, in bytes, of the above data structure in dwOSVersionInfoSize before calling GetVersionEx function.
public static String GetOSVersion(int [] osver) throws SecurityException {
byte [] osversion=new byte[276];
Coroutine.setDWORDAtOffset(osversion,osversion.length,0);
Coroutine coro=new Coroutine("KERNEL32","GetVersionExW");
coro.addArg(osversion);
if(0!=coro.invoke() || !coro.answerAsBoolean ())
return null;
osversion=coro.byteArrayFromParameterAt(0,148);
for(int j=0;j<4;j++)
//return dwMajorVersion,dwMinorrVersion,dwBuildNumber, and dwPlatformId
osver[j]=coro.getDWORDAtOffset(osversion,4*(j+1));
return Coroutine.UnicodeStringFromByteArray(osversion,20); //CSDVersion
}
|
Passing Complex Objects.
Interface CoroutineComplexObject facilitates passing of complex objects to external functions.
Let's illustrate this with the following example:
We have an external function void ProcessPoint(/*[in,out]*/ POINT* point) where POINT structure defines the x- and y- coordinates of a point.
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT;
|
On the Java side we will define Point2 class as follows
public class Point2 extends java.awt.Point implements CoroutineComplexParameter {
private ExternalObject eo=null;
public Point2(int x, int y) {
super(x,y);
}
public int asCoroutineParameter() {
discard();
byte [] tmp=new byte[8];
com.neva.Coroutine.setDWORDAtOffset(tmp,x,0);
com.neva.Coroutine.setDWORDAtOffset(tmp,y,4);
eo=new ExternalObject(tmp,tmp.length);
return eo.getValue();
}
public void discard() {
if(eo!=null && eo.isAllocated())
eo.free();
eo=null;
}
public void fromPointer() {
if(eo==null)
return;
byte [] tmp=eo.valueAsByteArray(8);
move(com.neva.Coroutine.getDWORDAtOffset(tmp,0),
com.neva.Coroutine.getDWORDAtOffset(tmp,4));
}
protected void finalize() {
discard();
}
}
|
We then use the following code to call ProcessPoint:
com.neva.Coroutine coro=new com.neva.Coroutine("library","ProcessPoint");
Point2 p=new Point2(10,10);
coro.addArg((CoroutineComplexParameter)p);
coro.invoke();
p.fromPointer();
|
Using JCallback

The JCallback class gives a way
for an external API to call back into Java.
Using JCallback requires at least three steps:
- Extend JCallback and implement a non-static public method that has as many integer parameters as there are 4-byte parameters in the native callback. The method must return an integer.
- Create an instance of above class and invoke setCallback() passing the method name, number of parameters in the native callback, callback calling convention.
- Invoke asWin32Callback() to generate "native" wrapper around Java method. The asWin32Callback() method answers an integer which is a pointer to the callback wrapper or, "natively" speaking, the callback address.
Here is an example of using EnumWindows API that enumerates all top-level windows on the screen by passing the handle to each window to a callback function
BOOL CALLBACK EnumWindowsProc( HWND hwnd, LPARAM lParam); |
BOOL, HWND, and LPARAM types
above are effectively 4-byte integers.
Here is Java implementation of above callback:
class EnumWindow extends com.neva.JCallback {
public int EnumWindowProc(int hwnd, int lParam) {
//we will retrieve each window title and print it along with hwnd
Coroutine coro=new Coroutine("USER32","GetWindowTextA");
coro.addArg(hwnd);
coro.addArg(new byte[512]);
coro.addArg(512);
coro.invoke();
String title=coro.StringFromParameterAt(1);
System.out.println("hwnd="+hwnd+ " title="+title);
return 1;
}
}
|
Here is how we use above callback:
EnumWindow ew=new EnumWindow();
ew.setCallback("EnumWindowProc",2, com.neva.JCallback.CC_stdcall);
int ptr=ew.asWin32Callback();
Coroutine co=new Coroutine("user32","EnumWindows");
co.addArg(ptr);
co.addArg(0);
co.invoke();
|
Callback with non-integer arguments.
In general, a callback may require non-integer arguments such as strings,
structures, etc., which usually correspond to native 4-byte pointers. Your
callback implementation should convert an integer argument to an associated Java
type using Coroutine's xxxFromPointer() method.
For example, the following Java code implements callback
BOOL WINAPI CallbackWithString(char* String) |
class Callback extends com.neva.JCallback {
public int CallbackWithString(int pString) {
String str;
try {
String str=Coroutine.StringFromPtr(pString);
} catch(Exception ex) {
…
str=null;
}
return 1;
}
}
|
The following example illustrates the case of a callback with an output parameter:
void WINAPI Callback2(int *out); |
class Callback extends com.neva.JCallback {
public int Callback2(int pInt) {
int iVal;
//calculate integer value iVal here
….
//return integer value via callback output parameter
new Coroutine().intAtPut(pInt,iVal);
return 0;
}
}
|
Handling of non-integer return types.
Even though a JCallback method answers an integer, the "native" code
that calls the callback can treat return value as any other 4-byte data type.
For example, when a callback is expected to return a BOOL, integer values 0 and
1 do the job.
When a callback is expected to answer void, an integer value returned by a
JCallback method is simply ignored.
The JCallback's life span.
A callback function created by asWin32Callback() is immortal. In other words,
event if GC reclaims original JCallback object, the callback function will stay
until being explicitly freed by calling release() method. In the example above,
we should call ew.release() after co.invoke().
Exception Handling

Coroutine provides extended exception handling for each external function call. A non-zero value returned by invoke() indicates that an exception occurred during external function call. To get an extended error description, use lastError() method.
In the following example
Coroutine coro=new Coroutine("KERNEL32","ABCD");
coro.invoke();
|
invoke() answers a non-zero value (-127) since there is no function "ABCD" in KERNEL32.exe. In this case, coro.lastError() yields
"Error 127: Unable to locate entry point ABCD" |
The following example will create an access violation exception
Coroutine er=new Coroutine("NTDLL","strcpy");
er.addArg(0);
er.addArg(0);
er.addArg(1);
er.invoke();
|
invoke will answer -5 and er.lastError() yields
"Exception 0xC0000005 when calling strcpy from NTDLL; argc=3; argv=(00000000,00000000,00000001)" |
Here are possible integer return values from invoke:
0 |
Invocation was successful |
-126 |
The specified module could not be found. |
-127 |
The specified procedure could not be found. |
| -1860 | Evaluation version of the product has expired. |
-n |
Exception n has occurred. The most common exceptions are 0x05 (access violation), 0x1d (illegal instruction), 0x94 integer divide by zero, 0xfd (stack overflow) |
Then an exception has been thrown, your application can retrieve native stack trace by calling
public String getExceptionNativeStackTrace() |
Normally, stack trace helps to identify the problem especially when the source code is available. Here is an example of such a trace
***Coroutine: Exception 0xC0000005 in ('efx78','ProcessData') at location 0x0B291049
Ebp=0x0006C8AC
Eip=0x0B291049
Esp=0x0006C8A4
Edi=0x00235078
Esi=0x00000005
Ebx=0x06AFD888
Edx=0x00000001
Ecx=0x00000FA5
Eax=0x0000005A
SegCs=0x0000001B
EFlags=0x00010202
SegSs=0x00000023
Call stack (17 frames)-----
Address Frame Logical addr Module
0B291049 0006C8AC 0001:00000049 D:\temp\efx78.exe
code: 8B 02 8B E5 5D C3 55 8B EC 83
0B291088 0006C8C0 0001:00000088 D:\temp\efx78.exe
code: 83 C4 04 8B E5 5D C3 55 8B EC
0B29109B 0006C8CC 0001:0000009B D:\temp\efx78.exe
code: 83 C4 04 5D C3 8B 44 24 08 83
0AE56254 0006F2FC 0001:00005254 E:\Program Files\Java\j2re1.4.0\lib\ext\x86\corojdk11.exe
code: 8B 4D 18 89 01 89 51 04 C7 45
0AE5695E 0006F344 0001:0000595E E:\Program Files\Java\j2re1.4.0\lib\ext\x86\corojdk11.exe
code: 83 C4 18 C7 45 FC FF FF FF FF
0AE5361B 0006FAB0 0001:0000261B E:\Program Files\Java\j2re1.4.0\lib\ext\x86\corojdk11.exe
code: 83 C4 1C 8B 8D C4 F8 FF FF 51
0097960D 0006FADC 0000:00000000
code: 8B 5D F8 64 8B 3D 18 00 00 00
00972E4B 0006FB0C 0000:00000000
code: 8B 75 E8 8B 7D EC 0F B7 4E 01
00972E4B 0006FB40 0000:00000000
code: 8B 75 E8 8B 7D EC 0F B7 4E 01
0097017D 0006FB60 0000:00000000
code: 8B 7D 0C 8B 75 10 83 FE 0B 0F
6D35CDBA 0006FBE4 0001:0002BDBA E:\Program Files\Java\j2re1.4.0\bin\client\jvm.exe
code: 33 DB 83 C4 20 39 5E 04 74 12
6D391102 0006FC20 0001:00060102 E:\Program Files\Java\j2re1.4.0\bin\client\jvm.exe
code: 83 C4 10 EB 0C FF 75 EC E8 23
6D35CCCE 0006FCA0 0001:0002BCCE E:\Program Files\Java\j2re1.4.0\bin\client\jvm.exe
code: 83 C4 14 C3 55 8B EC 83 EC 50
6D3640C8 0006FCFC 0001:000330C8 E:\Program Files\Java\j2re1.4.0\bin\client\jvm.exe
code: 8B 4E 20 83 C4 1C 39 7E 04 E8
00401485 0006FF4C 0001:00000485 E:\WINNT\system32\java.exe
code: 8B 45 FC 83 C4 10 8B 08 50 FF
00402D82 0006FFC0 0001:00001D82 E:\WINNT\system32\java.exe
code: 83 C4 30 89 45 DC 50 FF 15 98
77EA847C 0006FFF0 0001:0002747C E:\WINNT\system32\KERNEL32.exe
code: 50 EB 27 8B 45 EC 8B 08 8B 09
end of stack-----
|
Retrieving Win32 Error Code
Windows maintains the 32-bit integer last-error code value on a per-thread basis. Most Win32 APIs set thread's last error code value when they fail. To retrieve last-error code value use
public int lastOsError(); |
To retrieve the error description, use the following method
public static String getOSErrorDescription(int errno) |
In rare cases when when there is no message description associated with the error code, the method answers null.
Example: Enumerating URL
Cache 
This example demonstrates how to deal with complex variable-length structures.
UrlCache.java enumerates the information stored in the URL Cache by calling both FindFirstUrlCacheEntry and FindNextUrlCacheEntry APIs. FindFirstUrlCacheEntry starts the enumeration by taking a search pattern and answers the first cache entry. FindNextUrlCacheEntry takes the handle issued by FindFirstUrlCacheEntry and answers the next cache entry. Both functions return cache entry information in INTERNET_CACHE_ENTRY_INFO structure which size varies for each entry. After the cache is enumerated, UrlCache.java calls FindCloseUrlCache to close the cache enumeration handle.
Example's source code is in Coroutine\Examples\UrlCache.java.
import java.util.*;
import java.io.*;
import com.neva.*;
public class UrlCache {
int ERROR_INSUFFICIENT_BUFFER=122;
int m_handle;
byte [] m_cacheEntry;
String m_searchPattern;
/** Pass a search pattern. This can be "cookie:" or "visited:" to
* enumerate the cookies or URL History.
* Empty search pattern enumerates all entries in the cache.
*/
public static void main(String[] args) throws Exception {
UrlCache uc=new UrlCache(args!=null && args.length>0?args[0]:null);
InternetCacheEntryInfo urlCache=uc.FindFirstUrlCacheEntry();
while(true) {
if(urlCache==null)
break;
System.out.println("url="+urlCache.url+" file="+urlCache.file+
" accessed:"+urlCache.LastAccessTime);
urlCache=uc.FindNextUrlCacheEntry();
}
}
public UrlCache() {
m_searchPattern=null;
m_handle=0;
}
public UrlCache(String searchPattern) {
m_searchPattern=searchPattern;
m_handle=0;
}
protected void finalize() {
if(m_handle!=0)
try {
FindCloseUrlCacheEntry();
} catch(Exception e) {
}
}
/** Starts the enumeration of the Internet cache */
InternetCacheEntryInfo FindFirstUrlCacheEntry() throws Exception {
int buffSize=100;
if(m_handle!=0)
FindCloseUrlCacheEntry();
ExternalObject eo=new ExternalObject(new byte[buffSize],buffSize);
while(true) {
Coroutine coro=new Coroutine("wininet","FindFirstUrlCacheEntryA");
coro.addArg(m_searchPattern);
coro.addArg(eo.getValue());
byte [] p3=new byte[4];
Coroutine.setDWORDAtOffset(p3,buffSize,0);
coro.addArg(p3);
int rc=coro.invoke();
if(rc!=0)
throw new Exception("Error in Coroutine "+coro.lastError());
m_handle = coro.answerAsInteger();
int osErr=coro.lastOsError();
if(m_handle==0) {
if(osErr!=ERROR_INSUFFICIENT_BUFFER)
throw new Exception("Error in FindFirstUrlCacheEntry "+coro.lastOsError());
buffSize=coro.intFromParameterAt(2);
eo=new ExternalObject(new byte[buffSize],buffSize);
} else
return new InternetCacheEntryInfo(eo.getValue());
}
}
/** Retrieves the next entry in the Internet cache */
InternetCacheEntryInfo FindNextUrlCacheEntry() throws Exception {
int buffSize=100;
ExternalObject eo=new ExternalObject(new byte[buffSize],buffSize);
while(true) {
Coroutine coro=new Coroutine("wininet","FindNextUrlCacheEntryA");
coro.addArg(m_handle);
coro.addArg(eo.getValue());
byte [] p3=new byte[4];
Coroutine.setDWORDAtOffset(p3,buffSize,0);
coro.addArg(p3);
int rc=coro.invoke();
if(rc!=0)
throw new Exception("Error in Coroutine "+coro.lastError());
int osErr=coro.lastOsError();
if(!coro.answerAsBoolean()) {
if(osErr!=ERROR_INSUFFICIENT_BUFFER)
return null;
buffSize=coro.intFromParameterAt(2);
eo=new ExternalObject(new byte[buffSize],buffSize);
} else
return new InternetCacheEntryInfo(eo.getValue());
}
}
/** Closes the specified cache enumeration handle */
void FindCloseUrlCacheEntry() throws Exception {
Coroutine coro=new Coroutine("wininet","FindCloseUrlCacheEntry");
coro.addArg(m_handle);
int rc=coro.invoke();
if(rc!=0)
throw new Exception("Error in Coroutine "+coro.lastError());
m_handle=0;
}
}
class InternetCacheEntryInfo {
/* this structure contains information about an entry in the Internet cache
typedef struct _INTERNET_CACHE_ENTRY_INFO {
DWORD dwStructSize;
LPTSTR lpszSourceUrlName;
LPTSTR lpszLocalFileName;
DWORD CacheEntryType;
DWORD dwUseCount;
DWORD dwHitRate;
DWORD dwSizeLow;
DWORD dwSizeHigh;
FILETIME LastModifiedTime;
FILETIME ExpireTime;
FILETIME LastAccessTime;
FILETIME LastSyncTime;
LPBYTE lpHeaderInfo;
DWORD dwHeaderInfoSize;
LPTSTR lpszFileExtension;
union {
DWORD dwReserved;
DWORD dwExemptDelta;
}
} INTERNET_CACHE_ENTRY_INFO;
*/
public int CacheEntryType = 0;
public int dwUseCount = 0;
public Date LastModifiedTime = null;
public Date ExpireTime = null;
public Date LastAccessTime = null;
public Date LastSyncTime = null;
public String url = "";
public String file = "";
public InternetCacheEntryInfo(int ptr) throws Exception {
int lpszSourceUrlName=Coroutine.intFromPtr(ptr+4);
int lpszLocalFileName=Coroutine.intFromPtr(ptr+8);
CacheEntryType=Coroutine.intFromPtr(ptr+12);
url=Coroutine.StringFromPtr(lpszSourceUrlName);
file=Coroutine.StringFromPtr(lpszLocalFileName);
LastModifiedTime=FileTimeToDate(Coroutine.longFromPtr(ptr+32));
ExpireTime = FileTimeToDate(Coroutine.longFromPtr(ptr+40));
LastAccessTime = FileTimeToDate(Coroutine.longFromPtr(ptr+48));
LastSyncTime = FileTimeToDate(Coroutine.longFromPtr(ptr+56));
dwUseCount=Coroutine.intFromPtr(ptr+16);
}
Date FileTimeToDate(long filetime) throws Exception {
if(filetime==0)
return null;
Coroutine coro=new Coroutine("ole32","CoFileTimeToDosDateTime");
byte [] temp=new byte[8];
Coroutine.setINT64AtOffset(temp,filetime,0);
coro.addArg(temp);
coro.addArg(new byte[2]);
coro.addArg(new byte[2]);
int rc=coro.invoke();
if(rc!=0)
throw new Exception("Error in Coroutine "+coro.lastError());
if(!coro.answerAsBoolean())
throw new Exception("Error in CoFileTimeToDosDateTime("+filetime+")");
short d=(short)coro.getWORDAtOffset(coro.byteArrayFromParameterAt(1,2),0);
short t=(short)coro.getWORDAtOffset(coro.byteArrayFromParameterAt(2,2),0);
return new Date(((d>>9)&0x7F)+80, ((d>>5)&0x0F)-1, d&0x1F,
(t>>11)&0x1F, (t>>5)&0x3F, (t&0x1F)<<1);
}
}
|
Example: Windows System Tray

This example demonstrates how to use Coroutine for Java to add an icon with a tool-tip to taskbar notification area, or Windows System Tray. When the mouse activity occurs on the icon, a custom SysTrayEvent event is triggered to enable Java application to respond with various actions.
Shell_NotifyIcon API
The Shell_NotifyIcon API
exported by shell32.exe is used to add, modify, or delete an icon from the
taskbar notification area. Here is
how Shell_NotifyIcon is defined in shellapi.h header file:
BOOL WINAPI Shell_NotifyIconA(DWORD dwMessage, NOTIFYICONDATA* lpData);
|
First
parameter is an integer that identifies an action to perform: add (NIM_ADD),
delete (NIM_DELETE), or modify (NIM_MODIFY).Second parameter is a pointer
to a NOTIFYICONDATA structure defined in in shellapi.h header file as:
typedef struct _NOTIFYICONDATA {
DWORD cbSize;
HWND hWnd;
UINT uID;
UINT uFlags;
UINT uCallbackMessage;
HICON hIcon;
CHAR szTip[64];
} NOTIFYICONDATA;
|
Member cbSize holds the size of the structure – 88 bytes. Member hWnd holds the handle of the window that receives all notification messages. Member uID is an arbitrary integer value that identifies the icon. Member uFlags specifies which of the other members contain valid data. Parameter uCallbackMessage is an integer that specifies the notification message id. Since notification messages are being delivered via standard window message mechanism, the message id must have a unique value not being used by the Windows. Parameter hIcon specifies an integer handle of the icon that is displayed in the task bar status area. Finally, szTip contains a string to be used as the tooltip text.
Later we will show how to obtain a window and an icon handles. Let’s finish with Shell_NotifyIcon API first.
To pass the NOTIFYICONDATA structure by
reference, we create a byte array and pass this array to a Coroutine via
addArg(byte []). The following Java method
builds 88 bytes long array that holds NOTIFYICONDATA structure. SysTray
member variables m_hwnd, m_icon, and m_tip contain window handle, icon, and a
tooltip text
byte [] NOTIFYICONDATA() { byte [] nif=new byte[88]; Coroutine.setDWORDAtOffset(nif,88,0); //set cbSise Coroutine.setDWORDAtOffset(nif,m_hwnd,4); Coroutine.setDWORDAtOffset(nif,m_tid,8); //use thread id int flag=NIF_MESSAGE; Coroutine.setDWORDAtOffset(nif,m_callbackMessage,16); if(m_icon!=null) { Coroutine.setDWORDAtOffset(nif,m_icon.getHandle(),20); flag|=NIF_ICON; } if(m_tip!=null && m_tip.length()!=0) { System.arraycopy(m_tip.getBytes(),0,nif,24,m_tip.length()>64?63:m_tip.length()); flag|=NIF_TIP; } Coroutine.setDWORDAtOffset(nif,flag,12); return nif; } |
Here
is how we call the Shell_NotifyIcon API and pass the NOTIFYICONDATA structure
along with the integer action
int perform(int action) {
Coroutine co=new Coroutine("shell32","Shell_NotifyIconA");
co.addArg(action);
co.addArg(NOTIFYICONDATA());
if(co.invoke()!=0)
return -1;
if(co.answerAsBoolean())
return 1;
else
return 0;
}
public int add() {return perform(NIM_ADD);}
public int update() {return perform(NIM_MODIFY); }
public int delete() {return perform(NIM_DELETE);}
|
Methods above return 1 when Shell_NotifyIcon succeeds, 0 when fails, and –1 when Coroutine object encounters an error.
Processing Notifications
To
process notifications we, first, need to get a “native” window and store its
handle in the NOTIFYICONDATA structure above. Secondly, we must get access to
“native” window procedure to intercept messages sent by the System Tray
icon. In other words, we must implement a “native” callback
function that follows the WNDPROC
type specification below and use a pointer to such a function in a call to the
SetWindowLong API that performs window subclassing.
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
|
If Java application has a Form or a Window we can easily get the handle to associated native window and perform subclassing. Unfortunately, this may interfere with existing or future Java implementations of Windows message handling.
Therefore, the better approach is to create our own thread that handles standard Windows message processing, create an invisible window and get access to its window procedure to intercept messages sent by the System Tray icon.
The following creates a thread
m_messagePump=new SysTrayMessagePump(this); new Thread(m_messagePump).start(); |
Class
SysTrayMessagePump implements Runnable interface and its method run() runs
standard Windows message loop. Before entering the loop, SysTrayMessagePump
creates a window, subclasses it, and inserts an icon in the System Tray.
class SysTrayMessagePump implements Runnable {
…
public void run() {
PeekMessage();
m_systray.m_hwnd=m_hwnd=CreateWindow();
//subclass window m_hwnd
//…
while(true) {
byte [] msg=GetMessage();
if(msg==null)
break;
DispatchMessage(msg);
}
}
|
To
implement a “native” callback function after WNDPROC type, we derive
SysTray class from com.neva.JCallback and define method winproc as
public int winproc(int hwnd, int message, int wParam, int lParam) {
Object _privateData=getPrivateData();
SysTray systray=(SysTray)_privateData;
if(message==m_callbackMessage) {
//process System Tray notification message here
if((WM_CONTEXTMENU==lParam || WM_RBUTTONUP==lParam) && m_menu!=null ) {
Point p=getMousePos();
int cmd=m_menu.trackPopupMenu(p.x,p.y,m_hwnd);
if(systray.m_listener!=null)
systray.m_listener.menuSelected((EventObject)new SysTrayEvent(this,cmd));
} else if(WM_LBUTTONDBLCLK==lParam) {
if(systray.m_listener!=null)
systray.m_listener.mouseLeftButtonDoubleClicked(
(EventObject)new SysTrayEvent(systray));
} else if(WM_LBUTTONUP==lParam) {
if(systray.m_listener!=null)
systray.m_listener.mouseLeftButtonClicked(
(EventObject)new SysTrayEvent(systray));
}
return 1;
}
Coroutine co=new Coroutine("user32","DefWindowProcA");
co.addArg(hwnd);
co.addArg(message);
co.addArg(wParam);
co.addArg(lParam);
co.invoke();
return co.answerAsInteger();
}
|
The winproc method above captures messages sent by the System Tray icon for further processing, and passes other messages directly to the default Windows message procedure.
The following code in SysTrayMessagePump.run() builds “native” callback function and performs window subclassing
//build callback m_systray.setCallback("winproc",4,com.neva.JCallback.CC_stdcall); m_systray.setPrivateData(m_systray); try { m_pCallback=m_systray.asWin32Callback(); } catch(Exception exc) { } //subclass int GWL_WNDPROC=-4; Coroutine co=new Coroutine("user32","SetWindowLongA"); co.addArg(m_hwnd); co.addArg(GWL_WNDPROC); co.addArg(m_pCallback); co.invoke(); //add an icon m_systray.add(); |
Note method getMousePos() used above. This method wraps the call to GetCursorPos(POINT*) function. This function receives the pointer to the 8-byte long structure POINT and fills it with the screen coordinates of the cursor. Below, co.addArg(new byte[8]) allocates 8 bytes memory block and passes the pointer to it as a single parameter to GetCursorPos. If the call succeeds, call co.byteArrayFromParameterAt(0,8) retrieves the contents of the buffer as a byte array. Finally, getDWORDAtOffset() reads individual members of the structure POINT.
private Point getMousePos() {
Coroutine co=new Coroutine("user32","GetCursorPos");
co.addArg(new byte[8]);
if(co.invoke()!=0)
return null;
if(!co.answerAsBoolean())
return null;
byte [] point=co.byteArrayFromParameterAt(0,8);
return new Point(Coroutine.getDWORDAtOffset(point,0),
Coroutine.getDWORDAtOffset(point,4));
}
|
Handling Windows Icons and Menus
The helper class WindowsIcon encapsulates a Windows icon. Method LoadIcon() loads an icon from the file specified by the method single parameter.
int LoadIcon(String path) {
int IMAGE_ICON=1;
int LR_DEFAULTSIZE=0x0040;
int LR_LOADFROMFILE=0x0010;
Coroutine co=new Coroutine("user32","LoadImageA");
co.addArg(0);
co.addArg(path);
co.addArg(IMAGE_ICON);
co.addArg(0);
co.addArg(0);
co.addArg(LR_LOADFROMFILE|LR_DEFAULTSIZE);
if(co.invoke()!=0)
return 0;
return co.answerAsInteger();
}
|
To access icon’s Win32 handle, use getHandle() method.
Please note method finalize that disposes the GDI resources when the object is collected by the garbage collector.
protected void finalize() {
if(m_handle!=0) {
Coroutine co=new Coroutine("user32","DestroyIcon");
co.addArg(m_handle);
co.invoke();
}
}
|
The helper class WindowsMenu encapsulates a Windows popup menu. Constructor WindowsMenu below creates a Win32 shortcut menu and builds its content.
public WindowsMenu(String [] items, int [] cmds) {
m_handle=0;
if(items==null || cmds==null || items.length==0 ||
items.length!=cmds.length)
return;
Coroutine co=new Coroutine("user32","CreatePopupMenu");
if(0!=co.invoke())
return;
m_handle=co.answerAsInteger();
if(m_handle==0)
return;
for(int j=0; j<items.length;j++) {
co=new Coroutine("user32","AppendMenuA");
co.addArg(m_handle);
co.addArg(items[j].length()==0?MF_SEPARATOR:MF_STRING);
co.addArg(cmds[j]);
co.addArg(items[j]);
if(0!=co.invoke()) {
m_handle=0;
return;
}
}
}
|
Parameter items above specifies an array of menu item names. When the item name is an empty String, menu separator as added instead of menu item. Second parameter cmds specifies an array of menu item identifies. When menu item is selected, menu item identifier is retuned by the trackPopupMenu() method. SysTray bean passes selected menu item identifier to SysTrayListener.menuSelected() via SysTrayEvent object.
Overloaded constructor
public WindowsMenu(String [] items, int [] cmds, int defaultitem) |
enables to specify which menu item is a default menu item.
Finally, the following constructor
public WindowsMenu(String [] items, int [] cmds, int defaultitem, int [] flags) |
enables to specify the state of each menu item
via its last parameter flags. Each element of the flags parameter can be one or
a combination of the following flags:
SysTray.MENUITEM_ENABLED -indicates that the
menu item is enabled
SysTray.MENUITEM_GRAYED - indicates that the menu item is disabled and grayed so
that it cannot be selected
SysTray.MENUITEM_DISABLED - indicates that
the menu item is disabled
SysTray.MENUITEM_CHECKED – indicated that the menu item has a check mark
Please note method finalize that disposes the resources and memory that the menu occupies.
SysTray Java Bean Details
SysTray is an invisible bean that adds an icon with a tool-tip to the Windows System Tray and triggers custom SysTrayEvent events when the mouse activity occurs on the icon.
SysTray bean has three properties: the tool-tip text, the icon, and the popup menu. Once properties are set, you can call start() method to add your icon to the System Tray and launch notification processing. Later you can modify any of the bean properties and call update() method to make those changes visible.
SysTrayEvent gets fired when the mouse left button click/double-click occurred over the icon or when the pop-up menu item was selected The menu pops up when the mouse right button click occurred over the icon. You should register a SysTrayEventListener with a source bean to be able to process mouse events. Please note that SysTrayEvent has a custom property command that contains selected menu item identifier.
And, finally, here is Java code that makes use of SysTray bean
public static void main(String [] s) {
final int EOJ=6;
final int CHECK=1;
final int CHNGICON=3;
final String [] m={"Checked Menu Item",
"Default Menu Item","",
"Change Icon Menu Item",
"Disabled Menu Item",
"Grayed Menu Item", "Exit"};
final int [] c={CHECK,2,0,CHNGICON,4,5,EOJ};
final int [] di={SysTray.MENUITEM_CHECKED,
SysTray.MENUITEM_ENABLED,
SysTray.MENUITEM_ENABLED,
SysTray.MENUITEM_ENABLED,
SysTray.MENUITEM_DISABLED,
SysTray.MENUITEM_GRAYED,
SysTray.MENUITEM_ENABLED};
final String icon1="ST_MAIL.ICO";
final String icon2="ST_PHONE.ICO";
//this program assumes that both icon files above reside in
//the current directory
SysTray t=new SysTray();
t.setTip("Java Application");
t.setIcon(icon1);
t.setPopupMenu(m,c,1,di);
t.start();
try {
t.addSysTrayEventListener(new SysTrayEventAdaptor() {
String currentIcon=icon1;
public void menuSelected(EventObject event) {
SysTrayEvent ev=(SysTrayEvent)event;
System.out.println("Menu selected "+ev.getCommand());
if(ev.getCommand()==EOJ) {
((SysTray)ev.getSource()).delete();
System.exit(0);
}
if(ev.getCommand()==CHECK) {
if(di[0]==MENUITEM_CHECKED)
di[0]=MENUITEM_ENABLED;
else
di[0]=MENUITEM_CHECKED;
((SysTray)ev.getSource()).setPopupMenu(m,c,1,di);
((SysTray)ev.getSource()).update();
}
if(ev.getCommand()==CHNGICON) {
if(currentIcon==icon1)
currentIcon=icon2;
else
currentIcon=icon1;
((SysTray)ev.getSource()).setIcon(currentIcon);
((SysTray)ev.getSource()).update();
}
}
public void mouseLeftButtonClicked(EventObject event) {
System.out.println("MouseLeftButtonClicked...");
}
public void mouseLeftButtonDoubleClicked(EventObject event) {
System.out.println("MouseLeftButtonDoubleClicked...");
}
});
} catch(Exception e) {
}
}
|

Example: Subclassing Windows

Subclassing is a powerful Windows technique that allows an application to monitor or
modify the default behavior of a window by intercepting messages sent to it. Subclassing is an effective way to change or extend the behavior of a window
without redeveloping the window itself.
To subclass a window, an application replaces window's existing window procedure
with a new one using the SetWindowLong API. The new procedure intercepts all
messages destined for the window and either acts on them or passes them to the original procedure for default processing. The new windows procedure can modify
original messages before passing them to the original window procedure. The application subclassing a window can also decide when to react to the messages it
receives: the application can process the message either before, or after, or both
before and after passing the message to the original window procedure.
Here is Java wrapper for the SetWindowLong API
/* Sets a new address for the window procedure. Answers old address */
public int SetWindowProc(int hwnd, int wndProc) {
int GWL_WNDPROC=-4;
Coroutine co=new Coroutine("user32","SetWindowLongA");
co.addArg(hwnd);
co.addArg(GWL_WNDPROC);
co.addArg(wndProc);
if(0!=co.invoke())
return 0;
return co.answerAsInteger();
}
|
The first parameter in above SetWindowProc specifies an integer handle to the
window you want to subclass. The second parameter specifies the address of the new window procedure. The address of the old window procedure is returned by the
method.
To build a window procedure we will use the JCallback class. Before we start, here
are a couple of helper classes.
The first one is the wrapper for the Windows message structure
class WindowMessage {
public int m_uMsg;
public int m_hwnd;
public int m_lParam;
public int m_wParam;
public int m_vRet;
public WindowMessage(int hwnd,int uMsg, int wParam, int lParam) {
m_hwnd=hwnd;
m_uMsg=uMsg;
m_lParam=lParam;
m_wParam=wParam;
m_vRet=0;
}
public String toString() {
return "MSG: wnd="+Integer.toString(m_hwnd,16)+" msg="+
Integer.toString(m_uMsg,16)+" param="+
Integer.toString(m_wParam,16)+"/"+Integer.toString(m_lParam,16);
}
}
|
The second helper class - WindowsEnumerator - implements the search for a native handle to an AWT object. To find a native Windows handle associated with an object we use the following approach: first, we search for the handle to the application's top-level window. We use EnumWindows API and construct EnumWindowsProc callback using JCallback.
class WindowsEnumerator extends com.neva.JCallback {
int m_hwnd=0;
int m_pid=0;
java.awt.Component m_wnd=null;
public int EnumWindowsProc(int hwnd, int param) {
Rectangle rect=GetWindowRect(hwnd);
Rectangle componentRect=new Rectangle(m_wnd.getLocationOnScreen(),
m_wnd.getSize());
int pid=GetWindowProcessId(hwnd);
if(rect.equals(componentRect) && pid==m_pid) {
m_hwnd=hwnd;
return 0;
}
return 1;
}
static int FindTopLevelWindow(java.awt.Component wnd) {
java.awt.Component oldParent=wnd;
java.awt.Component parent=oldParent.getParent();
while(parent!=null) {
oldParent=parent;
parent=oldParent.getParent();
}
parent=oldParent;
if(parent==null)
return 0;
WindowsEnumerator enum=new WindowsEnumerator();
enum.m_wnd=parent;
enum.m_pid=GetCurrentProcessId();
enum.setCallback("EnumWindowsProc",2);
int ptr=enum.asWin32Callback();
Coroutine co=new Coroutine("user32","EnumWindows");
co.addArg(ptr);
co.addArg(0);
if(0!=co.invoke())
return 0;
enum.release();
return enum.m_hwnd;
}
static Rectangle GetWindowRect(int hwnd) {
Coroutine co=new Coroutine("user32","GetWindowRect");
co.addArg(hwnd);
co.addArg(new byte[16]);
if(0!=co.invoke())
return null;
if(!co.answerAsBoolean())
return null;
int [] recb=co.intArrayFromParameterAt(1,4);
return new Rectangle(recb[0],recb[1],recb[2]-recb[0],recb[3]-recb[1]);
}
public static int GetWindowProcessId(int hwnd) {
Coroutine co=new Coroutine("user32","GetWindowThreadProcessId");
co.addArg(hwnd);
co.addArg(new byte[4]);
if(0!=co.invoke())
return 0;
return co.intFromParameterAt(1);
}
public static int GetCurrentProcessId() {
Coroutine co=new Coroutine("kernel32","GetCurrentProcessId");
if(0!=co.invoke())
return 0;
return co.answerAsInteger();
}
}
|
Then we use the EnumChildWindows API searching down the hierarchy. Note that we use the window's position and size to determine whether the current window in the callback is the one we are looking for.
public int EnumChildProc(int hwnd, int param) {
Rectangle rect=GetWindowRect(hwnd);
Rectangle componentRect=new Rectangle(m_wnd.getLocationOnScreen(),
m_wnd.getSize());
if(rect.equals(componentRect)) {
m_hwnd=hwnd;
return 0;
}
return 1;
}
public static int GetWindowHandle(java.awt.Component wnd) {
int topHwnd=FindTopLevelWindow(wnd);
WindowsEnumerator enum=new WindowsEnumerator();
enum.m_wnd=wnd;
enum.setCallback("EnumChildProc",2);
int ptr=enum.asWin32Callback();
Coroutine co=new Coroutine("user32","EnumChildWindows");
co.addArg(topHwnd);
co.addArg(ptr);
co.addArg(0);
if(0!=co.invoke())
return 0;
enum.release();
return enum.m_hwnd;
}
|
Finally, here is the NativeWindowMessageHook class that implements the whole process of subclassing a window. Note that the WindowProc method implements a generic window procedure, and all details are handled by an arbitrary object that implements the NativeWindowMessageProcessor interface below and is registered with the procedure.
interface NativeWindowMessageProcessor {
public static int CALL_OLD_PROC=0;
public static int DONOT_CALL_OLD_PROC=1;
public static int DESTROY_HOOK=2;
public static int CALL_HOOK_BEFORE_WINDOWS=0;
public static int CALL_HOOK_AFTER_WINDOWS=1;
public int ProcessWindowMessage(WindowMessage msg) ;
public int GetWindowMessageProcessingOptions();
}
|
class NativeWindowMessageHook extends com.neva.JCallback {
int m_hwnd;
int m_wndProc;
int m_oldWndProcPtr;
NativeWindowMessageProcessor m_processor;
public NativeWindowMessageHook(int hwnd,
NativeWindowMessageProcessor processor) {
m_hwnd=hwnd;
setCallback("WindowProc",4);
m_wndProc=asWin32Callback();
m_oldWndProcPtr=SetWindowProc(m_hwnd,m_wndProc);
m_processor=processor;
setPrivateData((Object)this);
}
public void Destroy() {
//restore original window procedure
SetWindowProc(m_hwnd,m_oldWndProcPtr);
// we do not destroy the JCallback itseft since there is a serious
// race condition here: if we destroy JCallback thus destroying
// wndProc's executable code while wndProc is active, JVM will crash
//this.release();
}
/** Generic Window procedure */
public int WindowProc(int hwnd, int uMsg,int wParam,int lParam) {
NativeWindowMessageHook obj=(NativeWindowMessageHook)getPrivateData();
WindowMessage msg=new WindowMessage(hwnd,uMsg,wParam,lParam);
if(obj.m_processor!=null) {
int ret,nRet;
if(obj.m_processor.CALL_HOOK_BEFORE_WINDOWS==
obj.m_processor.GetWindowMessageProcessingOptions()) {
//do subclassing
ret=obj.m_processor.ProcessWindowMessage(msg);
if((ret&NativeWindowMessageProcessor.DESTROY_HOOK)>0)
obj.Destroy();
if((ret&NativeWindowMessageProcessor.DONOT_CALL_OLD_PROC) > 0)
return msg.m_vRet;
else
return CallWindowProc(obj.m_oldWndProcPtr,msg.m_hwnd,
msg.m_uMsg,msg.m_wParam,msg.m_lParam);
}
//do superclassing
CallWindowProc(obj.m_oldWndProcPtr,hwnd, uMsg,wParam,lParam);
ret=obj.m_processor.ProcessWindowMessage(msg);
if((ret&NativeWindowMessageProcessor.DESTROY_HOOK)>0)
SetWindowProc(obj.m_hwnd,m_oldWndProcPtr);
return msg.m_vRet;
}
return CallWindowProc(obj.m_oldWndProcPtr,hwnd, uMsg,wParam,lParam);
}
/** Calls the specified window procedure. */
public int CallWindowProc(int proc,int hwnd, int uMsg,int wParam,int lParam) {
Coroutine co=new Coroutine("user32","CallWindowProcA");
co.addArg(proc);
co.addArg(hwnd);
co.addArg(uMsg);
co.addArg(wParam);
co.addArg(lParam);
if(0!=co.invoke()) {
System.out.println(co.lastError());
return 0;
}
return co.answerAsInteger();
}
/** Answers the address for the window procedure */
public int GetWindowProc(int hwnd) {
int GWL_WNDPROC=-4;
Coroutine co=new Coroutine("user32","GetWindowLongA");
co.addArg(hwnd);
co.addArg(GWL_WNDPROC);
if(0!=co.invoke())
return 0;
return co.answerAsInteger();
}
/* Sets a new address for the window procedure. Answers old address */
public int SetWindowProc(int hwnd, int wndProc) {
int GWL_WNDPROC=-4;
Coroutine co=new Coroutine("user32","SetWindowLongA");
co.addArg(hwnd);
co.addArg(GWL_WNDPROC);
co.addArg(wndProc);
if(0!=co.invoke())
return 0;
return co.answerAsInteger();
}
}
|
As an example, we implemented an edit control that allows only hexadecimal characters from "0" to "9" and "A" to "F". Subclassing does take care of conversion lover case characters to upper case characters, and prevents non-valid characters from being copied from the Windows Clipboard.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.neva.*;
public class WindowSubclassing extends Frame
implements NativeWindowMessageProcessor {
public TextField m_text;
NativeWindowMessageHook m_hook;
public static void main(String [] param) {
new WindowSubclassing();
}
public WindowSubclassing() {
super("WindowSubclassing");
setLayout(null);
Font font=new Font("Verdana",Font.PLAIN,10);
m_text=new TextField("");
add(m_text);
m_text.setBounds(10,40,250,20);
m_text.setFont(font);
enableEvents(java.awt.event.WindowEvent.WINDOW_CLOSING|
java.awt.event.WindowEvent.WINDOW_OPENED);
setSize(300,170);
setLocation(100,100);
show();
}
public void processEvent(AWTEvent e) {
if(e.getID()==java.awt.event.WindowEvent.WINDOW_OPENED ) {
try {
int win=WindowsEnumerator.GetWindowHandle(m_text);
m_hook=new NativeWindowMessageHook(win,this);
} catch(Exception ex) {
ex.printStackTrace();
System.exit(0);
}
}
if(e.getID()==java.awt.event.WindowEvent.WINDOW_CLOSING) {
try {
m_hook.Destroy();
} catch(Exception ex) {
ex.printStackTrace();
}
System.exit(0);
}
}
public int ProcessWindowMessage(WindowMessage msg) {
int WM_CHAR= 0x0102;
int WM_KEYUP=0x101;
int WM_PASTE=0x0302;
if(WM_PASTE==msg.m_uMsg) {
//message is sent to direct the window to copy the current content of the
//clipboard at the current caret position.
//Check whether clipboard contains permitted characters
OpenClipboard();
String clipboardTextData=GetClipboardTextData();
if(clipboardTextData!=null && clipboardTextData.length()>0) {
//we will permit only 0-9, A-F
clipboardTextData=clipboardTextData.toUpperCase();
for(int j=0;j<clipboardTextData.length();j++) {
char c=clipboardTextData.charAt(j);
if(!(Character.isDigit(c) || (c>='A' && c<='F'))) {
CloseClipboard();
Beep(1270,100);
return NativeWindowMessageProcessor.DONOT_CALL_OLD_PROC;
}
}
//modify the clipboard data before letting edit control to process
SetClipboardTextData(clipboardTextData);
CloseClipboard();
}
return NativeWindowMessageProcessor.CALL_OLD_PROC;
}
if(WM_CHAR==msg.m_uMsg) {
//allow only 0 to 9, A to F, TAB, BKSPACE
if((msg.m_wParam>=0x30 && msg.m_wParam<=0x39) ||
(msg.m_wParam>=0x41 && msg.m_wParam<=0x46) ||
msg.m_wParam==0x08 ||msg.m_wParam==0x09)
return NativeWindowMessageProcessor.CALL_OLD_PROC;
else if(msg.m_wParam>=0x61 && msg.m_wParam<=0x66) {
msg.m_wParam-=0x20; //convert to upper case
return NativeWindowMessageProcessor.CALL_OLD_PROC;
} else {
Beep(1270,100);
return NativeWindowMessageProcessor.DONOT_CALL_OLD_PROC;
}
}
return NativeWindowMessageProcessor.CALL_OLD_PROC;
}
public int GetWindowMessageProcessingOptions() {
return NativeWindowMessageProcessor.CALL_HOOK_BEFORE_WINDOWS;
}
public void Beep(int freq, int dura) {
Coroutine co=new Coroutine("kernel32","Beep");
co.addArg(freq);
co.addArg(dura);
co.invoke();
}
public static boolean OpenClipboard() {
Coroutine coro=new Coroutine("USER32","OpenClipboard");
coro.addArgNull();
int rc=coro.invoke();
if(rc != 0)
return false;
return coro.answerAsBoolean();
}
public static boolean CloseClipboard() {
Coroutine coro=new Coroutine("USER32","CloseClipboard");
coro.addArgNull();
int rc=coro.invoke();
if(rc != 0)
return false;
return coro.answerAsBoolean();
}
public static String GetClipboardTextData() {
Coroutine coro=new Coroutine("USER32","GetClipboardData");
coro.addArg(1); //CF_TEXT
int rc;
rc=coro.invoke();
if(rc != 0)
return (String)null;
int handle=coro.answerAsInteger();
coro=new Coroutine("KERNEL32","GlobalLock");
coro.addArg(handle);
rc=coro.invoke();
if(rc != 0)
return (String)null;
int ptr=coro.answerAsInteger();
String str=Coroutine.StringFromPtr(ptr);
coro=new Coroutine("KERNEL32","GlobalUnlock");
coro.addArg(handle);
rc=coro.invoke();
if(rc != 0)
return (String)null;
return str;
}
public static int SetClipboardTextData(String data) {
Coroutine coro=new Coroutine("KERNEL32","GlobalAlloc");
coro.addArg(8194); // GMEM_DDESHARE|GMEM_MOVEABLE
coro.addArg(data.length()+1);
int rc;
rc=coro.invoke();
if(rc != 0)
return rc;
int handle=coro.answerAsInteger();
if(handle == 0)
return coro.lastOsError();
coro=new Coroutine("KERNEL32","GlobalLock");
coro.addArg(handle);
rc=coro.invoke();
if(rc != 0)
return rc;
int ptr=coro.answerAsInteger();
if(ptr == 0)
return coro.lastOsError();
ExternalObject ext=new ExternalObject(data);
coro.copyMemory(ptr,ext.getValue(),data.length());
coro.copyByte(ptr+data.length(),(byte)0);
coro=new Coroutine("USER32","SetClipboardData");
coro.addArg(1); //CF_TEXT
coro.addArg(handle);
rc=coro.invoke();
if(rc != 0)
return rc;
if(coro.answerAsInteger() == 0)
return coro.lastOsError();
coro=new Coroutine("KERNEL32","GlobalUnlock");
coro.addArg(handle);
rc=coro.invoke();
if(rc != 0)
return rc;
return 0;
}
}
|
Example: Creating a
Non-Rectangle Window 
The SetWindowRgn API allows you to set the clipping region for a window including the border. You can create elliptical, polygonal, rectangular, and round regions via calls to the CreateEllipticalRgn, CreatePolygonRgn, CreateRectRgn, and CreateRoundRgn functions. You pass to the creation function the coordinates bounding the region that you wish to set, and the function returns a handle to the region. Once you have the handle to the region, you can pass it to SetWindowRgn, whereupon the window assumes the shape. You can also join regions together to make composite regions. As a result, the region for your window can have a hole in it (like the window below) or consist of disjointed regions.

Here is the code.
import java.util.*;
import com.neva.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
public class NonRectangleWindow extends com.neva.JCallback {
public static final int RGN_AND=1;
public static final int RGN_OR=2;
public static final int RGN_XOR=3;
private NonRectangleWindow() {
}
/** Creates a rectangular region with rounded corners */
public static int CreateRoundRectangleRgn(Rectangle rect,
int widthEllipse,int heightEllipse) throws Exception {
Coroutine co=new Coroutine("gdi32","CreateRoundRectRgn");
co.addArg(rect.x);
co.addArg(rect.y);
co.addArg(rect.x+rect.width);
co.addArg(rect.y+rect.height);
co.addArg(widthEllipse);
co.addArg(heightEllipse);
if(0!=co.invoke())
throw new Exception("Error in CreateRoundRectRgn "+co.lastError());
return co.answerAsInteger();
}
/** Creates an elliptical region */
public static int CreateEllipticRgn(Rectangle rect) throws Exception {
Coroutine co=new Coroutine("gdi32","CreateEllipticRgn");
co.addArg(rect.x);
co.addArg(rect.y);
co.addArg(rect.x+rect.width);
co.addArg(rect.y+rect.height);
if(0!=co.invoke())
throw new Exception("Error in CreateEllipticRgn "+co.lastError());
return co.answerAsInteger();
}
/** Creates a rectangular region */
public static int CreateRectRgn(Rectangle rect) throws Exception {
Coroutine co=new Coroutine("gdi32","CreateRectRgn");
co.addArg(rect.x);
co.addArg(rect.y);
co.addArg(rect.x+rect.width);
co.addArg(rect.y+rect.height);
if(0!=co.invoke())
throw new Exception("Error in CreateRectRgn "+co.lastError());
return co.answerAsInteger();
}
/** Creates a polygonal region */
public static int CreatePolygonRgn(Point [] points) throws Exception {
byte [] b=new byte[points.length*8];
for(int j=0;j<points.length;j++) {
Coroutine.setDWORDAtOffset(b,points[j].x,j*8);
Coroutine.setDWORDAtOffset(b,points[j].y,j*8+4);
}
Coroutine co=new Coroutine("gdi32","CreatePolygonRgn");
co.addArg(b);
co.addArg(points.length);
co.addArg(1);
if(0!=co.invoke())
throw new Exception("Error in CreateRoundRectRgn "+co.lastError());
return co.answerAsInteger();
}
/** Combines two regions and stores the result in a third region */
public static int CombineRgn(int hrgn1, int hrgn2, int how ) throws Exception {
int hrgn=CreateRectRgn(new Rectangle(0,0,1,1));
Coroutine co=new Coroutine("gdi32","CombineRgn");
co.addArg(hrgn);
co.addArg(hrgn1);
co.addArg(hrgn2);
co.addArg(how);
if(0!=co.invoke()) {
DeleteRgn(hrgn);
throw new Exception("Error in CombineRgn "+co.lastError());
}
if(co.answerAsInteger()==0) {
DeleteRgn(hrgn);
throw new Exception("Os error in CombineRgn "+co.lastOsError());
}
return hrgn;
}
/** Delete region object */
public static void DeleteRgn(int hrgn) throws Exception {
Coroutine co=new Coroutine("gdi32","DeleteObject");
co.addArg(hrgn);
if(0!=co.invoke()) {
throw new Exception("Error in DeleteObject "+co.lastError());
}
if(co.answerAsInteger()==0)
throw new Exception("Os error in DeleteObject "+co.lastOsError());
}
/** Sets the window region of a window */
public static void SetWindowRgn(int hwnd, int hrgn) throws Exception {
Coroutine co=new Coroutine("user32","SetWindowRgn");
co.addArg(hwnd);
co.addArg(hrgn);
co.addArg(true);
if(0!=co.invoke()) {
throw new Exception("Error in SetWindowRgn "+co.lastError());
}
if(co.answerAsInteger()==0)
throw new Exception("Os error in SetWindowRgn "+co.lastOsError());
}
/** Finds a handle to the top-level window which title matches the specified string */
public static int FindWindow(String title) throws Exception {
Coroutine co=new Coroutine("user32","FindWindowA");
co.addArg(0);
co.addArg(title);
if(0!=co.invoke()) {
throw new Exception("Error in FindWindow "+co.lastError());
}
return co.answerAsInteger();
}
/** Answers the bounding rectangle of the window */
public static Rectangle GetWindowRect(int hwnd) throws Exception {
Coroutine co=new Coroutine("user32","GetWindowRect");
co.addArg(hwnd);
co.addArg(new byte[16]);
if(0!=co.invoke())
throw new Exception("Error in GetWindowRect "+co.lastError());
if(!co.answerAsBoolean())
throw new Exception("Os error in GetWindowRect "+co.lastOsError());
byte [] r=co.byteArrayFromParameterAt(1,16);
int left=Coroutine.getDWORDAtOffset(r,0);
int top=Coroutine.getDWORDAtOffset(r,4);
int right=Coroutine.getDWORDAtOffset(r,8);
int bottom=Coroutine.getDWORDAtOffset(r,12);
return new Rectangle(left,top,right-left,bottom-top);
}
/** Answers the id of the thread that created the window */
public static int GetWindowThreadId(int hwnd) throws Exception {
Coroutine co=new Coroutine("user32","GetWindowThreadProcessId");
co.addArg(hwnd);
co.addArg(0);
if(0!=co.invoke())
throw new Exception("Error in GetWindowThreadId "+co.lastError());
return co.answerAsInteger();
}
public int EnumThreadWndProc(int hwnd, int param){
Object [] obj=(Object [])getPrivateData();
try {
Rectangle rect=GetWindowRect(hwnd);
if(rect!=null && rect.equals((Rectangle)obj[0])) {
obj[1]=new Integer(hwnd);
return 0;
}
} catch(Exception ex) {
}
return 1;
}
/** Enumerates all windows associated with a thread,
finds the window by its bounding rectangle */
public static int FindWindowAtLocation(int tid, Rectangle rect) throws Exception {
NonRectangleWindow enum=new NonRectangleWindow();
enum.setCallback("EnumThreadWndProc",2,com.neva.JCallback.CC_stdcall);
Object [] param=new Object[2];
param[0]=rect;
param[1]=null;
enum.setPrivateData(param);
int pCallback=enum.asWin32Callback();
Coroutine co=new Coroutine("user32","EnumThreadWindows");
co.addArg(tid);
co.addArg(pCallback);
co.addArg(0);
if(0!=co.invoke())
throw new Exception("Error in FindWindow "+co.lastError());
enum.release();
if(param[1]==null)
return 0;
return ((Integer)param[1]).intValue();
}
public static void main(String [] s) {
new NonRectangleWindow().runTest();
}
public void runTest() {
new ExampleWindow(new Frame("NONRECTANGLEWINDOW"));
}
class ExampleWindow extends Window implements
MouseMotionListener,MouseListener {
boolean dragStarted=false;
Point dragStartLocation=null;
public ExampleWindow(Frame parent) {
super(parent);
setLayout(null);
Rectangle rect=new Rectangle(-500,-500,200,200); //create offscreen
setBounds(rect);
setBackground(Color.yellow);
setVisible(true);
try {
int phwnd=NonRectangleWindow.FindWindow(parent.getTitle());
int tid=NonRectangleWindow.GetWindowThreadId(phwnd);
int hwnd=NonRectangleWindow.FindWindowAtLocation(tid,getBounds());
//create the shape of a star
Point [] points=new Point[10];
points[0]=new Point(97, 0);
points[1]=new Point(120, 65);
points[2]=new Point(193, 65);
points[3]=new Point(134, 106);
points[4]=new Point(156, 171);
points[5]=new Point(97, 131);
points[6]=new Point(37, 171);
points[7]=new Point(59, 106);
points[8]=new Point(0, 65);
points[9]=new Point(74, 65);
int rgn1=CreatePolygonRgn(points);
int rgn2=NonRectangleWindow.CreateEllipticRgn(new Rectangle(82,75,30,30));
int rgn=NonRectangleWindow.CombineRgn(rgn1,rgn2,
NonRectangleWindow.RGN_XOR);
NonRectangleWindow.SetWindowRgn(hwnd,rgn);
} catch(Exception ex) {
ex.printStackTrace();
}
addMouseMotionListener((MouseMotionListener)this);
addMouseListener((MouseListener)this);
setLocation(200,200);
}
public void mouseDragged(MouseEvent me) {
if(!dragStarted) {
dragStarted=true;
dragStartLocation=me.getPoint();
} else {
Point newMouseLocation=me.getPoint();
Point location=getLocation();
setLocation(location.x+(newMouseLocation.x-dragStartLocation.x),
location.y+(newMouseLocation.y-dragStartLocation.y));
}
}
public void mouseReleased(MouseEvent me) {
dragStarted=false;
}
public void mouseMoved(MouseEvent me) {}
public void mousePressed(MouseEvent me) {}
public void mouseEntered(MouseEvent me) {}
public void mouseExited(MouseEvent me) {}
public void mouseClicked(MouseEvent me) {}
}
}
|