Go – DECODE https://decode.red/blog data decode, decoder or decoded ... design of code Mon, 15 Dec 2025 06:15:00 +0000 ja hourly 1 https://wordpress.org/?v=4.7.29 Call Rust from Golang ../../../202309031697/ Sun, 03 Sep 2023 06:45:00 +0000 ../../../?p=1697 今回はGo、Rustの相互呼び出しを試してみました。これまでCを介していましたが直接呼出しです。

参考)https://qiita.com/momotaro98/items/92e39e214b0e92f454a7

環境)

$ gcc -v

gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)
$ go version
go version go1.20.4 linux/amd64
$ cargo version
cargo 1.69.0 (6e9a83356 2023-04-12)
$ rustc –version
rustc 1.69.0 (84c898d65 2023-04-16)

Call Rust

callrs.go

package main
/*
#cgo LDFLAGS: -L./lib -lhello
#include <stdlib.h>
#include "./lib/hello.h"
*/
import "C"
import (
    "fmt"
    "unsafe"
)
func main() {
    s := "Go String"

    input := C.CString(s)
    defer C.free(unsafe.Pointer(input))

    o := C.hello(input)

    output := C.GoString(o)
    fmt.Printf("Go main: \"%s\"\n", output)
}

lib/hello/src/lib.rs

extern crate libc;
use std::ffi::{CStr, CString};

#[no_mangle]
pub extern "C" fn hello(name: *const libc::c_char) -> *const libc::c_char {
    let cstr = unsafe { CStr::from_ptr(name) };
    let s = cstr.to_str().unwrap().to_string();
    println!("Rust func hello: \"{}\"", s);
    let rstr: &str = "Rust String";
    CString::new(rstr).unwrap().into_raw()
}

lib/hello.h

char* hello(char *name);

lib/hello/Cargo.toml

[package]
name = "hello"
version = "0.1.0"

[lib]
crate-type = ["cdylib"]

[dependencies]
libc = "0.2.2"

Makefile

ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))

build:
        cd lib/hello && cargo build --release
        cp lib/hello/target/release/libhello.so lib/
        echo 'ROOT_DIR is $(ROOT_DIR)'
        go build -ldflags="-r $(ROOT_DIR)lib" callrs.go

実行画面

$ ./callrs
Rust func hello: “Go String”
Go main: “Rust String”

Call Go

src/main.rs

use std::ffi::{CStr, CString};
use std::os::raw::c_char;

extern "C" {
    fn hello(name: GoString) -> *const c_char;
}

#[repr(C)]
struct GoString {
    a: *const c_char,
    b: i64,
}

fn main() {
    let s = CString::new("Rust String").expect("CString::new failed");
    let ptr = s.as_ptr();
    let input = GoString {
        a: ptr,
        b: s.as_bytes().len() as i64,
    };

    let result = unsafe { hello(input) };
    let c_str = unsafe { CStr::from_ptr(result) };
    let output = c_str.to_str().expect("to_str failed");
    println!("Rust main: \"{}\"", output);
}

golib/main.go

package main

import "C"
import "fmt"

//export hello
func hello(s string) *C.char {
        fmt.Printf("Go Func hello: \"%s\" \n", s)
        str := "Go String"
        return C.CString(str)
}

func main() {}

golib/libhello.h (自動生成)

build.rs

fn main() {
    let path = "./golib";
    let lib = "hello";

    println!("cargo:rustc-link-search=native={}", path);
    println!("cargo:rustc-link-lib=static={}", lib);
}

Cargo.toml

[package]
name = "callgo"
version = "0.1.0"

Makefile

build:
        cd golib && go build -buildmode=c-archive -o libhello.a main.go
        cargo build
        cp target/debug/callgo .

実行画面

$ ./callgo
Go Func hello: “Rust String”
Rust main: “Go String”

参考サイトに丁寧にかかれていますので、とても参考になりました。
実行場所と文字列の受け渡しをわかりやすくしてみました。
やはりRustは記述が複雑です。それゆえ事例を残しておくことで、あとで参照したいと思っています。

]]>
WebAssembly / Go ../../../202307161660/ Sun, 16 Jul 2023 01:27:39 +0000 ../../../?p=1660 Go言語が、WebAssemblyを標準でサポートしているということで、実際に動かしてみました。
ブラウザでJavaScript経由で呼び出します。

参考)
https://atmarkit.itmedia.co.jp/ait/articles/2305/19/news002.html

環境は前回のProtocolBufferと同じで、Go は、v1.20.5 です。

前準備

mkdir go_wasm
cd go_wasm
go mod init sample
go get github.com/shurcooL/goexec
go install github.com/shurcooL/goexec@latest

main.go (用意するのはこれだけ)

package main
import "fmt"
func main() {
    fmt.Println("Hello, WebAssembly!")
}

コンパイル

GOOS=js GOARCH=wasm go build -o test.wasm main.go

ファイル確認

% file test.wasm
test.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)

Webで実行させるために生成されたファイルをカレントにコピー
cp $(go env GOROOT)/misc/wasm/wasm_exec.js .
cp $(go env GOROOT)/misc/wasm/wasm_exec.html .

WebServerの起動

~/go/bin/goexec ‘http.ListenAndServe(:8080, http.FileServer(http.Dir(.)))’

実行結果

参考サイトそのままですが、それだけ何もいじることをしなくても良い、というシンプルな仕組みになっています。
miscフォルダにrunボタンでwasmを実行するjsとhtmlを生成することは、至れり尽くせりだと思いました。

wasmは、バイナリでありながら様々な場所で実行できるため、いろいろな使われ方をします。またGoも様々な使われ方を考えたいろいろな機能を持っています。今回はとりあえず、やってみた、という感じですが、次はデータのやり取りなどやってみようと思っています。

以下にhtmlを引用します。jsはサイスが大きいためとりやめ。
wasm_exec.html

<!doctype html>
<!--
Copyright 2018 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<html>

<head>
        <meta charset="utf-8">
        <title>Go wasm</title>
</head>

<body>
        <!--
        Add the following polyfill for Microsoft Edge 17/18 support:
        <script src="https://cdn.jsdelivr.net/npm/text-encoding@0.7.0/lib/encoding.min.js"></script>
        (see https://caniuse.com/#feat=textencoder)
        -->
        <script src="wasm_exec.js"></script>
        <script>
                if (!WebAssembly.instantiateStreaming) { // polyfill
                        WebAssembly.instantiateStreaming = async (resp, importObject) => {
                                const source = await (await resp).arrayBuffer();
                                return await WebAssembly.instantiate(source, importObject);
                        };
                }

                const go = new Go();
                let mod, inst;
                WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => {
                        mod = result.module;
                        inst = result.instance;
                        document.getElementById("runButton").disabled = false;
                }).catch((err) => {
                        console.error(err);
                });

                async function run() {
                        console.clear();
                        await go.run(inst);
                        inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
                }
        </script>

        <button onClick="run();" id="runButton" disabled>Run</button>
</body>

</html>

]]>
Protocol Buffers ../../../202307091642/ Sat, 08 Jul 2023 23:28:42 +0000 ../../../?p=1642 Remote Procedure Call で使われてきたデータフォーマットが、XMLのXML-RPC、JSONのJSON-RPCと使われてきましたが、どちらもHTTPのテキストデータであることから、バイナリデータのサイズが大きくなったり、データ変換の効率が悪かったりしました。そこでgRPCというHTTP/2のストリーム多重化通信の上に、バイナリデータを効率よく送ることができるフォーマットProtocol Buffersを最近よく見かけるようになりました。(gRPCのロードバランシングとか)
そこで、構造データのシリアライズ(marshalling)、デシリアライズ(unmarshalling)をしている部分を実際にためしてみました。(バイナリデータを確認)

「HTTP/2 packet」
https://decode.red/net/archives/79

Protocol Buffersの優れているところは開発言語を選ばないことです。.protoというファイルにデータ構造を定義すると、そのファイルからさまざまな言語のインターフェイスとなるコードを生成してくれます。これを利用してデータ変換をします。
ここではGo言語でテストしました。
またKubernetesの管理KVSであるetcdのValue値にもProtocol Buffersのエンコードが使われているようです。

「kubernetesのetcd上のデータを読み解く」
https://qiita.com/kuro_1116/items/358e82547e25c1c60820

あらゆるデータフォーマットの共通化は、この世界の過去の事例を見ても難しいことですが、データにアクセスする仕組み共通化するというアイディアによって、現実的になった感があります。

環境)
Mac Ventura / M2

参考)
https://qiita.com/nozmiz/items/fdbd052c19dad28ab067

コンパイラのダウンロード
https://github.com/protocolbuffers/protobuf/releases/
https://github.com/protocolbuffers/protobuf/releases/download/v23.4/protoc-23.4-osx-aarch_64.zip

内容

