Introduction

JNA provides the mapping relationship between JAVA type and native type, but this mapping relationship is only an approximate mapping. We still have a lot of things to pay attention to in practical applications. This article will explain in detail the possibility of using type mapping. problems will arise. Let's take a look together.

String

The first is the mapping of String. String in JAVA actually corresponds to two native types: const char and const wchar_t . String is converted to char* by default.

char is the data type of ANSI type, and wchar_t is the data type of Unicode characters, also called wide characters.

If the unicode characters of JAVA are to be converted into char arrays, some encoding operations are required. If jna.encoding is set, the set encoding method will be used for encoding. By default the encoding is "UTF8".

If it is WString, then Unicode values can be copied directly into WString without any encoding.

Let's start with a simple example:

 char* returnStringArgument(char *arg) {
  return arg;
}

wchar_t* returnWStringArgument(wchar_t *arg) {
  return arg;
}

The above native code can be mapped as:

 String returnStringArgument(String s);
WString returnWStringArgument(WString s);

Let's look at a different example, if the native method is defined like this:

 int getString(char* buffer, int bufsize);

int getUnicodeString(wchar_t* buffer, int bufsize);

We define two methods whose parameters are char and wchar_t respectively.

Next, let's take a look at how to define the mapping of methods in JAVA:

 // Mapping A:
int getString(byte[] buf, int bufsize);
// Mapping B:
int getUnicodeString(char[] buf, int bufsize);

The following is the specific use:

 byte[] buf = new byte[256];
int len = getString(buf, buf.length);
String normalCString = Native.toString(buf);
String embeddedNULs = new String(buf, 0, len);

Some students may ask, since String in JAVA can be converted into char*, why do you need to use byte array here?

This is because the getString method needs to modify the contents of the incoming char array, but because String is immutable, String cannot be used directly here, we need to use byte array.

Then we use Native.toString(byte[]) to convert the byte array into a JAVA string.

Let's look at the case of a return value:

 // Example A: Returns a C string directly
const char* getString();
// Example B: Returns a wide character C string directly
const wchar_t* getString();

In general, if the native method directly returns a string, we can use String to map:

 // Mapping A
String getString();
// Mapping B
WString getString();

If the native code allocates memory space for String, then we'd better use the Pointer in JNA as the return value, so that we can release the occupied space at some point in the future, as follows:

 Pointer getString();

Buffers, Memory, Arrays and Pointers

When do you need to use Buffers and Memory?

Under normal circumstances, if the array of basic data is passed as a parameter to the function, the array of the basic class can be used directly in JAVA instead. But if the native method needs to access the array after the method returns (saving the pointer to the array), it is not suitable to use the array of the base class in this case. In this case, we need to use ByteBuffers or Memory .

We know that the array in JAVA has a length, but for the native method, the returned array is actually a pointer to the array, and we cannot know the length of the returned array, so if the native method returns an array pointer , it is inappropriate to use arrays for mapping in JAVA code. In this case, you need to use Pointer.

Pointer represents a pointer. Let's take a look at the example of Pointer. First, the native code:

 void* returnPointerArgument(void *arg) {
  return arg;
}

void* returnPointerArrayElement(void* args[], int which) {
  return args[which];
}

Next is the JAVA mapping:

 Pointer returnPointerArgument(Pointer p);

Pointer returnPointerArrayElement(Pointer[] args, int which);

In addition to the basic Pointer, you can also customize the typed Pointer, which is PointerType. Just inherit PointerType, as shown below:

 public static class TestPointerType extends PointerType {
            public TestPointerType() { }
            public TestPointerType(Pointer p) { super(p); }
        }
TestPointerType returnPointerArrayElement(TestPointerType[] args, int which);

Take another look at the string array:

 char* returnStringArrayElement(char* args[], int which) {
  return args[which];
}

wchar_t* returnWideStringArrayElement(wchar_t* args[], int which) {
  return args[which];
}

The corresponding JAVA mapping is as follows:

 String returnStringArrayElement(String[] args, int which);

WString returnWideStringArrayElement(WString[] args, int which);

Corresponding to Buffer, JAVA NIO provides many types of buffers, such as ByteBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer and DoubleBuffer. Here we take ByteBuffer as an example to see the specific use.

First look at the native code:

 int32_t fillInt8Buffer(int8_t *buf, int len, char value) {
  int i;

  for (i=0;i < len;i++) {
    buf[i] = value;
  }
  return len;
}

The buff is filled here. Obviously, this buffer needs to be used later, so it is not suitable to use an array here. We can choose to use ByteBuffer:

 int fillInt8Buffer(ByteBuffer buf, int len, byte value);

Then see how to use it:

 TestLibrary lib = Native.load("testlib", TestLibrary.class);

        ByteBuffer buf  = ByteBuffer.allocate(1024).order(ByteOrder.nativeOrder());
        final byte MAGIC = (byte)0xED;
        lib.fillInt8Buffer(buf, 1024, MAGIC);
        for (int i=0;i < buf.capacity();i++) {
            assertEquals("Bad value at index " + i, MAGIC, buf.get(i));
        }

variable parameter

For both native and JAVA itself, variable parameters are supported. For example, in the native method:

 int32_t addVarArgs(const char *fmt, ...) {
  va_list ap;
  int32_t sum = 0;
  va_start(ap, fmt);

  while (*fmt) {
    switch (*fmt++) {
    case 'd':
      sum += va_arg(ap, int32_t);
      break;
    case 'l':
      sum += (int) va_arg(ap, int64_t);
      break;
    case 's': // short (promoted to 'int' when passed through '...') 
    case 'c': // byte/char (promoted to 'int' when passed through '...')
      sum += (int) va_arg(ap, int);
      break;
    case 'f': // float (promoted to ‘double’ when passed through ‘...’)
    case 'g': // double
      sum += (int) va_arg(ap, double);
      break;
    default:
      break;
    }
  }
  va_end(ap);
  return sum;
}

The corresponding JAVA method mapping is as follows:

 public int addVarArgs(String fmt, Number... args);

The corresponding calling code is as follows:

 int arg1 = 1;
int arg2 = 2;
assertEquals("32-bit integer varargs not added correctly", arg1 + arg2,
                     lib.addVarArgs("dd", arg1, arg2));

Summarize

This article introduces some details and specific usage issues that should be paid attention to when using JNA method mapping.

Code for this article: https://github.com/ddean2009/learn-java-base-9-to-20.git

This article has been included in http://www.flydean.com/05-jna-type-mapping-details-md/

The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!


flydean
890 声望433 粉丝

欢迎访问我的个人网站:www.flydean.com