尝试编译要通过 JNI 从 Java 调用的 Go 共享对象时出错

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

我正在尝试通过 JNI 调用从 Java 调用 Go 函数。 Java编译正常。 当我尝试构建 Go 共享对象 (.so) 时,它给出了有关可从 Java.

调用的 C 函数包装器的“多个定义”的错误

这是Java代码:

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)));
   }
}

我编译它:

javac Locksmith.java

然后我生成了头文件:

javac -h 。 Locksmith.java

这是生成的文件:


/* 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

然后我编写了 Go 动态对象:


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
}


然后我编译 Go 代码:

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

这会给出以下错误消息:

/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

尝试编译添加 -x 标志导致:


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

程序版本为:

go版本go1.15.6linux/amd64

javac 15.0.1

gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0

GNU ld(Ubuntu 的 GNU Binutils)2.30

下面来自cgo documentation的问题是:

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.

移动线条

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

到文件 locksmith.hlocksmith.c 的 C 定义将产生以下序言,它成功构建:

/*
#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"

locksmith.c 的开头将包含以下内容:

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

此外,构建命令需要只是

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

最后没有 locksmith.go