2018/03/25

[c/c++]protobuf-c (6)

前回作った、protobuf-cの書込みプログラムを見ていこう。

Githubのgistにも残しておこう。
結果の出力をファイルに変更している。
https://gist.github.com/hirokuma/11fed489bd97fa62daed686dec622646


ルールとして、xxx__INITを使った初期化は、インスタンスの定義と同時に行うしかない、というものがある。
こんなところや、こんなところである。

addrbook.peopleなんかは、malloc()してmemcpy()なんかでコピーさせたかったのだが、初期値用のインスタンスを作っておいて、それをmemcpy()(実装では代入)させている。
まあ、forの中で初期化させる意味は無いので、外に出した方がよかろう。


よく見ると、tutorial__address_book__init()という関数も用意されていた。
やってることは、同じだ。

01: void   tutorial__address_book__init
02:                      (Tutorial__AddressBook         *message)
03: {
04:   static const Tutorial__AddressBook init_value = TUTORIAL__ADDRESS_BOOK__INIT;
05:   *message = init_value;
06: }

static constになっているから、メモリが静的に割り当てられそうな気がするけど、ROM側になるからそこまで気にしなくてよいか。
マクロでの代入と関数での初期化をそれぞれやると、それぞれメモリを取りそうだから、複数使うのであれば関数呼び出しにした方が小さくできるかもしれない。

いっそのこと、static inlineにしてヘッダに置いた方が・・・などと思ったが、初期化にそこまで気合い入れなくてもいいんじゃなかろうかね。


それ以外は、取り立てて書くことがないなー、と思っていたのだが、ヘッダファイルを見ると使っていない構造体があった。

  • ProtobufCAllocator
  • ProtobufCBuffer

pack_to_buffer()とunpack/free_unpacked()の引数で使われている。
名前からすると、メモリ関係をやってくれそうだが、どうだろう?


https://github.com/protobuf-c/protobuf-c/wiki/The-protobuf-c-Library#virtual-buffers

ProtobufCBufferは、append()を持つようだ。
ここでは、FILE*への書込みを例にしている。

gistに置いたサンプルでは、get_packed_size()でサイズを取ってきて、malloc()してpack()、とやっているが、ファイルに吐き出していくのであれば、サイズは気にせず、malloc()もいらず、pack()の代わりにpack_to_buffer()でオープンしているFILE*に吐き出していけばよいだろう。


RAMに置きたい場合は、ProtobufCBufferSimpleが使えるかもしれん。
ただ、これは配列でメモリを確保したものを与えるらしい。
しかし、allocatorとしてmalloc/free()を使ってもいるようだ・・・。
なんだこりゃ?

protobuf_c_buffer_simple_append()を見た感じでは、初期バッファを超えない間は配列を使い、超したらmallocしていくのかな?
realloc()ではなくmalloc()しか使って無さそうだけど、リークしないんだろうか?
メモリを確保して、コピーしてからfreeしているような感じもするので、大丈夫なのか。


全部見ていく気力は無いので、動かしてみるのがよいかな。

2018/03/22

[c/c++]NULLチェックの悩み

私は、引数のNULLチェックをあまりやらない方だ。

NULLにする場合がある(NULLだったらこう動く)という場合には調べるが、与えられたポインタがNULLだったらエラー、みたいな処理は、あまり書きたくない。


そうは思いつつも、やはり、悩む。
たまに、NULLチェックしさえすればすぐに不具合の場所がわかったのに、ということが起きるからだ。
OSがある場合には、比較的見つかりやすいと思うのだが、OSが無い場合だと、なかなか見つけにくい。
NULLはだいたい(void*)0みたいな定義になってると思うが、アドレス0はベクタテーブルが配置されていることが多く、単に書き込めなないアドレスに書き込んだというだけでスルーされてしまうからだ。


私がNULLチェックしない理由は簡単で、それはコーディングのバグであることがほとんどだからだ。
コーディングのバグをチェックして、エラーを返したとしよう。
それで、エラーの処理を正しく行って、アプリが生き続けたとして、それに何の意味があるのだろうか。
いっそのこと、アプリが異常な動作をしてくれた方が助かるではないか。


だから、チェックしたい場合は、assert()を使うことが多い。
それだと、NDEBUGしてなければ即座にabortするし、NDEBUGしたらコードとして残らない。
そういう経路は、デバッグで潰してしまえ、というわけだ。


もしチェックできないままだった場合、NDEBUGするとスルーして動くだろう。
それ以降の動作は、どうなるか想像できない。
もしエラーチェックしていれば、少なくとも想定した動作にはなるだろう。
だったら・・・エラーチェックした方が、まだ動作の推測ができる分、ましなのか???



そんなことを悩みながら、日々生きております。

2018/03/18

[c/c++]protobuf-c (5)

ようやく、本題のprotobuf-cだ。

https://github.com/protobuf-c/protobuf-c

protocへPATHを通しておく。

git clone https://github.com/protobuf-c/protobuf-c.git
cd protobuf-c
./configure

...

configure: error: required protobuf header file not found

怒られた。。。って、これは1回目と同じ話じゃないか。
C++のprotobufはローカルのディレクトリにインストールしたので、確かに探せないのは分かる。

こんな感じにすると、configureできた。
CFLAGSではダメで、CPPFLAGSを使うというのがポイントか。
また、インストール先はC++版と同じ場所にしておいた方がよさそうだ。

./configure --prefix=`pwd`/../../../cpplang/protobuf/install CPPFLAGS=-I`pwd`/../../../cpplang/protobuf/install/include LDFLAGS=-L`pwd`/../../../cpplang/protobuf/install/lib
make
make install


protocへPATHを通しているが、それはcpplang/protobuf/install/binに方になっている前提で話を進める。
protobuf-cでmake installしたときにバイナリがそこへコピーされるためだ。


まず、--c_outを試す。

mkdir c
protoc --c_out=./c/ addressbook.proto

pb-c.cとpb-c.hが生成されたが、もちろんC++版とは違う内容だ。

ヘッダの構造は、こういう感じだ。

image

アンダースコア2つでつないである。
先頭がアンダースコアなものは、structのタグ名だ。
特に使っていないようだから、最初からtypedefだけでもいい気はするが、まあよかろう。


ソースファイルは、こう。

image

見た目でstaticかどうか区別が付かないのだが、Functionは全部public、Variableは「__descriptor」とついているものがpublic constだ。


では、googleのサンプルと同じようなものを作っていこう。

https://github.com/protobuf-c/protobuf-c/wiki/Examples


01: #include <stdio.h>
02: #include <stdlib.h>
03: #include <string.h>
04: 
05: #include "addressbook.pb-c.h"
06: 
07: 
08: int main(int argc, char *argv[])
09: {
10:     Tutorial__AddressBook addrbook = TUTORIAL__ADDRESS_BOOK__INIT;
11:     uint8_t *buf;
12:     unsigned int len;
13: 
14:     addrbook.n_people = 1;
15:     addrbook.people = (Tutorial__Person **)malloc(sizeof(Tutorial__Person*) * addrbook.n_people);
16:     for (int lp = 0; lp < addrbook.n_people; lp++) {
17:         addrbook.people[lp] = (Tutorial__Person *)malloc(sizeof(Tutorial__Person));
18:         Tutorial__Person init = TUTORIAL__PERSON__INIT;
19:         *addrbook.people[lp] = init;
20:     }
21: 
22:     addrbook.people[0]->id = 1;
23:     addrbook.people[0]->name = "yoshio";
24:     addrbook.people[0]->email = "yoshio@mail.com";
25: 
26:     len = tutorial__address_book__get_packed_size(&addrbook);
27:     buf = (uint8_t *)malloc(len);
28:     tutorial__address_book__pack(&addrbook, buf);
29:     fprintf(stderr, "Writing %u serialized bytes\n", len);
30:     for (size_t lp = 0; lp < len; lp++) {
31:         printf("%02x ", buf[lp]);
32:     }
33:     printf("\n");
34: 
35:     for (int lp = 0; lp < addrbook.n_people; lp++) {
36:         free(addrbook.people[lp]);
37:     }
38:     free(buf);
39:     return 0;
40: }

$ gcc -o tst -I../install/include addressbook.pb-c.c write.c -L../install/lib -lprotobuf-c
$ LD_LIBRARY_PATH=../install/lib ./tst
Writing 29 serialized bytes
0a 1b 0a 06 79 6f 73 68 69 6f 10 01 1a 0f 79 6f 73 68 69 6f 40 6d 61 69 6c 2e 63 6f 6d

C++版のファイルと比較しよう。

image

おお、一致した。


やっぱりというかなんというか、動的なメモリ管理が面倒だ。
repeatedもそうだし、char*もだ。
phonesを入力しようとしたら、またそっちも同じようにmalloc/freeがいるので、さらに面倒になるのだ。


もう少しやってみるが、protobuf-cは後から導入するよりも、最初から使うつもりで考えていないと、大変そうだ。
例えば、最初はchar[20]みたいな配列で管理していたものが、後から置き換えてchar*による動的管理になると、解放漏れなんかやってしまいそうだ。

あるいは、シリアライズしたいときだけコピーする、という方法があるか。
shallow copyしてアドレスだけ渡してやれば済むし。
ただ、repeatedのrepeatedなんかが出てくると、やはりめんどうか。


実はメモリダンプで十分、というときもあるだろうが、まあ、まずは使い慣れないとね。
使える道具が少ないと、「金槌を持っている人はすべてが釘に見える」みたいなことになりかねん。

[c/c++]protobuf-c (4)

タイトルと違って、C++のprotobufを見ているが、今回までそれをやって、次からprotobuf-cを見に行く予定だ。


では、前回の続きで、protobufのサンプルソースを見る。


まず、書込みから。

https://developers.google.com/protocol-buffers/docs/cpptutorial#writing-a-message

includeやコメントを入れても100行程度だ。


main()は、いきなり「GOOGLE_PROTOBUF_VERIFY_VERSION」というマクロで始まる。
VerifyVersion()を呼ぶマクロで、ライブラリが持つバージョンとヘッダファイルば持つバージョンをチェックして、許容できないバージョンであればabortさせているようだ。

まあ、if文が2つ入っているだけだし、コストはそんなに高くないだろうけど、自動生成したソースでは呼び出すようになっているらしい。
ルールとして最初に呼び出すだけにしておく方がよいように思うのだけど、何か理由があるのかいな?
protocにオプションがあるかと思ったが、各言語への変換はプラグインのようだから、そっちを見ないといかんのか。
思い出したら、見よう。


本体の構造は、シンプルだ。
tutorial::AddressBookのインスタンスを作り、引数で与えたファイル名があればParseFromIstream()で読み込む。
add_people()でAddressBook::Personのインスタンスを作り、それに中身を詰める。
SerializeToOstream()で出力し、メモリ解放APIを呼び出す。

チュートリアルにはシリアライズとして、std::stringとstd::fstreamが載っている。
他にもあるのかとソースを検索しようとしたが、どこがどうつながっているのかわからず断念。。。
C++のリファレンスページを見ると、ParsingSerializerあたりのようだ。
Code / ZeroCopyStream / String / Arrayなのかな?


Peopleに中身を詰めるところは、そんなに凝ったことはしていない。
setterを使って引数に値を指定するか、mutableで直接書き換えるか。


では、読み込みも見よう。
書込みのサンプルに既存ファイルから読み込む箇所があったので、そんなに見なくてもよいかも。

https://developers.google.com/protocol-buffers/docs/cpptutorial#reading-a-message

こちらは70行に満たない。


main()で、引数で与えられたファイル名をParseFromIstream()で読み込んで、あとはリスト出力する関数を呼んで、メモリ解放。
リスト出力するサイズは、peopleがPersonのrepeatedだから、sizeで取得できる。
iteratorみたいなやり方では無くて、引数にindexを指定して取得している。


