I'm writing a small tool, it can play audio file in the command/terminal like sox. I'm using bass.dll and Golang syscall for Windows.
Here is my code, files can downloaded from comments, only run on Windows X64.
bass.go on github gist
package main
import (
"fmt"
"syscall"
"time"
"unsafe"
)
/*
基于 [bass.dll](http://us2.un4seen.com/files/bass24.zip)
和 [Golang syscall](https://github.com/golang/go/wiki/WindowsDLLs)
实现的命令行版播放器。
*/
type BassLib struct {
libBass syscall.Handle
init uintptr
free uintptr
streamCreateFile uintptr
channelPlay uintptr
channelPause uintptr
channelStop uintptr
}
func (bass *BassLib) LoadBass(bassDllPath string) bool {
bass.libBass, _ = syscall.LoadLibrary(bassDllPath)
if bass.libBass == 0 {
fmt.Println("load library result")
fmt.Println(bass.libBass)
return false
}
bass.init, _ = syscall.GetProcAddress(bass.libBass, "BASS_Init")
// BASS_init(device, freq, flags, win, clsid)
// see http://www.un4seen.com/doc/#bass/BASS_Init.html
device := 1
syscall.Syscall6(bass.init, 5, uintptr(device), uintptr(44100), uintptr(0), uintptr(0), uintptr(0), 0)
return true
}
func StrPtr(s string) uintptr {
// return uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(s)))
p, _ := syscall.UTF16PtrFromString(s)
return uintptr(unsafe.Pointer(p))
}
func (bass *BassLib) PlayFile(filePath string) {
bass.streamCreateFile, _ = syscall.GetProcAddress(bass.libBass, "BASS_StreamCreateFile")
// hstream = BASS_StreamCreateFile(mem=0, &file, offset=0, length=0, flags=(A_IsUnicode ? 0x80000000 : 0x40000))
// see http://www.un4seen.com/doc/#bass/BASS_StreamCreateFile.html
var bassUnicode uint32 = 0x80000000
hstream, _, _ := syscall.Syscall6(bass.streamCreateFile, 5, uintptr(0), StrPtr(filePath), uintptr(0), uintptr(0), uintptr(bassUnicode), 0)
// bassErrorGetCode, _ := syscall.GetProcAddress(bass.libBass, "BASS_ErrorGetCode")
// errCode, _, _ := syscall.Syscall(uintptr(bassErrorGetCode), 0, 0, 0, 0)
// fmt.Println(errCode)
fmt.Println("hstream")
fmt.Println(hstream)
bass.channelPlay, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelPlay")
// BASS_ChannelPlay(hstream)
// see http://www.un4seen.com/doc/#bass/BASS_ChannelPlay.html
ret, _, _ := syscall.Syscall(bass.channelPlay, 2, hstream, uintptr(0), 0)
bassErrorGetCode, _ := syscall.GetProcAddress(bass.libBass, "BASS_ErrorGetCode")
errCode, _, _ := syscall.Syscall(bassErrorGetCode, 0, 0, 0, 0)
fmt.Println(errCode)
fmt.Println(ret)
// sleep to wait playing mp3 file
time.Sleep(time.Second * 10)
// bass.channelPause, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelPause")
// bass.channelStop, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelStop")
// return true
}
func (bass *BassLib) UnLoad() {
if bass.libBass != 0 {
bass.free, _ = syscall.GetProcAddress(bass.libBass, "BASS_Free")
syscall.Syscall(bass.free, 0, 0, 0, 0)
// BASS_Free()
// see http://www.un4seen.com/doc/#bass/BASS_Free.html
syscall.FreeLibrary(bass.libBass)
}
}
func main() {
bass := &BassLib{}
bass.LoadBass("C:\\workspace\\play\\bass.dll")
bass.PlayFile("C:\\workspace\\play\\sample.mp3")
bass.UnLoad()
}
There is a big problem:
if time.Sleep code (bass.go line 68) not added , no sound played with quickly quit out.
When I added time.Sleep(time.Second * 10) code, maybe the audio duration more than 10 seconds.
Is there any possibility that make the program automatically exit after audio played over?
I think you can use defer keyword of golang to trigger an exit when play function have done.
You can refer here: A Tour of Go | Defer
Or here: Golang Blog | Defer
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
==========
$ go run main.go
hello
world
I would strongly recommend going through Effective Go on the golang.org website (it's not a long read, I am sure you can go through all the ideas in a single day), paying special attention to the concurrency section.
The whole idea behind Go is to make concurrency and asynchronous programming easy, and it uses several language constructs (channels, goroutines) especially designed to help you handle these cases.
For example, you can use a channel to signal:
func main() {
// end signal
finished := make(chan bool)
// create and run a goroutine
go func() {
// do your bass stuff here
...
// send a signal
finished <- true
}()
// wait
<-finished
}
A common pattern is also to pass the channel to the function doing the job:
func main() {
// end signal
finished := make(chan bool)
// PlayFile is responsible for
// signalling 'finished' when done
go PlayFile(someFile, finished);
// wait
<-finished
}
Or, if you have multiple routines, you will use a WaitGroup:
func main() {
// create the waitgroup
var wg sync.WaitGroup
// number of semaphores
wg.Add(1)
go func() {
// notify WaitGroup when done
// (the 'defer' keyword means
// this call will be executed before
// returning from the method)
defer wg.Done()
// do your bass stuff here
...
}()
wg.Wait()
}
Thanks everyone. Can solve the problem with BASS_ChannelGetLength and BASS_ChannelGetPosition functions.
Here is the code:
// +build windows
package main
import (
"fmt"
"syscall"
"time"
"unsafe"
)
/*
基于 [bass.dll](http://us2.un4seen.com/files/bass24.zip)
和 [Golang syscall](https://github.com/golang/go/wiki/WindowsDLLs)
实现的命令行版播放器。
*/
type (
BASSErrorGetCode int32
)
const (
BassUnicode uint32 = 0x80000000 // BASS_UNICODE
BassSampleFloat uint32 = 256 // BASS_SAMPLE_FLOAT
BassPosByte uint64 = 0 // BASS_POS_BYTE
)
type BassLib struct {
libBass syscall.Handle
init uintptr
free uintptr
streamCreateFile uintptr
channelPlay uintptr
channelPause uintptr
channelStop uintptr
channelGetLength uintptr
channelGetPosition uintptr
channelBytes2Seconds uintptr
}
func (bass *BassLib) LoadBass(bassDllPath string) bool {
bass.libBass, _ = syscall.LoadLibrary(bassDllPath)
if bass.libBass == 0 {
fmt.Println("Load `bass.dll` library failed!")
errCode := bass.GetBassErrorGetCode()
fmt.Println("Bass_Init failed!")
fmt.Println(errCode)
return false
}
bass.init, _ = syscall.GetProcAddress(bass.libBass, "BASS_Init")
// BASS_Init(device, freq, flags, win, clsid)
// see http://www.un4seen.com/doc/#bass/BASS_Init.html
device := 1
r, _, _ := syscall.Syscall6(bass.init, 5, uintptr(device), uintptr(44100), uintptr(0), uintptr(0), uintptr(0), 0)
// var ret = *(* int)(unsafe.Pointer(&r))
if r == 0 {
errCode := bass.GetBassErrorGetCode()
fmt.Println("Bass_Init failed!")
fmt.Println(errCode)
return false
}
return true
}
func StrPtr(s string) uintptr {
p, _ := syscall.UTF16PtrFromString(s)
return uintptr(unsafe.Pointer(p))
}
func (bass *BassLib) PlayFile(filePath string) {
bass.streamCreateFile, _ = syscall.GetProcAddress(bass.libBass, "BASS_StreamCreateFile")
// hStream = BASS_StreamCreateFile(mem=0, &file, offset=0, length=0, flags=(A_IsUnicode ? 0x80000000 : 0x40000))
// see http://www.un4seen.com/doc/#bass/BASS_StreamCreateFile.html
hStream, _, _ := syscall.Syscall6(bass.streamCreateFile, 5, uintptr(0), StrPtr(filePath), uintptr(0), uintptr(0), uintptr(BassUnicode|BassSampleFloat), 0)
bass.channelPlay, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelPlay")
// BASS_ChannelPlay(hStream)
// see http://www.un4seen.com/doc/#bass/BASS_ChannelPlay.html
r, _, _ := syscall.Syscall(bass.channelPlay, 2, hStream, uintptr(0), 0)
if r == 1 {
totalDuration := bass.GetAudioByteLength(hStream)
// currentPos := bass.GetAudioCurrentBytePosition(hStream)
fmt.Println(totalDuration)
// fmt.Println(currentPos)
time.Sleep(time.Second*1)
for {
currentPos := bass.GetAudioCurrentBytePosition(hStream)
if currentPos >= totalDuration {
break
}
}
} else {
errCode := bass.GetBassErrorGetCode()
fmt.Println("Bass_ChannelPlay failed!")
fmt.Println(errCode)
}
}
func (bass *BassLib) GetBassErrorGetCode() BASSErrorGetCode {
bassErrorGetCode, _ := syscall.GetProcAddress(bass.libBass, "BASS_ErrorGetCode")
// BASS_ErrorGetCode()
// BASS_OK BASSErrorGetCode = 0 // all is OK
// BASS_ERROR_MEM BASSErrorGetCode = 1 // memory error
// ...
// see http://www.un4seen.com/doc/#bass/BASS_ErrorGetCode.html
errCode, _, _ := syscall.Syscall(bassErrorGetCode, 0, 0, 0, 0)
var iErrCode = *(*BASSErrorGetCode)(unsafe.Pointer(&errCode))
return iErrCode
}
func (bass *BassLib) GetAudioByteLength(handle uintptr) uintptr {
// (QWORD) BASS_ChannelGetLength(handle=hStream, mode)
// see http://www.un4seen.com/doc/#bass/BASS_ChannelGetLength.html
bass.channelGetLength, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelGetLength")
len, _, _ := syscall.Syscall(bass.channelGetLength, 2, handle, uintptr(BassPosByte), 0)
return len
}
func (bass *BassLib) GetAudioCurrentBytePosition(handle uintptr) uintptr {
// BASS_ChannelGetPosition(handle=hStream, mode)
// see http://www.un4seen.com/doc/#bass/BASS_ChannelGetPosition.html
bass.channelGetPosition, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelGetPosition")
pos, _, _ := syscall.Syscall(bass.channelGetPosition, 2, handle, uintptr(BassPosByte), 0)
return pos
}
func (bass *BassLib) GetChannelBytes2Seconds(handle uintptr, pos uintptr) uintptr {
// BASS_ChannelBytes2Seconds(handle=hStream, pos)
// see http://www.un4seen.com/doc/#bass/BASS_ChannelBytes2Seconds.html
// bass.channelBytes2Seconds, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelBytes2Seconds")
len, _, _ := syscall.Syscall(bass.channelBytes2Seconds, 2, handle, pos, 0)
return len
}
func (bass *BassLib) UnLoad() {
if bass.libBass != 0 {
bass.free, _ = syscall.GetProcAddress(bass.libBass, "BASS_Free")
syscall.Syscall(bass.free, 0, 0, 0, 0)
// BASS_Free()
// see http://www.un4seen.com/doc/#bass/BASS_Free.html
syscall.FreeLibrary(bass.libBass)
}
}
func main() {
bass := &BassLib{}
bass.LoadBass("C:\\workspace\\play\\bass.dll")
bass.PlayFile("C:\\workspace\\play\\sample.mp3")
bass.UnLoad()
}
Also you can get at https://gist.github.com/ycrao/e7d1df181f870091b4a6d298d6ea2770#file-bass_play-go-L81-L91 .
Related
Getting to know the sync.Pool i wrote 2 different New methods for it:
Returns a slice;
Returns a ptr to slice;
Just alloc a new slice without pool (for compare performance);
Benchmarks surprised me
6ns/op and 1 alloc
17ns/op and 0 alloc
130ns/op and 1 alloc
Please explain why is such a big difference between 1 and 3, given that both allocate memory?
And why 1st allocates?
package main
import (
"bytes"
"sync"
)
func main() {}
type MyBuffer struct {
pool sync.Pool
}
func New() *MyBuffer {
return &MyBuffer{pool: sync.Pool{
New: func() any {
buf := make([]int, 0, 128)
return &buf
},
}}
}
func New2() *MyBuffer {
return &MyBuffer{pool: sync.Pool{
New: func() any {
buf := make([]int, 0, 128)
return buf
},
}}
}
func Fill[S ~[]E, E any](buf *S) {
var some E
for i := 0; i < cap(*buf); i++ {
*buf = append(*buf, some)
}
}
func BenchmarkAlloc(b *testing.B) {
b.RunParallel(func(p *testing.PB) {
for p.Next() {
// Get buf
buf := make([]int, 0, 128)
// fill
Fill(&buf)
// Reset
buf = buf[:0]
}
})
}
func BenchmarkPoolPtr(b *testing.B) {
mb := New()
b.RunParallel(func(p *testing.PB) {
for p.Next() {
// Get buf
buf, _ := mb.pool.Get().(*[]int)
// fill
Fill(buf)
// reset
*buf = (*buf)[:0]
// put
mb.pool.Put(buf)
}
})
}
func BenchmarkPoolNoPtr(b *testing.B) {
mb := New2()
b.RunParallel(func(p *testing.PB) {
for p.Next() {
// Get buf
buf, _ := mb.pool.Get().([]int)
// fill
Fill(&buf)
// reset
buf = buf[:0]
// put
mb.pool.Put(&buf)
}
})
}
I think in the 1st case, the allocation is a reference to your slice. In the 3rd case, the allocation is the complete slice.
When you look the size allocated, it's 32 B/op vs 1024 B/op
so the allocation is the reference to your slice, that is not needed when your variable is a pointer.
the question is. i have NewVScroll in the NewVScroll i have NewVBoxLayout in NewVBoxLayout i have NewButton . the problem is when i press the NewButton it should loop over NewVScroll and then loop over NewVBoxLayout to find NewButton . but i don't know how to do it in fyne because i can't loop over var v1 fyne.CanvasObject
the error is : cannot range over v1 (variable of type fyne.CanvasObject)
this is the code
package main
import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
)
var cEntries *fyne.Container
func FindObject(object fyne.CanvasObject, objects []fyne.CanvasObject) (int, bool) {
for k1, v1 := range objects {
for _, v2 := range v1 {// the problem is here. i can't loop over fyne.CanvasObject
if v2 == object {
return k1, true
}
}
}
return 0, false
}
func w1() *fyne.Container {
wb := widget.NewButton("", nil)
wb.OnTapped = func() {
index, isin := FindObject(wb, cEntries.Objects)
fmt.Println(index, isin)
}
return container.New(layout.NewVBoxLayout(), wb)
}
func main() {
a := app.New()
w := a.NewWindow("")
wbAdd := widget.NewButton("+", nil)
cEntries = container.New(layout.NewVBoxLayout(), wbAdd, w1())
wbAdd.OnTapped = func() {
cEntries.Add(w1())
}
wsEntries := container.NewVScroll(cEntries)
w.SetContent(wsEntries)
w.ShowAndRun()
}
The object is a container, so in general you could iterate v1.(*fyne.Container).Objects.
However it looks like you could avoid looping in your code because the button always refers to an index that is set when the button is added - so you could pass the current index (length) to w1 and then increment it.
you don't need to loop over NewVBoxLayout because it is fyne.CanvasObject then if you want to know the index of the layout that contain this widget NewButton you can do it by just search for NewVBoxLayout in NewVScroll
this is the code
package main
import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
)
var cEntries *fyne.Container
func FindObject(object fyne.CanvasObject, objects []fyne.CanvasObject) (int, bool) {
for k1, v1 := range objects {
if v1 == object {
return k1, true
}
}
return 0, false
}
func w1() *fyne.Container {
wb := widget.NewButton("", nil)
c := container.New(layout.NewVBoxLayout(), wb)
wb.OnTapped = func() {
index, isin := FindObject(c, cEntries.Objects)
fmt.Println(index, isin)
}
return c
}
func main() {
a := app.New()
w := a.NewWindow("")
wbAdd := widget.NewButton("+", nil)
cEntries = container.New(layout.NewVBoxLayout(), wbAdd, w1())
wbAdd.OnTapped = func() {
cEntries.Add(w1())
}
wsEntries := container.NewVScroll(cEntries)
w.SetContent(wsEntries)
w.ShowAndRun()
}
see in this code you make the container that handle the button and then you search for the container that contain this button
I am trying to write a go app that will monitor the status of a server application I run in windows. The application will run for roughly 16 hours before throwing out the following error: (Small snippet)
fatal error: too many callback functions
goroutine 137 [running]:
runtime.throw(0xc4c0a1, 0x1b)
H:/Program Files/Go/src/runtime/panic.go:1117 +0x79 fp=0xc000639d30 sp=0xc000639d00 pc=0x899379
syscall.compileCallback(0xbd18a0, 0xc00041fce0, 0x1, 0x0)
H:/Program Files/Go/src/runtime/syscall_windows.go:201 +0x5e5 fp=0xc000639e28 sp=0xc000639d30 pc=0x8c90e5
syscall.NewCallback(...)
H:/Program Files/Go/src/syscall/syscall_windows.go:177
main.FindWindow(0xc47278, 0x13, 0xc000639f50, 0x2, 0x2)
I have two files. One is the file that is calling a bunch of Windows API stuff, and one is a goroutine that is being performed every 30 seconds to get an update.
I am fairly new to go, especially in windows related development, so I am struggling to find the issue and how to prevent it.
Here is the main file (test example).
func main() {
go updateServerStats()
select {}
}
func ServerStats() {
serverStatsTicker := time.NewTicker(30 * time.Second)
for range serverStatsTicker.C {
serverRunning, serverHung, err := ServerHangCheck()
if err != nil {
ErrorLogger.Println("Server Check Error: ", err)
}
if serverHung {
fmt.Println("Server is hung")
}
}
}
Here is the primary callback/windows file. Its very much still a very rough, very work in progress. Something I found and modified from go playground.
package main
import (
"fmt"
"syscall"
"unsafe"
)
var (
user32 = syscall.MustLoadDLL("user32.dll")
procEnumWindows = user32.MustFindProc("EnumWindows")
procGetWindowTextW = user32.MustFindProc("GetWindowTextW")
procIsHungAppWindow = user32.MustFindProc("IsHungAppWindow")
)
//EnumWindows iterates over each window to be used for callbacks
func EnumWindows(enumFunc uintptr, lparam uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, uintptr(enumFunc), uintptr(lparam), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
//GetWindowText gets the description of the Window, which is the "Text" of the window.
func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (len int32, err error) {
r0, _, e1 := syscall.Syscall(procGetWindowTextW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount))
len = int32(r0)
if len == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
//IsHungAppWindow uses the IsHungAppWindow to see if Windows has been getting responses.
func IsHungAppWindow(hwnd syscall.Handle) (ishung bool, err error) {
r2, _, err := syscall.Syscall(procIsHungAppWindow.Addr(), 2, uintptr(hwnd), 0, 0)
if r2 == 1{
return true, err
}
return false, err
}
//FindWindow uses EnumWindows with a callback to GetWindowText, and if matches given Title, checks if its hung, and returns state.
func FindWindow(title string) (bool ,bool, error) {
var hwnd syscall.Handle
var isHung bool = false
cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
b := make([]uint16, 200)
_, err := GetWindowText(h, &b[0], int32(len(b)))
if err != nil {
// ignore the error
return 1 // continue enumeration
}
if syscall.UTF16ToString(b) == title {
// note the window
isHung, _ = IsHungAppWindow(h)
hwnd = h
return 0 // stop enumeration
}
return 1 // continue enumeration
})
EnumWindows(cb, 0)
if hwnd == 0 {
return false, false, fmt.Errorf("DCS Not Found")
}
if isHung == true {
return true, isHung, fmt.Errorf("DCS Is Running But Hung")
}
return true, isHung, nil
}
//ServerHangCheck checks the server to see if the window is hung or process is running.
func ServerHangCheck() (bool, bool, error) {
const title = "server_application"
running, hung, err := FindWindow(title)
return running, hung, err
}
Looking at syscall.NewCallback, we find that it's actually implemented via runtime/syscall_windows.go as the function compileCallback:
func NewCallback(fn interface{}) uintptr {
return compileCallback(fn, true)
}
Looking at the runtime/syscall_windows.go code we find that it has a fixed size table of all registered Go callbacks. This code varies a lot between Go releases so it's not too productive to delve any further here. However, there's one thing that is clear: the code checks to see if the callback function is already registered, and if so, re-uses it. So a single callback function occupies one table slot, but adding multiple functions will eventually use up all the table slots and result in the fatal error that you encountered.
You asked in a comment (the comments popped up while I was writing this):
Would I need to reuse it, or can I "close" the original? – Mallachar 7 mins ago
You cannot close out the original. There's an actual table of functions elsewhere in memory; a registered callback uses up a slot in this table, and slots are never released.
Per the advice of torek I got a work around.
I created a global variable
var callbacker uintptr
Then then I created a function which is called by init
func init() {
callbacker = syscall.NewCallback(CallBackCreator)
}
func CallBackCreator(h syscall.Handle, p uintptr) uintptr {
hwnd = 0
b := make([]uint16, 200)
_, err := GetWindowText(h, &b[0], int32(len(b)))
if err != nil {
// ignore the error
return 1 // continue enumeration
}
if syscall.UTF16ToString(b) == title {
// note the window
isHung, _ = IsHungAppWindow(h)
hwnd = h
return 0 // stop enumeration
}
return 1 // continue enumeration
}
which is now called by the function
EnumWindows(callbacker, 0)
Likely not the most "Go" appropriate way, but I am now making better progress.
I have the following Go code in Windows, and I want to port it to Mac.
What are equal API for syscall.StringToUTF16, syscall.CreateFile in Mac?
import (
"syscall"
"unsafe"
"github.com/golang/glog" )
import "C"
const loggerDeviceName string = `\\.\Global\DGAPIMon`
var (
loghandle syscall.Handle
logdevid uint32 )
func InitLogger() (err error) {
// Setup the device id for the logger ioctl
deviceType := 0x00008307
access := 0
function := 0x72 // LOG STRING
method := 0
logdevid = uint32(((deviceType) << 16) | ((access) << 14) | ((function) << 2) | (method))
devPath := syscall.StringToUTF16(loggerDeviceName)
loghandle, err = syscall.CreateFile(&devPath[0], syscall.GENERIC_READ|syscall.GENERIC_WRITE,
0, nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0)
if err == nil {
glog.V(3).Infof("loghandle: %v logdevid: 0x%x", loghandle, logdevid)
}
return err }
loghandle and logdevid are using as below to log the message to a file.
typedef struct tagLOGSTRING
{
unsigned long level;
wchar_t message[1024];
}LOGSTRING;
func LogMsg(b []byte) (err error) {
var logstring C.LOGSTRING
logstring.level = 0
tmp := syscall.StringToUTF16(string(b[:]))
for i := range tmp {
logstring.message[i] = C.uint16_t(tmp[i])
}
err = syscall.DeviceIoControl(loghandle, logdevid,
(*byte)(unsafe.Pointer(&logstring)),
(uint32)(unsafe.Sizeof(logstring)),
nil, 0, nil, nil)
return err
}
I am writing a function that enumerates the base address of a process. This is done through a few Windows API calls, however, I believe it's my call to EnumProcessModules that is creating the error.
It should also be noted that this error does not occur for every process.
I have already tried changing around some of the uint32 to uint64 and vice versa, and different ways of creating the moduleHandles array, but I can't get anything working.
This is just one function in an application I'm creating to retrieve and scan another process's memory.
This is the source code of my function:
func getBaseAddress(handle uintptr) int64 {
// GetProcessImageFileNameA
var imageFileName [200]byte
var fileSize uint32 = 200
var fileName string
ret, _, _ := procGetProcessImageFileNameA.Call(handle, uintptr(unsafe.Pointer(&imageFileName)), uintptr(fileSize))
for _, char := range imageFileName {
if char == 0 {
break
}
fileName += string(char)
}
fileName = fileName[24:]
// EnumProcessModules
var n uint32
var needed uint64
ret, _, _ = procEnumProcessModules.Call(handle, 0, uintptr(n), uintptr(unsafe.Pointer(&needed)))
moduleHandles := make([]syscall.Handle, int(needed))
if ret == 1 && needed > 0 {
ret, _, _ = procEnumProcessModules.Call(handle, uintptr(unsafe.Pointer(&moduleHandles)), uintptr(needed), uintptr(unsafe.Pointer(&needed)))
}
// GetModuleFileNameExA
var finalModuleHandle uintptr
for _, moduleHandle := range moduleHandles {
if moduleHandle > 0 {
var moduleFileName [200]byte
var moduleSize uint32 = 200
var moduleName string
ret, _, _ = procGetModuleFileNameExA.Call(handle, uintptr(moduleHandle), uintptr(unsafe.Pointer(&moduleFileName)), uintptr(moduleSize))
if ret != 0 {
for _, char := range moduleFileName {
if char == 0 {
break
}
moduleName += string(char)
}
moduleName = moduleName[3:]
if moduleName == fileName {
finalModuleHandle = uintptr(moduleHandle)
break
}
}
}
}
return int64(finalModuleHandle)
}
The 2nd parameter to EnumProcessModules cannot be a null pointer (0) even if you are just trying to determine the number of entries needed.
I figured out the issue. I was interacting with the EnumProcessModules incorrectly. Here's a working function:
func getBaseAddress(handle uintptr) int64 {
// GetProcessImageFileNameA
var imageFileName [200]byte
var fileSize uint32 = 200
var fileName string
ret, _, _ := procGetProcessImageFileNameA.Call(handle, uintptr(unsafe.Pointer(&imageFileName)), uintptr(fileSize))
for _, char := range imageFileName {
if char == 0 {
break
}
fileName += string(char)
}
fileName = fileName[24:]
// EnumProcessModules
moduleHandles := make([]uintptr, 1024)
var needed int32
const handleSize = unsafe.Sizeof(moduleHandles[0])
ret, _, _ = procEnumProcessModules.Call(uintptr(handle), uintptr(unsafe.Pointer(&moduleHandles[0])), handleSize*uintptr(len(moduleHandles)), uintptr(unsafe.Pointer(&needed)))
// GetModuleFileNameExA
var finalModuleHandle uintptr
for _, moduleHandle := range moduleHandles {
if moduleHandle > 0 {
var moduleFileName [200]byte
var moduleSize uint32 = 200
var moduleName string
ret, _, _ = procGetModuleFileNameExA.Call(handle, uintptr(moduleHandle), uintptr(unsafe.Pointer(&moduleFileName)), uintptr(moduleSize))
if ret != 0 {
for _, char := range moduleFileName {
if char == 0 {
break
}
moduleName += string(char)
}
moduleName = moduleName[3:]
if moduleName == fileName {
finalModuleHandle = uintptr(moduleHandle)
break
}
}
}
}
return int64(finalModuleHandle)
}
I hope this can help someone, I spent a lot of time working on this function.