How does one map the function below to java with jnr-ffi?
BOOLEAN PacketRequest(LPADAPTER AdapterObject,BOOLEAN Set,PPACKET_OID_DATA OidData);
Example (C): https://github.com/patmarion/winpcap/blob/master/WpcapSrc_4_1_3/Examples/PacketDriver/GetMacAddress/GetMacAddress.c
public interface NativeMappings {
public static class PPACKET_OID_DATA extends Struct {
public final UnsignedLong Oid = new UnsignedLong();
public final UnsignedLong Length = new UnsignedLong();
public final byte[] Data = new byte[6];
public PPACKET_OID_DATA(Runtime runtime) {
super(runtime);
}
}
Pointer PacketOpenAdapter(String AdapterName);
int PacketRequest(Pointer AdapterObject, int set, @Out PPACKET_OID_DATA OidData);
void PacketCloseAdapter(Pointer lpAdapter);
public static class Main {
public static void main(String[] args) {
NativeMappings mappings = LibraryLoader.create(NativeMappings.class).load("Packet");
Runtime runtime = Runtime.getRuntime(mappings);
Pointer adapterObject = mappings.PacketOpenAdapter("\\Device\\NPF_{53152A2F-39F7-458E-BD58-24D17099256A}");
PPACKET_OID_DATA oid_data = new PPACKET_OID_DATA(runtime);
oid_data.Oid.set(0x01010102L);
oid_data.Length.set(6L);
int status = mappings.PacketRequest(adapterObject, 0, oid_data);
if (status == 0) {
System.out.println("Fail.");
} else {
System.out.println("Success.");
}
mappings.PacketCloseAdapter(adapterObject);
}
}
}
First of all to make a propper mapping you should look at the definitions of types you are mapping. PacketRequest
function returns BOOLEAN
variable. According to windows data type description, BOOLEAN
is declared as typedef BYTE BOOLEAN;
. That means, that you can use byte
type as function type in java. But JNR also supports mapping boolean
type to/from native byte
. So both definitions are correct:
byte PacketRequest (...)
boolean PacketRequest (...)
Next, you need to map parameters. Same here, look at the definitions and you'll know what types to use. The result would be:
boolean PacketRequest(Pointer AdapterObject, boolean set, PPACKET_OID_DATA OidData);
The set
parameter was incorrectly declared as int
. Moreover the @Out
annotation of the last field tells JNR to pass an empty structure without copying values to native memory. So no pre-set values will be passed.
The last parameter has PPACKET_OID_DATA
type - a structure.
struct _PACKET_OID_DATA {
ULONG Oid; ///< OID code. See the Microsoft DDK documentation or the file ntddndis.h
///< for a complete list of valid codes.
ULONG Length; ///< Length of the data field
UCHAR Data[1]; ///< variable-lenght field that contains the information passed to or received
///< from the adapter.
};
Structure mapping is a bit more complex than for native types. You can't use java types here. Instead you should use jnr.ffi.Struct
inner classes to define struct fields. This rule includes array definitions. The correct definition for your structure will look like this:
class PPACKET_OID_DATA extends Struct {
public final UnsignedLong Oid = new UnsignedLong();
public final UnsignedLong Length = new UnsignedLong();
public final Unsigned8[] Data = array(new Unsigned8[6]);
public PPACKET_OID_DATA(Runtime runtime) {
super(runtime);
}
}
Notice this UCHAR
array definition. Natively this type is defined as unsigned char
, so for JNR structure it would map to jnr.ffi.Strunc.Unsigned8
class or jnr.ffi.Struct.BYTE
(which is pretty much the same).
To declare an array field you should initialize array in construction time. You need to use jnr.ffi.Struct#array(...)
functions to do that properly. That aslo means that you should know the size of the array. The example is shown above.
Why we should define it this way?
During initialization, Struct
is some kind of a pointer of variable length. Each inner-class field, initialized in it reserves its own space increases maximun size of this pointer. So each field is a "view" to some memory fragment with its own way of interacting with this memory (public methods). But to make an array of such views you need to fill the empty array with view instances. That is just what array
function does.