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

Go(lang) binding #75

Open
metakeule opened this issue Oct 23, 2024 · 44 comments
Open

Go(lang) binding #75

metakeule opened this issue Oct 23, 2024 · 44 comments

Comments

@metakeule
Copy link

metakeule commented Oct 23, 2024

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

@rbdannenberg
Copy link
Contributor

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.

@metakeule
Copy link
Author

I try just to compile for windows with just these files:
(c-compiler is gcc.exe (x86_64-win32-seh-rev0, Built by MinGW-W64 project) 8.1.0)

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

@metakeule
Copy link
Author

no matter which file I include or not, I either get duplicated symbols or missing references....

@metakeule
Copy link
Author

metakeule commented Oct 23, 2024

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.

Thanks for your message.

So if I wanted to compile pm_test in windows just with gcc.exe 8.1.0 (x86_64- MinGW-W64) (without go) . What would be the correct way to invoke gcc?

@metakeule
Copy link
Author

metakeule commented Oct 23, 2024

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.

@metakeule
Copy link
Author

Maybe it helps to know that I want to compile statically, as it is common for Go.

@metakeule
Copy link
Author

metakeule commented Oct 23, 2024

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)

@metakeule
Copy link
Author

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?

@rbdannenberg
Copy link
Contributor

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.]
the CMake files are not set up for gcc on Windows, but it might work anyway. Try using the GUI version of CMake and make a project for gcc. At best you'll get a long hard-to-read makefile and lots of support files, but you can run make with some debugging flags to see what it does to, say, compile mm.

@metakeule
Copy link
Author

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:

https://gitlab.com/gomidi/midi/-/blob/master/v2/drivers/rtmididrv/imported/rtmidi/rtmidi.go?ref_type=heads

inside

https://gitlab.com/gomidi/midi/-/tree/master/v2/drivers/rtmididrv/imported/rtmidi?ref_type=heads

@rbdannenberg
Copy link
Contributor

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?

@metakeule
Copy link
Author

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.

@metakeule
Copy link
Author

metakeule commented Oct 23, 2024

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

@metakeule
Copy link
Author

metakeule commented Oct 23, 2024

so basically, the question is, how this part has to be:

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

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.)

@metakeule
Copy link
Author

this would be the most logical for me:

/*
#cgo windows CFLAGS: -I pm_common -I pm_win -I porttime
#cgo windows LDFLAGS: -Wl,--subsystem,windows

#include <stdlib.h>
#include <stdint.h>
#include "portmidi.h"
#include "porttime.h"
#include "ptwinmm.c"
#include "pmwin.c"
*/

but it results in

# command-line-arguments
In file included from .\porttime/ptwinmm.c:4,
                 from .\main.go:12:
.\porttime/porttime.h:40:5: error: redeclaration of enumerator 'ptNoError'
     ptNoError = 0,         /* success */
     ^~~~~~~~~
In file included from .\main.go:11:
.\porttime/porttime.h:40:5: note: previous definition of 'ptNoError' was here
     ptNoError = 0,         /* success */
     ^~~~~~~~~
In file included from .\porttime/ptwinmm.c:4,
                 from .\main.go:12:
.\porttime/porttime.h:41:5: error: redeclaration of enumerator 'ptHostError'
     ptHostError = -10000,  /* a system-specific error occurred */
     ^~~~~~~~~~~
In file included from .\main.go:11:
.\porttime/porttime.h:41:5: note: previous definition of 'ptHostError' was here
     ptHostError = -10000,  /* a system-specific error occurred */
     ^~~~~~~~~~~
In file included from .\porttime/ptwinmm.c:4,
                 from .\main.go:12:
.\porttime/porttime.h:42:5: error: redeclaration of enumerator 'ptAlreadyStarted'
     ptAlreadyStarted,      /* cannot start timer because it is already started */
     ^~~~~~~~~~~~~~~~
In file included from .\main.go:11:
.\porttime/porttime.h:42:5: note: previous definition of 'ptAlreadyStarted' was here
     ptAlreadyStarted,      /* cannot start timer because it is already started */
     ^~~~~~~~~~~~~~~~
In file included from .\porttime/ptwinmm.c:4,
                 from .\main.go:12:
.\porttime/porttime.h:43:5: error: redeclaration of enumerator 'ptAlreadyStopped'
     ptAlreadyStopped,      /* cannot stop timer because it is already stopped */
     ^~~~~~~~~~~~~~~~
In file included from .\main.go:11:
.\porttime/porttime.h:43:5: note: previous definition of 'ptAlreadyStopped' was here
     ptAlreadyStopped,      /* cannot stop timer because it is already stopped */
     ^~~~~~~~~~~~~~~~
In file included from .\porttime/ptwinmm.c:4,
                 from .\main.go:12:
.\porttime/porttime.h:44:5: error: redeclaration of enumerator 'ptInsufficientMemory'
     ptInsufficientMemory   /* memory could not be allocated */
     ^~~~~~~~~~~~~~~~~~~~
In file included from .\main.go:11:
.\porttime/porttime.h:44:5: note: previous definition of 'ptInsufficientMemory' was here
     ptInsufficientMemory   /* memory could not be allocated */
     ^~~~~~~~~~~~~~~~~~~~
In file included from .\porttime/ptwinmm.c:4,
                 from .\main.go:12:
.\porttime/porttime.h:45:3: error: conflicting types for 'PtError'
 } PtError; /**< @brief @enum  PtError PortTime error code; a common return type.
   ^~~~~~~
In file included from .\main.go:11:
.\porttime/porttime.h:45:3: note: previous declaration of 'PtError' was here
 } PtError; /**< @brief @enum  PtError PortTime error code; a common return type.
   ^~~~~~~
In file included from .\porttime/ptwinmm.c:4,
                 from .\main.go:12:
.\porttime/porttime.h:66:18: error: conflicting types for 'Pt_Start'
 PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData);
                  ^~~~~~~~
In file included from .\main.go:11:
.\porttime/porttime.h:66:18: note: previous declaration of 'Pt_Start' was here
 PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData);
                  ^~~~~~~~
In file included from .\porttime/ptwinmm.c:4,
                 from .\main.go:12:
.\porttime/porttime.h:72:18: error: conflicting types for 'Pt_Stop'
 PMEXPORT PtError Pt_Stop(void);
                  ^~~~~~~
In file included from .\main.go:11:
.\porttime/porttime.h:72:18: note: previous declaration of 'Pt_Stop' was here
 PMEXPORT PtError Pt_Stop(void);
                  ^~~~~~~
In file included from .\main.go:12:
.\porttime/ptwinmm.c:24:18: error: conflicting types for 'Pt_Start'
 PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
                  ^~~~~~~~
In file included from .\main.go:11:
.\porttime/porttime.h:66:18: note: previous declaration of 'Pt_Start' was here
 PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData);
                  ^~~~~~~~
In file included from .\main.go:12:
.\porttime/ptwinmm.c:41:18: error: conflicting types for 'Pt_Stop'
 PMEXPORT PtError Pt_Stop()
                  ^~~~~~~
In file included from .\main.go:11:
.\porttime/porttime.h:72:18: note: previous declaration of 'Pt_Stop' was here
 PMEXPORT PtError Pt_Stop(void);
                  ^~~~~~~

@metakeule
Copy link
Author

metakeule commented Oct 23, 2024

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?

@metakeule
Copy link
Author

Thanks for your patience btw

@rbdannenberg
Copy link
Contributor

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:

#include <stdlib.h>
#include <stdint.h>
#include "portmidi.h"
#include "porttime.h"
#include "ptwinmm.c"
#include "pmwin.c"
are trying to compile ptwinmm.c and pmwin.c as well as the .h files (which are already included in ptwinmm.c and pmwin.c) as one monolithic compilation unit.

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.

@metakeule
Copy link
Author

metakeule commented Oct 23, 2024

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).

@metakeule
Copy link
Author

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).

How would I do this? The gcc command to do that should be all that I need.

@metakeule
Copy link
Author

I get the impression that portmidi was not compiled on Windows for a long time...

@rbdannenberg
Copy link
Contributor

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.

@rbdannenberg
Copy link
Contributor

I just wonder how you compile in c then without having the headers included multiple times?
Portmidi does not include the headers multiple times. Maybe you got the impression from Go examples that the way you compile C is list a bunch of files including header files that are all concatenated and compiled, but that's not how it works. You compile a single .c file at a time. The .c file itself includes whatever headers it needs. The .c files do not include the same header file multiple times.

@metakeule
Copy link
Author

metakeule commented Oct 23, 2024

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?
I mean on Linux it is probably compiled with the GCC, so why shouldn't it work on Windows? Why should we need a tool like VS?

@metakeule
Copy link
Author

I just wonder how you compile in c then without having the headers included multiple times?
Portmidi does not include the headers multiple times. Maybe you got the impression from Go examples that the way you compile C is list a bunch of files including header files that are all concatenated and compiled, but that's not how it works. You compile a single .c file at a time. The .c file itself includes whatever headers it needs. The .c files do not include the same header file multiple times.

Ok, so we need to compile in "batches".
And can you tell me the order of compilation? Maybe I can then find a way to make it more comfortable for the users (I don't know, need to try out).

@rbdannenberg
Copy link
Contributor

Yeah, I meant without depending on tools like VS.
This has nothing to do with VS; it works the same way on MacOS and Linux. None of these compile all the .c files in one shot. You may have that impression because you can put all the .c files on a command line and ask the compiler to compile and even link them, but it happens by running a compiler separately on each .c source file. The multiple header inclusion issue is yours -- you are trying to splice together a bunch of files intended for separate compilation and compile them as one unit. That's not normal practice by any means.

@metakeule
Copy link
Author

Maybe you got the impression from Go examples that the way you compile C is list a bunch of files including header files that are all concatenated and compiled, but that's not how it works. You compile a single .c file at a time.

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

@metakeule
Copy link
Author

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?

@rbdannenberg
Copy link
Contributor

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.)

@metakeule
Copy link
Author

Ah, I see, thank you.

@metakeule
Copy link
Author

Yeah, I meant without depending on tools like VS.
This has nothing to do with VS; it works the same way on MacOS and Linux. None of these compile all the .c files in one shot. You may have that impression because you can put all the .c files on a command line and ask the compiler to compile and even link them, but it happens by running a compiler separately on each .c source file. The multiple header inclusion issue is yours -- you are trying to splice together a bunch of files intended for separate compilation and compile them as one unit. That's not normal practice by any means.

Yeah, but I also was not able to compile single c files. starting with portmidi.c this would always give me errors.

@metakeule
Copy link
Author

ok, but maybe the problem was, because I was testing by building executables.
maybe, if just build libraries, it should work.

@rbdannenberg
Copy link
Contributor

Google's AI says to combine multiple C files into a Go library you can just do:
ar rcs goportmidi.a portmidi.o porttime.o ptwinmm.o pmwinmm.o ...
where, for example, portmidi.a only includes portmidi.h, e.g. from
go build -buildmode=c-archive -o portmidi.a
but you'd have to compile portmidi.o porttime.o ptwinmm.o pmwinmm.o in separate steps before or after the go build step that just builds the glue to connect Go to the C functions.

@rbdannenberg
Copy link
Contributor

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?

It might work to simply add guards to the .h files, e.g.

#ifndef _PORTTIME_H_
#define _PORTTIME_H_
.... existing porttime.h ...
#endif 

Many .h files have this, but not all, e.g. pmwinmm.h that gets included in two .c files.
The danger is that if a compilation unit has a static variable declaration, e.g.
static int foo = 5; and another declares static int foo = 6; this is fine when compiled separately, and the two "foo"s are different variables, but if you concatenate the files, the compiler sees this as a conflicting double declaration of foo.

@rbdannenberg
Copy link
Contributor

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.

@metakeule
Copy link
Author

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

@metakeule
Copy link
Author

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)
         ^~~~~
         ;

@metakeule
Copy link
Author

is it because of win32? in the first line, it says:
/* ptwinmm.c -- portable timer implementation for win32 */

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).

@metakeule
Copy link
Author

got the same error when compiling for 32bit

@rbdannenberg
Copy link
Contributor

I think your "C.PmTimestamp" should be "C.PtTimestamp"

@metakeule
Copy link
Author

Yeah, thank you for catching it, but now:

.\porttime.go:19:13: could not determine kind of name for C.PtTimestamp
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)
         ^~~~~

@metakeule
Copy link
Author

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.

@rbdannenberg
Copy link
Contributor

OK - I'm off until tomorrow. Good luck. I appreciate the work to bring Portmidi to Go. A great language.

@metakeule
Copy link
Author

Yeah, thank you for your help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants