vuco
vuco

Reputation: 153

Error trying to compile Go shared object to be called from Java through JNI

I am trying to call Go functions from Java through JNI call. Java compilation is ok. When I try to build the Go shared object (.so) it gives me errors about "multiple definitions" regarding the C function wrappers callable from Java.

This is the Java code:

package yada.yada.locksmith;

import java.io.*;

public class Locksmith {

   private native void setup(String ClientID, String ClientSecret, String RedirectURL, String AuthURL, String TokenURL, String UserInfURL);
   private native String auth(String user, String pw);

   static {
      System.loadLibrary("Locksmith");
   }

   public static void Locksmith(String[] args) {
      Locksmith locksmith = new Locksmith();

      locksmith.setup(
         "yadayadayadayadayadayadayadayadayadayada",
         "yadayadayadayadayadayadayadayadayadayada",
         "https://yada.yada/Yada/yada",
         "https://yada.yada/Yada/yada2",
         "https://yada.yada/Yada/yada3",
         "https://yada.yada/Yada/yada4"
      );

      // Create the console object
      Console cnsl = System.console();

      if (cnsl == null) {
         System.out.println("No console available");
         return;
      }

      String user = cnsl.readLine("Enter username : ");
      char[] pw = cnsl.readPassword("Enter password : ");
      System.out.println(locksmith.auth(user,new String(pw)));
   }
}

I compiled it with:

javac Locksmith.java

Then I generated the header file with:

javac -h . Locksmith.java

This is the generated file:


/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class yada_yada_locksmith_Locksmith */

#ifndef _Included_yada_yada_locksmith_Locksmith
#define _Included_yada_yada_locksmith_Locksmith
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     yada_yada_locksmith_Locksmith
 * Method:    setup
 * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_yada_yada_locksmith_Locksmith_setup
  (JNIEnv *, jobject, jstring, jstring, jstring, jstring, jstring, jstring);

/*
 * Class:     yada_yada_locksmith_Locksmith
 * Method:    auth
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_yada_yada_locksmith_Locksmith_auth
  (JNIEnv *, jobject, jstring, jstring);

#ifdef __cplusplus
}
#endif
#endif

Then I coded the Go dynamic object:


package main

import (
//   "io"
//   "fmt"
   "log"
   "sync"
   "net/url"
   "strings"
   "context"
   "net/http"
   "io/ioutil"
   "golang.org/x/oauth2"
   "github.com/chromedp/chromedp"
   "github.com/chromedp/cdproto/network"
)

/*
#cgo CFLAGS: -I/usr/local/bin/jdk-15.0.1/include -I/usr/local/bin/jdk-15.0.1/include/linux

#include <string.h>
#include <jni.h>        // JNI header provided by JDK
#include "yada_yada_locksmith_Locksmith.h"

extern void Setup(char *, char *, char *, char *, char *, char *);
extern char *Auth(char *, char *);

JNIEXPORT void JNICALL Java_yada_yada_locksmith_Locksmith_setup
  (JNIEnv *env, jobject this, jstring ClientID, jstring ClientSecret, jstring RedirectURL, jstring AuthURL, jstring TokenURL, jstring UserInfURL) {

   char *CClientID;
   char *CClientSecret;
   char *CRedirectURL;
   char *CAuthURL;
   char *CTokenURL;
   char *CUserInfURL;

   CClientID = ((char *)(*env)->GetStringUTFChars(env, ClientID, 0));
   CClientSecret = ((char *)(*env)->GetStringUTFChars(env, ClientSecret, 0));
   CRedirectURL = ((char *)(*env)->GetStringUTFChars(env, RedirectURL, 0));
   CAuthURL = ((char *)(*env)->GetStringUTFChars(env, AuthURL, 0));
   CTokenURL = ((char *)(*env)->GetStringUTFChars(env, TokenURL, 0));
   CUserInfURL = ((char *)(*env)->GetStringUTFChars(env, UserInfURL, 0));

   Setup(CClientID, CClientSecret, CRedirectURL, CAuthURL, CTokenURL, CUserInfURL);

   (*env)->ReleaseStringUTFChars(env, ClientID, CClientID);
   (*env)->ReleaseStringUTFChars(env, ClientSecret, CClientSecret);
   (*env)->ReleaseStringUTFChars(env, RedirectURL, CRedirectURL);
   (*env)->ReleaseStringUTFChars(env, AuthURL, CAuthURL);
   (*env)->ReleaseStringUTFChars(env, TokenURL, CTokenURL);
   (*env)->ReleaseStringUTFChars(env, UserInfURL, CUserInfURL);
}

JNIEXPORT jstring JNICALL Java_yada_yada_locksmith_Locksmith_auth
  (JNIEnv *env, jobject this, jstring User, jstring Pw) {

   char *CUser;
   char *CPw;

   CUser = ((char *)(*env)->GetStringUTFChars(env, User, 0));
   CPw = ((char *)(*env)->GetStringUTFChars(env, Pw, 0));


   // Call
   Auth(CUser, CPw);

   (*env)->ReleaseStringUTFChars(env, User, CUser);
   (*env)->ReleaseStringUTFChars(env, Pw, CPw);
}


*/
import "C"

