Showing posts with label Go. Show all posts
Showing posts with label Go. Show all posts

Wednesday, April 24, 2024

Goodbye Go

On this morning, I just completely wiped away the Go compiler from my computer after finishing the Rust rewritten of my very useful ID3v2 editor, then written in Go. Now I have no tools written in Go anymore.

I consider I spent way too much time banging my head against the wall writing Windigo, trying to write a decent API. Go is way too limited, its lack of expressiveness won’t allow you to write any decent API more complex than a simple CRUD. And Win32 is hard. Windigo reached a point where there was nothing else I could do to make the API better – it was so bad it actually pushed me back to C++, where all my uneasiness started.

Another upsetting fact I noticed is that Go programmers tend to be unexperienced people. Beginners with trivial questions. Very demotivating.

I think anyone talking to me at this point would label me a “Go hater”. And as such, I can no more invest my scarce time in it. I remember in the early Windigo days I was completely in love with Go, with its first lines being written in my then girlfriend’s laptop. Yes, I installed the compiler there, and I don’t remember if it’s still there.

I plan to leave Windigo repo, and all its open issues, abandoned for a while. Maybe someone forks it and keeps it going. In the future, I will archive it, and I hope I never have to write Go ever again.

And that’s a shame, because “Windigo” is one of the coolest project names I ever came up with.

Monday, May 9, 2022

Taking a screenshot in Windows with Go

I’ve tried to code a Windows screenshot utility before, following the example of the official Windows documentation I found it tricky though, so I just gave up at the time.

This Monday morning, after receiving a request to implement GetDIBits in WinSafe, I tried to implement it Windigo first. To my surprise, it went incredibly smooth. Go’s defer mechanism is much to praise.

Here’s the whole code

package main

import (
	"runtime"
	"unsafe"

	"github.com/rodrigocfd/windigo/win"
	"github.com/rodrigocfd/windigo/win/co"
)

func main() {
	runtime.LockOSThread()

	cxScreen := win.GetSystemMetrics(co.SM_CXSCREEN)
	cyScreen := win.GetSystemMetrics(co.SM_CYSCREEN)

	hdcScreen := win.HWND(0).GetDC()
	defer win.HWND(0).ReleaseDC(hdcScreen)

	hBmp := hdcScreen.CreateCompatibleBitmap(cxScreen, cyScreen)
	defer hBmp.DeleteObject()

	hdcMem := hdcScreen.CreateCompatibleDC()
	defer hdcMem.DeleteDC()

	hBmpOld := hdcMem.SelectObjectBitmap(hBmp)
	defer hdcMem.SelectObjectBitmap(hBmpOld)

	hdcMem.BitBlt(
		win.POINT{X: 0, Y: 0},
		win.SIZE{Cx: cxScreen, Cy: cyScreen},
		hdcScreen,
		win.POINT{X: 0, Y: 0},
		co.ROP_SRCCOPY,
	)

	bi := win.BITMAPINFO{
		BmiHeader: win.BITMAPINFOHEADER{
			BiWidth:       cxScreen,
			BiHeight:      cyScreen,
			BiPlanes:      1,
			BiBitCount:    32,
			BiCompression: co.BI_RGB,
		},
	}
	bi.BmiHeader.SetBiSize()

	bmpObj := win.BITMAP{}
	hBmp.GetObject(&bmpObj)
	bmpSize := bmpObj.CalcBitmapSize(bi.BmiHeader.BiBitCount)

	rawMem := win.GlobalAlloc(co.GMEM_FIXED|co.GMEM_ZEROINIT, bmpSize)
	defer rawMem.GlobalFree()

	bmpSlice := rawMem.GlobalLock(bmpSize)
	defer rawMem.GlobalUnlock()

	hdcScreen.GetDIBits(hBmp, 0, int(cyScreen), bmpSlice, &bi, co.DIB_RGB_COLORS)

	bfh := win.BITMAPFILEHEADER{}
	bfh.SetBfType()
	bfh.SetBfOffBits(uint32(unsafe.Sizeof(bfh) + unsafe.Sizeof(bi.BmiHeader)))
	bfh.SetBfSize(bfh.BfOffBits() + uint32(bmpSize))

	fo, _ := win.FileOpen("C:\\Temp\\foo.bmp", co.FILE_OPEN_RW_OPEN_OR_CREATE)
	defer fo.Close()

	fo.Write(bfh.Serialize())
	fo.Write(bi.BmiHeader.Serialize())
	fo.Write(bmpSlice)

	println("Done")
}

