ctypes4j tutorial

overview :: tutorial

This tutorial describes version 0.2.0 of ctypes4j, and is heavily focused on the Windows version.

Installing

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

Loading dynamic link libraries

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>

 

Accessing functions from loaded dlls

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.

Calling functions

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.

Simple data types

ctypes defines a number of primitive C compatible data types :

ctypes.java type

C type

Java type

CChar

char

character

CByte

char

byte

not implemented

unsigned char

integer

CShort

short

short

not implemented

unsigned short

integer

CInt

int

integer

not implemented

unsigned int

integer

CLong

long

integer

CULong

unsigned long

long

CLongLong

__int64 or long long

long

not implemented

unsigned __int64 or unsigned long long

long

CString

char * (NUL terminated)

string

CWStrig

wchar_t * (NUL terminated)

string

CPointer

void *

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.

Calling functions, continued

  		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:

Return types

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.

Passing pointers (or: passing parameters by reference)

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

(Note: Unions are not yet implemented)

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

Arrays are sequences, containing a fixed number of instances of the same type. They are implemented throught the CArray Class.

Callback functions

(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!

Bugs, ToDo and non-implemented things

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).

 


SourceForge.net Logo