type oauthConfT struct {
   Cfg *oauth2.Config
   UserInfURL string
}

func main() {
}

var cfg oauthConfT

//export Setup
func Setup(ClientID, ClientSecret, RedirectURL, AuthURL, TokenURL, UserInfURL *C.char) {
   // DO stuff
}

//export Auth
func Auth(user, pw *C.char) *C.char {
   // DO stuff
}


Then I compile the Go code with:

go build -o liblocksmith.so -buildmode=c-shared locksmith.go

This gives the follwoing error messages:

/tmp/go-build051144078/b001/_x002.o: In function "Java_yada_yada_locksmith_Locksmith_setup":
./locksmith.go:29: multiple definition in "Java_yada_yada_locksmith_Locksmith_setup"
/tmp/go-build051144078/b001/_x001.o:/tmp/go-build/locksmith.go:29: defined first here
/tmp/go-build051144078/b001/_x002.o: In function "Java_yada_yada_locksmith_Locksmith_auth":
./locksmith.go:56: multiple definition in "Java_yada_yada_locksmith_Locksmith_auth"
/tmp/go-build051144078/b001/_x001.o:/tmp/go-build/locksmith.go:56: defined first here
collect2: error: ld returned 1 exit status

Trying to compile adding the -x flag results in:


WORK=/tmp/go-build051144078
mkdir -p $WORK/b056/
cd /home/vuco/repos/go/src/runtime/cgo
CGO_LDFLAGS='"-g" "-O2" "-lpthread"' /home/vuco/repos/go/pkg/tool/linux_amd64/cgo -objdir $WORK/b056/ -importpath runtime/cgo -import_runtime_cgo=false -import_syscall=false -exportheader=$WORK/b056/_cgo_install.h -- -I $WORK/b056/ -g -O2 -Wall -Werror ./cgo.go
cd $WORK
gcc -fno-caret-diagnostics -c -x c - -o /dev/null || true
gcc -Qunused-arguments -c -x c - -o /dev/null || true
gcc -fdebug-prefix-map=a=b -c -x c - -o /dev/null || true
gcc -gno-record-gcc-switches -c -x c - -o /dev/null || true
cd $WORK/b056
TERM='dumb' gcc -I /home/vuco/repos/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -Wall -Werror -o ./_x001.o -c _cgo_export.c
TERM='dumb' gcc -I /home/vuco/repos/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -Wall -Werror -o ./_x002.o -c cgo.cgo2.c
cd /home/vuco/repos/go/src/runtime/cgo
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I $WORK/b056/ -g -O2 -Wall -Werror -o $WORK/b056/_x003.o -c gcc_context.c
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I $WORK/b056/ -g -O2 -Wall -Werror -o $WORK/b056/_x004.o -c gcc_fatalf.c
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I $WORK/b056/ -g -O2 -Wall -Werror -o $WORK/b056/_x005.o -c gcc_libinit.c
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I $WORK/b056/ -g -O2 -Wall -Werror -o $WORK/b056/_x006.o -c gcc_linux_amd64.c
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I $WORK/b056/ -g -O2 -Wall -Werror -o $WORK/b056/_x007.o -c gcc_mmap.c
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I $WORK/b056/ -g -O2 -Wall -Werror -o $WORK/b056/_x008.o -c gcc_setenv.c
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I $WORK/b056/ -g -O2 -Wall -Werror -o $WORK/b056/_x009.o -c gcc_sigaction.c
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I $WORK/b056/ -g -O2 -Wall -Werror -o $WORK/b056/_x010.o -c gcc_traceback.c
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I $WORK/b056/ -g -O2 -Wall -Werror -o $WORK/b056/_x011.o -c gcc_util.c
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I $WORK/b056/ -g -O2 -Wall -Werror -o $WORK/b056/_x012.o -c gcc_amd64.S
cd $WORK/b056
TERM='dumb' gcc -I /home/vuco/repos/go/src/runtime/cgo -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -Wall -Werror -o ./_cgo_main.o -c _cgo_main.c
cd /home/vuco/repos/go/src/runtime/cgo
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b056=/tmp/go-build -gno-record-gcc-switches -o $WORK/b056/_cgo_.o $WORK/b056/_cgo_main.o $WORK/b056/_x001.o $WORK/b056/_x002.o $WORK/b056/_x003.o $WORK/b056/_x004.o $WORK/b056/_x005.o $WORK/b056/_x006.o $WORK/b056/_x007.o $WORK/b056/_x008.o $WORK/b056/_x009.o $WORK/b056/_x010.o $WORK/b056/_x011.o $WORK/b056/_x012.o -g -O2 -lpthread
TERM='dumb' /home/vuco/repos/go/pkg/tool/linux_amd64/cgo -dynpackage cgo -dynimport $WORK/b056/_cgo_.o -dynout $WORK/b056/_cgo_import.go -dynlinker
mkdir -p $WORK/b051/
cd /home/vuco/repos/go/src/net
CGO_LDFLAGS='"-g" "-O2"' /home/vuco/repos/go/pkg/tool/linux_amd64/cgo -objdir $WORK/b051/ -importpath net -exportheader=$WORK/b051/_cgo_install.h -- -I $WORK/b051/ -g -O2 ./cgo_linux.go ./cgo_resnew.go ./cgo_socknew.go ./cgo_unix.go
cd $WORK/b051
TERM='dumb' gcc -I /home/vuco/repos/go/src/net -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b051=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -o ./_x001.o -c _cgo_export.c
TERM='dumb' gcc -I /home/vuco/repos/go/src/net -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b051=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -o ./_x002.o -c cgo_linux.cgo2.c
TERM='dumb' gcc -I /home/vuco/repos/go/src/net -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b051=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -o ./_x003.o -c cgo_resnew.cgo2.c
TERM='dumb' gcc -I /home/vuco/repos/go/src/net -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b051=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -o ./_x004.o -c cgo_socknew.cgo2.c
TERM='dumb' gcc -I /home/vuco/repos/go/src/net -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b051=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -o ./_x005.o -c cgo_unix.cgo2.c
TERM='dumb' gcc -I /home/vuco/repos/go/src/net -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b051=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -o ./_cgo_main.o -c _cgo_main.c
cd /home/vuco/repos/go/src/net
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b051=/tmp/go-build -gno-record-gcc-switches -o $WORK/b051/_cgo_.o $WORK/b051/_cgo_main.o $WORK/b051/_x001.o $WORK/b051/_x002.o $WORK/b051/_x003.o $WORK/b051/_x004.o $WORK/b051/_x005.o -g -O2
TERM='dumb' /home/vuco/repos/go/pkg/tool/linux_amd64/cgo -dynpackage net -dynimport $WORK/b051/_cgo_.o -dynout $WORK/b051/_cgo_import.go
mkdir -p $WORK/b001/
cd /home/vuco/repos/yada/src/main/java/yada/yada/locksmith
CGO_LDFLAGS='"-g" "-O2"' /home/vuco/repos/go/pkg/tool/linux_amd64/cgo -objdir $WORK/b001/ -importpath command-line-arguments -exportheader=$WORK/b001/_cgo_install.h -- -I $WORK/b001/ -g -O2 -I/usr/local/bin/jdk-15.0.1/include -I/usr/local/bin/jdk-15.0.1/include/linux ./locksmith.go
cd $WORK/b001
TERM='dumb' gcc -I /home/vuco/repos/yada/src/main/java/yada/yada/locksmith -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b001=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -I/usr/local/bin/jdk-15.0.1/include -I/usr/local/bin/jdk-15.0.1/include/linux -o ./_x001.o -c _cgo_export.c
TERM='dumb' gcc -I /home/vuco/repos/yada/src/main/java/yada/yada/locksmith -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b001=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -I/usr/local/bin/jdk-15.0.1/include -I/usr/local/bin/jdk-15.0.1/include/linux -o ./_x002.o -c locksmith.cgo2.c
TERM='dumb' gcc -I /home/vuco/repos/yada/src/main/java/yada/yada/locksmith -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b001=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -I/usr/local/bin/jdk-15.0.1/include -I/usr/local/bin/jdk-15.0.1/include/linux -o ./_cgo_main.o -c _cgo_main.c
cd /home/vuco/repos/yada/src/main/java/yada/yada/locksmith
TERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b001=/tmp/go-build -gno-record-gcc-switches -o $WORK/b001/_cgo_.o $WORK/b001/_cgo_main.o $WORK/b001/_x001.o $WORK/b001/_x002.o -g -O2
# command-line-arguments
/tmp/go-build051144078/b001/_x002.o: In function "Java_yada_yada_locksmith_Locksmith_setup":
./locksmith.go:29: multiple definition in "Java_yada_yada_locksmith_Locksmith_setup"
/tmp/go-build051144078/b001/_x001.o:/tmp/go-build/locksmith.go:29: defined first here
/tmp/go-build051144078/b001/_x002.o: In function "Java_yada_yada_locksmith_Locksmith_auth":
./locksmith.go:56: multiple definition in "Java_yada_yada_locksmith_Locksmith_auth"
/tmp/go-build051144078/b001/_x001.o:/tmp/go-build/locksmith.go:56: defined first here
collect2: error: ld returned 1 exit status

Program versions are:

go version go1.15.6 linux/amd64

javac 15.0.1

gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0

GNU ld (GNU Binutils for Ubuntu) 2.30

Upvotes: 0

Views: 464

Answers (1)

Trevor Kropp
Trevor Kropp

Reputation: 319

The following from the cgo documentation is the problem:

Using //export in a file places a restriction on the preamble: since it is copied into two different C output files, it must not contain any definitions, only declarations. If a file contains both definitions and declarations, then the two output files will produce duplicate symbols and the linker will fail. To avoid this, definitions must be placed in preambles in other files, or in C source files.

Moving the lines

extern void Setup(char *, char *, char *, char *, char *, char *);
extern char *Auth(char *, char *);

to the file locksmith.h and the C definitions to locksmith.c will yield the following preamble, which builds successfully:

/*
#cgo CFLAGS: -I/usr/local/bin/jdk-15.0.1/include -I/usr/local/bin/jdk-15.0.1/include/linux

#include "locksmith.h"
*/
import "C"

The beginning of locksmith.c will contain the following:

#include <string.h>
#include <jni.h>        // JNI header provided by JDK
#include "locksmith.h"
#include "yada_yada_locksmith_Locksmith.h"

Also, the build command needs to be just

go build -o liblocksmith.so -buildmode=c-shared

without the locksmith.go at the end.

Upvotes: 2

Related Questions