% ls bin
protoc
% ls include/google/protobuf
any.proto duration.proto struct.proto
api.proto empty.proto timestamp.proto
compiler field_mask.proto type.proto
descriptor.proto source_context.proto wrappers.proto

protoc は、$GOPATH/bin に、include/googleフォルダは、コンパイルする.protoファイルと同じ場所に配置。

プラグインインストール

go get google.golang.org/protobuf/cmd/protoc-gen-go

確認

% protoc –version
libprotoc 23.4
% protoc-gen-go –version
protoc-gen-go v1.31.0

.proto ファイル取得

wget https://raw.githubusercontent.com/protocolbuffers/protobuf/main/examples/addressbook.proto

コンパイル

protoc addressbook.proto –go_out=./

下記に出力

github.com/protocolbuffers/protobuf/examples/go/tutorialpb/addressbook.pb.go

テストプログラム

https://github.com/protocolbuffers/protobuf/tree/main/examples/go/cmd

add_person でユーザ情報をコンソールからインタラクティブに登録してファイルに書き出し、list_peopleでファイルを読み込んで表示します。

addressbook.pb.go がローカルでインポートできなかったため、(tutorialpbがgithubにないためか・・相対パスは禁止) 禁じ手ですが、/opt/homebrew/Cellar/go/1.20.5/libexec/src/example におきました。(要ソース変更)

実行の様子

—-

.protoファイルから Go言語ソースを生成し、それを呼び出してアプリケーションadd_person.go、list_people.goを作っています。

import変更部分

//pb “github.com/protocolbuffers/protobuf/examples/go/tutorialpb”
pb “example”

バイナリデータを確認できたことで、理解がふかまりました。

アメリカの物理学者リチャード・フィリップス・ファインマンの名言に「自分に作れないものは、理解できない」というものがあります。最近新しい概念やキーワードが飛び交うクラウド関連の学習をしていて特に響く言葉です。
今回のテストでもいろいろとドキュメントはあるのですが、実際に最後までやったものがなかったため、試すことにしました。こういうことはこれまでもよくあります。

まだまだ、調べたいことばかりですが、一つずつ消化していきたいと思います。

addressbook.proto

// See README.md for information and build instructions.
//
// Note: START and END tags are used in comments to define sections used in
// tutorials.  They are not part of the syntax for Protocol Buffers.
//
// To get an in-depth walkthrough of this file and the related examples, see:
// https://developers.google.com/protocol-buffers/docs/tutorials

// [START declaration]
syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";
// [END declaration]

// [START java_declaration]
option java_multiple_files = true;
option java_package = "com.example.tutorial.protos";
option java_outer_classname = "AddressBookProtos";
// [END java_declaration]

// [START csharp_declaration]
option csharp_namespace = "Google.Protobuf.Examples.AddressBook";
// [END csharp_declaration]

// [START go_declaration]
option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";
// [END go_declaration]

// [START messages]
message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;

  google.protobuf.Timestamp last_updated = 5;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person people = 1;
}
// [END messages]

addressbook.pb.go

// See README.md for information and build instructions.
//
// Note: START and END tags are used in comments to define sections used in
// tutorials.  They are not part of the syntax for Protocol Buffers.
//
// To get an in-depth walkthrough of this file and the related examples, see:
// https://developers.google.com/protocol-buffers/docs/tutorials

// [START declaration]

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// 	protoc-gen-go v1.31.0
// 	protoc        v4.23.4
// source: addressbook.proto

package tutorialpb

import (
	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
	reflect "reflect"
	sync "sync"
)

const (
	// Verify that this generated code is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
	// Verify that runtime/protoimpl is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

type Person_PhoneType int32

const (
	Person_MOBILE Person_PhoneType = 0
	Person_HOME   Person_PhoneType = 1
	Person_WORK   Person_PhoneType = 2
)

// Enum value maps for Person_PhoneType.
var (
	Person_PhoneType_name = map[int32]string{
		0: "MOBILE",
		1: "HOME",
		2: "WORK",
	}
	Person_PhoneType_value = map[string]int32{
		"MOBILE": 0,
		"HOME":   1,
		"WORK":   2,
	}
)

(以下省略)

add_person.go

(上部省略)

// Main reads the entire address book from a file, adds one person based on
// user input, then writes it back out to the same file.
func main() {
	if len(os.Args) != 2 {
		log.Fatalf("Usage:  %s ADDRESS_BOOK_FILE\n", os.Args[0])
	}
	fname := os.Args[1]

	// Read the existing address book.
	in, err := ioutil.ReadFile(fname)
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Printf("%s: File not found.  Creating new file.\n", fname)
		} else {
			log.Fatalln("Error reading file:", err)
		}
	}

	// [START marshal_proto]
	book := &pb.AddressBook{}
	// [START_EXCLUDE]
	if err := proto.Unmarshal(in, book); err != nil {
		log.Fatalln("Failed to parse address book:", err)
	}

	// Add an address.
	addr, err := promptForAddress(os.Stdin)
	if err != nil {
		log.Fatalln("Error with address:", err)
	}
	book.People = append(book.People, addr)
	// [END_EXCLUDE]

	// Write the new address book back to disk.
	out, err := proto.Marshal(book)
	if err != nil {
		log.Fatalln("Failed to encode address book:", err)
	}
	if err := ioutil.WriteFile(fname, out, 0644); err != nil {
		log.Fatalln("Failed to write address book:", err)
	}
	// [END marshal_proto]
}

