Search code examples
javaandroidcswigswig-typemap

Android C to Java SWIG unable to compile: incompatible types: byte cannot be converted to SWIGTYPE_p_uint8_t


I am trying to use a c library that is our command protocol. The structure of the library is such that each command has a tx and rx struct type and there is a reader and writer to build or reconstruct the packet on send and receive. However, as that code is proprietary I have created a sample project and the rest of this question is in reference to that code as it demonstrates the same issue.

I receive the following error messages when trying to compile the library that SWIG generates:

error: incompatible types: int cannot be converted to SWIGTYPE_p_int32_t
    swigtest_native_swigJNI.process(foo.getCPtr(baz), baz, SWIGTYPE_p_void.getCPtr(data), SWIGTYPE_p_int32_t.getCPtr(sz));
                                                                                                                     ^

error: incompatible types: byte cannot be converted to SWIGTYPE_p_uint8_t
    swigtest_native_swigJNI.foo_bar_set(swigCPtr, this, SWIGTYPE_p_uint8_t.getCPtr(value));
                                                                                   ^

I have tried to throw different type mappings and specifying the functions in the interface files to no avail. The code that is used below and on the link is the closest I have gotten to compile.

Thank you for your time.


Linked is a sample project demonstrating the error sample project. It was created as an Android Native C++ template with the below modifications/code.

CMakeLists.txt:

cmake_minimum_required(VERSION 3.22.1)

project("swigtest")

set(CMAKE_C_STANDARD 99)

find_package(SWIG REQUIRED) # Locate SWIG installation
include(UseSWIG) # Add SWIG to CMake

set(SWIG_INTERFACE interface.i) # Path to SWIG interface file
set(CMAKE_SWIG_FLAGS -package com.micro_leads.swigtest.test) # Custom package name parameter

set_property(SOURCE interface.i PROPERTY SWIG_MODULE_NAME swigtest_native_swig) # Set module name

include_directories(${JAVA_INCLUDE_PATH})
include_directories(
        .
)

file(REMOVE_RECURSE ${CMAKE_SOURCE_DIR}/.swig ${CMAKE_SOURCE_DIR}/../java/com/micro_leads/swigtest/test)
file(REMOVE_RECURSE ${CMAKE_SOURCE_DIR}/../java/com/micro_leads/swigtest/test)

# Add native source files
set(SOURCES
        native-lib.h
        native-lib.c
)

add_library(${CMAKE_PROJECT_NAME} SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        ${SOURCES}
)

# Add SWIG library
swig_add_library(
        "${CMAKE_PROJECT_NAME}_native_swig" # name
        LANGUAGE java # language
        SOURCES ${SWIG_INTERFACE} # interface file
        OUTFILE_DIR ${CMAKE_SOURCE_DIR}/.swig # output wrapper file path
        OUTPUT_DIR ${CMAKE_SOURCE_DIR}/../java/com/micro_leads/swigtest/test # output Java files path
)

set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX)

# Link swig lib with native lib
target_link_libraries("${CMAKE_PROJECT_NAME}_native_swig" ${JNI_LIBRARIES} ${CMAKE_PROJECT_NAME})

target_link_libraries(${CMAKE_PROJECT_NAME}
        # List libraries link to the target library
        android
        log
        ${JNI_LIBRARIES}
)

native-lib.h:

#include <stdint.h>

extern "C" {
typedef struct _foo {
    uint8_t bar;
} foo;

void process(foo *baz, const void *data, int32_t sz);
};

native-lib.c:

#include <string.h>

extern "C" {

void process(foo *baz, const void *data, int32_t sz) {
    if (sizeof(foo) < sz) { return; }

    memcpy(baz, data, sz);
}
};

interface.i

%module swigtest_native_swig

%{
#include "native-lib.h"
%}

%include "./typemaps.i"

%include "native-lib.h"

typemaps.i:

%typemap(jtype) foo "foo"
%typemap(jstype) foo "foo"
%typemap(jni) foo "jobject"
%typemap(in) foo {
    jsclass cls = (*jenv)->FindClass(jenv, "foo");
    jmethodID constructor = (*jenv)->GetMethodID(jenv, cls, "<init>", "(B)V");
    jfieldID bar_field = (*jenv)->GetFieldID(jenv, cls, "bar", "B");
    jobject obj = (*jenv)->NewObject(jenv, cls, constructor, $1.bar);
    $result = obj;
}
%typemap(out) foo {
    jclass cls = (*jenv)->FindClass(jenv, "foo");
    jfieldID bar_field = (*jenv)->GetFieldID(jenv, cls, "bar", "B");

    $result.bar = (*jenv)->GetByteField(jenv, $input, bar_field);
}

%typemap(jtype) uint8_t "byte"
%typemap(jstype) uint8_t "byte"
%typemap(jni) uint8_t "jbyte"
%typemap(javaout) uint8_t {
    return ($jnicall);
}
%typemap(in) uint8_t {
    $1 = (uint8_t)$input;
}
%typemap(out) uint8_t {
    $result = (jbyte)$1;
}

%typemap(jtype) (const void *, int32_t) "kotlin.ByteArray"
%typemap(jstype) (const void *, int32_t) "kotlin.ByteArray"
%typemap(jni) (const void *, int32_t) "jbyteArray"
%typemap(javaout) (const void *, int32_t) {
    return ($jnicall);
}
%typemap(in) (const void *data, int32_t size) {
    if ($input == null || $input.length != size) {
        throw new IllegalArgumentException("Expected a byte array of length " + size);
    }
    memcpy($1, $input, size);
}
%typemap(out) (const void *, int32_t) {
    jbyteArray result = (*jenv)->NewByteArray(jenv, size);
    if (result != NULL && $1 != NULL) {
        (*jenv)->SetByteArrayRegion(jenv, result, 0, size, (const jbyte *)$1);
    }
    $result = result;
}

MainActivity.kt:

package com.micro_leads.swigtest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import com.micro_leads.swigtest.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

//        val foo = test.foo()
//        test.swigtest_native_swig.process(foo, 1, 1)
    }

    companion object {
        // Used to load the 'swigtest' library on application startup.
        init {
            System.loadLibrary("swigtest")
            System.loadLibrary("swigtest_native_swig")
        }
    }
}

Solution

  • SWIG doesn't know the definition of int32_t. Add %include <stdint.i> to the interface file.