Skip to content

Commit

Permalink
Add preprocessor macro to opt in to integer signed cast
Browse files Browse the repository at this point in the history
  • Loading branch information
hunyadi committed Dec 3, 2024
1 parent b6338e1 commit cec5236
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 22 deletions.
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
15 changes: 12 additions & 3 deletions include/javabind/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@
#include <cstdint>
#include <vector>

#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 <typename T>
struct boxed
Expand Down Expand Up @@ -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.");
Expand Down Expand Up @@ -737,7 +742,11 @@ namespace javabind
template <> struct ArgType<int16_t> { using type = JavaShortType<int16_t>; };
template <> struct ArgType<int32_t> { using type = JavaIntegerType<int32_t>; };
template <> struct ArgType<int64_t> { using type = JavaLongType<int64_t>; };
#ifdef JAVABIND_INTEGER_WIDENING_CONVERSION
#if defined(JAVABIND_INTEGER_SIGNED_CAST)
template <> struct ArgType<uint8_t> { using type = JavaByteType; };
template <> struct ArgType<uint16_t> { using type = JavaShortType<int16_t>; };
template <> struct ArgType<uint32_t> { using type = JavaIntegerType<int32_t>; };
#elif defined(JAVABIND_INTEGER_WIDENING_CONVERSION)
template <> struct ArgType<uint8_t> { using type = JavaShortType<uint8_t>; };
template <> struct ArgType<uint16_t> { using type = JavaIntegerType<uint16_t>; };
template <> struct ArgType<uint32_t> { using type = JavaLongType<uint32_t>; };
Expand Down
14 changes: 11 additions & 3 deletions java/hu/info/hunyadi/test/StaticSample.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
14 changes: 10 additions & 4 deletions java/hu/info/hunyadi/test/TestJavaBind.java
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
2 changes: 1 addition & 1 deletion launch.bat
Original file line number Diff line number Diff line change
Expand Up @@ -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%
Expand Down
2 changes: 1 addition & 1 deletion launch.sh
Original file line number Diff line number Diff line change
@@ -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
Expand Down
39 changes: 31 additions & 8 deletions test/javabind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,20 @@ struct StaticSample
return value;
}

#if defined(JAVABIND_INTEGER_SIGNED_CAST)
template <typename T>
static T pass_cast(T value)
{
JAVA_OUTPUT << "pass_cast(" << value << ")" << std::endl;
return value;
}
#endif

#if defined(JAVABIND_INTEGER_WIDENING_CONVERSION)
template <typename T>
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
Expand Down Expand Up @@ -569,16 +578,30 @@ JAVA_EXTENSION_MODULE()
.function<StaticSample::pass_utf8_string>("pass_utf8_string")
.function<StaticSample::pass_utf16_string>("pass_utf16_string")

#if defined(JAVABIND_INTEGER_SIGNED_CAST)
// signed cast for unsigned integer types
.function<StaticSample::pass_cast<uint8_t>>("pass_cast_byte")
.function<StaticSample::pass_cast<uint16_t>>("pass_cast_short")
.function<StaticSample::pass_cast<uint32_t>>("pass_cast_int")
.function<StaticSample::pass_cast<uint64_t>>("pass_cast_long")
#else
// keep signatures to support the same native interface in Java
.function<StaticSample::pass_value<int8_t>>("pass_cast_byte")
.function<StaticSample::pass_value<int16_t>>("pass_cast_short")
.function<StaticSample::pass_value<int32_t>>("pass_cast_int")
.function<StaticSample::pass_value<int64_t>>("pass_cast_long")
#endif

#if defined(JAVABIND_INTEGER_WIDENING_CONVERSION)
// widening conversion for unsigned integer types
.function<StaticSample::pass_unsigned<uint8_t>>("pass_unsigned_byte")
.function<StaticSample::pass_unsigned<uint16_t>>("pass_unsigned_short")
.function<StaticSample::pass_unsigned<uint32_t>>("pass_unsigned_int")
.function<StaticSample::pass_widen<uint8_t>>("pass_widen_byte")
.function<StaticSample::pass_widen<uint16_t>>("pass_widen_short")
.function<StaticSample::pass_widen<uint32_t>>("pass_widen_int")
#else
// keep signatures to support the same native interface in Java
.function<StaticSample::pass_value<int16_t>>("pass_unsigned_byte")
.function<StaticSample::pass_value<int32_t>>("pass_unsigned_short")
.function<StaticSample::pass_value<int64_t>>("pass_unsigned_int")
.function<StaticSample::pass_value<int16_t>>("pass_widen_byte")
.function<StaticSample::pass_value<int32_t>>("pass_widen_short")
.function<StaticSample::pass_value<int64_t>>("pass_widen_int")
#endif

// boxing and unboxing
Expand Down

0 comments on commit cec5236

Please sign in to comment.