Передать массив структур от C до Golang - PullRequest
2 голосов
/ 09 апреля 2020

Цель:

  • Отправить массив данных struct от C до Golang, используя cgo.

Рабочий код (без массивов)

Отказ от ответственности : Это мой первый функциональный 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)
}

Конечно, это работает - но занимает от 30 до 55 секунд для Экран 1080p.


Попытка 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 l oop работает, я могу только думать, что полностью неправильно понимать массивы в C, или как передавать их в Golang, или как читать / повторять их в Golang.


На данный момент я понятия не имею, что еще попробовать четыре страницы Переполнения стека и 20-я sh страниц Google дали мне много разных ответов для другого направления (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

1 Ответ

1 голос
/ 09 апреля 2020

Большинство сбоев здесь связано с использованием unsafe.Pointer(&p) для возвращенного массива C. Поскольку массив C является указателем, p уже имеет тип *C.Colour3. Использование &p пытается использовать адрес самой переменной p, который может быть где угодно в памяти.

Правильная форма преобразования будет выглядеть следующим образом:

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

См. Также Что делает (* [1 << 30] C .YourType) именно в CGo </a>

...