如何从剪贴板内存(uintptr)中检索图像数据缓冲区?

huangapple go评论80阅读模式
英文:

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

问题

我正在尝试使用user32.dll中的syscall来获取剪贴板的内容。我期望它是来自Print Screen的图像数据。

目前我有以下代码:

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

handle := getClipboardData(CF_BITMAP)

// 获取缓冲区

img, _, err := Decode(buffer)

我需要使用句柄将数据读入可读的缓冲区。

我从GitHub上的AllenDang/w32和atotto/clipboard中得到了一些启发。以下代码可以用于文本,基于atotto的实现:

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

但是,我如何获取包含可解码的图像数据的缓冲区呢?

【更新】

根据@kostix提供的解决方案,我拼凑了一个半工作的示例:

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]
        // 数据可能是DIB格式并且缺少BITMAPFILEHEADER,或者存在其他问题,因此此时无法解码
        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包含了大部分你所需的内容,但有时你需要自己实现一些东西,比如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)
}

也许有人会提出解决方案来获取BMP数据。与此同时,我将尝试其他方法。

英文:

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(&quot;Failed to open Clipboard&quot;)
}

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 &lt;&lt; 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(&quot;bmp&quot;, &quot;bmp&quot;, bmp.Decode, bmp.DecodeConfig)

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

//fmt.Printf(&quot;Format: %d\n&quot;, 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 &lt;&lt; 25]byte)(pData)[:size]
        // The data is either in DIB format and missing the BITMAPFILEHEADER
        // or there are other issues since it can&#39;t be decoded at this point
		buffer := bytes.NewBuffer(data)
		img, _, err := image.Decode(buffer)
		if err != nil {
			fmt.Printf(&quot;Failed decoding: %s&quot;, 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(&quot;kernel32.dll&quot;)
    procGlobalSize = modkernel32.NewProc(&quot;GlobalSize&quot;)
)

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

    if ret == 0 {
	    panic(&quot;GlobalSize failed&quot;)
    }

    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

得分: 1

@JimB是正确的:user32!GetClipboardData()返回一个HGLOBAL,并且这里的一个注释示例建议使用kernel32!GlobalLock()来a)全局锁定该句柄,并b)生成指向其所引用的内存的正确指针。

在使用完之后,你需要使用kernel32!GlobalUnlock()解锁句柄。

至于将从Win32 API函数获取的指针转换为Go可读的内容,通常的技巧是将指针转换为一个非常大的切片。引用“the Go wiki article on cgo”中的“Turning C arrays into Go slices”:

要创建一个由C数组支持的Go切片(而不是复制原始数据),需要在运行时获取此长度,并使用类型转换为指向非常大数组的指针,然后将其切片到所需的长度(如果使用Go 1.2或更高版本,请记得设置cap),例如(参见http://play.golang.org/p/XuC0xqtAIC的可运行示例):

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

需要牢记的是,Go垃圾回收器不会与此数据交互,如果它从C端释放,使用切片的任何Go代码的行为是不确定的。

在你的情况下,会更简单:

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

然后你需要实际读取位图。

这取决于从剪贴板中可导出的设备无关位图(DIB)的格式。

可以参考这个这个作为起点。

通常情况下,BITMAPINFOHEADER等的定义在MSDN网站上很容易找到。

英文:

@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 &lt;&lt; 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.

huangapple
  • 本文由 发表于 2016年12月19日 18:31:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/41220587.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定