diff --git a/CMakeLists.txt b/CMakeLists.txt index e0c2e73..23b8da1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,8 @@ cmake_minimum_required(VERSION 3.24) project(javabind LANGUAGES CXX VERSION 0.1) -option(JAVABIND_INTEGER_WIDENING_CONVERSION "Enable support for integer widening conversions (unsigned ints)" OFF) +option(JAVABIND_INTEGER_SIGNED_CAST "Enable support for integer signed/unsigned cast" OFF) +option(JAVABIND_INTEGER_WIDENING_CONVERSION "Enable support for integer widening conversion" OFF) # Java integration find_package(JNI REQUIRED) @@ -28,6 +29,9 @@ set_target_properties(javabind PROPERTIES POSITION_INDEPENDENT_CODE ON ) +if(JAVABIND_INTEGER_SIGNED_CAST) + target_compile_definitions(javabind INTERFACE JAVABIND_INTEGER_SIGNED_CAST) +endif() if(JAVABIND_INTEGER_WIDENING_CONVERSION) target_compile_definitions(javabind INTERFACE JAVABIND_INTEGER_WIDENING_CONVERSION) endif() diff --git a/README.md b/README.md index c64f099..66d81df 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,15 @@ The C++ type `u16string_view` translates to JNI calls `GetStringCritical` and `R ## C++ unsigned integer types -Unsigned integers are not supported in Java. However, it is possible to marshal a C++ unsigned integer type to a wider Java signed integer type such that the target type can accommodate all values the source type can assume. Widening conversions are disabled by default. You can opt in to convert unsigned integers to a wider Java type by defining the preprocessor symbol `JAVABIND_INTEGER_WIDENING_CONVERSION` (or enabling the CMake option `JAVABIND_INTEGER_WIDENING_CONVERSION`). +Unsigned integers are not supported in Java. However, it is possible to marshal a C++ unsigned integer type to a compatible Java signed integer type. Two modes are supported. Conversions for C++ unsigned types are disabled by default, and require explicit opt-in. + +### Signed cast + +Signed cast assigns the value of a C++ unsigned integer type to a Java signed integer type of the same width, copying all bits. You can opt in to signed cast by defining the preprocessor symbol `JAVABIND_INTEGER_SIGNED_CAST` (or enabling the CMake option `JAVABIND_INTEGER_SIGNED_CAST`). + +### Widening conversion + +Widening converts the value of a C++ unsigned integer type to a wider Java signed integer type such that the target type can accommodate all values the source type can assume. You can opt in to convert unsigned integers to a wider Java type by defining the preprocessor symbol `JAVABIND_INTEGER_WIDENING_CONVERSION` (or enabling the CMake option `JAVABIND_INTEGER_WIDENING_CONVERSION`). If enabled, the following integer type conversions take place: diff --git a/include/javabind/core.hpp b/include/javabind/core.hpp index e68200d..890e72f 100644 --- a/include/javabind/core.hpp +++ b/include/javabind/core.hpp @@ -16,13 +16,18 @@ #include #include +#if defined(JAVABIND_INTEGER_SIGNED_CAST) && defined(JAVABIND_INTEGER_WIDENING_CONVERSION) +#error Preprocessor symbols JAVABIND_INTEGER_SIGNED_CAST and JAVABIND_INTEGER_WIDENING_CONVERSION are mutually exclusive. +#endif + namespace javabind { /** * Represents a raw Java object. */ struct object - {}; + { + }; template struct boxed @@ -59,7 +64,7 @@ namespace javabind using native_type = NativeType; using java_type = JavaType; -#ifdef JAVABIND_INTEGER_WIDENING_CONVERSION +#if defined(JAVABIND_INTEGER_WIDENING_CONVERSION) static_assert(sizeof(native_type) <= sizeof(java_type), "JNI type is expected to be at least the size of the C++ type."); #else static_assert(sizeof(native_type) == sizeof(java_type), "C++ and JNI types are expected to match in size."); @@ -737,7 +742,11 @@ namespace javabind template <> struct ArgType { using type = JavaShortType; }; template <> struct ArgType { using type = JavaIntegerType; }; template <> struct ArgType { using type = JavaLongType; }; -#ifdef JAVABIND_INTEGER_WIDENING_CONVERSION +#if defined(JAVABIND_INTEGER_SIGNED_CAST) + template <> struct ArgType { using type = JavaByteType; }; + template <> struct ArgType { using type = JavaShortType; }; + template <> struct ArgType { using type = JavaIntegerType; }; +#elif defined(JAVABIND_INTEGER_WIDENING_CONVERSION) template <> struct ArgType { using type = JavaShortType; }; template <> struct ArgType { using type = JavaIntegerType; }; template <> struct ArgType { using type = JavaLongType; }; diff --git a/java/hu/info/hunyadi/test/StaticSample.java b/java/hu/info/hunyadi/test/StaticSample.java index 98a8da7..441e8f0 100644 --- a/java/hu/info/hunyadi/test/StaticSample.java +++ b/java/hu/info/hunyadi/test/StaticSample.java @@ -40,11 +40,19 @@ public class StaticSample { public static native long pass_long(long value); - public static native short pass_unsigned_byte(short value); + public static native byte pass_cast_byte(byte value); - public static native int pass_unsigned_short(int value); + public static native short pass_cast_short(short value); - public static native long pass_unsigned_int(long value); + public static native int pass_cast_int(int value); + + public static native long pass_cast_long(long value); + + public static native short pass_widen_byte(short value); + + public static native int pass_widen_short(int value); + + public static native long pass_widen_int(long value); public static native float pass_float(float value); diff --git a/java/hu/info/hunyadi/test/TestJavaBind.java b/java/hu/info/hunyadi/test/TestJavaBind.java index ef0d5ee..c97a516 100644 --- a/java/hu/info/hunyadi/test/TestJavaBind.java +++ b/java/hu/info/hunyadi/test/TestJavaBind.java @@ -53,13 +53,19 @@ public static void main(String[] args) { StaticSample.pass_utf16_string("árvíztűrő tükörfúrógép"); System.out.println("PASS: class functions with simple types"); + assert StaticSample.pass_cast_byte(Byte.MIN_VALUE) == Byte.MIN_VALUE; + assert StaticSample.pass_cast_short(Short.MIN_VALUE) == Short.MIN_VALUE; + assert StaticSample.pass_cast_int(Integer.MIN_VALUE) == Integer.MIN_VALUE; + assert StaticSample.pass_cast_long(Long.MIN_VALUE) == Long.MIN_VALUE; + System.out.println("PASS: class functions with casting unsigned integer types"); + short max_unsigned_byte = 255; - assert StaticSample.pass_unsigned_byte(max_unsigned_byte) == max_unsigned_byte; + assert StaticSample.pass_widen_byte(max_unsigned_byte) == max_unsigned_byte; int max_unsigned_short = 65535; - assert StaticSample.pass_unsigned_short(max_unsigned_short) == max_unsigned_short; + assert StaticSample.pass_widen_short(max_unsigned_short) == max_unsigned_short; long max_unsigned_int = 4294967295l; - assert StaticSample.pass_unsigned_int(max_unsigned_int) == max_unsigned_int; - System.out.println("PASS: class functions with unsigned integer types"); + assert StaticSample.pass_widen_int(max_unsigned_int) == max_unsigned_int; + System.out.println("PASS: class functions with widening unsigned integer types"); assert StaticSample.pass_boxed_boolean(Boolean.valueOf(true)).equals(Boolean.valueOf(true)); assert StaticSample.pass_boxed_integer(Integer.valueOf(23)).equals(Integer.valueOf(23)); diff --git a/launch.bat b/launch.bat index 683d7e1..8234a56 100644 --- a/launch.bat +++ b/launch.bat @@ -2,7 +2,7 @@ setlocal rem Build the C++ library -cmake -B build -D JAVABIND_INTEGER_WIDENING_CONVERSION=ON +cmake -B build -D JAVABIND_INTEGER_SIGNED_CAST=ON -D JAVABIND_INTEGER_WIDENING_CONVERSION=OFF if errorlevel 1 exit /b %ERRORLEVEL% cmake --build build if errorlevel 1 exit /b %ERRORLEVEL% diff --git a/launch.sh b/launch.sh index a96e123..abb4cdb 100755 --- a/launch.sh +++ b/launch.sh @@ -1,7 +1,7 @@ set -e # Build the C++ library -cmake -B build -D JAVABIND_INTEGER_WIDENING_CONVERSION=ON +cmake -B build -D JAVABIND_INTEGER_SIGNED_CAST=ON -D JAVABIND_INTEGER_WIDENING_CONVERSION=OFF cmake --build build # Emit Java function signatures diff --git a/test/javabind.cpp b/test/javabind.cpp index 3d3e832..33d02c3 100644 --- a/test/javabind.cpp +++ b/test/javabind.cpp @@ -250,11 +250,20 @@ struct StaticSample return value; } +#if defined(JAVABIND_INTEGER_SIGNED_CAST) + template + static T pass_cast(T value) + { + JAVA_OUTPUT << "pass_cast(" << value << ")" << std::endl; + return value; + } +#endif + #if defined(JAVABIND_INTEGER_WIDENING_CONVERSION) template - static T pass_unsigned(T value) + static T pass_widen(T value) { - JAVA_OUTPUT << "pass_unsigned(" << value << ")" << std::endl; + JAVA_OUTPUT << "pass_widen(" << value << ")" << std::endl; return value; } #endif @@ -569,16 +578,30 @@ JAVA_EXTENSION_MODULE() .function("pass_utf8_string") .function("pass_utf16_string") +#if defined(JAVABIND_INTEGER_SIGNED_CAST) + // signed cast for unsigned integer types + .function>("pass_cast_byte") + .function>("pass_cast_short") + .function>("pass_cast_int") + .function>("pass_cast_long") +#else + // keep signatures to support the same native interface in Java + .function>("pass_cast_byte") + .function>("pass_cast_short") + .function>("pass_cast_int") + .function>("pass_cast_long") +#endif + #if defined(JAVABIND_INTEGER_WIDENING_CONVERSION) // widening conversion for unsigned integer types - .function>("pass_unsigned_byte") - .function>("pass_unsigned_short") - .function>("pass_unsigned_int") + .function>("pass_widen_byte") + .function>("pass_widen_short") + .function>("pass_widen_int") #else // keep signatures to support the same native interface in Java - .function>("pass_unsigned_byte") - .function>("pass_unsigned_short") - .function>("pass_unsigned_int") + .function>("pass_widen_byte") + .function>("pass_widen_short") + .function>("pass_widen_int") #endif // boxing and unboxing