取り立てて、説明することも無かろう。


あとは、注意事項などが書かれている。

  • tagの数字は書き換えないこと(MUST NOT)
  • required fieldを追加/削除しないこと(MUST NOT)
  • optional fieldとrepeated fieldは削除できる(MAY)
  • optional fieldとrepeated fieldは新規追加できる(MAY)
    • が、tagは新しい数字を割り当てること(MUST)

requredは、削除だけじゃ無くて追加もできないのか。
過去バージョンとの整合性がとれなくなるからかな。


あとは、メッセージのオブジェクトはできるだけ使い回そうとか、tcmallocを使うのも検討しよう・・・って、tcmallocのリンク先がないやん。
これかな?


では、次回からようやくprotobuf-cを見ていくことにしよう。
C++版もいろいろ機能は残っているのだが、全部見ることができそうな気がしないし、シリアライズしてセーブ/ロードができるのだから、もういいだろう。


しかし、これとKey-Value Store型のDBは、まあまあ近い関係にあると思った。
私はlmdbしか使ったことが無いのだけど、あれはデータ構造が平たくなっていると思えばよいか。
データを残すだけだったらlmdbの方が簡単かもしれんが、メモリ上で使っている状態をそのまま保存しておきたいならprotobufの方がプログラムでのデータ構造とセットになっている分、後腐れが無い。
かといって、マルチプロセスでうまいこと運用したいとなるとDBの方がいいだろうし、そう簡単に割り切れるものではないか。

protobufではstd::stringやarrayでも吐き出せるので、KVSのデータの1つとしてprotobufを使うというのはありかもしれんな。

2018/03/17

[c/c++]sscanf()でSSHっぽい名前を読む

C言語の標準ライブラリで文字列をパースしようとしたら、sscanf()が候補に出てくると思う。

数字のCSV形式を読むのに使っていて動いていたのだが、文字列をやろうとしてつまづいたので、記録を残しておこう。


01: #include <stdio.h>
02: #include <stdlib.h>
03: 
04: int main(void)
05: {
06:     const char STR[] = "yoshio@192.168.0.1:1234";
07:     char name[20] = "";
08:     char ipaddr[15 + 1] = "";
09:     int port = -1;
10:     int ret = sscanf(STR, "%s@%s:%d", name, ipaddr, &port);
11:     printf("ret=%d\n", ret);
12:     printf("name=%s\n", name);
13:     printf("ipaddr=%s\n", ipaddr);
14:     printf("port=%d\n", port);
15: }

ret=1
name=yoshio@192.168.0.1:1234
ipaddr=
port=-1


こうすると、期待通りになった。

01: #include <stdio.h>
02: #include <stdlib.h>
03: 
04: int main(void)
05: {
06:     const char STR[] = "yoshio@192.168.0.1:1234";
07:     char name[20] = "";
08:     char ipaddr[15 + 1] = "";
09:     int port = -1;
10:     int ret = sscanf(STR, "%[^@]@%[^:]:%d", name, ipaddr, &port);
11:     printf("ret=%d\n", ret);
12:     printf("name=%s\n", name);
13:     printf("ipaddr=%s\n", ipaddr);
14:     printf("port=%d\n", port);
15: }

ret=3
name=yoshio
ipaddr=192.168.0.1
port=1234

「文字列の中にデリミタの文字は入ってませんよー」という宣言がいる、ということかな。

2018/03/11

[nfc]Debian9とRC-S370とnfcpy

久々に、PaSoRiを触っている。
BeagleBone Greenに挿して使えるのか、確認しているだけだがね。


http://nfcpy.readthedocs.io/en/latest/topics/get-started.html

せっかくなので、nfcpyを使うことにしよう。
なるべく普通のやり方で使えるに越したことはないのだ。


nfcpyはpython2.xで動くようになっている。
BeagleBone GreenにインストールしたDebianではpython2.7.13が使えるようになっていたので、都合がよい。

$ sudo pip install -U nfcpy

これでインストールできたようなので、下の方に書かれていた「import nfc」の手順を順番にやってみた。
が、open()に失敗する。

不思議に思い、インストール手順にあった「python -m nfc」をやってみると、使用中になっているようだった。

$ python -m nfc
No handlers could be found for logger "nfc.llcp.sec"
This is the 0.13.4 version of nfcpy run in Python 2.7.13
on Linux-4.9.82-ti-r102-armv7l-with-debian-9.4
I'm now searching your system for contactless devices
** found usb:054c:02e1 at usb:001:002 but it's already used
-- scan sysfs entry at '/sys/bus/usb/devices/1-1:1.0/'
-- the device is used by the 'pn533_usb' kernel driver
-- this kernel driver belongs to the linux nfc subsystem
-- you can remove it to free the device for this session
   sudo modprobe -r pn533_usb
-- and blacklist the driver to prevent loading next time
    sudo sh -c 'echo blacklist pn533_usb >> /etc/modprobe.d/blacklist-nfc.conf'
I'm not trying serial devices because you haven't told me
-- add the option '--search-tty' to have me looking
-- but beware that this may break other serial devs
Sorry, but I couldn't find any contactless device

赤文字を実行してやると、openできるようになった。
デバイス関係だからsudoがいるかと思ったが、なくても大丈夫そうだ。


読んでみよう。

>>> import nfc
No handlers could be found for logger "nfc.llcp.sec"
>>> clf=nfc.ContactlessFrontend()
>>> clf.open('usb:054c:02e1')
True
>>> tag=clf.connect(rdwr={'on-connect': lambda tag: False})
>>> print(tag)
Type3Tag 'FeliCa Lite (RC-S965)' ID=0127005D19FE4B78 PMM=00F0000002060300 SYS=88B4

おお、読めた。
SystemCodeが0x88B4になっているから、NDEF用にしていないカードだったのだろう。

Type2のNDEFタグを読ませてみたが、特にそれっぽい表示はしなかった。