list_people.go

(上部省略)  
// Main reads the entire address book from a file and prints all the
// information inside.
func main() {
	if len(os.Args) != 2 {
		log.Fatalf("Usage:  %s ADDRESS_BOOK_FILE\n", os.Args[0])
	}
	fname := os.Args[1]

	// [START unmarshal_proto]
	// Read the existing address book.
	in, err := ioutil.ReadFile(fname)
	if err != nil {
		log.Fatalln("Error reading file:", err)
	}
	book := &pb.AddressBook{}
	if err := proto.Unmarshal(in, book); err != nil {
		log.Fatalln("Failed to parse address book:", err)
	}
	// [END unmarshal_proto]

	listPeople(os.Stdout, book)
}

]]>
Call C code from Golang ../../../202305211622/ Sun, 21 May 2023 08:12:54 +0000 ../../../?p=1622 Prometheus, Grafana, Influxdbを使ってみてGo言語で実装されているということから、C言語とのインターフェイスを調べてみました。

環境)

$go version
go version go1.20.4 linux/amd64

参考)
https://ja.wikibooks.org/wiki/Go/cgo%E3%81%A7Go%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89%E3%81%8B%E3%82%89C%E3%81%AE%E9%96%A2%E6%95%B0%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B
https://schima.hatenablog.com/entry/20100523/1274620423
https://karthikkaranth.me/blog/calling-c-code-from-go/

hello.c

void hello()
{
	printf("hello!\n");
}

callc.go

package main

import (
/*
#include <stdio.h>
#include <stdlib.h>
#include "hello.c"
int add(int a, int b){
	return a+b;
}
*/
	"C"
	"unsafe"
	"fmt"
)
func div(n, d int) (int, int){
	divmode := C.div(C.int(n), C.int(d))
	return int(divmode.quot), int(divmode.rem)
}
func main(){
	C.hello();
	fmt.Printf("add: %d\n", C.add(1,2))
	p := C.CString("abcdefg")
	defer C.free(unsafe.Pointer(p))

	C.puts(p)

	for i := 0; i<5; i++ {
		quot, rem :=div(i, 3)
		fmt.Printf("%d / 3: %v .. %v\n", i, quot, rem)
	}
}

上記Go言語からC言語を呼び出していますが、とても簡単に記述できることにおどろかされました。(コメント内に記述)
ここでは言葉での説明よりコードを書いた方が理解しやすいため、いろんな呼び出し方を併記しました。

実行結果

$ go run callc.go
hello!
add: 3
abcdefg
0 / 3: 0 .. 0
1 / 3: 0 .. 1
2 / 3: 0 .. 2
3 / 3: 1 .. 0
4 / 3: 1 .. 1

今度は逆に、CからGoの関数を呼び出してみました。

gofunc.go

package main

import(
	"C"
	"fmt"
)

//export add
func add(a int, b int)  int {
	return  a + b
}

func main(){
	fmt.Printf("%d + %d = %d\n", 1, 2, add(1,2))
}

#include<stdio.h>
#include"gofunc.h"

int main()
{
	GoInt a = 1, b=2;

	printf("%d + %d = %d\n", (int)a, (int)b, (int)add(a,b));
}

ビルド

go build -buildmode=c-archive gofunc.go
gcc -o callgo callgo.c gofunc.a -lpthread

gofunc.h(自動生成)

/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"

#include <stddef.h>
--中略--
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
--中略--
extern GoInt add(GoInt a, GoInt b);

実行結果

$ ./callgo
1 + 2 = 3

Go言語のコードは、バイナリーコンパイル実行もできるため、C言語とのリンクも容易です。
いざというときのためのメモでした。

]]>