将结构数组从 C 传递到 Golang
Pass Array of Structs from C to Golang
目标:
- 使用 cgo 将给定
struct
的数组从 C 发送到 Golang。
工作代码(无数组)
免责声明:这是我的第一个功能性 C 代码,可能有问题。
GetPixel.c
#include "GetPixel.h"
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
Display *display;
XImage *im;
void open_display()
{
// xlib: must be called before any other X* methods, enables multithreading for this client
XInitThreads();
// save display in a global variable so we don't allocate it per `get_pixel`
// TODO: figure out classes later (or are these C++ only? Can I use them in Golang?)
display = XOpenDisplay((char *) NULL);
}
void close_display()
{
// xlib: use XCloseDisplay instead of XFree or free for Display objects
XCloseDisplay(display);
}
void create_image(int x, int y, int w, int h)
{
// save image in a global variable so we don't allocate it per `get_pixel`
im = XGetImage(display, XRootWindow(display, XDefaultScreen(display)), x, y, w, h, AllPlanes, XYPixmap);
}
void destroy_image()
{
// xlib: use XDestroyImage instead of XFree or free for XImage objects
XDestroyImage(im);
}
void get_pixel(struct Colour3 *colour, int x, int y)
{
// TODO: could I return `c` without converting it to my struct?
XColor c;
c.pixel = XGetPixel(im, x, y);
XQueryColor(display, XDefaultColormap(display, XDefaultScreen(display)), &c);
// xlib: stored as values 0-65536
colour->r = c.red / 256;
colour->g = c.green / 256;
colour->b = c.blue / 256;
}
GetPixel.h
# Trial and Error:
# - Golang needs me to define crap in an H file
# - C needs me to define my struct in an H file
# - C needs me to use `typedef` and name my struct twice (???)
#ifndef __GETPIXEL_
#define __GETPIXEL_
typedef struct Colour3 {
int r, g, b ;
} Colour3 ; # redundant?
void open_display();
void close_display();
void create_image(int x, int y, int w, int h);
void destroy_image();
void get_pixel(struct Colour3 *colour, int x, int y);
#endif
GetPixel.go
package x11util
func Screenshot(sx, sy, w, h int, filename string) {
img := image.NewRGBA(image.Rectangle{
image.Point{sx, sy}, image.Point{w, h},
})
defer trace(sx, sy, w, h, filename)(img)
C.open_display()
C.create_image(C.int(sx), C.int(sy), C.int(w), C.int(h))
defer func() {
# does this work?
C.destroy_image()
C.close_display()
}()
# Trial and Error
# - C needs me to pass a pointer to a struct
p := C.Colour3{}
for x := sx; x < w; x++ {
for y := sy; y < h; y++ {
C.get_pixel(&p, C.int(x), C.int(y))
img.Set(x, y, color.RGBA{uint8(p.r), uint8(p.g), uint8(p.b), 255})
}
}
f, err := os.Create(filename)
if err != nil {
log.Error("unable to save screenshot", "filename", filename, "error", err)
}
defer f.Close()
png.Encode(f, img)
}
当然可以,但对于 1080p 屏幕需要 30 到 55 秒。
尝试 2
所有现有代码,但不是 get_pixel
,让我们尝试一次获取 (3x3) 9 像素的补丁,作为优化。
GetPixel.c
# ... existing code ...
struct Colour3* get_pixel_3x3(int sx, int sy)
{
XColor c;
# the internet collectively defines this as "a way to define C arrays where the data remains after the function returns"
struct Colour3* pixels = (struct Colour3 *)malloc(9 * sizeof(Colour3));
for(int x=sx; x<sx+3; ++x)
{
for(int y=sy; y<sy+3; ++y)
{
# ... existing code from Attempt 1 ...
// Is this even the correct way to into C arrays?
pixels++;
}
}
return pixels;
}
GetPixel.h
# ... existing code ...
struct Colour3* get_pixel_3x3(int sx, int sy);
GetPixel.go
package x11util
func ScreenshotB(sx, sy, w, h int, filename string) {
# ... existing code ...
var i int
for x := sx; x < w; x += 3 {
for y := sy; y < h; y += 3 {
// returns an array of exactly 9 pixels
p := C.get_pixel_3x3(C.int(x), C.int(y))
// unsafely convert to an array we can use -- at least, this was supposed to work, but never did
// pb := (*[9]C.Colour3)(unsafe.Pointer(&p))
// convert to a slice we can use... with reflect -- this code is magic, have no idea how it works.
var pa []C.Colour3
sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&pa)))
sliceHeader.Cap = 9
sliceHeader.Len = 9
sliceHeader.Data = uintptr(unsafe.Pointer(&p))
// assign pixels from base (adding an offset 0-2) to print 3x3 blocks
for xo := 0; xo < 3; xo++ {
for yo := 0; yo < 3; yo++ {
img.Set(x+xo, y+yo, color.RGBA{uint8(pa[i].r), uint8(pa[i].g), uint8(pa[i].b), 255})
i++
}
}
i = 0
}
}
# ... existing code ...
}
这减少了 20% 的执行时间,优化确实存在——但是:生成的图像是一团乱七八糟的粉色或黄色像素,而不是预期的结果。这让我相信我正在阅读随机记忆而不是我的意图。
因为我知道读取单个像素有效并且 C 循环有效,我只能认为我完全误解了 C 中的数组,或者如何将它们传递给 Golang,或者如何 read/iterate在 Golang 中。
在这一点上,我不知道还能尝试什么,四页的 Stack Overflow 和 20 多页的谷歌搜索给了我很多关于另一个方向 (Go->C) 的不同答案——但是这里不是很多。我也找不到 C.GoBytes
的示例。
尝试 3
让 Golang 处理分配简化了对数组的访问。此代码现在适用于 3x3,但在尝试获取 "the whole screen at once" 时失败(参见 Attempt 4)
GetPixel.c
# ... existing code from Attempt 1 ...
# out-param instead of a return variable, let Golang allocate
void get_pixel_3x3(struct Colour3 *pixels, int sx, int sy)
{
XColor c;
for(int x=sx; x<sx+3; ++x)
{
for(int y=sy; y<sy+3; ++y)
{
# ... existing code from Attempt 2 ...
}
}
}
GetPixel.h
# ... existing code from Attempt 1 ...
void get_pixel_3x3(struct Colour3* pixels, int sx, int sy);
GetPixel.go
package x11util
func ScreenshotB(sx, sy, w, h int, filename string) {
# ... existing code from Attempt 1 ...
var i int
var p C.Colour3 // why does this even work?
for x := sx; x < w; x += 3 {
for y := sy; y < h; y += 3 {
// returns an array of 9 pixels
C.get_pixel_3x3(&p, C.int(x), C.int(y))
// unsafely convert to an array we can use
pb := (*[9]C.Colour3)(unsafe.Pointer(&p))
pa := pb[:] // seems to be required?
// assign pixels from base (adding an offset 0-2) to print 3x3 blocks
for xo := 0; xo < 3; xo++ {
for yo := 0; yo < 3; yo++ {
# ... existing code from Attempt 1 ...
}
}
i = 0
}
}
# ... existing code from Attempt 1 ...
}
尝试 4
导致分段违规 (SEGFAULT)
GetPixel.c
# ... existing code from Attempt 1 ...
void get_pixel_arbitrary(struct Colour3 *pixels, int sx, int sy, int w, int h)
{
XColor c;
for(int x=sx; x<sx+w; ++x)
{
for(int y=sy; y<sy+h; ++y)
{
# ... existing code from Attempt 3 ...
}
}
}
GetPixel.h
# ... existing code from Attempt 1 ...
void get_pixel_arbitrary(struct Colour3 *pixels, int sx, int sy, int w, int h);
GetPixel.go
package x11util
func ScreenshotC(sx, sy, w, h int, filename string) {
# ... existing code from Attempt 1 ...
var p C.Colour3 // I'm sure this is the culprit
// returns an array of "all the screens"
// 240x135x3x8 = 777600 (<768KB)
C.get_pixel_arbitrary(&p, 0, 0, C.int(w), C.int(h)) // segfault here
// unsafely convert to an array we can use
pb := (*[1 << 30]C.Colour3)(unsafe.Pointer(&p)) // internet showed this magic 1<<30 is required because Golang won't make an array with an unknown length
pa := pb[:w*h] // not sure if this is correct, but it doesn't matter yet, we don't get this far (segfault happens above.)
// assign pixels from base (adding an offset 0-2) to print 3x3 blocks
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
# ... existing code from Attempt 1 ...
}
}
# ... existing code from Attempt 1 ...
}
尝试 5
GetPixel.c
struct Colour3* get_pixel_arbitrary(int sx, int sy, int w, int h)
{
XColor c;
struct Colour3* pixels = (struct Colour3 *)malloc(w*h * sizeof(Colour3));
struct Colour3* start = pixels;
for(int x=sx; x<sx+w; ++x)
{
for(int y=sy; y<sy+h; ++y)
{
c.pixel = XGetPixel(im, x, y);
XQueryColor(display, XDefaultColormap(display, XDefaultScreen(display)), &c);
pixels->r = c.red / 256;
pixels->g = c.green / 256;
pixels->b = c.blue / 256;
pixels++;
}
}
return start;
}
GetPixel.go
p := C.get_pixel_arbitrary(0, 0, C.int(w), C.int(h))
// unsafely convert to an array we can use
pb := (*[1 << 30]C.Colour3)(unsafe.Pointer(&p))
pa := pb[: w*h : w*h] // magic :len:len notation shown in docs but not explained? (if [start:end] then [?:?:?])
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
img.Set(x, y, color.RGBA{uint8(pa[i].r), uint8(pa[i].g), uint8(pa[i].b), 255})
i++
}
}
// assume I should be freeing in C instead of here?
C.free(unsafe.Pointer(p))
这会产生一个 "stretched mess" 然后我溢出(现在我不得不假设我在 C 端又做错了,除非在提到我应该分配时这是完全错误的想法回到 C?)
此时我非常愿意接受带有示例的指南作为答案,因为我确实遗漏了一些东西——但是,当谷歌搜索(甚至在 GitHub 上)寻找示例时其中,我找不到任何。我很乐意获取该线程的结果并做到这一点 :)。
这里的大部分失败都集中在对返回的 C 数组使用 unsafe.Pointer(&p)
上。由于 C 数组是指针,因此 p
已经是 *C.Colour3
类型。使用 &p
试图使用 p
变量本身的地址,它可以在内存中的任何地方。
正确的转换形式如下:
pa := (*[1 << 30]C.Colour3)(unsafe.Pointer(p))[:w*h:w*h]
另见
目标:
- 使用 cgo 将给定
struct
的数组从 C 发送到 Golang。
工作代码(无数组)
免责声明:这是我的第一个功能性 C 代码,可能有问题。
GetPixel.c
#include "GetPixel.h"
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
Display *display;
XImage *im;
void open_display()
{
// xlib: must be called before any other X* methods, enables multithreading for this client
XInitThreads();
// save display in a global variable so we don't allocate it per `get_pixel`
// TODO: figure out classes later (or are these C++ only? Can I use them in Golang?)
display = XOpenDisplay((char *) NULL);
}
void close_display()
{
// xlib: use XCloseDisplay instead of XFree or free for Display objects
XCloseDisplay(display);
}
void create_image(int x, int y, int w, int h)
{
// save image in a global variable so we don't allocate it per `get_pixel`
im = XGetImage(display, XRootWindow(display, XDefaultScreen(display)), x, y, w, h, AllPlanes, XYPixmap);
}
void destroy_image()
{
// xlib: use XDestroyImage instead of XFree or free for XImage objects
XDestroyImage(im);
}
void get_pixel(struct Colour3 *colour, int x, int y)
{
// TODO: could I return `c` without converting it to my struct?
XColor c;
c.pixel = XGetPixel(im, x, y);
XQueryColor(display, XDefaultColormap(display, XDefaultScreen(display)), &c);
// xlib: stored as values 0-65536
colour->r = c.red / 256;
colour->g = c.green / 256;
colour->b = c.blue / 256;
}
GetPixel.h
# Trial and Error:
# - Golang needs me to define crap in an H file
# - C needs me to define my struct in an H file
# - C needs me to use `typedef` and name my struct twice (???)
#ifndef __GETPIXEL_
#define __GETPIXEL_
typedef struct Colour3 {
int r, g, b ;
} Colour3 ; # redundant?
void open_display();
void close_display();
void create_image(int x, int y, int w, int h);
void destroy_image();
void get_pixel(struct Colour3 *colour, int x, int y);
#endif
GetPixel.go
package x11util
func Screenshot(sx, sy, w, h int, filename string) {
img := image.NewRGBA(image.Rectangle{
image.Point{sx, sy}, image.Point{w, h},
})
defer trace(sx, sy, w, h, filename)(img)
C.open_display()
C.create_image(C.int(sx), C.int(sy), C.int(w), C.int(h))
defer func() {
# does this work?
C.destroy_image()
C.close_display()
}()
# Trial and Error
# - C needs me to pass a pointer to a struct
p := C.Colour3{}
for x := sx; x < w; x++ {
for y := sy; y < h; y++ {
C.get_pixel(&p, C.int(x), C.int(y))
img.Set(x, y, color.RGBA{uint8(p.r), uint8(p.g), uint8(p.b), 255})
}
}
f, err := os.Create(filename)
if err != nil {
log.Error("unable to save screenshot", "filename", filename, "error", err)
}
defer f.Close()
png.Encode(f, img)
}
当然可以,但对于 1080p 屏幕需要 30 到 55 秒。
尝试 2
所有现有代码,但不是 get_pixel
,让我们尝试一次获取 (3x3) 9 像素的补丁,作为优化。
GetPixel.c
# ... existing code ...
struct Colour3* get_pixel_3x3(int sx, int sy)
{
XColor c;
# the internet collectively defines this as "a way to define C arrays where the data remains after the function returns"
struct Colour3* pixels = (struct Colour3 *)malloc(9 * sizeof(Colour3));
for(int x=sx; x<sx+3; ++x)
{
for(int y=sy; y<sy+3; ++y)
{
# ... existing code from Attempt 1 ...
// Is this even the correct way to into C arrays?
pixels++;
}
}
return pixels;
}
GetPixel.h
# ... existing code ...
struct Colour3* get_pixel_3x3(int sx, int sy);
GetPixel.go
package x11util
func ScreenshotB(sx, sy, w, h int, filename string) {
# ... existing code ...
var i int
for x := sx; x < w; x += 3 {
for y := sy; y < h; y += 3 {
// returns an array of exactly 9 pixels
p := C.get_pixel_3x3(C.int(x), C.int(y))
// unsafely convert to an array we can use -- at least, this was supposed to work, but never did
// pb := (*[9]C.Colour3)(unsafe.Pointer(&p))
// convert to a slice we can use... with reflect -- this code is magic, have no idea how it works.
var pa []C.Colour3
sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&pa)))
sliceHeader.Cap = 9
sliceHeader.Len = 9
sliceHeader.Data = uintptr(unsafe.Pointer(&p))
// assign pixels from base (adding an offset 0-2) to print 3x3 blocks
for xo := 0; xo < 3; xo++ {
for yo := 0; yo < 3; yo++ {
img.Set(x+xo, y+yo, color.RGBA{uint8(pa[i].r), uint8(pa[i].g), uint8(pa[i].b), 255})
i++
}
}
i = 0
}
}
# ... existing code ...
}
这减少了 20% 的执行时间,优化确实存在——但是:生成的图像是一团乱七八糟的粉色或黄色像素,而不是预期的结果。这让我相信我正在阅读随机记忆而不是我的意图。
因为我知道读取单个像素有效并且 C 循环有效,我只能认为我完全误解了 C 中的数组,或者如何将它们传递给 Golang,或者如何 read/iterate在 Golang 中。
在这一点上,我不知道还能尝试什么,四页的 Stack Overflow 和 20 多页的谷歌搜索给了我很多关于另一个方向 (Go->C) 的不同答案——但是这里不是很多。我也找不到 C.GoBytes
的示例。
尝试 3
让 Golang 处理分配简化了对数组的访问。此代码现在适用于 3x3,但在尝试获取 "the whole screen at once" 时失败(参见 Attempt 4)
GetPixel.c
# ... existing code from Attempt 1 ...
# out-param instead of a return variable, let Golang allocate
void get_pixel_3x3(struct Colour3 *pixels, int sx, int sy)
{
XColor c;
for(int x=sx; x<sx+3; ++x)
{
for(int y=sy; y<sy+3; ++y)
{
# ... existing code from Attempt 2 ...
}
}
}
GetPixel.h
# ... existing code from Attempt 1 ...
void get_pixel_3x3(struct Colour3* pixels, int sx, int sy);
GetPixel.go
package x11util
func ScreenshotB(sx, sy, w, h int, filename string) {
# ... existing code from Attempt 1 ...
var i int
var p C.Colour3 // why does this even work?
for x := sx; x < w; x += 3 {
for y := sy; y < h; y += 3 {
// returns an array of 9 pixels
C.get_pixel_3x3(&p, C.int(x), C.int(y))
// unsafely convert to an array we can use
pb := (*[9]C.Colour3)(unsafe.Pointer(&p))
pa := pb[:] // seems to be required?
// assign pixels from base (adding an offset 0-2) to print 3x3 blocks
for xo := 0; xo < 3; xo++ {
for yo := 0; yo < 3; yo++ {
# ... existing code from Attempt 1 ...
}
}
i = 0
}
}
# ... existing code from Attempt 1 ...
}
尝试 4
导致分段违规 (SEGFAULT)
GetPixel.c
# ... existing code from Attempt 1 ...
void get_pixel_arbitrary(struct Colour3 *pixels, int sx, int sy, int w, int h)
{
XColor c;
for(int x=sx; x<sx+w; ++x)
{
for(int y=sy; y<sy+h; ++y)
{
# ... existing code from Attempt 3 ...
}
}
}
GetPixel.h
# ... existing code from Attempt 1 ...
void get_pixel_arbitrary(struct Colour3 *pixels, int sx, int sy, int w, int h);
GetPixel.go
package x11util
func ScreenshotC(sx, sy, w, h int, filename string) {
# ... existing code from Attempt 1 ...
var p C.Colour3 // I'm sure this is the culprit
// returns an array of "all the screens"
// 240x135x3x8 = 777600 (<768KB)
C.get_pixel_arbitrary(&p, 0, 0, C.int(w), C.int(h)) // segfault here
// unsafely convert to an array we can use
pb := (*[1 << 30]C.Colour3)(unsafe.Pointer(&p)) // internet showed this magic 1<<30 is required because Golang won't make an array with an unknown length
pa := pb[:w*h] // not sure if this is correct, but it doesn't matter yet, we don't get this far (segfault happens above.)
// assign pixels from base (adding an offset 0-2) to print 3x3 blocks
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
# ... existing code from Attempt 1 ...
}
}
# ... existing code from Attempt 1 ...
}
尝试 5
GetPixel.c
struct Colour3* get_pixel_arbitrary(int sx, int sy, int w, int h)
{
XColor c;
struct Colour3* pixels = (struct Colour3 *)malloc(w*h * sizeof(Colour3));
struct Colour3* start = pixels;
for(int x=sx; x<sx+w; ++x)
{
for(int y=sy; y<sy+h; ++y)
{
c.pixel = XGetPixel(im, x, y);
XQueryColor(display, XDefaultColormap(display, XDefaultScreen(display)), &c);
pixels->r = c.red / 256;
pixels->g = c.green / 256;
pixels->b = c.blue / 256;
pixels++;
}
}
return start;
}
GetPixel.go
p := C.get_pixel_arbitrary(0, 0, C.int(w), C.int(h))
// unsafely convert to an array we can use
pb := (*[1 << 30]C.Colour3)(unsafe.Pointer(&p))
pa := pb[: w*h : w*h] // magic :len:len notation shown in docs but not explained? (if [start:end] then [?:?:?])
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
img.Set(x, y, color.RGBA{uint8(pa[i].r), uint8(pa[i].g), uint8(pa[i].b), 255})
i++
}
}
// assume I should be freeing in C instead of here?
C.free(unsafe.Pointer(p))
这会产生一个 "stretched mess" 然后我溢出(现在我不得不假设我在 C 端又做错了,除非在提到我应该分配时这是完全错误的想法回到 C?)
此时我非常愿意接受带有示例的指南作为答案,因为我确实遗漏了一些东西——但是,当谷歌搜索(甚至在 GitHub 上)寻找示例时其中,我找不到任何。我很乐意获取该线程的结果并做到这一点 :)。
这里的大部分失败都集中在对返回的 C 数组使用 unsafe.Pointer(&p)
上。由于 C 数组是指针,因此 p
已经是 *C.Colour3
类型。使用 &p
试图使用 p
变量本身的地址,它可以在内存中的任何地方。
正确的转换形式如下:
pa := (*[1 << 30]C.Colour3)(unsafe.Pointer(p))[:w*h:w*h]
另见