-
Notifications
You must be signed in to change notification settings - Fork 34
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
Go(lang) binding #75
Comments
pm_common/CMakeLists.txt shows the portmidi library always uses portmidi.c, pmutil.c and porttime.c. Reading further, you can find other dependencies such as some frameworks and ptmacosx_mach.c and others for MacOS, and ptlinux.c, pmlinuxalsa.c, etc. for Linux. You can also just compile test programs, e.g. pm_test/mm.c, and see what files they use on any particular platform. Obviously, if mm runs, then all the dependencies are satisfied. I'm not sure what problems you are running into or how Go deals with system libraries like MacOS frameworks (e.g. CoreMIDI), but if you have a particular undefined symbol and want to know where it's defined, please ask. |
I try just to compile for windows with just these files: pminternal.h
pmutil.c
pmutil.h
pmwin.c
pmwinmm.c
pmwinmm.h
portmidi.c
portmidi.h
porttime.c
porttime.dsp
porttime.h
ptwinmm.c
main.go where main.go contains: package main
// #cgo LDFLAGS:
//
// #include <stdlib.h>
// #include <portmidi.h>
import "C"
import (
"errors"
"fmt"
)
func main() {
err := Initialize()
if err != nil {
fmt.Printf("error: %s\n", err.Error())
}
fmt.Println("ok")
}
func Initialize() error {
if code := C.Pm_Initialize(); code != 0 {
return convertToError(code)
}
return nil
}
// convertToError converts a portmidi error code to a Go error.
func convertToError(code C.PmError) error {
if code >= 0 {
return nil
}
return errors.New(C.GoString(C.Pm_GetErrorText(code)))
} it does not compile: $ go run main.go
# command-line-arguments
C:\Program Files\Go\pkg\tool\windows_amd64\link.exe: running C:\mingw-w64\mingw64\bin\gcc.exe failed: exit status 1
C:\mingw-w64\mingw64\bin\gcc.exe -m64 -s -mconsole -Wl,--tsaware -Wl,--nxcompat -Wl,--major-os-version=6 -Wl,--minor-os-version=1 -Wl,--major-subsystem-version=6 -Wl,--minor-subsystem-version=1 -Wl,--dynamicbase -Wl,--high-entropy-va -o $WORK\b001\exe\main.exe -Wl,--no-insert-timestamp Q:\Temp\go-link-2306562543\go.o Q:\Temp\go-link-2306562543\000000.o Q:\Temp\go-link-2306562543\000001.o Q:\Temp\go-link-2306562543\000002.o Q:\Temp\go-link-2306562543\000003.o Q:\Temp\go-link-2306562543\000004.o Q:\Temp\go-link-2306562543\000005.o Q:\Temp\go-link-2306562543\000006.o Q:\Temp\go-link-2306562543\000007.o Q:\Temp\go-link-2306562543\000008.o Q:\Temp\go-link-2306562543\000009.o -O2 -g -O2 -g -Wl,-T,Q:\Temp\go-link-2306562543\fix_debug_gdb_scripts.ld -Wl,--start-group -lmingwex -lmingw32 -Wl,--end-group -lkernel32
Q:\Temp\go-link-2306562543\000001.o: In function `_cgo_3f1b187f7721_Cfunc_Pm_GetErrorText':
/tmp/go-build/cgo-gcc-prolog:55: undefined reference to `Pm_GetErrorText'
Q:\Temp\go-link-2306562543\000001.o: In function `_cgo_3f1b187f7721_Cfunc_Pm_Initialize':
/tmp/go-build/cgo-gcc-prolog:73: undefined reference to `Pm_Initialize'
collect2.exe: error: ld returned 1 exit status |
no matter which file I include or not, I either get duplicated symbols or missing references.... |
Thanks for your message. So if I wanted to compile |
My problem is that I totaly do not get, how symbols are resolved on windows. Yes, I read in the internets, but it did not help at all. My impression was, that if I copy the file into the current directory, it should be found and the symbols would be resolved, but apparently this is not (always) the case. So I am even unable to compile e.g. the file qtest.c inside pm_test with gcc. And top of that comes the way, Go does handle it. But I guess, if can get it to work with gcc, I could then make it work with Go. |
Maybe it helps to know that I want to compile statically, as it is common for Go. |
and I need the direct compile command since I can't use makefile, cmake or whatsoever when linking to Go. And when I mean compiling, I really mean "compiling and linking". in the Go world this is really one step (for the user) and I am not really sure, what exactly the linker is doing in the C world (I doget that you usually build libraries first and then link them together somehow) |
ok, so by reading https://stackoverflow.com/questions/39767917/how-to-link-a-simple-program-in-windows-with-mingw-gcc I found out that the linking is the problem. So I get qtest.o:qtest.c:(.text+0x4c5): undefined reference to `Pm_QueueEmpty'
qtest.o:qtest.c:(.text+0x4e5): undefined reference to `puts'
qtest.o:qtest.c:(.text+0x4fb): undefined reference to `puts'
qtest.o:qtest.c:(.text+0x535): undefined reference to `Pm_Enqueue'
qtest.o:qtest.c:(.text+0x58f): undefined reference to `Pm_Dequeue'
qtest.o:qtest.c:(.text+0x5a0): undefined reference to `puts'
qtest.o:qtest.c:(.text+0x608): undefined reference to `Pm_Dequeue'
qtest.o:qtest.c:(.text+0x61b): undefined reference to `puts'
qtest.o:qtest.c:(.text+0x631): undefined reference to `puts'
qtest.o:qtest.c:(.text+0x66e): undefined reference to `Pm_Enqueue'
qtest.o:qtest.c:(.text+0x67e): undefined reference to `puts'
qtest.o:qtest.c:(.text+0x697): undefined reference to `Pm_QueuePeek'
qtest.o:qtest.c:(.text+0x6e8): undefined reference to `Pm_Dequeue'
qtest.o:qtest.c:(.text+0x6f9): undefined reference to `puts'
qtest.o:qtest.c:(.text+0x757): undefined reference to `Pm_QueueDestroy' but these are all part of pmutil and I copied pmutil.c and pmutil.h to the directory, so what can't it find them? |
Pm_Initialize is defined in portmidi.c, so it looks like you are not compiling and linking to portmidi.c. Have you tried makeing a Go library with a simple "hello world" program or function in C just to prove to yourself that you can get Go to execute a function in a C source file? [As I was typing, your latest post appeared - do you have any examples of getting Go to execute a C function on Windows using gcc? I'm not a Go developer and can't answer.] |
yes, this is not the problem, this works with the rtmidi binding without a problem: inside https://gitlab.com/gomidi/midi/-/tree/master/v2/drivers/rtmididrv/imported/rtmidi?ref_type=heads |
Have you tried making a Go library with a simple "hello world" program or function in C just to prove to yourself that you can get Go to execute a function in a C source file? |
yes, that is not the problem. the problem is getting the includes together, and I can't even get it to work in c (e.g. pm_test). If you could just give me an example of a simple c program including the necessary files on windows and the corresponding gcc command to make it work. from there on it would be no problem. |
the closest I could come, I think: package main
/*
#cgo windows CFLAGS: -I pm_common -I pm_win -I porttime
#cgo windows LDFLAGS: -Wl,--subsystem,windows
#include <stdlib.h>
#include <stdint.h>
#include "ptwinmm.c"
#include "pmwin.c"
*/
import "C"
import (
"errors"
"fmt"
)
func main() {
err := Initialize()
if err != nil {
fmt.Printf("error: %s\n", err.Error())
}
fmt.Println("ok")
}
func Initialize() error {
/*
if code := C.Pm_Initialize(); code != 0 {
// return convertToError(code)
fmt.Printf("code: %v", code)
}
*/
return nil
}
// convertToError converts a portmidi error code to a Go error.
func convertToError(code C.PmError) error {
if code >= 0 {
return nil
}
return errors.New(C.GoString(C.Pm_GetErrorText(code)))
} with these files/dirs: main.go
pm_common/
pm_win/
porttime/ and the error is: # command-line-arguments
C:\Program Files\Go\pkg\tool\windows_amd64\link.exe: running C:\mingw-w64\mingw64\bin\gcc.exe failed: exit status 1
C:\mingw-w64\mingw64\bin\gcc.exe -m64 -s -mconsole -Wl,--tsaware -Wl,--nxcompat -Wl,--major-os-version=6 -Wl,--minor-os-version=1 -Wl,--major-subsystem-version=6 -Wl,--minor-subsystem-version=1 -Wl,--dynamicbase -Wl,--high-entropy-va -o $WORK\b001\exe\main.exe -Wl,--no-insert-timestamp Q:\Temp\go-link-3398801446\go.o Q:\Temp\go-link-3398801446\000000.o Q:\Temp\go-link-3398801446\000001.o Q:\Temp\go-link-3398801446\000002.o Q:\Temp\go-link-3398801446\000003.o Q:\Temp\go-link-3398801446\000004.o Q:\Temp\go-link-3398801446\000005.o Q:\Temp\go-link-3398801446\000006.o Q:\Temp\go-link-3398801446\000007.o Q:\Temp\go-link-3398801446\000008.o Q:\Temp\go-link-3398801446\000009.o -O2 -g -Wl,--subsystem,windows -O2 -g -Wl,-T,Q:\Temp\go-link-3398801446\fix_debug_gdb_scripts.ld -Wl,--start-group -lmingwex -lmingw32 -Wl,--end-group -lkernel32
Q:\Temp\go-link-3398801446\000001.o: In function `Pt_Time':
I:/programmierung/go/work/gomidi/midi/v2/drivers/portmididrv/imported/portmidi/pm_win/porttime/ptwinmm.c:63: undefined reference to `__imp_timeGetTime'
Q:\Temp\go-link-3398801446\000001.o: In function `pm_get_default_device_id':
I:/programmierung/go/work/gomidi/midi/v2/drivers/portmididrv/imported/portmidi/pm_win/pm_win/pmwin.c:68: undefined reference to `Pm_Initialize'
I:/programmierung/go/work/gomidi/midi/v2/drivers/portmididrv/imported/portmidi/pm_win/pm_win/pmwin.c:111: undefined reference to `pm_find_default_device'
Q:\Temp\go-link-3398801446\000001.o: In function `Pt_Start':
I:/programmierung/go/work/gomidi/midi/v2/drivers/portmididrv/imported/portmidi/pm_win/porttime/ptwinmm.c:27: undefined reference to `__imp_timeBeginPeriod'
I:/programmierung/go/work/gomidi/midi/v2/drivers/portmididrv/imported/portmidi/pm_win/porttime/ptwinmm.c:29: undefined reference to `__imp_timeGetTime'
I:/programmierung/go/work/gomidi/midi/v2/drivers/portmididrv/imported/portmidi/pm_win/porttime/ptwinmm.c:33: undefined reference to `__imp_timeSetEvent'
Q:\Temp\go-link-3398801446\000001.o: In function `Pt_Stop':
I:/programmierung/go/work/gomidi/midi/v2/drivers/portmididrv/imported/portmidi/pm_win/porttime/ptwinmm.c:50: undefined reference to `__imp_timeEndPeriod'
I:/programmierung/go/work/gomidi/midi/v2/drivers/portmididrv/imported/portmidi/pm_win/porttime/ptwinmm.c:45: undefined reference to `__imp_timeKillEvent'
Q:\Temp\go-link-3398801446\000001.o: In function `Pt_Time':
I:/programmierung/go/work/gomidi/midi/v2/drivers/portmididrv/imported/portmidi/pm_win/porttime/ptwinmm.c:63: undefined reference to `__imp_timeGetTime'
Q:\Temp\go-link-3398801446\000001.o: In function `_cgo_57de27fb9411_Cfunc_Pm_GetErrorText':
/tmp/go-build/cgo-gcc-prolog:55: undefined reference to `Pm_GetErrorText'
Q:\Temp\go-link-3398801446\000001.o:I:/programmierung/go/work/gomidi/midi/v2/drivers/portmididrv/imported/portmidi/pm_win/pm_win/pmwin.c:112: undefined reference to `pm_winmm_term'
Q:\Temp\go-link-3398801446\000001.o: In function `pm_init':
I:/programmierung/go/work/gomidi/midi/v2/drivers/portmididrv/imported/portmidi/pm_win/pm_win/pmwin.c:49: undefined reference to `pm_winmm_init'
Q:\Temp\go-link-3398801446\000001.o: In function `pm_term':
I:/programmierung/go/work/gomidi/midi/v2/drivers/portmididrv/imported/portmidi/pm_win/pm_win/pmwin.c:55: undefined reference to `pm_winmm_term'
Q:\Temp\go-link-3398801446\000001.o:main.cgo2.c:(.rdata$.refptr.pm_descriptors[.refptr.pm_descriptors]+0x0): undefined reference to `pm_descriptors'
Q:\Temp\go-link-3398801446\000001.o:main.cgo2.c:(.rdata$.refptr.pm_descriptor_len[.refptr.pm_descriptor_len]+0x0): undefined reference to `pm_descriptor_len'
collect2.exe: error: ld returned 1 exit status |
so basically, the question is, how this part has to be:
and this is basically c and is just passed through by the go compiler (the comment is just, that the go compiler knows that is it not go code, it is a special syntax/comment to tell what is passed to the c- compiler.) |
this would be the most logical for me:
but it results in
|
Could it be that the same header files are included several times and your building tool (make, cmake, whatever) takes care that they are de facto not included twice, but when I include manually they are included twice and therefor the gcc errors out? |
Thanks for your patience btw |
I could be wrong, but I think Go simply builds a program out of the commented lines and tries to compile it. So the following:
If that's the case, I think you should compile portmidi as a static library and tell Go to link with it. For the Go file, I think you'll need to include portmidi.h (but only portmidi.h). If that's objectionable and I'm right about concatenate-all-files-and-make-single-compilation-unit, then the first problem is that .h files will need guards so they are only included once. The next question will be are there conflicts between .c files such as static variables, which are normally local to the compilation unit. Building with a single compilation unit is fragile because it's not supported or tested, but if it works, portmidi is pretty stable, so it's unlikely that future changes will break anything. |
Yeah, I think that is the reason. It is concatenating and portmidi is not made for this, because headers are included twice. I just wonder how you compile in c then without having the headers included multiple times? The whole point of my repo-change was to include the source so that the user does not have to precompile. Obviously I also don't want the user to download the precompiler lib. This is the whole point about opensource, right? So, if it is possible to compile a static c library, it should also be possible by CGO. (the c wrapper of Go). If there was a way to have single c file per os which is including just what is needed, then I can make it work. (Go has an easy way to just compile certain files for certain OSes). |
How would I do this? The gcc command to do that should be all that I need. |
I get the impression that portmidi was not compiled on Windows for a long time... |
I think every commit is compiled on 3 platforms, and since you posted that impression and my VS 2022 installation is relatively new, I just compiled everything here to double check. There are no errors or warnings compiling all tests on Windows. |
|
Thank you very much for your effort. Yeah, I meant without depending on tools like VS. I don't know how VS code does handle the multiple header inclusion issue, I just read in your readme file and it said that we should try to compile several times before all dependencies are met. I mean, it should also be able to compile with plain old Ming Gcc and just in one shot, shouldn't it? |
Ok, so we need to compile in "batches". |
|
Yeah, you are right, I had a totally wrong conception. Now I see why it is such a relief that you mostly compile statically in Go....(like "in one go" ;-) |
so this means, if portmidi was organized to include everything in just one big file and have the platform dependent code behind preprocessor switches, that would mean a total rewrite of portmidi, right? |
Sorry, you're typing questions faster than I can reply. The order of compilation doesn't matter. Compilation units are independent by design. (You'd never build big systems if you had to recompile from sources every time you change something.) |
Ah, I see, thank you. |
Yeah, but I also was not able to compile single c files. starting with portmidi.c this would always give me errors. |
ok, but maybe the problem was, because I was testing by building executables. |
Google's AI says to combine multiple C files into a Go library you can just do: |
It might work to simply add guards to the .h files, e.g.
Many .h files have this, but not all, e.g. pmwinmm.h that gets included in two .c files. |
If you can't compile a single file like portmidi.c, send me the errors and command line, and I can probably tell you what's wrong. But note that you have to compile with -o (create an object file). If you just gcc portmidi.c, then gcc will try to compile and link, and of course there are dependencies so the link step will fail. |
Ah, yeah, that is probably the problem. I now tried an easier way: have one go file or each c file, so that the go file refers only to what is inside the corresponding c file. these go files form together a package. so at least as a library, I was able to compile "portmidi.c" and "pmutil.c" without errors. If I understand correctly, I somehow also need to compile the windows specific files in order to create something "runnable". I just wonder, if the libraries from the different go file compilations will then be c-linked together... I will try |
ok, so for pmutil.c and portmidi.c it did work, for ptwinmm.c I got the following error: package portmididrv
/*
#cgo windows CFLAGS: -I ../pm_win -I ../porttime
#cgo windows LDFLAGS: -Wl,--subsystem,windows -g -Wl,--allow-multiple-definition
#include <stdlib.h>
#include <stdint.h>
#include "ptwinmm.c"
*/
import "C"
import "fmt"
type Timestamp int64
func pmTimestamp(ts Timestamp) int {
fmt.Println("hiho")
return int(C.PmTimestamp(ts))
} error: .\porttime.go:19:13: could not determine kind of name for C.PmTimestamp
cgo:
C:\mingw-w64\mingw64\bin\gcc.exe errors for preamble:
In file included from .\porttime.go:10:
..\porttime/ptwinmm.c:14:8: error: unknown type name 'PtCallback'
static PtCallback *time_callback;
^~~~~~~~~~
..\porttime/ptwinmm.c: In function 'winmm_time_callback':
..\porttime/ptwinmm.c:19:6: error: called object is not a function or function pointer
(*time_callback)(Pt_Time(), (void *) dwUser);
~^~~~~~~~~~~~~~~
..\porttime/ptwinmm.c: At top level:
..\porttime/ptwinmm.c:23:1: error: unknown type name 'PMEXPORT'; did you mean 'PXFORM'?
PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
^~~~~~~~
PXFORM
..\porttime/ptwinmm.c:23:18: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'Pt_Start'
PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
^~~~~~~~
..\porttime/ptwinmm.c:40:1: error: unknown type name 'PMEXPORT'; did you mean 'PXFORM'?
PMEXPORT PtError Pt_Stop()
^~~~~~~~
PXFORM
..\porttime/ptwinmm.c:40:18: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'Pt_Stop'
PMEXPORT PtError Pt_Stop()
^~~~~~~
..\porttime/ptwinmm.c:54:9: error: expected ';' before 'int'
PMEXPORT int Pt_Started()
^~~~
;
..\porttime/ptwinmm.c:60:1: error: unknown type name 'PMEXPORT'; did you mean 'PXFORM'?
PMEXPORT PtTimestamp Pt_Time()
^~~~~~~~
PXFORM
..\porttime/ptwinmm.c:60:22: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'Pt_Time'
PMEXPORT PtTimestamp Pt_Time()
^~~~~~~
..\porttime/ptwinmm.c:66:9: error: expected ';' before 'void'
PMEXPORT void Pt_Sleep(int32_t duration)
^~~~~
; |
is it because of win32? in the first line, it says: maybe, I need to tell the compiler something, that it is win32, although that could conflict with the go compiler target (I could set that also to win32, the question is just, if it then works with the other c files). |
got the same error when compiling for 32bit |
I think your "C.PmTimestamp" should be "C.PtTimestamp" |
Yeah, thank you for catching it, but now:
|
ok, interesting, if I put it into a different go package, it works. Ok, instead of bothering you all the time, I now make more educated experiments and tell you, if I could make it. |
OK - I'm off until tomorrow. Good luck. I appreciate the work to bring Portmidi to Go. A great language. |
Yeah, thank you for your help! |
Hi, I am the maintainer of https://gitlab.com/gomidi/midi
which is a library (probably the most used) for dealing with midi in Go.
I do know very little when it comes to C and the only reason, why I did
implement a way to talk to midi-devices (I call them drivers) was, because
I wanted a general Go interface for all drivers (which also enables a webdriver and a test-driver). But obviously the most important drivers are real-crossplatform system drivers like rtmidi and portmidi.
Luckily I found projects that already did Go bindings for them (via CGO) and I could make wrappers around them to adhere to the common interface.
It turned out that these dependecies had issues or did break often, so I decided to include the C source code in the gomidi/midi repository, so that I have control over the versions and the updates. For rtmidi it turned out not to be a big issue, but for portmidi I am not able to make it happen in a crossplatform (windows, linux, macosx) way.
My code is based on https://github.com/rakyll/portmidi, but has some slight variations which make it to need porttime as well.
Can you help me find out, which files I would need to include in order to be able to compile portmidi without external dependencies (apart from the obvious alsa header on linux) via CGO?
The code in question resides here:
https://gitlab.com/gomidi/midi/-/tree/master/v2/drivers/portmididrv/imported/portmidi?ref_type=heads
The text was updated successfully, but these errors were encountered: