How can I retrieve an image data buffer from clipboard memory (uintptr)?

818 views Asked by At

I'm trying to use syscall with user32.dll to get the contents of the clipboard. I expect it to be image data from a Print Screen.

Right now I've got this:

if opened := openClipboard(0); !opened {
    fmt.Println("Failed to open Clipboard")
}

handle := getClipboardData(CF_BITMAP)

// get buffer

img, _, err := Decode(buffer)

I need to get the data into a readable buffer using the handle.

I've had some inspiration from AllenDang/w32 and atotto/clipboard on github. The following would work for text, based on atotto's implementation:

text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(handle))[:])

But how can I get a buffer containing image data I can decode?

[Update]

Going by the solution @kostix provided, I hacked together a half working example:

image.RegisterFormat("bmp", "bmp", bmp.Decode, bmp.DecodeConfig)

if opened := w32.OpenClipboard(0); opened == false {
    fmt.Println("Error: Failed to open Clipboard")
}

//fmt.Printf("Format: %d\n", w32.EnumClipboardFormats(w32.CF_BITMAP))
handle := w32.GetClipboardData(w32.CF_DIB)
size := globalSize(w32.HGLOBAL(handle))
if handle != 0 {
    pData := w32.GlobalLock(w32.HGLOBAL(handle))
    if pData != nil {
        data := (*[1 << 25]byte)(pData)[:size]
        // The data is either in DIB format and missing the BITMAPFILEHEADER
        // or there are other issues since it can't be decoded at this point
        buffer := bytes.NewBuffer(data)
        img, _, err := image.Decode(buffer)
        if err != nil {
            fmt.Printf("Failed decoding: %s", err)
            os.Exit(1)
        }

        fmt.Println(img.At(0, 0).RGBA())
    }

    w32.GlobalUnlock(w32.HGLOBAL(pData))
}
w32.CloseClipboard()

AllenDang/w32 contains most of what you'd need, but sometimes you need to implement something yourself, like globalSize():

var (
    modkernel32    = syscall.NewLazyDLL("kernel32.dll")
    procGlobalSize = modkernel32.NewProc("GlobalSize")
)

func globalSize(hMem w32.HGLOBAL) uint {
    ret, _, _ := procGlobalSize.Call(uintptr(hMem))

    if ret == 0 {
        panic("GlobalSize failed")
    }

    return uint(ret)
}

Maybe someone will come up with a solution to get the BMP data. In the meantime I'll be taking a different route.

1

There are 1 answers

0
kostix On BEST ANSWER

@JimB is correct: user32!GetClipboardData() returns a HGLOBAL, and a comment example over there suggests using kernel32!GlobalLock() to a) globally lock that handle, and b) yield a proper pointer to the memory referred to by it.

You will need to kernel32!GlobalUnlock() the handle after you're done with it.

As to converting pointers obtained from Win32 API functions to something readable by Go, the usual trick is casting the pointer to an insanely large slice. To cite the "Turning C arrays into Go slices" of "the Go wiki article on cgo":

To create a Go slice backed by a C array (without copying the original data), one needs to acquire this length at runtime and use a type conversion to a pointer to a very big array and then slice it to the length that you want (also remember to set the cap if you're using Go 1.2 > or later), for example (see http://play.golang.org/p/XuC0xqtAIC for a runnable example):

import "C"
import "unsafe"
...
var theCArray *C.YourType = C.getTheArray()
length := C.getTheArrayLength()
slice := (*[1 << 30]C.YourType)(unsafe.Pointer(theCArray))[:length:length]

It is important to keep in mind that the Go garbage collector will not interact with this data, and that if it is freed from the C side of things, the behavior of any Go code using the slice is nondeterministic.

In your case it will be simpler:

h := GlobalLock()
defer GlobalUnlock(h)
length := somehowGetLengthOfImageInTheClipboard()
slice := (*[1 << 30]byte)(unsafe.Pointer((uintptr(h)))[:length:length]

Then you need to actually read the bitmap.

This depends on the format of the Device-Independent Bitmap (DIB) available for export from the clipboard.

See this and this for a start.

As usually, definitions of BITMAPINFOHEADER etc are easily available online in the MSDN site.