>>> tag=clf.connect(rdwr={'on-connect': lambda tag: False})
>>> print(tag)
Type2Tag 'NXP NTAG216' ID=043968D29C3981

>>> for record in tag.ndef.records:
...     print(record)
...
NDEF Uri Record ID '' Resource 'http://www.nxp.com/demoboard/OM5578'

わかりづらいが、printの前にはスペースを入れている。


APIを見て、まねした。


import nfc

def on_connect(tag):
    print('connect: ')
    print(tag)

#def on_discover(tag):
#    print('discover!');
#    return tag;

options = {
        'on-connect': on_connect,
#        'on-discover': on_discover,
        }

while True:
    with nfc.ContactlessFrontend('usb') as clf:
        tag = clf.connect(rdwr=options)
        if tag.ndef:
            print('return:')
            print(tag.ndef.message.pretty())


discoverで検出すると思ったのだが、on-connectでもやれそうだ。
これ、on-connectを指定しないと、tagはboolになるようだった。

whileにしたのは、一度検出すると抜けてしまうからなのだが、これはこれでカードをかざしている間はループし続けてしまう。
on-releaseで離れたのを検知させた方がよさそうだ。

[c/c++]protobuf-c (3)

ようやく、続きをやる気になった。
C++の方のprotobufチュートリアルをやる。


前回、addressbook.protoをこちらからコピー&ペーストして作ったが、オリジナルではproto2と明示されていて、コンパイルしてもワーニングが出なかった。

$ protoc -I=. --cpp_out=. addressbook.proto
$

v3.5.1のexamplesにもaddressbook.protoがあり、こちらはproto3になっている。
proto2版では、PhoneNumberのPhoneTypeがoptionalだったのだが、proto3版にはそれがない。
試しに書き加えてみると、エラーになった。

addressbook.proto:39:14: Explicit 'optional' labels are disallowed in the Proto3 syntax. To define 'optional' fields in Proto3, simply remove the 'optional' label, as fields are 'optional' by default.

optionalを外した。

addressbook.proto: Explicit default values are not allowed in proto3.

proto3ではデフォルトで"optional"だし、デフォルト値を許可していないということか。
"explicit"と書いているから、暗黙のデフォルト値があるのか、デフォルト値を設定するAPIがあるとか、そういうことかもしれん。

まあ、深くは追うまい。


今回は、チュートリアルに従い、proto2にする。

https://developers.google.com/protocol-buffers/docs/cpptutorial


文法が今の時点と変わるかもしれないので、ソースを貼っておこう。

01: syntax = "proto2";
02: 
03: package tutorial;
04: 
05: message Person {
06:   required string name = 1;
07:   required int32 id = 2;
08:   optional string email = 3;
09: 
10:   enum PhoneType {
11:     MOBILE = 0;
12:     HOME = 1;
13:     WORK = 2;
14:   }
15: 
16:   message PhoneNumber {
17:     required string number = 1;
18:     optional PhoneType type = 2 [default = HOME];
19:   }
20: 
21:   repeated PhoneNumber phones = 4;
22: }
23: 
24: message AddressBook {
25:   repeated Person people = 1;
26: }

Javaっぽいというか、C++っぽいというかで、なんとなく読める。
こんな感じか。

  • namespaceは"tutorial"
  • classで"Person"と"AddressBook"が作られるし、"Person"の内側に"PhoneNumber"が作られる
  • enumでPhotoTypeが作られるが、これも"Person"の内側か
  • requiredは必須で、optionalはあってもなくてもよい。optionalはデフォルト値を付けられる。
  • repeatedは...?

生成されたヘッダファイルを見ると、classは6つで、1つのmessageに対して、その名前と、名前に"DefaultTypeInternal"が付加されたclassができている。

PhoneNumberのようにmessage内にあるmessageは、名前で見分けられるようになっている。
ここでは、Person_PhoneNumber、となっていた。
enumも同様で、Person_PhoneType、だ。


そして、それぞれに1だの2だのという数字が付いている。
これは"tag"というもので、1~15は効率がよいので、頻繁に使うものはそういう数字にしておくとよいらしい。
昔、その辺は調べた記憶がある。
4bitまでなら、keyが1byteで収まるかららしい。

requiredは後からoptionalにはできないから注意せよ、となっている。
だから、proto3ではデフォルトがoptionalになったのか。


自動生成されたAPIの説明。
https://developers.google.com/protocol-buffers/docs/cpptutorial#the-protocol-buffer-api

requireとoptionalは"has_"がある。
repeatedは、つまり複数持つことができるというわけか。だから代わりに"_size"がある。

setterは、文字列の場合はconst char*とstd::string&の両方が用意される。
数値は、たぶんそのままだろう。
ここの例ではint32だからint32_tかと思ったが、実際は::google::protobuf::int32になっていた。
https://developers.google.com/protocol-buffers/docs/proto#scalar

src/google/protobuf/stubs/port.hあたりか。



requireとoptionalにはgetter/setter/clearがある。
repeatedのclearはわかるが、getterはphones(index)とRepeatedPtrField<>か?
setterはadd_phones()のような気がするのだが、引数を取らずに戻り値がある。
ああ、説明がちゃんと書かれていた。add_phones()で追加して、戻り値に自分で設定してやるようだ。
indexを指定して取得してupdateできるようなことが書かれているが、constになってるのは何でだろう?


mutable、というAPIもある。
単語としては「変わりやすい」なのだが、C++のmutableとも別なのだろうか?
"direct pointer"と書いてあるから、左辺値にもなることができるのだろう。
repeatedのupdateできるというやつは、このmutable APIを使えばよいのか。


あとは、message全体向けのAPIや、シリアライズのAPIが説明されているが、まあ、あとでいいや。


まずは、動かそう。

https://developers.google.com/protocol-buffers/docs/cpptutorial#writing-a-message

main()があるので、コンパイルできれば動きそうだが、その前にC++のprotobufライブラリを用意せねばなるまい。

