目标:

  • 使用cgo从C向Golang发送给定的struct数组。


  • 工作代码(无数组)

    免责声明:这是我的第一个功能性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

    作为优化,让我们尝试一次获取(3x3)9像素的补丁,而不是get_pixel,所有现有的代码。

    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,或者如何在Golang中读取/迭代它们。

    在这一点上,我不知道还有什么可以尝试的,四页的Stack Overflow和20页的Googling为我提供了另一个方向(Go-> C)的不同答案-但这里并没有很多。我找不到有效的C.GoBytes示例。

    尝试3

    让Golang处理句柄可以简化对数组的访问。该代码现在适用于3x3,但是在尝试一次获取“整个屏幕”时失败(请参阅尝试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))
    

    这会产生一个“困惑的局面”,然后我溢出(现在我不得不假设我在C端又做错了事,除非这完全是错误的想法,否则我应该重新分配给C?)

    我非常愿意接受带有示例的指南作为答案,因为我肯定会缺少某些内容-但是,当Googling(甚至在GitHub上)查找此类示例时,我找不到任何示例。我很乐意接受此线程的结果,然后就可以做到这一点:)。

    https://imgur.com/a/TDfrehX

    最佳答案

    这里的大多数失败都集中在对返回的C数组使用unsafe.Pointer(&p)上。由于C数组是指针,因此p已经是*C.Colour3类型。使用&p尝试使用p变量本身的地址,该地址可以在内存中的任何位置。

    正确的转换形式如下所示:

    pa := (*[1 << 30]C.Colour3)(unsafe.Pointer(p))[:w*h:w*h]
    

    另请参阅What does (*[1 << 30]C.YourType) do exactly in CGo?

    09-25 18:38