overview :: tutorial
This tutorial describes version 0.2.0 of ctypes4j
, and is heavily
focused on the Windows version.
You will need the ctypes4j.jar on your classpath. Ctypes4j requires a native DLL to be accessable from the java VM.
java -Dctypes.dll.path=c:\temp\ctypes4j\ -cp .\ctypes4j.jar;.\lib\junit.jar ctypes.java.test.TestAllWin32
ctypes4j
provides the CDLL object for accessing native DLL's.
You can load libraries by either using the CDLL.LoadLibrary(name) static method, or by instanciating a CDLL object.
Here are some examples for Windows, note that msvcrt
is the MS
standard C library containing most standard C functions, and uses the cdecl
calling convention:
CDLL dll = CDLL.LoadLibrary("msvcrt.dll");
System.out.println(dll.toString()); <CDLL 'msvcrt', handle 78000000 at 80b010>
Functions are accessed by loading a CFunction using the CDLL already loaded:
CDLL dll = CDLL.LoadLibrary("msvcrt.dll");
CFunction fp = dll.loadFunction("time");
We throw SymbolNotFoundExceptions when a symbol is not found in the DLL.
Note that win32 system dlls like kernel32
, user32
,
and others, sometimes export ANSI as well as UNICODE versions of functions. The
UNICODE version is exported with an W
appended to the name, while
the ANSI version is exported with an A
appended to the name. The
win32 GetModuleHandle
function, which returns a module
handle for a given module name, has these C prototypes, and a C macro is
used to expose one of them as GetModuleHandle depending on whether UNICODE is
defined or not:
/* ANSI version */ HMODULE GetModuleHandleA(LPCSTR lpModuleName); /* UNICODE version */ HMODULE GetModuleHandleW(LPCWSTR lpModuleName);
CDLL
does not try to select on of them automatically, you must
load the version you need by specifying GetModuleHandleA
or GetModuleHandleW
explicitely, and call them with normal strings or unicode strings respectively.
You can call these functions, by the call() method of the CFunction object. Since the CFunction has no idea of the arguments that are required, you will need to take the responsibility of that.
This example uses the time()
function, which returns system time
in seconds since the UNIX epoch.
This example calls the function with a NULL pointer. Note that we pass an array of objects as the arguments to the function. CFunction.FUNCFLAG_CDECL is used to specify the CDECL calling convention, which is used by all functions in the MS Visual C library.
CDLL dll = CDLL.LoadLibrary("msvcrt.dll"); CFunction fp = dll.loadFunction("time");
Object[] ary = { null };
Object o = fp.call(null, ary, CFunction.FUNCFLAG_CDECL); >>> 1048777320
On Windows, ctypes
uses win32 structured exception handling to
prevent crashes from general protection faults when functions are called with
invalid argument values:.
There are, however, enough ways to crash java with ctypes
, so
you should be careful anyway.
Java integers are only objects that can directly be used as parameters in these function calls.
Before we move on calling functions with other parameter types, we have to
learn more about ctypes
data types.
ctypes
defines a number of primitive C compatible data types
:
ctypes.java type |
C type |
Java type |
||
---|---|---|---|---|
|
|
character |
||
|
|
byte |
||
|
|
integer |
||
|
|
short |
||
|
|
integer |
||
|
|
integer |
||
|
|
integer |
||
|
|
integer |
||
|
|
long |
||
|
|
long |
||
|
|
long |
||
|
|
string |
||
|
|
string |
||
|
|
reference |
All these types can be created by calling them with an optional initializer of the correct type and value:
CFloat a = new CFloat(0); CInt b = new CInt(-1);
Since these types are mutable, their value can also be changed afterwards:
CInt b = new CInt(-1); b.setValue(100);
Assigning a new value to instances of the pointer types CString
,
CWString and CPointer
changes the memory location they
point to, not the contents of the memory block.
String s = "Hello, World";
CWString c_s = new CWString(s);
System.out.println(c_s);
c_s.setValue("Hi, there");
System.out.println(c_s);
System.out.println(s); Hello, World
Hi, there
Hello, World
These strings are initially stored in memory as java objects. When a reference to a CWString is passed as an argument to a CFunction, the contents of the CWString are shuttled to the C heap, so that the C funtions can operate on them.
When your java program accesses the CWString again, it knows to take the contents from the C heap, rather than the (now invalid) Java cached copy. The characters of the string are pushed on to the C heap and pulled off them by the byte[] getData() and setData(byte[]) methods of CBuffer, which CWString inherits.
CBuffer is a generic representation of a segment of memory allocated on the C heap.
CDLL dll = CDLL.LoadLibrary("msvcrt.dll");
>>> WinDLL 'msvcrt.dll,' handle 78000000 CFunction fp = dll.loadFunction("swprintf"); CWString dest = new CWString(60);
CWString buf = new CWString("Hello %d"); Object[] ary = {dest, buf, new Integer(100) };
fp.call(null, ary , CFunction.FUNCFLAG_CDECL); System.out.println(dest.getValue());
>>> "Hello 100"
As has been mentioned before, all java types except intergers have to be wrapped
in their corresponding ctypes
type, so that they can be converted
to the required C data type:
By default functions are assumed to return integers. Other return types can be specified by passing the result type class as the first argument to the call() function object.
Allowed values for restype
are simple data types like CInt
,
CLong
, CCHar
and so on as well as pointers to other
data types.
Here is a more advanced example, it uses the strchr function, which expects a string pointer and a char, and returns a pointer to a string:
CDLL dll = CDLL.LoadLibrary("msvcrt.dll");
assertEquals("WinDLL 'msvcrt.dll,' handle 78000000", dll.toString());
CFunction fp = dll.loadFunction("wcschr"); CWString buf = new CWString("abcdef"); Object[] ary = {buf, new CWChar('c') };
Object res = fp.call(CWCharPtr.class, ary , CFunction.FUNCFLAG_CDECL); CWCharPtr cptr = (CWCharPtr) res;
CWString cs = cptr.getCWString();
assertEquals("cdef", cs.getValue());
Note here that we have use CWCharPtr, instead of CWString as the return type, as a CWString requires knowledge of the size of the memory buffer it is pointing to. CWCharPtr however expects the memory it is pointing to to be a null terminated string, and knows how to create a CWString from it. This behavior is going to change- I plan to remove the CWCharPtr type.
Sometimes a C api function expects a pointer to a data type as parameter, probably to write into the corresponding location, or if the data is too large to be passed by value. This is also known as passing parameters by reference.
ctypes
exports the CPointer
object which is used
to pass parameters by reference:
CDLL dll = CDLL.LoadLibrary("msvcrt.dll");
CFunction fp = dll.loadFunction("sscanf"); CString s = new CString(32); CInt i = new CInt(0); CFloat f = new CFloat((float)0); Object[] ary = {new CString("1 3.14 Hello"), new CString("%d %f %s"), new CPointer(i), new CPointer(f), s }; Object res = fp.call(null, ary , CFunction.FUNCFLAG_CDECL); assertEquals("Hello", s.getValue()); assertEquals(i.getValue(), 1); assertTrue(f.getValue() == (float)3.14);
Structures and unions must derive from the CStructure
and CUnion
base classes which are defined in the ctypes4j
package.
The field types must be a ctypes
type like CInt
,
or any other derived ctypes
type: structure, union, array, pointer.
Here is a simple example of a RECTANGLE structure, which contains four two
integers named x,
y
, w, h. The order of the public
attributes of the class determine their layout in memory:
public class RECT extends CStruct { public CInt x = new CInt(); public CInt y = new CInt(); public CInt w = new CInt(); public CInt h = new CInt(); }
You can, however, build much more complicated structures. Structures can themselves contain structures, and so on.
Arrays are sequences, containing a fixed number of instances of the same type. They are implemented throught the CArray Class.
(This example is too long, Thomas should have used a shorter array ;)
ctypes4j allows to create C callable function pointers from Java callables. These are sometimes called callback functions.
First, you must create a class for the callback function, the class knows the calling convention, the result type the function has to return, and the number and types of the arguments this function will receive.
ctypes4j provides the CCallbackFunction abstract class for implementing these semantics.
I will present an example here which uses the standard microsoft C library's qsort function, this is used to sort items with the help of a callback function. qsort will be used to sort an array of integers:
CType[] array = { new CInt(5),
new CInt(4),
new CInt(3),
new CInt(1),
new CInt(7),
new CInt(9),
new CInt(33),
new CInt(2),
new CInt(99),
new CInt(0)};
CArray intArray = new CArray(array);
qsort must be called with a pointer to the data to sort, the number of items
in the data array, the size of one item, and the sort function, which is the
callback. The callback function will then be called with two pointers to items,
and it must return a negative integer if the first item is smaller than the
second, a 0 if they are equal, and a positive integer else.
So our callback function receives pointers to integers, and must return an integer.
Furthermore, the callback function must compare the digits in some way. We implement
this to subtract one from the other.
CCallbackFunction cmp_func = new CCallbackFunction() {
public Class getResultType() {
return CInt.class;
}
// declare the type of the parameters for the callback
public Class[] getConverters() {
Class[] converters = { CInt.class, CInt.class };
return converters;
}
public CReturnable call(CType[] argv) {
int a = ((CInt)argv[1]).getValue();
int b = ((CInt)argv[0]).getValue();
return new CInt(a-b);
}
};
Now we call the qsort function
// get the MSVCRT quicksort function CDLL dll = CDLL.LoadLibrary("msvcrt.dll"); CFunction qsort = dll.loadFunction("qsort"); // the arguments to the quicksort function Object[] args = { intArray, // the array to be sorted new CInt(intArray.getLength()), // length of array new CInt(4), // size of array element (in bytes) cmp_func }; // comparator callback function // quicksort the ints (call it) qsort.call(CInt.class, args, CFunction.FUNCFLAG_CDECL );
// check that it is sorted. CType[] results = intArray.getElements(); assertEquals(99,((CInt)results[0]).getValue()); assertEquals(0, ((CInt)results[9]).getValue());
Yep, it worked!
Unions are not implemented.
Bitfields are not implemented.
Enumeration types are not implemented. You can do it easily yourself, using
CInt
as the base class.
long double
is not implemented.
You cannot pass structures to functions as arguments, and you cannot set them as return type (only pointers).