$ cd protobuf-3.5.1
$ mkdir install
$ ./configure --prefix=`pwd`/install
$ make

怒られた。

protobuf-3.5.1/missing: line 81: aclocal-1.14: command not found
WARNING: 'aclocal-1.14' is missing on your system.
          You should only need it if you modified 'acinclude.m4' or
          'configure.ac' or m4 files included by 'configure.ac'.
          The 'aclocal' program is part of the GNU Automake package:
          <http://www.gnu.org/software/automake>
          It also requires GNU Autoconf, GNU m4 and Perl in order to run:
          <http://www.gnu.org/software/autoconf>
          <http://www.gnu.org/software/m4/>
          <http://www.perl.org/>
Makefile:1488: recipe for target 'aclocal.m4' failed
make: *** [aclocal.m4] Error 127

aclocal --versionすると、1.15だということがわかった。
検索すると、バージョンは違うもののautoreconf -f -iすればなんとかなりそうな雰囲気だ。
configure前と書いているが、気にせずにやってmakeすると、進んだ。
WSLのUbuntuだからか、ビルドにかなり時間がかかった。
Releaseのlinux.zipにライブラリも入っていればよいのだが、そうはなってないのだ。


ライブラリは、-liteが付いたものと、そうでないものがある。
.aファイルだと、8.2MBと100MBというかなりの差だ。

$ g++ -o tst_w write.cc addressbook.pb.cc -I. -Iinstall/include -Linstall/lib -lprotobuf -pthread -static
$ ./tst_w abc.book

これでビルドできた。
installディレクトリは、make後にmake installしてインストールしたディレクトリだ。
addressbookたちはカレントディレクトリにある前提である。
protobuf-liteだとリンクでエラーになったので、限定的なのだろう。

-staticにしたのは、共有ライブラリにパスを通すのが面倒だったからだ。
これでもよい。

$ g++ -o tst_w write.cc addressbook.pb.cc -I. -Iinstall/include -Linstall/lib -lprotobuf
$ LD_LIBRARY_PATH=./install/lib ./tst_w abc.book

実行すると、新規ファイルを作り、パラメータの入力を求められた。

abc.book: File not found.  Creating a new file.
Enter person ID number: 1
Enter name: yoshio
Enter email address (blank for none): yoshio@mail.com
Enter a phone number (or leave blank to finish):

これで、29byteのファイルが生成された。
もう1回、同じことをやる。

Enter person ID number: 1
Enter name: yoshida
Enter email address (blank for none): yoshida@mail.com
Enter a phone number (or leave blank to finish):

IDは同じにしたのだが、追加になって、ファイルサイズは60byteになった。

同じ要領で、read側もソースファイルを持ってきて、ビルドする。

$ g++ -o tst_r read.cc addressbook.pb.cc -I. -Iinstall/include -Linstall/lib -lprotobuf
$ LD_LIBRARY_PATH=./install/lib ./tst_r abc.book
Person ID: 1
   Name: yoshio
   E-mail address: yoshio@mail.com
Person ID: 1
   Name: yoshida
   E-mail address: yoshida@mail.com

まあ、Person IDが重複するかどうかというのはアプリの作りであって、protobufとは関係ないな。


今回はここまで。

次回は、動かしたソースファイルを見ていこう。
そこら辺までやれば、C版も分かるようになるんじゃなかろうかね。

2018/03/10

[sm]SourceMonitor v3.5.6でCの関数数が正しく出てくれない

出てくれないのだ・・・。

http://www.campwoodsw.com/sourcemonitor.html

今日の最新版は、v3.5.6.334だ。


1年くらい作ってるC言語のプログラムがあって、久々にメトリクスを見てみようと思った。
何も考えずに作っているので、そろそろそういう方面にも目を向けないと。

処理作って、追加追加追加・・・とやっていくと、気付かぬうちに膨れ上がっていたりするではないか。
ああいうのを、整理しておきたいのである。


ソースコードのメトリクスというと、SourceMonitorしか思いつかなかった。
そして、やってみると・・・結果が違う箇所がちらほら見られた。


全部は見ていないのだが、まずFunctionの数が違う。
UTF-8のせいかと思ってSJIS変換したのだが、変わらない。
コメントが悪さしているのかと思ったが、そうでもない。


C++ならどうなるかとやってみると、そうするとFunction数はちゃんと出てくれた。
その代わりというか、#if 0なんかを見てくれていないような気がする。
そして何より、メトリクスとして関数のステップ数が出てくれない。
メソッドだけを評価するようで、評価項目の追加もできないようだ。
うーーーむ。。。。。


v3.6が出るらしいが、まだダウンロードはできないようだ。
無料で使わせてもらっているので、文句言えないし、現象がよくわからんから報告もできんしなぁ。
githubに上げればCoverityが使えるのだけど、ここ数週間Coverity Scanがメンテナンスで落ちているのだ。

こうやって、またソースファイルをきれいにする作業が後回しにされてしまうのであった。。。

[BBG]BeagleBone GreenのWindows10 RNDISドライバ

疲れているので、たまには違うことをしよう、と思い立ち、BeagleBone Greenで遊ぶことにした。


BeagleBoneはBlackの方が有名なようだが、Greenというのもある。
秋月通商さんで売っていて、Blackよりも安かったのだ。
http://akizukidenshi.com/catalog/g/gM-09683/

有線LANにつなぎやすい。
無線LANにつなぐなら、USBドングルを挿すことになるだろう。


設定は、ここのGetting Startedを見ていくのがよさそうだ。
http://wiki.seeed.cc/BeagleBone_Green/

STEP2でドライバをホスト側にインストールする項目があるのだが、今のWindows10だからかわからないが、インストール自体に失敗してしまう。。。

こちらはWindows7だが、手動でMicrosoftの互換ドライバを選択すると、警告は出るもののつなげられた。
https://developer.toradex.com/knowledge-base/how-to-install-microsoft-rndis-driver-for-windows-7


