วิธีเข้าถึงอาร์เรย์ตัวชี้ C จาก Golang

ฉันกำลังเขียนแอปสำหรับแพลตฟอร์ม windows โดยใช้ FFmpeg และเป็น golang wrapper goav แต่ฉันมีปัญหาในการทำความเข้าใจวิธีใช้ตัวชี้ C เพื่อเข้าถึงอาร์เรย์

ฉันกำลังพยายามเก็บสตรีมไว้ในคลาส AVFormatContext เพื่อใช้งานในระหว่างเดินทาง และในที่สุดก็เพิ่มเฟรมให้กับพื้นผิวใน OpenGl เพื่อสร้างเครื่องเล่นวิดีโอที่มีการเปลี่ยนภาพที่ยอดเยี่ยม

ฉันคิดว่าการทำความเข้าใจวิธีการส่งและเข้าถึงข้อมูล C จะทำให้การเขียนโค้ดง่ายขึ้นมาก

ฉันได้แยกส่วนที่เกี่ยวข้องทั้งหมดของโค้ด C, Wrapper และโค้ดของฉันออกตามที่แสดงด้านล่าง:

รหัส C - libavformat/avformat.h

typedef struct AVFormatContext { 
    unsigned int nb_streams; 
    AVStream **streams; 
}

กระดาษห่อ Golang goav

package avutil

//#cgo pkg-config: libavformat
//#include <libavformat/avformat.h>
import "C"
import (
    "unsafe"
)

type Context C.struct_AVFormatContext; 

func (ctxt *Context) StreamsGet(i uintptr) *Stream {
    streams := (**Stream)(unsafe.Pointer(ctxt.streams));
    // I think this is where it's going wrong, I'm brand new to this stuff 
    return (*Stream)(unsafe.Pointer(uintptr(unsafe.Pointer(streams)) + i*unsafe.Sizeof(*streams)));
}

รหัส Golang ของฉัน

package main

import "github.com/giorgisio/goav/avformat"

func main() {
    ctx := &avformat.Context{} // the actual function to initiate this does an mallocz for the streams

    stream := ctx.StreamsGet(0)

    //do stuff with stream...
}

ใน C ดูเหมือนว่าฉันต้องทำเพียงแค่ streams[i] แต่นั่นจะไม่ได้ผล ดังนั้นฉันจึงเพิ่มฟังก์ชันให้กับ wrapper โดยใช้เทคนิคจากคำถามของฉัน ที่นี่ อย่างไรก็ตาม ฉันไม่ได้รับข้อมูล ดูเหมือนว่าฉันได้รับตัวชี้ไปยังที่ไหนสักแห่งในหน่วยความจำแบบสุ่ม แล้วฉันจะเข้าถึงองค์ประกอบเหล่านี้จาก golang ได้อย่างไร? แหล่งข้อมูลใด ๆ ก็จะเป็นประโยชน์เช่นกัน ฉันจะลงทุนเวลาพอสมควรกับเรื่องนี้


person nevernew    schedule 23.04.2018    source แหล่งที่มา


คำตอบ (1)


ตามที่คุณสังเกตเห็น ปัญหาอยู่ในรหัสต่อไปนี้:

func (ctxt *Context) StreamsGet(i uintptr) *Stream {
    streams := (**Stream)(unsafe.Pointer(ctxt.streams));
    // I think this is where it's going wrong, I'm brand new to this stuff 
    return (*Stream)(unsafe.Pointer(uintptr(unsafe.Pointer(streams)) + i*unsafe.Sizeof(*streams)));
}

ในโค้ด ตัวแปร streams คือ ตัวชี้คู่ ดังนั้นผลลัพธ์ของการเพิ่มออฟเซ็ตให้กับ streams ก็เป็นตัวชี้คู่ด้วย (เช่น ประเภทคือ **Stream) แต่ในตัวอย่างของคุณ มีการแคสต์ไปที่ *Stream ซึ่งไม่ถูกต้อง รหัสที่ถูกต้องคือ:

func (ctxt *Context) StreamsGet(i uintptr) *Stream {
    streams := (**Stream)(unsafe.Pointer(ctxt.streams))
    // Add offset i then cast it to **Stream
    ptrPtr := (**Stream)(unsafe.Pointer(uintptr(unsafe.Pointer(streams)) + i*unsafe.Sizeof(*streams)))
    return *ptrPtr
}

หมายเหตุเพิ่มเติม:
หากคุณต้องการหลีกเลี่ยงเลขคณิตของตัวชี้ในด้าน Go คุณสามารถกำหนดฟังก์ชัน ตัวช่วย สำหรับการเข้าถึงองค์ประกอบของตัวชี้ (เช่น สตรีม) ใน ด้าน C ดังนี้:

/*
void * ptr_at(void **ptr, int idx) {
    return ptr[idx];
}

struct AVStream * stream_at(struct AVFormatContext *c, int idx) {
    if (i >= 0 && i < c->nb_streams)
        return c->streams[idx];
    return NULL;
}
*/
import "C"
import (
    "unsafe"
)

type Context C.struct_AVFormatContext
type Stream C.struct_AVStream

func (ctx *Context) StreamAt(i int) *Stream {
    p := (*unsafe.Pointer)(unsafe.Pointer(ctx.streams))
    ret := C.ptr_at(p, C.int(i))

    return (*Stream)(ret)
}

func (ctx *Context) StreamAt2(i int) *Stream {
    ret := C.stream_at((*C.struct_AVFormatContext)(ctx), C.int(i))

    return (*Stream)(ret)
}

คุณสามารถเลือกฟังก์ชัน ptr_at ซึ่งยอมรับตัวชี้คู่ทั่วไป (ใดก็ได้) เป็นอาร์กิวเมนต์ หรือฟังก์ชัน stream_at ที่เฉพาะเจาะจงมากกว่าซึ่งยอมรับเฉพาะตัวชี้ไปยัง AVFormatContext เป็นอาร์กิวเมนต์เท่านั้น วิธีการแบบเดิมสามารถใช้เพื่อเข้าถึงองค์ประกอบจากตัวชี้คู่ใดๆ เช่น: AVProgram **, AVChapter ** เป็นต้น วิธีการแบบหลังจะดีกว่าหากเราจำเป็นต้องใช้การประมวลผลเพิ่มเติม เช่น การตรวจสอบขอบเขต

person putu    schedule 24.04.2018
comment
เยี่ยมเลย ฟังก์ชัน ptr_at และ stream_at มีลักษณะเหมือนกัน stream_at ทำงานอย่างไร ในตัวอย่างที่คุณส่งผ่าน AVFormatContext (ctx) แทนที่จะเป็น AVStream (ctx.streams) มันจะค้นหาสตรีมในคลาสนั้นหรืออะไร? - person nevernew; 24.04.2018
comment
@nevernew ขออภัย มันเป็นความผิดพลาดของฉัน คุณต้องส่งพอยน์เตอร์ไปที่ AVFormatContext เพื่อฟังก์ชัน stream_at ฉันอัปเดตคำอธิบายแล้ว - person putu; 24.04.2018
comment
โอ้ เยี่ยมเลย ฉันไม่รู้มาก่อนเลย C.stream_at - person Thomas; 24.04.2018
comment
คำถามต่อเนื่อง: stackoverflow.com/questions/50010555/ - person nevernew; 24.04.2018