Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/go: missing entry in c-shared .dll #42409

Closed
SamuelYvon opened this issue Nov 5, 2020 · 5 comments
Closed

cmd/go: missing entry in c-shared .dll #42409

SamuelYvon opened this issue Nov 5, 2020 · 5 comments
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Windows
Milestone

Comments

@SamuelYvon
Copy link

What version of Go are you using (go version)?

$ go version
go version go1.15.3 windows/amd64

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\samue\AppData\Local\go-build
set GOENV=C:\Users\samue\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\samue\go\gopath\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\samue\go\gopath
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Users\samue\sdk\go1.15.3
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=C:\Users\samue\sdk\go1.15.3\pkg\tool\windows_amd64
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\samue\AppData\Local\Temp\go-build596418074=/tmp/go-build -gno-record-gcc-switches

What did you do?

I am trying to compile my code to a shared library (specifically a dll on Windows; there is no issue on Linux).

Here is what I found to be a minimally reproducible example.

package main

import "C"
import "fmt"
import "github.com/karalabe/hid"

//export HelloWorld
func HelloWorld() *C.char {
	var err error
	fmt.Print("Hello world!")

	devices := hid.Enumerate(0, 0)

	for _ = range devices {
		fmt.Printf("Found a device\n")
	}

	if nil != err {
		return C.CString(err.Error())
	} else {
		return C.CString("Hello World!")
	}
}

func main() {
	HelloWorld()
}

I then compile the shared library using the following commands:

SET CGO_ENABLED=1
go build -buildmode=c-shared -o main.dll main.go

I get the following files:

  • main.dll
  • main.h

Here is the header file:

/* Code generated by cmd/cgo; DO NOT EDIT. */

/* package command-line-arguments */


#line 1 "cgo-builtin-export-prolog"

#include <stddef.h> /* for ptrdiff_t below */

#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif

#endif

/* Start of preamble from import "C" comments.  */




/* End of preamble from import "C" comments.  */


/* Start of boilerplate cgo prologue.  */
#line 1 "cgo-gcc-export-header-prolog"

#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H

typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;

/*
  static assertion to make sure the file is being used on architecture
  at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

#endif

/* End of boilerplate cgo prologue.  */

#ifdef __cplusplus
extern "C" {
#endif

extern char* HelloWorld();

#ifdef __cplusplus
}
#endif

The function is declared in the header, as expected. However,

rundll32.exe main.dll,HelloWorld

I get an ErrorBox:
image

Expectations / things that would show it works

I would expect this error box not to show up basically. If I remove the dependency to the HID library it works, the call is made. I believe this might be a bug with Go and not the library because it works on Linux and I get an otherwise valid header file, and basically no relevant warning while compiling.

I would have expected that if no entry was to be created, I would get a compilation error / warning / advisory. I do get a warning while compiling but it's from the included library. The same warning occurs while building the app (which works). I am including it here just in case, but I do not believe it has anything to do with the issue; it's a fairly standard warning when using strncpy

# github.com/karalabe/hid
In file included from C:\Users\samue\go\gopath\src\github.com\karalabe\hid\hid_enabled.go:40:
C:\Users\samue\go\gopath\src\github.com\karalabe\hid/hidapi/windows/hid.c: In function 'hid_enumerate':
C:\Users\samue\go\gopath\src\github.com\karalabe\hid/hidapi/windows/hid.c:431:5: warning: 'strncpy' specified bound depends on the length of the source argument [-Wstringop-overflow=]
     strncpy(cur_dev->path, str, len+1);
     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
C:\Users\samue\go\gopath\src\github.com\karalabe\hid/hidapi/windows/hid.c:429:11: note: length computed here
     len = strlen(str);
           ^~~~~~~~~~~
@AlexRouSg
Copy link
Contributor

AlexRouSg commented Nov 6, 2020

Loads and runs 32-bit dynamic-link libraries (DLLs).
Rundll32 can only call functions from a DLL explicitly written to be called by Rundll32.
https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/rundll32

As you are on windows/amd64 Go by default builds a 64 bit dll.
Try building a 32 bit dll (set GOARCH=386) and/or calling the dll from a C program you wrote.

@SamuelYvon
Copy link
Author

Calling the DLL from an external program does not work, I get a java.lang.UnsatisfiedLinkError: Error looking up function '...': The specified procedure could not be found. using JNA. I can try a simpler C program later if we want to rule out JNA.

While rundll32 does not correctly run the function, it does seem to detect the missing entry regardless. I aIso first tried with Dependency Walker which does not find anything either:

(without the HID library)
image

(with the HID library)
image

I did not try 32 bit, my toolchain seems to be broken somewhere.

@AlexRouSg
Copy link
Contributor

cc @ianlancetaylor @alexbrainman

@toothrot toothrot changed the title Build: Missing entry in c-shared .dll cm/dgo: missing entry in c-shared .dll Nov 6, 2020
@toothrot toothrot added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Nov 6, 2020
@toothrot toothrot added this to the Backlog milestone Nov 6, 2020
@toothrot toothrot changed the title cm/dgo: missing entry in c-shared .dll cmd/go: missing entry in c-shared .dll Nov 6, 2020
@SamuelYvon
Copy link
Author

@AlexRouSg

This seems to have been fixed on the latest beta.

> go version
go version go1.16beta1 windows/amd64
> go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\syvon\AppData\Local\go-build
set GOENV=C:\Users\syvon\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Frameworks\gopath\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Frameworks\gopath
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Users\syvon\sdk\go1.16beta1
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=C:\Users\syvon\sdk\go1.16beta1\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.16beta1
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=C:\Users\syvon\Documents\M3Touch\ioboard\tools\m3s_flasher\go.mod
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\syvon\AppData\Local\Temp\go-build2090912467=/tmp/go-build -gno-record-gcc-switches

Using the previous minimal example:

package main

import "C"
import "fmt"
import "github.com/karalabe/hid"

//export HelloWorld
func HelloWorld() *C.char {
	var err error
	fmt.Print("Hello world!")

	devices := hid.Enumerate(0, 0)

	for _ = range devices {
		fmt.Printf("Found a device\n")
	}

	if nil != err {
		return C.CString(err.Error())
	} else {
		return C.CString("Hello World!")
	}
}

func main() {
	HelloWorld()
}

Here is the output of Dep. Walker:

gobuildmodescreen

The entry for HelloWorld is there as expected.

Running rundll32.exe main.dll,HelloWorld now yields no error box, as expected.

@qmuntal
Copy link
Contributor

qmuntal commented Jan 7, 2021

This might be caused by github.com/karalabe/hid using __declspec(dllexport) in its C API:

https://github.com/karalabe/hid/blob/9c14560f9ee858c43f40b5cd01392b167aacf4e8/hidapi/hidapi/hidapi.h#L32-L35

When binutils linker sees a __declspec(dllexport) it disables the default symbol auto-export behavior and only functions with that annotation are exposed by the resulting DLL. See binutils docs for more info.

Go will start annotating exported functions with __declspec(dllexport) in 1.16 due to CL262797, done as part of #30674, so the binutils linker will add these functions to the DLL export table.

@qmuntal qmuntal closed this as completed May 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Windows
Projects
None yet
Development

No branches or pull requests

4 participants