数年前に使ったきりで、システムがかなり古い。
COMポート経由でログインすると、イメージが2015-10-20になっていた。。。
cat /etc/debian_versionとすると、7.11だった。
今は、9らしい。


latestのイメージを取ってきて、7zipで展開してimgファイルを取り出し、ImageWriteでSDカードに焼き込み、BBGの電源を切ってSDカードを挿し・・・。
あれ、デバイスで「CDC ECM」というやつが出てきた。。。
"Ethernet Networking Control Model"らしいが、COMポートもEthernetも使えるので、困るまで放置しておこう。

COMポートでログインし、/etc/debian_versionを見ると、9.3になっていた。


ただ、これだけだとSDカードからの起動になるようだから、今のうちに本体側を書き換えておきたい。
/boot/uEnv.txtを編集すればよいようだが、SDカードにはFATのパーティションがなさそうだし、rootのパスワードが分からん。。。

これで、いけた。
http://dumb-looks-free.blogspot.jp/2014/04/beaglebone-black-bbb-debian-root.html

そしてsudo rebootしてしばらく放置しておくと、microUSB近くのLEDがナイトライダーっぽく点灯し始めた(表現が古い...)。
10分くらいかかるようだから、暇なときにやるのがよいだろう。


いつの間にか、LEDが4つとも消灯していた。
これで終わっているはず。

何も考えずにUSBケーブルを抜いて挿したのだが、SDカードを抜くのを忘れないようにしよう。
たぶん、また同じことを始めてしまうはずだ。

COMポート経由で/etc/debian_versionをcatし、9.3なのを確認。
SSHもできたので、まあいいんじゃないだろうか。

2018/03/09

[c/c++][lmdb]同じ名前のDBが複数出来てしまう謎 (3) - 番外編

また、lmdbで(自分で)やらかしていることに気付いた。


lmdbで、mdp_dumpというコマンドがある。
DBのフォルダを指定すると、どういう構成になっているか分からなくても、取りあえずダンプ出力してくれる。
便利だ。

サンプルとしてソースファイルもあるので、やり方をまねしていた。


が、まねの仕方が甘かった。


https://github.com/LMDB/lmdb/blob/mdb.master/libraries/liblmdb/mdb_dump.c#L275-L277

この部分を、いつの間にか違う実装にしていた。
ええ、前回の失敗と同じですよ。。。

ちゃんとここでは、keyのsize + 1だけバッファをとり、コピーして、末尾に\0を設定しているのだけど、私は何も考えずにconst char*でキャストして使っていたのだ。


これは何がkeyになっているかというと、DB名だ。
つまり、mdb_dbi_open()で渡したconst char*である。
だから、ここは\0も含めてDBに入っているのだろうと思い込んでいたのだけど、実際に収められているのはstrlen()のサイズだった。

困ったことに、キャストしただけでもほとんどの場合でうまくいってて、まったく気付いてなかった・・・。


まあ、DB名でこういうことをやるのは、あまりよろしくないのかもしれない。
とはいえ、Key-Value store型でDBを複数持てるのであれば、cursorでDBを串刺しでの検索をやりたくなる状況は多いだろう。


ルールはそう多くないのだが、ちょっと気を抜くと牙をむいてくるところが難しいですな。

2018/03/08

[c/c++]protobuf-c (2)

いきなりタイトルと違うことになるが、まずC++版を動かしてみよう。


敵を欺くには、まず自らを欺くところから始めねば。。。
いや、敵では無いな。

ともかく、Googleが標準として出しているものがあるから、その動作を先に見ておきたいのだ。


環境は、Windows10のWSLにした。


インストールは、ここを見た。
http://blog.systemjp.net/entry/20080710/p1


今日の時点では、v3.5.1が最新。

protobuf-cpp-3.5.1.tar.gzprotoc-3.5.1-linux-x86_64.zipをダウンロードして、展開。
PATHは、protocのbinにだけ通した。


そして、書いてあるとおりにprotoファイルを作って、コンパイラを動かす。

$ protoc -I=. --cpp_out=. addressbook.proto
[libprotobuf WARNING google/protobuf/compiler/parser.cc:546] No syntax specified for the proto file: addressbook.proto. Please use 'syntax = "proto2";' or 'syntax = "proto3";' to specify a syntax version. (Defaulted to proto2 syntax.)

そういえば、proto2と3があると書かれていたな。
2008年の記事だからおそらくproto2で、protocのデフォルトもproto2だからコンパイルできたのだろう。


生成されたソースファイルを見ると、こういう記述があった。

#if GOOGLE_PROTOBUF_VERSION < 3005000
...
#if 3005001 < GOOGLE_PROTOBUF_MIN_PROTOC_VERSION
...

protobuf-cppの、src/google/protobuf/stubs/common.hには、こうあった。

#define GOOGLE_PROTOBUF_VERSION 3005001

まあ、v3.5.1だから、そうなのだな。

2018/03/06

[c/c++][lmdb]同じ名前のDBが複数出来てしまう謎 (2) - やはり私か

なんとなく予想できていたと思うが、昨日の件はやはり私が悪かった・・・。
疑って済まん、lmdbよ。


原因は・・・mdb_dbi_open()に与えるname(DB名)の末尾に、\0を付け忘れた、だッ!!


あああああ、恥ずかしい。。。。。


以下、全部言い訳だ。


DB名を作るのに、プレフィクスと16進数の組み合わせを使っていた。
"HK23a8bc24"みたいな感じだ。

16進数の部分は変数の値を文字列にしたもので、桁数が多いので関数を作っていた。
イメージとしては、こんな感じか。

char dbname[60 + 1];
strcpy(dbname, "HK");
hex2str(dbname + 2, hexarray);

これで、hex2str()で\0まで付けるようにしていた。


今まではそれでやっていたのだが、これの配列版がほしくなった。
for文で末尾に付ければいいや、と思ったのだが、ここで変なことを考えてしまった。

「桁数が決まっているのだから、strcat()とかで追加せずに、memcpy()でやった方が早いんじゃないの?」と。

