Reputation: 153
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
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