Drop dlfcn dependency on Windows

Use direct syscalls instead. Needed for static builds.
This commit is contained in:
sergystepanov 2026-07-01 15:38:56 +03:00
parent 8361215a84
commit 3018a7da0d
5 changed files with 113 additions and 109 deletions

View file

@ -54,17 +54,17 @@ a better sense of performance.
## Development environment
* Install [Go](https://golang.org/doc/install)
* Install GStreamer and SDL2:
* Install GStreamer
```
# Ubuntu / Windows (WSL2)
apt-get install -y make gcc pkg-config libsdl2-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-good
apt-get install -y make gcc pkg-config libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-good
# macOS
brew install pkg-config sdl2 gstreamer gst-plugins-base gst-plugins-good
brew install pkg-config gstreamer gst-plugins-base gst-plugins-good
# Windows (MSYS2)
pacman -Sy --noconfirm --needed git make mingw-w64-ucrt-x86_64-{gcc,pkgconf,dlfcn,SDL2,gstreamer,gst-plugins-base,gst-plugins-good}
pacman -Sy --noconfirm --needed git make mingw-w64-ucrt-x86_64-{gcc,pkgconf,gstreamer,gst-plugins-base,gst-plugins-good}
```
Because the coordinator and workers need to run simultaneously. Workers connect to the coordinator.

View file

@ -0,0 +1,54 @@
//go:build !windows
package nanoarch
import (
"fmt"
"unsafe"
)
/*
#cgo linux LDFLAGS: -ldl
#include <stdlib.h>
#include <dlfcn.h>
*/
import "C"
type dlib struct {
ptr unsafe.Pointer
}
func open(file string) (*dlib, error) {
cs := C.CString(file)
defer C.free(unsafe.Pointer(cs))
handle := C.dlopen(cs, C.RTLD_LAZY)
if handle == nil {
e := C.dlerror()
if e != nil {
return nil, fmt.Errorf("couldn't load the lib: %s", C.GoString(e))
}
return nil, fmt.Errorf("couldn't load the lib")
}
return &dlib{ptr: handle}, nil
}
func (d *dlib) load(name string) unsafe.Pointer {
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
ptr := C.dlsym(d.ptr, cs)
if ptr == nil {
panic("lib function not found: " + name)
}
return ptr
}
func (d *dlib) close() error {
if d.ptr == nil {
return nil
}
code := int(C.dlclose(d.ptr))
if code != 0 {
return fmt.Errorf("couldn't close the lib (%d)", code)
}
return nil
}

View file

@ -0,0 +1,30 @@
//go:build windows
package nanoarch
import (
"syscall"
"unsafe"
)
type dlib struct {
h syscall.Handle
}
func open(file string) (*dlib, error) {
h, err := syscall.LoadLibrary(file)
if err != nil {
return nil, err
}
return &dlib{h: h}, nil
}
func (d *dlib) load(name string) unsafe.Pointer {
proc, err := syscall.GetProcAddress(d.h, name)
if err != nil {
panic("lib function not found: " + name)
}
return unsafe.Pointer(proc)
}
func (d *dlib) close() error { return syscall.FreeLibrary(d.h) }

View file

@ -1,75 +0,0 @@
package nanoarch
import (
"errors"
"os"
"path"
"strconv"
"strings"
"unsafe"
)
/*
#cgo LDFLAGS: -ldl
#include <stdlib.h>
#include <dlfcn.h>
*/
import "C"
func loadFunction(handle unsafe.Pointer, name string) unsafe.Pointer {
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
ptr := C.dlsym(handle, cs)
if ptr == nil {
panic("lib function not found: " + name)
}
return ptr
}
func loadLib(filepath string) (handle unsafe.Pointer, err error) {
handle = open(filepath)
if handle == nil {
e := C.dlerror()
if e != nil {
err = errors.New(C.GoString(e))
} else {
err = errors.New("couldn't load the lib")
}
}
return
}
func loadLibRollingRollingRolling(filepath string) (handle unsafe.Pointer, err error) {
dir, lib := path.Dir(filepath), path.Base(filepath)
files, err := os.ReadDir(dir)
if err != nil {
return nil, errors.New("couldn't find 'n load the lib")
}
for _, file := range files {
if !file.IsDir() && strings.HasPrefix(file.Name(), lib) {
handle = open(path.Join(dir, file.Name()))
if handle != nil {
return handle, nil
}
}
}
return nil, errors.New("couldn't find 'n load the lib")
}
func open(file string) unsafe.Pointer {
cs := C.CString(file)
defer C.free(unsafe.Pointer(cs))
return C.dlopen(cs, C.RTLD_LAZY)
}
func closeLib(handle unsafe.Pointer) (err error) {
if handle == nil {
return
}
code := int(C.dlclose(handle))
if code != 0 {
return errors.New("couldn't close the lib (" + strconv.Itoa(code) + ")")
}
return
}

View file

@ -209,37 +209,32 @@ func (n *Nanoarch) CoreLoad(meta Metadata) {
n.options4rom = meta.Options4rom
corePath := meta.LibPath + meta.LibExt
coreLib, err = loadLib(corePath)
// fallback to sequential lib loader (first successfully loaded)
coreLib, err = open(corePath)
if err != nil {
n.log.Error().Err(err).Msgf("load fail: %v", corePath)
coreLib, err = loadLibRollingRollingRolling(corePath)
if err != nil {
n.log.Fatal().Err(err).Msgf("core load: %s", corePath)
}
n.log.Fatal().Err(err).Msgf("core load: %s", corePath)
}
retroInit = loadFunction(coreLib, "retro_init")
retroDeinit = loadFunction(coreLib, "retro_deinit")
retroAPIVersion = loadFunction(coreLib, "retro_api_version")
retroGetSystemInfo = loadFunction(coreLib, "retro_get_system_info")
retroGetSystemAVInfo = loadFunction(coreLib, "retro_get_system_av_info")
retroSetEnvironment = loadFunction(coreLib, "retro_set_environment")
retroSetVideoRefresh = loadFunction(coreLib, "retro_set_video_refresh")
retroSetInputPoll = loadFunction(coreLib, "retro_set_input_poll")
retroSetInputState = loadFunction(coreLib, "retro_set_input_state")
retroSetAudioSample = loadFunction(coreLib, "retro_set_audio_sample")
retroSetAudioSampleBatch = loadFunction(coreLib, "retro_set_audio_sample_batch")
retroReset = loadFunction(coreLib, "retro_reset")
retroRun = loadFunction(coreLib, "retro_run")
retroLoadGame = loadFunction(coreLib, "retro_load_game")
retroUnloadGame = loadFunction(coreLib, "retro_unload_game")
retroSerializeSize = loadFunction(coreLib, "retro_serialize_size")
retroSerialize = loadFunction(coreLib, "retro_serialize")
retroUnserialize = loadFunction(coreLib, "retro_unserialize")
retroSetControllerPortDevice = loadFunction(coreLib, "retro_set_controller_port_device")
retroGetMemorySize = loadFunction(coreLib, "retro_get_memory_size")
retroGetMemoryData = loadFunction(coreLib, "retro_get_memory_data")
retroInit = coreLib.load("retro_init")
retroDeinit = coreLib.load("retro_deinit")
retroAPIVersion = coreLib.load("retro_api_version")
retroGetSystemInfo = coreLib.load("retro_get_system_info")
retroGetSystemAVInfo = coreLib.load("retro_get_system_av_info")
retroSetEnvironment = coreLib.load("retro_set_environment")
retroSetVideoRefresh = coreLib.load("retro_set_video_refresh")
retroSetInputPoll = coreLib.load("retro_set_input_poll")
retroSetInputState = coreLib.load("retro_set_input_state")
retroSetAudioSample = coreLib.load("retro_set_audio_sample")
retroSetAudioSampleBatch = coreLib.load("retro_set_audio_sample_batch")
retroReset = coreLib.load("retro_reset")
retroRun = coreLib.load("retro_run")
retroLoadGame = coreLib.load("retro_load_game")
retroUnloadGame = coreLib.load("retro_unload_game")
retroSerializeSize = coreLib.load("retro_serialize_size")
retroSerialize = coreLib.load("retro_serialize")
retroUnserialize = coreLib.load("retro_unserialize")
retroSetControllerPortDevice = coreLib.load("retro_set_controller_port_device")
retroGetMemorySize = coreLib.load("retro_get_memory_size")
retroGetMemoryData = coreLib.load("retro_get_memory_data")
C.bridge_retro_set_environment(retroSetEnvironment, C.core_environment_cgo)
C.bridge_retro_set_input_state(retroSetInputState, C.core_input_state_cgo)
@ -390,7 +385,7 @@ func (n *Nanoarch) Shutdown() {
setRotation(0)
Nan0.sys.av = C.struct_retro_system_av_info{}
if err := closeLib(coreLib); err != nil {
if err := coreLib.close(); err != nil {
n.log.Error().Err(err).Msg("lib close failed")
}
n.options = nil
@ -611,11 +606,11 @@ func byteCountBinary(b int64) string {
func (m Metadata) HasHack(h string) bool { return slices.Contains(m.Hacks, h) }
var (
coreLib *dlib
retroAPIVersion unsafe.Pointer
retroDeinit unsafe.Pointer
retroGetSystemAVInfo unsafe.Pointer
retroGetSystemInfo unsafe.Pointer
coreLib unsafe.Pointer
retroInit unsafe.Pointer
retroLoadGame unsafe.Pointer
retroReset unsafe.Pointer