For reference, this example is now on GetDIBits documentation.

Tuesday, November 9, 2021

Stateful iterators in Go

I’m writing an insane amount of Go code lately. It’s really unobtrusive, allowing you focus on the problem you have to solve. And I’ve been dabbling with iterators in Go for a while, since I started to used them in Rust – which is so much better than C++ in this regard.

Today, when researching the topic again, I found a very nice article on the matter, exploring a few possibilities. The most performant approach is a stateful iterator, which can be implemented as:

type IterFive struct {
	count int
}

func NewIterFive() IterFive {
	return Iter{}
}

func (it *IterFive) Value() int {
	it.count++
	return it.count - 1
}

func (it *IterFive) Ok() bool {
	return it.count < 5
}

func main() {
	for iter := NewIterFive(); iter.Ok(); {
		println(iter.Value())
	}
}

I’m still unsure whether I should adopt interators instead of simply allocating and returning slices for some functions, but I’m considering it. The main reason would be performance, but it could be qualified as premature in the cases I’m dealing with in Windigo.

Saturday, May 29, 2021

Sum types in Go

One thing I always missed in Go was sum types. I’ve seen some discussion before. Although Go doesn’t have this as an explicit, native feature, I found a pattern that suits my needs by defining types and interfaces.

In the example below, I simulate a function that would load application resources, which can be extracted by ID, position or a string identifier. First, we define the interface and the subtypes:

type (
	// Variant type for: ResId, ResPos, ResStr.
	Res interface{ implRes() }

	ResId  uint32
	ResPos uint32
	ResStr string
)

func (ResId)  implRes() {}
func (ResPos) implRes() {}
func (ResStr) implRes() {}

The isRes() function acts like a “tag” for each subtype.

Now, a function that receives the variant type:

func LoadResource(variant Res) {
	switch v := variant.(type) {
	case ResId:
		println("ID", uint32(v))
	case ResPos:
		println("Position", uint32(v))
	case ResStr:
		println("String", string(v))
	default:
		panic("Res does not accept a nil value.")
	}
}

Usage is now trivial:

LoadResource(ResId(2001))
LoadResource(ResPos(4))
LoadResource(ResStr("MY_ICON"))

It’s clean and it works remarkably well. I applied this technique in my Windigo library.

Thursday, February 18, 2021

Using local dependencies with Go modules

Go 1.16, released this week, deprecated GOPATH. I used it extensively to develop my libs before publishing them, but now I’m forced to convert them to modules, which work with remote repos by default. However, there’s a way to work with local dependencies.

As I found here, we can instruct Go toolchain to search for a local repo, instead of a remote one by using this command:

go mod edit -replace github.com/username/repo=../repo

This changes the go.mod file. Now, to clean up the go.sum file, run:

go mod tidy

After that, you should be able to use a local dependency just like the old GOPATH days.

The downloaded files are still cached, though. To finally clean the entire cache, run:

go clean -cache -modcache

Modules eventually needed by other applications will be downloaded again when due.

Monday, June 15, 2020

Embedding rc files into Go Win32 executables

When building Go executables in Windows, the Go toolchain recognizes *.syso files and automatically embeds then into the *.exe – this allows the embedding of resources scripts, which can contain icon and manifest, among other resources. Visual C++ resource compiler, however, only generate *.res files.

I’ve seen people using rsrc tool to add manifest files to executables. The problem is that this tool doesn’t really compile a resource file, nor it converts *.res into *.syso.

Turns out resource file compilation can be done with windres tool, from MinGW package. Since I already have the whole Visual C++ stuff, I didn’t want to install all the package, which is rather big, and I already have MinGW terminal from Git. Fortunately, windres works standalone, the only requisite is that gcc is in the same directory. And we can download both.

Portable MinGW tools can be downloaded from here:

From both packages, extract gcc and windres and save them somewhere else. All the rest can be deleted.

Compile the *.rc file into *.res with Visual C++ Developer Power Shell for VS 2019:

rc /r my-resource.rc

Then, on MinGW prompt, convert *.res into *.syso:

windres -i my-resource.res -o my-resource.syso

Now place my-resource.syso in the same directory of your main.go, and that’s it. For some weird reason, when debugging in VSCode, the program icon may appear wrong, but it will show fine in release builds, which are the ones that really matter:

go build -ldflags "-s -w -H=windowsgui"

Ideally windres would be used to compile the *.rc directly into *.syso, but to do so it needs the Win32 headers from MinGW, which are not present – you’ll get an error. Probably there’s a way to point Visual C++ headers directory to windres, but by now I’m satisfied using windres tool just to convert the *.res.

Wednesday, October 16, 2019

Let Go website generate your documentation

When trying to figure out how go doc command works, I just found out that I don’t need to use it.

There’s an incredible feature of Go documentation website: it can generate and display the documentation of all your project, straight from GitHub – and other websites too. The generated HTML is very polished.

To use it, append your GitHub user name and repo to their URL, such as:

https://godoc.org/github.com/username/reponame

Even more interesting: all types and Go built-in types are automatically linked. And it also provides direct links to GitHub source files. On top of all that, it’s very fast. Written in Go, I suppose.

Being a C language admirer, the more I work with Go, the more I like its simplicity.

Wednesday, October 9, 2019

Using Go modules in separated local directories

Suppose I have a Go project, “foo”, which writes to the console. In this project, I have some .go files which have all the functions that actually write to the console.

Now I want to make these writing routines a separated module: “writer”, in another directory. I want two sibling directories: “foo” and “writer”. The “writer” will be used as a dependency within “foo”.

Within “writers” folder, run go mod init writer to create the go.mod file. Back in “foo”, edit its go.mod adding the new dependency:

module Foo

go 1.13.1

require (
  shoutergo v0.0.0
)

replace (
  shoutergo => ../shoutergo
)

The replace line will inform Go where to look for the module. Without it, Go will search in the internet, that’s why GitHub URLs work right away.

The version of the dependency, as far as I could find, is a Git tag, and to local modules it’s completely irrelevant, although necessary.

Now, in “foo”, we can import “writer” just like any other package:

import (
  "writer"
)

func main() {
  writer.SomeFunc()
}

As I keep learning Go and exploring the limits of the language, although I’m not entirely satisfied – lack of enums and namespaces –, I’m very pleased with its design choices with opted for simplicity. Feels like a better C.

Thursday, October 3, 2019

How Go caches downloaded packages

Here I intend to summarize how the Go package cache system works, after spending half of my afternoon running tests and figuring out this weird thing.

First off, to create a new Go project outside the awful GOPATH directory, you run:

mkdir my-project
cd my-project
go mod init my-project

This will create a go.mod file.

To add a dependency from GitHub, you run:

go get github.com/username/reponame

This will create a go.sum file, which contains the dependency tree. The package files themselves will be downloaded and buried within ~/go/pkg/mod directory, so they are effectively cached at global, machine-wide scope.

To list the packages inside my-project, run:

go list ./...

This won’t list globally downloaded, cached dependencies installed with go get.

To list the packages installed (not the cached ones) globally, run:

go list ...

Downloaded dependencies will be listed if you are in my-project folder, and if they have been downloaded already. If you run the command above from within a project directory, it will download and install any missing packages for current project, and then they will be listed.

So, if you’re outside my-project directory, there’s no way to list globally cached packages.

There’s no way to remove a globally cached package from cache individually. All you can do is delete the whole cache, with:

go clean -cache -modcache -i -r