Protobuf messages are gaining popularity across many domains. There are libraries like nanopb in C to support the protobuf encoding/decoding. However they are slow and also pose significant challenge in implementation. GoLang protobuf encoding/decoding is fast and provides support of unmarshalling data from JSON to proto format.
We will be creating a C shared library containing functions that read from json file, protobuf encode and decode.
Full Code can be found here:
brundhasv / GoC-Protobuf
Go functions integrated with C
GoC Protobuf Encoding/Decoding
Go functions for Protobuf Encoding/decoding integrated with C
Usage:
protoc --go_out=. Student.proto
go build -o student.so -buildmode=c-shared Student.pb.go student_en_dc.go
gcc -o student student_cgo.c ./student.so
Go program can also be run in standalone mode:
go run student_en_dc.go Student.pb.go
Explanation of Code here:
https://dev.to/brundhasv/go-protobuf-encoding-decoding-integrated-with-c-3ig0
GoLang Code
Lets start with Student.proto. I am using proto3 for this example:
syntax="proto3"; package main; message Parents { string dad=1; string mom=2; } enum Gender { Female = 0; Male = 1; } message Student { string name = 1; int32 age = 2; Parents parents=3; Gender gender=4; } message Students { repeated bytes StudentEntry=1; }
As you can see above proto includes enum, nested proto and repeated bytes. Student represents a student's data, each student's data will be encoded and added to Students StudentEntry and all the encoded entries will be encoded again.
Below is input JSON file student_list.json which is used to populate proto fields. We have to use the same names and nested levels for the unmarshal of Json to proto.
{ "0" : { "Name": "Elliot", "Age" : 15, "Parents" : { "Dad" : "George", "Mom" : "Elena" }, "Gender" : 0 }, "1" : { "Name": "David", "Age" : 14, "Parents" : { "Dad" : "Keith", "Mom" : "Sarah" }, "Gender" : 1 } }
Encoding Protobuf from JSON
To integrate with C, library C is used to convert GoData to C datatypes. Comment export is used to export functions to C lib.
We will create a new GoLang program file student_en_dc.go and add encode and decode functions to it.
Json file is input parameter of this function get_student_enbuf passed from C. This function returns encoded buffer and its length to C.
//export get_student_enbuf func get_student_enbuf(student_json_file *C.char) (unsafe.Pointer,C.int) { . . return C.CBytes(students_en),C.int(int32(len(students_en)))
First step in encoding is to Unmarshall JSON to proto. As there are more than one student entries, I have added entries using number keys. This cannot be directly unmarshalled, we would have to extract json data of each student and then unmarshal. I am first unmarshalling json file data to an interface. Below is the code:
jsonbytes, _ := ioutil.ReadAll(jsonFile) var parsed_proto map[string]interface{} json.Unmarshal(jsonbytes, &parsed_proto)
Then parse each interface to obtain student json data and unmarshall it into our proto. Aftermath proto is encoded as shown.
for i=0;i<len(parsed_proto);i++ { selectedjb, err := json.Marshal(parsed_proto[strconv.Itoa(i)]) if err != nil { log.Println("Error in reading json index",err) return nil,0 } var parsed_jf map[string]interface{} //Each student data to proto json.Unmarshal(selectedjb, &parsed_jf) student := &Student{} //Proto Encoding of student json.Unmarshal(selectedjb, student)
Encoded data is added to StudentEntry of Students. Then Students data is encoded. We have the encoded buffer.
students.StudentEntry=append(students.StudentEntry,student_en) } students_en, err := proto.Marshal(students)
Decoding protobuf
Students is decoded first following all the student entries.
Function decode_student_enbuf takes encoded data and its length as input passed from C and returns decoded buffer to C.
//export decode_student_enbuf func decode_student_enbuf(data *C.char,leng C.int) *C.char { . . return C.CString(decoded_student) }
Before decode, *char is converted to GoBytes and then unmarshalled to proto.
new_students := &Students{} student_en:=C.GoBytes(unsafe.Pointer(data),leng) err := proto.Unmarshal(student_en, new_students)
Decoded students data is used to obtain each StudentEntry and is decoded. Decoded student is appended to a string and is returned to C.
for i=0;i<len(new_students.StudentEntry);i++ { new_student := &Student{} err = proto.Unmarshal(new_students.StudentEntry[i], new_student) decoded_student=decoded_student+"\n"+proto.MarshalTextString(new_student)
Building C-Shared file
Lets compile proto to generate Student.pb.go.Then build c-shared file.
protoc --go_out=. Student.proto go build -o student.so -buildmode=c-shared Student.pb.go student_en_dc.go
student.so and student.h files are generated.
Following lines would be present in the generated header file. Golang function get_student_enbuf returns tuple(more than one return parameters), this is converted to struct when header files are generated as seen below.
/* Return type for get_student_enbuf */ struct get_student_enbuf_return { void* r0; int r1; }; extern struct get_student_enbuf_return get_student_enbuf(char* p0); extern char* decode_student_enbuf(char* p0, int p1);
C Code
From C side, generated header file is included. Students list JSON file is passed to function get_student_enbuf, returned encoded buffer and its length is populated in struct stu. The same are passed to decode function decode_student_enbuf, the returned decoded string is printed.
Finally both encoded and decoded buffers are freed.
#include <stdio.h> #include "student.h" #include <string.h> #include <stdlib.h> #include <stdbool.h> struct get_student_enbuf_return stu; int main() { stu = get_student_enbuf("student_list.json"); int stu_list_len = stu.r1; if(stu.r0!=NULL) { char *decoded_stu = decode_student_enbuf((char*)stu.r0,stu.r1); if(decoded_stu != NULL){ printf("%s",decoded_stu); free((char*)decoded_stu); } free((char*)stu.r0); } return 0; }
Compiling and executing C
Link with the created student.so, create object student and run the object.
gcc -o student student_cgo.c ./student.so ./student name: "Elliot" age: 15 parents: < dad: "George" mom: "Elena" > name: "David" age: 14 parents: < dad: "Keith" mom: "Sarah" > gender: Male
Full Code can be found here:
brundhasv / GoC-Protobuf
Go functions integrated with C
GoC Protobuf Encoding/Decoding
Go functions for Protobuf Encoding/decoding integrated with C
Usage:
protoc --go_out=. Student.proto
go build -o student.so -buildmode=c-shared Student.pb.go student_en_dc.go
gcc -o student student_cgo.c ./student.so
Go program can also be run in standalone mode:
go run student_en_dc.go Student.pb.go
Explanation of Code here:
https://dev.to/brundhasv/go-protobuf-encoding-decoding-integrated-with-c-3ig0
Top comments (0)