プレフィクスも固定長だから、memcpy()した方がいいよねー(たぶん)、とか考えて、結果的にこうなった。

char dbname[60 + 3 + 1];
hex2str(dbname + 2, hexarray);
memcpy(dbname, "HK", 2);
for (int lp = 0; lp < xxx; lp++) {
  char str[3 + 1];
  sprintf("str, "%03d", lp);
  memcpy(dbname + 60, str, 3);
  mdb_dbi_open(xxx, dbname, ....);
}

はい、結果として、dbnameの末尾は未初期化のままとなったのでした。


なんとなく心配したので、printf()でdbnameをログに出していたのだけど、見事にゴミでは無いだったのだろう。
期待したdbnameしか出力されていなかったのだった。
わかったあとで"0x02x"でバイナリ出力させると、0x7Fだったり0x00だったり。
0x7FはDELだっけ? あれは出力に出てこないのか。


こういう話をすると「だからスタック変数は最初に初期化すべきだ!」という人が出てくる。
そして、こちらもやらかした身だから、全然強く言えない。


でも、やはり無条件でスタック変数を初期化するというのは、あまり好きになれないのだ。
初期化する処理だって、コードが発生するし、処理時間も発生してしまうと思うと、バグで苦労することになったとしても、極力最小限で抑えてしまう。


全くの未初期化であればコンパイラも検知できるのだが、今回のように\0だけ忘れた文字列、となると、ちょっと難しいだろう。
静的解析ツールでも無理なんじゃなかろうか。

そうなると、VCみたいに、デバッグモードの時は無意味な値(0xccだっけ)で埋められる方がよいのかもしれないが、今回のように「忘れた」となると、デバッグモードでは動作してリリースモードでバグが現れる、という、しばしば見られる事態が発生してしまう。


そう思うと・・・初期化しないことが悪なのだろうか。。。
何か私を力づける手法を見つけたいものだ。

2018/03/05

[c/c++][lmdb]同じ名前のDBが複数出来てしまう謎 (1)

protobufの調査をしたかったのだが、緊急事態発生だ。


mdb_dbi_open()でデータベース名を指定する場合、MDB_CREATEを付けていれば、なければ新規作成してくれるし、あれば既存のものをオープンする。
単純な話だ。


しかーし!

昨日、不思議な現象が起きるようになり、DBを作り直しても必ず再現するのだが、未だに解決方法が分かっていない。


mdb_put()を何回も行い、mdb_dumpコマンドで確認して、データが1種類しか無いことは確認できた。

しかし、それをmdb_drop()しようとしたら、MDB_NOTFOUNDが発生してしまった。
よくよく見ると、mdb_drop()ではなく、その前のmdb_dbi_open()で発生しているのだ。
でも、チェックするとデータは存在しているのである。


よくわからないので、dropするときのmdb_dbi_open()で、MDB_CREATEを指定して、dropしないようにしてみた。
その結果をmdb_dumpコマンドで確認すると、同じデータベース名が複数出てきたのだ。


違いがあるのは、mdb_dbi_open()するまでの過程だ。

うまくいっているときは、mdb_txn_begin()でトランザクションを取って、mdb_dbi_open()でデータベースをオープンして、mdb_put()なりmdb_get()なりしている。


現象が起きるときは、トランザクションを取るところは同じなのだが、mdb_dbi_open()で名前を指定していない。
そして、取得したdbiでmdb_cursor_open()し、これをぐるぐる回して、全部のデータベース名を見に行っているのだ。
期待するデータベース名が見つかったら、その名前でmdb_dbi_open()したり、そこからアクセスして得た名称でmdb_dbi_open()して、あーだこーだしたあと、dropさせている。


「あーだこーだ」と省略したが、いろいろやっているのだ。
だから、正統派としては、現象に影響しないところをじわじわ切り取っていって、キツネ狩りのように追い込んでいくのがよいのだろう。

が、気力が無いときは、えいや、で片付けてしまいたいではないか。
昨日から今日に掛けてがそういう気分で、そして、未だに解決していない。。。


ちなみに、名前を指定せずにmdb_dbi_open()するのは、mdb_dumpコマンドを参考にしている。

https://github.com/LMDB/lmdb/blob/mdb.master/libraries/liblmdb/mdb_dump.c#L253

memchr()で\0を探し、見つかったらやり直している。
たぶん、DB名をstrlen()で保存しているから、DB名だったら\0がその中に無いように作っているのだろう。


envの中に複数のdbiがあり、dbiの中にkeyがある。
だから、まずはdbiをcursorで回して、次に各dbiの中でkeyをcursorで回す、というところか。


全部を検索したいのであれば、そういう方法しか無いのだろうと思う。
じゃあ、これのどこがまずいのだ?


まずいのであれば、MDB_NOTFOUND、ではないはずだ。
そして、同じDB名でデータ生成できないはずである。
それができているということは、なにか想定外というか、lmdbが禁じていることを行っていると考えた方がよいか。


それがわかれば苦労はしないのだが、想像がつかない。
スレッドとかプロセスとかがありがちなのだけど、「あーだこーだ」の最初の方ではopenに成功しているのよねぇ。

2018/03/03

[c/c++]protobuf-c (1)

重い腰を上げ、protobufのことを調べることにした。


https://github.com/protobuf-c/protobuf-c

protobuf-c-rpcというものもあるようだが、今回はprotobuf-cのみ見ていこう。


説明を読むと、`.proto` ファイルをコンパイルするツールのように見える。
コンパイルすると、 `.pb-c.c` と `.pb-c.h` が生成されて、それをincludeするなどして使うとよいのだろう。


まずは素直にビルドさせ・・・させてくれない。
`protobuf` が無いって言われるけど、それをビルドするんじゃ無いの???
いや、確かに手順として protobuf がいるとは書かれていたのだが、知識無しで進めるのは危険そうなので、protobufそのもののことを調べておこう。


https://developers.google.com/protocol-buffers/

Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data

らしい。
コンパイラとか何とかじゃなくて、メカニズムなのか。

XMLを意識しているようだが、まあそこはいいだろう。
JSONとかXMLのようなテキスト形式は、私にとっても扱いにくいので、バイナリでシリアライズしてくれるというのは大切だ。

.protoファイルに、データ型やメンバを記載していくようだ。
数字が出てきているが、Cでいうところのenum値みたいなものだろうか。
たぶん、この例の `message Person` が構造体みたいなもので、その構造体で使うものは、型を含めてこの中で定義するのだろう。
型はenum、構造体はmessage、かな。

まあ、推測で書いていくと、後で間違っていたときに書き直すのが面倒だから、ここまでにしておこう。


protoのバージョンは2と3があるらしい。
今はproto3が最新のようだから、そちらだけ見よう。
・・・量が多いので、やめた。


protocだけあればいいかと思ったのだが、そう簡単では無いらしい。
pkg-configでprotobufが無いと言われる。
CLAGSみたいなものを定義してもよいらしいが、C++版のインストールがいるのだろうか?


なんとなく分かったが、protobuf-cのprotoc-cは、protocのプラグインとして動作してCソースを生成するもののようだ。
そしてprotobuf-cがそれを使うためのライブラリ、ということか。

ならば、libprotobuf-devをapt installすればよいのか。。。だめだ、includeファイルが見つからないらしい。

checking google/protobuf/compiler/command_line_interface.h usability... no
checking google/protobuf/compiler/command_line_interface.h presence... no
checking for google/protobuf/compiler/command_line_interface.h... no
configure: error: required protobuf header file not found

むう、apt installでは足りんのか。。。

[c/c++]構造体の構造体も平たくする

前回の続きだ。
が、protobufはまた次以降にする。



やりたいのは、こうだ。

  • 構造体のデータを、DBに読み書きしたい
  • 労力は少なめにしたい
  • 構造体のメンバがしばしば変更される


DBとか何とか書いているが、しょせんデータというのは、データの開始位置を示すアドレスと、そのデータが何バイト続いているかというバイト数だけだ。

そのデータをどう解釈するかは、型による。
が、読み書きだけであれば型はそれほど重要では無く、このアドレスからnバイト続いているんだ、くらいしかいらないだろう。


いるとすれば、型情報とエンディアンか。
まあ、今回は同じマシンにしか戻せなくてよいということと、DBのkey名と型は結びついているという前提であれば、いらないか。


そう割り切ってしまえば、面倒ではあるが、難しくは無い。

構造体メンバを各メンバごとに保存したければ、こうなるか。


01: typedef struct {
02:     int xxx;
03: } st1;
04: 
05: typedef struct {
06:   st1 yyy;
07: };


  1. 構造体メンバyyyのオフセットを得る(offsetof()など)
  2. その中の構造体メンバxxxのオフセットとサイズを得る


両方のオフセットを足せば、開始アドレスが分かるだろう。


問題があるとすれば、ひどく面倒だ、ということだ。
protobufだったら、もう少し便利なのだろうか?

2018/03/01

[c/c++]構造体のメンバの構造体のメンバのサイズを知るのはめんどうそうだ

前回と似ているが、さらにめんどうな話だ。

struct {
  struct {
    int aaa;
  } bbb;
} ccc;

こんな形になっているときの、aaaのサイズを知りたい。


まずは、前回と同じようなプログラムを書いてみよう。

01: #include <stdio.h>
02: #include <inttypes.h>
03: 
04: #define ARRAY_SIZE(a)       (sizeof(a) / sizeof(a[0]))
05: #define M_SIZE(type, mem)   sizeof(((type *)0)->mem)
06: #define M_ITEM(type, mem)   { #mem, M_SIZE(type, mem) }
07: 
08: struct aaa {
09:     uint8_t     item8;
10:     uint64_t    item64;
11:     const char* itemc;
12:     int16_t     item16;
13:     struct {
14:         uint8_t     item8;
15:         uint64_t    item64;
16:         const char* itemc;
17:         int16_t     item16;
18:     } itemst;
19: };
20: 
21: const struct {
22:     const char  *name;
23:     size_t      sz;
24: } DB[] = {
25:     M_ITEM(struct aaa, item8),
26:     M_ITEM(struct aaa, item64),
27:     M_ITEM(struct aaa, itemc),
28:     M_ITEM(struct aaa, item16),
29:     M_ITEM(struct aaa, itemst),
30: };
31: 
32: 
33: int main(void)
34: {
35:     for (int lp = 0; lp < ARRAY_SIZE(DB); lp++) {
36:         printf("name=%s: %lu\n", DB[lp].name, DB[lp].sz);
37:     }
38: }

うちの環境だと、こうなった。

name=item8: 1
name=item64: 8
name=itemc: 8
name=item16: 2
name=itemst: 32

これだと、構造体まるまるのサイズになっている。
もし構造体の構成を変更してしまったら、DBに保存しているデータと互換性が無くなってしまう(あ、ここではDBに構造体のデータをまるごと残すという目標でやってます)。


もし、構造体メンバに構造体がいたとしても何とかしたいなら、その中身もまたシリアライズしなくてはならない。
ああ、シリアライズ地獄だ。。。


ここまでくると、もうマクロとか実装だけで解決するのでは無く、前処理をして、現在の構造体を処理するデータやコードを自動生成してもらって、それを埋め込みたくなってくる。

これで思い出すのが、protobufだ。
ずいぶん前にちょっとだけ調べたことがあるけれども、あのときはプロトコルのエンコード/デコードをする手段として探していたので、データ保存として見ていなかった。

protobuf自体は、もともとシリアライズする何かだったはずだ。
バイナリのサイズが大きくなりそうだから避けていたけど、もうLinuxでしか動かさない前提になってきたし、そもそもDBを使う時点でそこそこのストレージを積めるくらいには大きなプラットフォームだろうから、とにかく一般的な方法で楽に安全に解決させたい。



そういうわけで、次回からはprotobufをあまり力を入れずに調べていこう。