2018/10/15

[c/c++]sizeof忘れでコンパイルエラーにならなかった(当たり前だけど)

なぜか、malloc()だか、そのfree()だかわからないけど、エラーが出ていた。
サイズが足りないのにアクセスしたような感じのエラーだ。


確保するサイズは、sizeof()で求めていた。
TLVじゃないけど、Length + Valueをいくつかつなげたようなサイズを確保したかったのだ。

Lengthは1byte分で、uint8_t型。
その箇所は、uint64_t型をValueに取りたかったので、こんな感じで書いていた(つもりだった)。


int length = sizeof(uint8_t) + sizeof(uint64_t);


しかし、私は最初の方にsizeofを書き忘れていたのだった。。。

int length = (uint8_t) + sizeof(uint64_t);


驚いたことに、これがコンパイルエラーにはならないのだ。
-Wや-Wallつけてるのに、warningも出ない。

01: #include <stdio.h>
02: #include <stdint.h>
03: 
04: int main(void)
05: {
06:     int length = (uint8_t) + sizeof(uint64_t);
07:     printf("len=%d\n", length);
08: }


なんとなく想像がつくだろうが、この結果は「8」だ。
"+sizeof(uint64_t)"をuint8_tでキャストしたことになったのだろう。


なかなか気付きませんでしたなぁ。。。

2018/10/06

[win10][wsl]defender firewallを設定する

Windows10 1809がリリースされたので、アップデートした。

https://docs.microsoft.com/en-us/windows/whats-new/whats-new-windows-10-version-1809#windows-defender-firewall-now-supports-windows-subsystem-for-linux-wsl-processes

どうやら、Windows Defender FirewallにWSLのプログラムを設定できるらしい。
私はセキュリティに強くないので、Windows Firewall Controlを使って「許可していないアプリは禁止」にしているのだが、WSLの場合はpicoとかいうプロセスがアクセスして、これがどうやっても許可させることができなかったのだ。


やり方は、こちら。
python2.7のIN方向を許可するサンプルが載っていた。

https://docs.microsoft.com/en-us/windows/wsl/release-notes#build-17627-skip-ahead

netsh.exe advfirewall firewall add rule name=wsl_python dir=in action=allow program="C:\users\<username>\appdata\local\packages\canonicalgrouplimited.ubuntuonwindows_79rhkp1fndgsc\localstate\rootfs\usr\bin\python2.7" enable=yes

単純に、Windowsから見たファイルパスを登録するだけのようだ。
残念ながらWindows Firewall Controlは*.exeのようなものしか登録できないようだ。
「セキュリティが強化されたWindows Defender ファイアウォール」(長いよ...)だと追加できるようだが、local/packages以下がダイアログから見えなかったので、Explorerでたどったパスをコピペで登録させた。


まず/usr/bin/wgetを登録。
うん、いける。

では、普段よく使う/usr/bin/aptを登録・・・したのだが、これが通信できない。
拒否しているタイミングを見計らってps auxしてみたところ、/usr/lib/apt/httpというプロセスが立ち上がっていた。
そいつを許可してやるとaptが通るようになった。
やれやれ。


しかし、これは使うのが面倒だ。
当面はWSL使うときだけFirewallを許可する方が楽そうだ。

と、ふとWindows Firewall Controlの遮断ログを見ると、先ほどの/usr/lib/apt/httpが出ているじゃないか!
そうか、picoが前面に出ていたものが、各ファイルになったのだな。
そして、Windows Firewall Controlは自分でルールを追加する場合には*.exeみたいなファイルしか設定できないものの、ログから許可設定するのはOKだった。
これなら便利ですな。

2018/09/16

[esp8266]RTOS v3.0

かなり久々だが、ESP8266のRTOS_SDK v3.0がリリースされていたので、Windows10のWSLで動かしてみた。

https://github.com/espressif/ESP8266_RTOS_SDK



だいたいこんな感じだった。

sudo apt install libncurses5-dev flex bison gperf
sudo apt install python-pip
sudo pip install pyserial

cd (project_templateまで移動)
make menuconfig
make all


なお、COMポートの設定はこちらを見ながら行った。
chmodで先に変更しないと、"sudo make flash"ではダメだった。

WSLでシリアル通信をする
http://matsuneko22.hateblo.jp/entry/2017/12/09/144803


IO0をLにして起動後にFLASH書込み実行。

make flash


このproject_templateで何が動くのかと思ったら、printf()するだけだった。
baud rateがデフォルトなので74880bpsで送信しているようなのだが、使っているUSBシリアル変換がその速度をサポートしてないので確認できなかった。
まあ、動いてるんだろう。


いずれはesp-idfと同じ形にするのか、統合するのか、ともかくそういう計画らしい。
私が使ってみたかったMbedTLSのchacha20は入ってなかったので、ライブラリのバージョンもesp-idfより古いものがあるようだ。


まあ、socketとかは使えるので、Linuxで書いたプログラムを移植しやすいのは助かるな。

2018/09/03

[linux][azure]OSディスクサイズを増やした後

会社でAzureを使っている。
月も変わったことだし、少しメンテナンスをしておこうと休日に見てしまったのが間違いだった。。。
ディスクがフルになっているVMがあったのだ。


開発用にVMを立てているだけなので、実はそれほど深刻ではない。
面倒でなければ、VMごと削除して新しく作っても良いくらいだ。
ただ、面倒なので、ディスクを増やすだけで対応させたい。


私はAzureやらAWSやら、そういうものに詳しくない。
なんとなく使えているので、これでいいか、と思っているだけだ。
その程度の知識の人が書いていると思って読み進めていただきたい。


まず、OSディスクサイズを増やすには、VMを停止させる。
そうすると、「設定>ディスク」とクリックして、出てきたOSディスクをクリックした先の画面で変更ができるようになる。
フルになっているものが30GiBになっていたので、ひとまず64GiBにしておいた。


そして保存してVMを起動させる。
で、ここからがよくわからないのだが、なぜかパーティション全体が拡張されるVMと、ディスクだけが拡張されているVMの2パターンが発生するのだ。
OSは、Ubuntu16.04。
gpartedか何かで拡張すればよさそうなものだが、そもそもディスクが空いていないのでインストールもできない。。。
同じようにVMを作ったような気がするけど、何が違うんだろうね?


まっとうな使い方としては、データディスクを追加して、データが大きいものはそっちを使うとかなんだろうかね。
この辺りは勘所が分からん。


仕方ないので、パーティションが拡張されなかったものは、パーティションを追加して対応させることにした。

fdiskでデフォルトサイズのままパーティションを作り、再起動して、mkfs.ext4。


問題は、これからだ。
/etc/fstabをどう書くか。。。苦手なのだ。
こんなのでどうだろうか?

/dev/sda2       /home/xxx/yyyy  ext4    default,discard 0 0

私にとって大切なのは、mountしたディスクにユーザ権限で書き込めるかどうかだ。
が、それ以上に大切なのは、起動するかどうかでs


あ、起動しなくなった。。。


Azureの助かるところは、SSHがだめでもブラウザからログインできることだ。
fstabの設定を消したら、やはりログインできるので、なんかよくないのだろう。

$ sudo mount -t ext4 /dev/sda2 yyyy

これも大丈夫だった。
何が悪いんだ・・・。


はっ、「default」ではなく「defaults」なのか!
無事起動しました。

ユーザからのアクセスも、sudo chownで対応できた。
mountするディレクトリはユーザが作ったから大丈夫かと思っていたけど、mount後にchownしないといかんのかな?

2018/09/02

[python]pythonの仮想環境

名前はよく聞くし、なんとなく仮想的なものを意味している感じがするのは分かっている。
そんなvirtualenvだが、意識的に使ったことが無い。
使い方の説明でコマンドが書かれていて、そのまま使っている程度だ。


歳を取ってきて覚えるのが面倒になってきているのを実感しているが、それに逆らってみよう。


https://virtualenv.pypa.io/en/stable/

virtualenv is a tool to create isolated Python environments.

isolatedとあるので、分離してくれるのだろう。


pipを使っていたので意識しなかったが、パッケージはどこかにインストールされる。
"site-packages/"というディレクトリらしい。
作っているものによってはバージョンが異なるパッケージを使いたいこともあるだろう。
そういうときの問題を解決できるらしい。


らしい、というのは、私がそういう状況に陥っていないからだ。
まあ、pythonで大したものを作っていないという意味でもあるが。。。


そういえば、最近は提供されるものでpython3を使っていることが多い。
pipがpip3になっているかのように、virtualenvもvirtualenv3なのだろうか?


そもそも、pipはpython2で、pip3はpython3なのだろうか?
python2がインストールされているからpip3になっていて、python3だけだったらpipでよいのだろうか?


https://docs.python.org/ja/3/tutorial/venv.html?highlight=pip#managing-packages-with-pip

ここではpipになっている。
このサイトがそう説明しているなら、そうなのだろう。


https://docs.python.org/ja/3/installing/index.html#installing-index

python3.4からは一緒にインストールされるようになったということは、それ以前はpipを単独でインストールしていることになる。
確かに、apt installした気がする。
Ubuntu16.04からアンインストールしてからpipを使ってみよう。

$ sudo pip install --upgrade pip
The directory '/home/xxx/.cache/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
The directory '/home/xxx/.cache/pip' or its parent directory is not owned by the current user and caching wheels has been disabled. check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
Collecting pip
  Downloading https://files.pythonhosted.org/packages/5f/25/e52d3f31441505a5f3af41213346e5b6c221c9e086a166f3703d2ddaf940/pip-18.0-py2.py3-none-any.whl (1.3MB)
    100% |████████████████████████████████| 1.3MB 1.1MB/s 
Installing collected packages: pip
  Found existing installation: pip 9.0.1
    Uninstalling pip-9.0.1:
      Successfully uninstalled pip-9.0.1
Successfully installed pip-18.0

うーん、よくわからん。

$ sudo -H pip install numpy
Requirement already satisfied: numpy in /usr/lib/python3/dist-packages (1.11.0)

これはpython3だろう。


ここで、ようやく--versionオプションに気付いた。

$ pip2 --version
pip 9.0.1 from /usr/local/lib/python2.7/dist-packages (python 2.7)

$ pip3 --version
pip 18.0 from /usr/local/lib/python3.5/dist-packages/pip (python 3.5)

$ pip --version
pip 18.0 from /usr/local/lib/python3.5/dist-packages/pip (python 3.5)

python-pipをアンインストールしてもpip2は使えるらしい。が、pipはpython3のものだ。


https://docs.python.org/ja/3/installing/index.html#installing-index

で、このサイトを見ると、仮想環境を作るのはvenvが標準らしい。
virtualenvはサードパーティの代替え案とのことだ。



https://docs.python.org/ja/3.7/library/venv.html#module-venv

見ながら使ってみよう。
Ubuntu16.04にpython3.5.2がインストールされていたから、3.7にしてみる。

https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa
こういうppaって、何も考えずに信用していいのか悩むな。。

tarでやるのが確実だろうけど、pythonはUbuntuのモジュールも使っているようだから、何か怖い。
pythonのバージョンごと仮想化する方法もあるんだろう。

では、カレントディレクトリに作ってみる。

$ python3 -m venv ./env1
The virtual environment was not created successfully because ensurepip is not
available.  On Debian/Ubuntu systems, you need to install the python3-venv
package using the following command.

    apt-get install python3-venv

You may need to use sudo with that command.  After installing the python3-venv
package, recreate your virtual environment.

Failing command: ['/home/hiroshi/Python/.env1/bin/python3', '-Im', 'ensurepip', '--upgrade', '--default-pip']

えっ、venvは標準じゃなかったの??
釈然としないが、apt installでpython3-venvをインストールしてやり直す。


フォルダができたので、移動。
そこから何をしたらいいのか分からんかったが、bin/activateをたたけば良いようだ。
https://qiita.com/fiftystorm36/items/b2fd47cf32c7694adc2e

そうすると、コマンドプロンプトの先頭に環境名が出てくるようになった。

~/Python/env1$ source bin/activate
(env1) ~/Python/env1$

試しにnumpyをpipでinstallさせてみた。bin/activate前は「既にインストールされています」というようなメッセージだけだったのだが、bin/activate後はちゃんとインストールされた。


あれ、どうやって元に戻ればいいんだ・・・と思ったら、deactivateというコマンドがあるようだ。
これはbin/ ではないし、whichでも見つからなかった。
でも、実行すると元に戻った。


ここまでやって気付いたが、使っているpython3はpython3.5.2のままだった。
どうも、"python3.7"とやらないとそうなるようだ。
そして、python3.7-venvをインストールしないとvenvが作れなかった。

python3.7でvenvを実行すると、その中の実行もpython3.7になるようだ。
バージョンはそれで保てそうだ。

2018/08/19

[nfc][rpi]最近のOM5578/PN7150S

掃除をしていると、Raspberry Pi2が見つかった。
NXPのプレゼントで当選したOM5578/PN7150Sが載ったままだった。
https://hiro99ma.blogspot.com/2017/01/nfcraspiom5578pn7150s-1.html

当時はkernelにドライバを入れるので苦労したのだが、最近は楽になっているかもしれない。
確かめておこう。


https://github.com/NXPNFCLinux/linux_libnfc-nci

現在の最新リリースは、R2.4。
PDFはAN11697


0. Raspberry Piのkernelのクロスコンパイル環境

ページの上の方はセルフビルド、下の方はクロスコンパイルの記述になっているので、下を見る。
.bashrcに設定を書くようになっているが、うちは毎回sourceコマンドで読み込むことにする。

echo export PATH=~/Raspi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin:$PATH > compiler.sh
source ./compiler.sh

1. Raspberry Piのkernelダウンロード

git clone --depth=1 https://github.com/raspberrypi/linux
cd linux

2. ドライバのダウンロード

cd drivers/misc
git clone https://github.com/NXPNFCLinux/nxp-pn5xx.git

3. Makefileの編集

vi Makefile
(obj-がならんでいる一番下に追加)

obj-y               += nxp-pn5xx/

4. Kconfigの変数

vi Kconfig
(sourceがならんでいる一番下に追加)

source "drivers/misc/nxp-pn5xx/Kconfig"

5. Creating the device node

やり方が2種類提示してある。Device treeとPlatform data。
ソースファイルを変更するのは気が進まないので、Device treeにしよう。
と思ったが、どういう設定にすればいいんだ?
ああ、前回と同じ流れじゃないか!

6. ビルド

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
   (Device Drivers ---> Misc devices ---> < > NXP PN5XX based driver)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs


・・・ここまでやった後で、PDFの「3.2 Alternative to pn5xx_i2c kernel driver」に気付いた。

image

この構成だと、Kernel spaceはI2CとGPIOのドライバで済みそうだ。

https://github.com/NXPNFCLinux/linux_libnfc-nci

src/halimpl/pn54x/tml/i2c/phTmlNfc_alt.h

/* Describe PN71xx connection
  *  0 = Custom configuration
  *  1 = OM557x on Raspberry Pi
  *  2 = OM557x on UdooNeo
  *  3 = OM557x on BeagleBone black
  *
  */

やる気十分じゃないか!
マクロのCONFIGURATIONを変更するだけでよさそうだし、githubに入っているものはデフォルトが1番だから今回は変更しなくて良い。


Raspberry Pi2にセルフビルドさせよう。
Raspbianは2018-06-27のDesktopで、apt update/upgradeも行っておく。
raspi-configなどでI2Cを有効にしておこう。

git clone https://github.com/NXPNFCLinux/linux_libnfc-nci.git
sudo apt install automake autoconf libtool
cd linux_libnfc-nci
./bootstrap
./configure --enable-alt
make
sudo make install

では、デモアプリを動かそう。

nfcDemoApp poll

1回目に実行したときはエラーがたくさん出てしまったが、タグをかざすと読み取っているようだ(エラーによってコンソールが流されて見えん)。
2回目は普通に動いた。
よくわからんが、深追いは止めておこう。


もしかしたら、前回も見逃していただけで、ドライバは何もせずとも動いたのかもしれんな。


私としては、BeagleBone Greenも持っているので、PN7150SとRapberry Pi用のアタッチメントとBeagleBone用のアタッチメントが分かれているとよいのだが、まあ高いものではないから仕方ないのか(Raspiモデルで3500円くらい)。
いつもだとSonyのRC-S620/Sを使うところなのだが、プロトコルを意識せず使用できるというのは強みがある。
まあ、Linuxが載る前提にはなってしまうが。


相手から読まれるだけでよいなら、FeliCa Linkという選択もある。
試作品を作る場合なんかは、Raspberry PiでもFeliCa Linkで済ませていたりする。
ボードにどーん、というのもよいのだが、FeliCa Linkみたいな小ささだと必要なピンだけつなげば良いので、小回りがきくのだ。


image(どーん)


まあ、適材適所ということで。

2018/07/28

[c/c++][mbedtls]ChaCha20-Poly1305サポートとIETF variant (3)

第3弾まで書く気はなかったのだが、ネタは思いついたときに残しておかねば。



libsodiumからMbedTLSに全部置き換えたいと思っている。
別にlibsodiumに不満があるわけではないのだが、使っている理由がChaCha20とPoly1305だけなので、MbedTLSが対応したなら統一したいのである。


ChaCha20-Poly1305については、IETF variantのAPIを使っていたため、MbedTLSと置き換えできた。
しかし、見逃していたのだが、別の箇所でChaCha20を使っていることに気付いた。
こっちは純粋にChaCha20単体で使っていたのだが、nonceが8byteになっているからオリジナルのChaCha20っぽい。


さて、困った。
MbedTLSはChaCha20単体もIETF variantっぽい(nonceが12byteになっていたから)。

もしかしたら同じ結果になるかもしれない、と思ってやってみると、あら不思議、同じになるではないか。
しかし、条件があるに違いない。


libsodiumのChaCha20処理は、このあたりにある。
https://github.com/jedisct1/libsodium/tree/0468e778d229054a966d0fdc629ec8b677bc37c6/src/libsodium/crypto_stream/chacha20


呼ばれているのは、これのようだ。
_ietfが付いているかどうか。
https://github.com/jedisct1/libsodium/blob/bac61ebf506c1b32185a3f3cc6a582445d55bc75/src/libsodium/crypto_stream/chacha20/ref/chacha20_ref.c#L226-L262

この2つの違いはchacha_ivsetup()かchacha_ietf_ivsetup()かだけみたいだ。


IV設定は、ここ。
https://github.com/jedisct1/libsodium/blob/bac61ebf506c1b32185a3f3cc6a582445d55bc75/src/libsodium/crypto_stream/chacha20/ref/chacha20_ref.c#L62-L78

counterのバイト数と、ivのバイト数が違うだけのように見える。


RFC-7539の7ページ目に図が載っている。
https://tools.ietf.org/html/rfc7539#page-7

image

IETF variantは、bが1つ、nが3つになっている。
ということは、オリジナル版は、bが2つ、nが2つということだろう。
それなら、IV設定の関数と整合がとれる。


ということは、これを利用して、MbedTLSでもcounterとnonceの配分をうまくやればいけるのだろう。
stream()ではcounterがNULLになっているから、0扱い。
nonceは8byteだから、何も考えずに置き換えて使えるんじゃなかろうか。
エンディアンがちょっとわからんのだが、必要があれば4バイトシフトさせればいいだけだ。


私が置き換えたかったのはcrypto_stream_chacha20()だったので、前回と同じようにソースを書いてみた。

https://gist.github.com/hirokuma/f9ee0da7bd428458662473964b57fa6c

nonceの前方に4byteの0を入れると同等になるみたいね。

[c/c++][mbedtls]ChaCha20-Poly1305サポートとIETF variant (2)

前回の続きだが、あまり大したことは書けない。

  • MbedTLSにChaCha20-Poly1305がサポートされたけどIETF variantなのか?
  • ソースファイルからするとIETF variantっぽいし、libsodiumのIETF variant APIで同じ結果になった
  • じゃあ、なんで私がやったAPI置き換え版は結果が同じにならないの??


はい、なんとなく予想できたかと思うが「置き換えに失敗していたから」でした・・・。



これだけだとあんまりなので、どう間違えたのか書いておこう。

libsodiumは、エンコードしたデータ+MACをまとまったデータとして扱っていたのだ。
しかし、MbedTLSでは別々に扱っている。
だから置き換える際に、MACのアドレスも指定するし、データ長もMACを外したサイズにせねばならなかったのだ。

そこら辺を間違えていたのですな。
まあ、ありがちといえばありがちだが。

[c/c++][mbedtls]ChaCha20-Poly1305サポートとIETF variant (1)

Mbed TLS 2.12.0, 2.7.5 and 2.1.14 released


いやあ、とうとうリリースされました。

私にとって大きいのは、ChaCha20-Poly1305サポート。
だいたいの部分はMbedTLSで実装していたのだが、ChaCha20-Poly1305だけはlibsodiumを使っていたのだ。
libsodiumを別の環境でビルドするのは難しそうだったのだけど、MbedTLSなら何とかできる気がする!



で、APIを置き換えてみたものの・・・結果が違う。
全部じゃなくて、MACのチェックで失敗するものがあるのだ。


何も考えずに置き換えていたのだが、libsodiumは3種類APIがあるのだ。

Authenticated Encryption with Additional Data using ChaCha20-Poly1305

  • The original construction can safely encrypt up to 2^64 messages with the same key (even more with most protocols), without any practical limit to the size of a message (up to 2^64 bytes for a 128-bit tag).
  • The IETF variant. It can safely encrypt a pratically unlimited number of messages, but individual messages cannot exceed 64*(2^32)-64 bytes (approximatively 256 GB).
  • The XChaCha20 variant, introduced in libsodium 1.0.12. It can safely encrypt a practically unlimited number of messages of any sizes, and random nonces are safe to use.

私が使っていたのは、この真ん中のIETF variantというAPI。
ソースファイルを見比べたのだが、originalのものに似ているが、追加されているような感じがする。

MbedTLSでも同じことができるものかは別として、IETF variantが何なのかを調べておこう。


"variant"は「変形」とか「変種」というような意味。
だから、originalに対してIETFなりの変更を加えたバージョンということだろう。

libsodiumのIETF variant APIページの一番下にリンクがあった。

ChaCha20 and Poly1305 for IETF protocols

IETF variantの仕様らしい。
リンク先は、RFC-7539。

あれー、と思うだろう。
だって、MbedTLSのソースファイルにもRFC-7539と書いてあるのだから。。。



むう、もしかして、APIが違うんじゃなくて、置き換え損なっているのだろうか?
APIが同じ種類かどうかは、テストデータを動かして確認するしかあるまい。



https://gist.github.com/hirokuma/e8d3e68300810d284b7d65df5473fe22

データをMbedTLSのtestから持ってきて、APIはlibsodium 1.0.16で実装。
困ったことに、"ok"が出力されてしまった。


なんで困ったかというと、encryptのテストしかないからだ。
失敗していたのはMACのチェックなので、どちらかといえばdecryptの方なのだ。

これは、もっとちゃんと調べろ、ということなんだろう。

2018/07/21

[git失敗]最新のpushから1つresetして編集

gitで起こしてしまった、現在進行形のお話。


別の場所で作業するので、一旦pushした。
かなり適当な内容なので、pushよりはファイルごとコピーしておきたいくらいだったのだが、面倒になったのでpushした。


で、今見直したところ、あまりにもいい加減な変更だったので、前のcommitは止めようと思った。
が、そのままではダメだけど、書き換えれば使い物にならないわけでもなさそうだったので、差分を見ながら書き換えようとした。
私はvscodeを使っているのだが、差分が見やすいので助かるのだ。


そして、何も考えずにこうやった。

$ git pull
$ git reset --soft HEAD^

そうすると、ソースファイルはpushしたものが残っているが、差分としてはまだ反映されていないような形になるので、最後に変更した箇所が見える。


無事に変更を終え、push。

$ git push

error: failed to push some refs to 'git@github.com:xxxx.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

エラーらしい。
resetしたからだろう。

$ git pull

CONFLICT (content): Merge conflict in xxxxxxx
Automatic merge failed; fix conflicts and then commit the result.

コンフリクトした。

そして困ったことに、オートマージされたので、前回削除したり追加したりしてコンフリクトしなかったところが反映されてしまったのだ。

「gitでやったらいかんこと(その1)」というところか。。。



gitだけで解決できそうだけど、調べるのが面倒だったので、手動で対応させることにした。


まず、今のコンフリクトしたファイルを適当に修正して、別の場所にコピー。
そして、ローカルでやった修正をなかったことにするため、git reset --hard HEAD^。
pushした内容に戻すため、git pull。
そして、その修正はいらないので、git revert HEAD。

あとは、移動させたファイルとオリジナルのファイルをWinMergeで比較しながら手動マージ。
アナログじゃろう?


今回はコンパイルエラーが出たので気付いたが、もっとぼーっとしていたら、オートマージに気付かなかったかもしれない。


さて、今回調べたいのは、どうするのが楽だったのか、だ。
やりたかったのは「最新pushを1つ前に戻しつつ、最新pushのソースも見ながら編集したい」だ。


うん、そうだね。

$ git pull
$ 差分を見たいソースファイルをどこかにコピー
$ git revert HEAD^
$ 差分を見ながら編集

ファイルをどこかに置けばよかっただけか。


ただ、vscode+GitLensだと、履歴と比較する機能がありそうだ。
Working Treeとも比較ができるので、次回はそれを使おう。

image

2018/07/09

[java]64bit値を扱うのにBigIntegerもある

Cからもらったunsignedな64bit値をビット演算するだけでよいかと思っていたら、数値としても扱う箇所があるのに気付いた。

うーん、uint64_tはプリミティブだから、Javaもlongで受け取って、中で数値に変換できないものか。。。


調べていると、BigIntegerというものがあった。
これを使って、引数はlongで受取り、中で16進数文字列に置き換え、それをBigIntegerにすればよさそうだ。

https://gist.github.com/hirokuma/f365c2a85dae3962ff3d5f19562b9029/a0e48bef4148691ce3fcc135c9911662ce21afc3

結果

long: -81985529216486896
long 2byte: -2
BigInteger: 18364758544493064720
BigInteger 2byte: 254

意味も無くビットシフトなんかさせてみたが、ちゃんと正の数として扱っている。

文字列は好きではないのだが、C側でやらなくてよいので、まあよかろう。

2018/07/07

[java]uint64_t相当のデータをビット演算したい

JNIを使って、CとJavaをつなごうとしている。
C側でuint64_tを使っている箇所があるので、Javaで何とかしなくてはならん。


Javaのlongは64bitなのだが、Javaにはunsignedがないので、たぶん最上位ビットが立ってたら負の数と思うだろう。
byteもそうだったし。

確認しておくべし。


longだけではなく、BigDecimalも使ってみよう。

https://gist.github.com/hirokuma/c8a67b1db8e44ce45236942088d81865/1aaa1e1893b3f83f9d1d14d494b1d4902da454ee

同じようなことをさせたいが、BigDecimalにビットシフトがないので、割り算で代用。
処理が遅くなりそうだが、まあ動作確認だからな。

結果

big decimal 2byte: fe
long 2byte: fffffffffffffffe

やはり、そうなるのか。
BigDecimalは演算してるから2byte分きっちりになったが、longはビットシフトで符号が引きずられたんだ。


そういえば、Javaには「>>>」があったな。

https://gist.github.com/hirokuma/c8a67b1db8e44ce45236942088d81865/0161393d1d388b1ad97754dbc84d3efb2eff8370

結果

big decimal 2byte: fe
long 2byte: fe

おお!
なんか、うれしくなりますな。


これはHEXで出しているから、変数として正負どちらになっているかわからん。
数字で出力させて確認。

BigDecimal : 正
longで>>:負
longで>>>:正

まあ、予想通りか。
当然だが、これをbyte型にキャストして代入すると、どれも負の数になる。

2018/06/30

[github]古いgistのURLを知りたい

ちょっとしたソースコードを載せるのに、githubのgistをしばしば使っている。
便利なのだが、編集すると、その前の内容を確認するのが難しい。
いや、差分はわかるのだけど、githubのcommit historyみたいに、その時の内容だけが見たいのだ。


どうやら、その時のハッシュが分かれば載せられるらしい。
例えば、これはzlibサンプルのrevision1のURLだ。

https://gist.github.com/hirokuma/0f038adc04c1ec38ba6c01f8b2db4fb5/4b920d2c12d7893c23a93343cd74d116de731077

URLの最後がハッシュ値なのだが、どうやって取ってくれば良いのだろう?


・・・というのを、長々と調べていた。。。
いや、なに、"Revisions"というタブがあるのは知っていたよ。
そこで差分も出てきているのは知ってる。


ただ、そこに全Revisionの差分が出ているということにまでは気がつかなかったのだ。
下の方にスクロールさせると、過去の差分があるし、Viewボタンまで付いている。
Viewをクリックすれば、そのときのURLが出てくるのだな。


はは、ははは。
私は乾いた笑いを飲み込むしかなかった。

[c/c++]zlibを使ってみよう

zlibを使わなくてはならなくなった。
名前はよく聞く・・・。
リンクするときに「-lz」と短く済むので、印象に残っている。

よく使われているので、使い方も難しくは無いだろう。


WSLのUbuntu 16.04で試す。

sudo apt install zlib1g-dev


zlibのマニュアルは、こちら。

zlib 1.2.11 Manual https://zlib.net/manual.html


ただ・・・圧縮と解凍さえ分かればよいので、使い方だけ見ておこう。
やりたいのは、ファイルではなくRAMで持っているデータなので、そこは読み替えていく。

zlib Usage Example https://zlib.net/zlib_how.html

https://zlib.net/zpipe.c


まず、テストデータを作ろう。
xxdというコマンドを使うと、ファイルをC言語の配列形式にしてくれる。
便利だ・・・。

xxd -i hirokuma.png > hirokuma.h


Exampleでは、CHUNKサイズのバッファin[]とout[]を準備している。
def()が圧縮、inf()が展開。
どちらも、二重のdo-while()になっている。

まず、def()から。
外側のdo-while()は、ファイルをCHUNKずつ読み取るためのループ。
内側のdo-while()は、deflate()を実行して、avail_outが0になっている間はループする。
「keeps calling deflate() until it is done producing output」と書いてあるので、avail_outが0ということは処理が継続しているという意味なのだろう。

圧縮中、deflate()の引数はZ_NO_FLUSHで、最後だけZ_FINISHにする。
ストリーミングで圧縮するから、ある程度データが揃うまで吐き出さないのだろうね。

簡単なサンプルを作った。
圧縮のみで、圧縮しているデータは、うちのアイコン(PNG)だ。
PNG画像もzlibで圧縮されているようだが、気にするまい。

https://gist.github.com/hirokuma/0f038adc04c1ec38ba6c01f8b2db4fb5/4b920d2c12d7893c23a93343cd74d116de731077

image

まあ、データは何でもよいのだが、内側のdo-while()がどういう周り方をするのかが気になった。

$ gcc -o tst zlib_comp.c -lz
$ ./tst
p=0x6010a0
   avail_out=1022
p=0x6014a0
p=0x6014a0
   avail_out=1024
p=0x6018a0
p=0x6018a0
   avail_out=1024
p=0x601ca0
p=0x601ca0
   avail_out=1024
p=0x6020a0
p=0x6020a0
   avail_out=1024
p=0x6024a0
p=0x6024a0
   avail_out=1024
p=0x6028a0
p=0x6028a0
   avail_out=1024
p=0x602ca0
p=0x602ca0
   avail_out=0
   avail_out=0
   avail_out=0
   avail_out=0
   avail_out=0
   avail_out=0
   avail_out=0
   avail_out=103
p=0x603030

next_inは自動的に進むようだから、全データがRAM上にあるなら、初期化だけしておけばよさそうだ。


内側のループが回ったのは、最後だけだ。
何かルールがあるのだろうか?
もし最後だけなら、通常は内側のdo-while無しでもよいのかもしれんが、これだけじゃわからんな。


というわけで、マニュアルを見てみよう。
けっこう、見づらい・・・。

https://zlib.net/manual.html#Basic

If deflate returns with avail_out == 0, this function must be called again with the same value of the flush parameter and more output space (updated avail_out), until the flush is complete (deflate returns with non-zero avail_out).

In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that avail_out is greater than six to avoid repeated flush markers due to avail_out == 0 on return.

avail_outが0の場合は、flushオプションを同じ状態で呼び続けなくてはならん。
ということは、Z_FINISHの場合だけではないということか。


"more output space (updated avail_out)"と書いてあるが、サンプルではavail_outを再設定しているだけで、out[]のサイズは同じだな。
そもそも、fwirte()するサイズは「CHUNK - strm.avail_out」だから、8KBくらいのデータでは外側のループで回っているときはout[]がほぼ出力されず、最後にZ_FINISHしたときに書き込まれていることになる。


flushオプションもいくつか種類があるようだが、圧縮したデータそのものがほしい場合はサンプル通りにやっておけばよいんじゃ無かろうかね。


展開する方も、同じようにやった。

https://gist.github.com/hirokuma/0f038adc04c1ec38ba6c01f8b2db4fb5/2ae53cdeb15bc6e79ed4c65474040a3327b5dbad

$ ./tst
exec: comp
retval=1
exec: decomp
retval=-5
retval=-5
retval=-5
retval=-5
retval=-5
retval=-5
retval=1

追加したのは、decomp()。
-5は、Z_BUF_ERROR

サンプルではエラー処理していないし、最後に1(Z_STREAM_END)が出るところから見ると、普通なんだろう。

Z_BUF_ERROR if no progress was possible or if there was not enough room in the output buffer when Z_FINISH is used.

Note that Z_BUF_ERROR is not fatal, and inflate() can be called again with more input and more output space to continue decompressing.


APIに何があるのか読みづらかったので、こちらの解説記事の方がよさそう。
http://s-yata.jp/docs/zlib/

compress()なんてあったのか・・・。
https://zlib.net/manual.html#Utility

2018/06/24

[excel]自由に線を引きたい

たまにはExcelのことを書こう。


私がExcelを使うのは、文字と画像をまぜこぜにして、なおかつ広いスペースが使えるからだ。
画像も、画像ファイルだけでなく、線画を描くこともできる。


よく使うのは、曲線コネクタだ。

image

直線だと他の図形とぶつかるけど、曲線だと迂回させることもできる。


できるのだが、あんまり自由度がないのが悩みどころだ。
黄色のマーカーを動かすと、少しは形が変わるのだが、そんなに大幅に変わらない。

image image


自分で使う図ならいいのだが、たまにはきれいな形で人に見せたいこともある。
ああ、大きく迂回させたい・・・。


どうしてもExcelを使って、迂回したような線を引きたい場合は、こういう技もある。

image


何のことはない、図形と直接コネクトさせるのではなく、別の図形にコネクトさせてしまうのだ。
ここでは、円にコネクトさせている。

image


円じゃなくて、四角形の方がうまくいくかも?
この辺は、倍率で見た目が変わったり、線の太さで影響がありそうなので、あれこれやってみるのがよかろう。

image

ただ、中継点のところが薄くなってしまうようだ。
image


曲線を使えば期待した線を描きやすいのだけど、あれは後から変更できないみたいだし。
うーむ。


他のお絵かきツールを探していると、draw.ioというものが紹介されていた。

https://www.draw.io/

image

ふむ、これは自由度が高い。
やはりExcelっぽくない絵にしたいときは、Excelじゃないツールを使うのがよいな。

2018/06/17

[wireshark]npcapを使う

Windows版のWireSharkを久々に起動させ、最新版にした。
そのせいかどうかわからないが、起動しない。。。
プログレスバーの途中、pluginsがどうのこうのというところまで文字が出て、そこから進まない。

アンインストールしてやり直したり、ユーザデータを消したりしたが、関係なさそう。


https://osqa-ask.wireshark.org/questions/48178/wireshark-fails-to-start-on-windows-10

WinPcapだと動かない人もいるみたいだから、npcapにしてみるといいかもね、というところか。

WinPcapごとアンインストールし、npcapのWindowsインストーラを https://nmap.org/npcap/ から探して(今は0.99-r6が最新のようだ)、インストールしてPCを再起動。

そうすると、WireSharkが起動した。


うまくWinPcapでうまくいかないときにPC再起動はさせていないので、もしかしたら再起動だけで直ったかもしれない。
まあ、npcapだとWindowsでもlocalhostを見ることができるから、ちょうどよいだろう。

2018/06/09

[intellij]gitignoreをどうするのか(2018/06/09)

3箇所にあるPCで、Javaの開発・・・・というほどでもないことをやっている。
どのPCで作業するかわからないので、GitHubにプロジェクトを突っ込んでいるだけだ。

2台はIntelliJ、1台はAndroid Studioを使っている。
Android Studioである必要はないのだが、IntelliJとAndroid Studioをわざわざインストールするのもなぁ、ということで、試しにやっている程度だ。


C/C++のイメージでやっていたので、ソースファイルをgitに載せていればなんとかなるんだろう、と思っていたのだが、そうではないらしい。
とにかく、どの環境で作業しても、しょっぱなからgitに差分があるのだ。
ソースファイルだったらよいのだけど、設定ファイルが変わっている。
変わるというよりは、ファイルごと削除になっていることが多い。
作業するディレクトリが違うからかと思ったのだが、どうもそれだけでもなさそう。


腹が立ってきたので、調べることにした。


まずは、直近の記事を検索。

JetBrains IDE(IntelliJ IDEAなど)を使う時のgitignoreについて
https://qiita.com/mokrai/items/1f668dd0d1bee15b56f0

記事の中のリンクに、JetBrainsの記事がある。
Updatedが2018/06/08なので、定期的に更新されるページなのだろう。


「.idea」というディレクトリがあるかどうかで、対応が変わるらしい。
うちのにはあるので、そっちを読む。

  • .ideaディレクトリの中の全部だけど、workspace.xmlとtasks.xmlは除外する
  • 拡張子が.imlのもの(IntelliJ IDEAが反映したもの : applies to IntelliJ IDEA)

意外と、シンプルな方針だ。
Qiitaの人が読んだときと、まだ変わっていないようだ。


それ以外に「Be careful」と「not to shared」も書いてある。
「Be careful」はパスワード関連のようだからよいとして、「not to shared」だ。

  • 拡張子が.imlのもの(GradleやMavenがベースになったプロジェクト)
  • gradle.xml
  • dictionaries
  • .idea/libraries以下にある拡張子が.xmlのファイル(GradleやMavenが生成したもの)


まてやー!
.imlでも、共有したりしなかったりがあるのかぁぁぁぁ。


これ、ちょっと、無理じゃないかい?
今の時点でGitHubのJetBrains gitignore例は2018/May/10で、JetBrainsの記事はJune/08だから、まだそっちには反映されていないのかもしれないし、今のままでもよいのかもしれない(QAを全部読む気力は無いのだ)。
読み間違えているかもしれんので、画像を残しておこう。


image


で、だ。


いま、別の場所のIntelliJで作業して、commit / pushしたものを、また別の場所で開いている。
こちらもIntelliJだ。

.idea/libraries/*.xmlは、書いてあるとおりに除外されている。
除外というか、削除されたことになっている。
.gitignoreの「.idea/**/libraries」が効いているのだろうか?
いや、私はその行を削除していたな。


ただ、JetBrainsが言う通りに共有されていないにしても、どうしてよいのか困る。
プロジェクトの設定でDependenciesを見ると、そいつらが名前だけ載っていて、赤文字になっているのだ。
いかにも「そのファイルはありません」みたいな感じで、ビルドも失敗する。
復活させればビルドできるから、その行をコメントアウトしたこと自体がダメだったのか。


なんとなくだが、変なことをしていなければ、Mavenだか何だか知らないが、持っていないライブラリは勝手にダウンロードしてくれているようだ。
プロジェクトを開いたときに妙に時間がかかることがあるから、あれはそうなんだろう。



今月はまだJavaを扱わないといけないようだから、急いで結論を出すことはあるまい。

[java][ubuntu]bitcoinjをコンソールでビルドしてみよう

bitcoinjをビルドしようとした。
環境としてJDKはインストールされていて、Android Studioもあったので、それからできるだろうと思った。

が、protobufのコンパイラが入っていないので、途中でエラーになってしまった。
Windows向けのprotocもありそうだったが、そこまでしてビルドしたいという気分でもない(なんだそりゃ?)。

READMEを見ると、Gradleでビルドするのが普通のようなのだ。
かといって、Windows環境にGradleだけインストールしたいという気分でもない。
Android Studioのところからパスを通せば良いのだろうが、結局はprotobufがいるだろう。


ごにょごにょ書いたが、Azure上にUbuntu 16.04 VMを立てているので、そっちでビルドできるようにしておいた方が後々いいんじゃなかろうかということで、やってみよう。


Gradleのインストール方法を見ると、SDKMAN!というものをインストールするのが楽らしい。
うちのUbuntuにzipやunzipが入っていなかったので、curlするスクリプトがエラーを返してくれた。
足りないものを入れていけばインストールできるだろう。

sudo apt install openjdk-8-jdk-headless zip unzip (他にいるもの...)
curl -s "https://get.sdkman.io" | bash
source "/home/xxx/.sdkman/bin/sdkman-init.sh"
sdk install gradle 4.8


protobufはaptでもいけそうだが、後々使いそうな気もするので、今の時点で最新のv3.5.1から持ってこよう。

mkdir protobuf
cd protobuf
wget https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip
unzip protoc-3.5.1-linux-x86_64.zip
export PATH=$HOME/protobuf/bin


protocにパスが通っているのを確認したら、bitcoinjをビルドしよう。

git clone https://github.com/bitcoinj/bitcoinj.git
cd bitcoinj
gradle clean build
....

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':wallettemplate:compileJava'.
> Compilation failed; see the compiler error output for details.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1m 56s
24 actionable tasks: 21 executed, 3 up-to-date


エラーで終わってしまった。。。
が、findでjarファイルを探すとできていたから、いいや。


これを書いている時点での最新リリースバージョンはv0.14.7だが、masterはそれよりも進んでいる。
構成に変更があるようで、例えばorg.bitcoinj.core.ScriptExceptionorg.bitcoinj.script.ScriptExceptionになっていた。

まあ、私も「使い始めました」という程度なので、何がどう変わったのかという以前に、使い方を覚えねば。。。
しかし、ExamplesのawaitRunning()が動かないので、最新版は私には無理かもしれんな。

・・・guavaのバージョンが古かっただけみたい。
しくみが、しくみが分かりません。。。

[win10][office]PNGのサムネイルが表示されない(未解決)

会社で使っているWindows10だが、Excelでファイル選択して画像を貼り付けたいことがある。
その際、ファイル選択するダイアログが表示されるのだが、画像ファイルのサムネイルが表示されない。
以前は気にしていなかったので、何かの際にそうなってしまったようだ。


Explorerの設定をいじったのかな、と思ったが、そうでもない。
そして、Excelだけでなく、Wordもダメだった。
かといって、全部がダメになったわけではない。ダメなのはOffice系だけのようだ。


不思議なことに、一度Explorerでサムネイルを表示させたファイルであれば、Officeでもサムネイルが表示されている。
キャッシュされたものであればOKなようだ。


ネットで検索して、現象として一番近いのはこちら。
Office系だけPNGサムネイルが表示されないのだが、解決案が「別のアカウントを使う」らしい。

https://answers.microsoft.com/ja-jp/windows/forum/windows_10-files/png%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE/3ae450be-e0c4-4fa1-a3b1-3de68f41ea4e

いやー、ちょっと待ってくれー、といいたい。
特定は難しいのかもしれないけど、多少は代替え案を出してほしいところだ。


この問題の難しいところは、Explorerの設定でサムネイルを表示させないようにできることだ。
検索すると、それが大量に引っかかってしまい、今回の現象にたどり着けないのだ。
上記で質問された方も、さんざん検索して見つからずに質問されたのではなかろうか。



日本語の検索だとらちが明かないので、英語で探してみた。
https://answers.microsoft.com/en-us/windows/forum/windows_10-files/some-jpg-and-png-thumbnails-disappeared/d2b83204-a38f-4fdd-aa32-74481923f4a7


これを書いているPCでは試せないので、次回会社に行ったときにやってみよう。


2018/06/02

[bitcoij]IntelliJでbitcoinjを使ってみよう

「唐突」の「唐」は、隋の次の唐だろうか?
隋の時代が長いこと続いていたのに、いきなり唐の時代に突入したとか、そんなことだろうか。


というわけで、今回は唐突にbitcoinjを使ってみようと思う。
どちらかといえば、IntelliJの設定方法を忘れそうなので、それをメモとして残しておきたいだけなのだがね。


まず、IntelliJと、JDK8をインストールする。
環境変数JAVA_HOMEなんかも設定しないといかんのかもしれんな。
今回は、jdk 1.8.0 172だ。


IntelliJの設定も、適当にやっておこう。
うちのWindows環境では年に数回しかIntelliJを起動しないので、起動するたびに最新版へのアップデートがいる。
今回は、2018.1だ。



当然、IntelliJの使い方も把握していないのだが、雰囲気でやっておこう。

まず、新規でプロジェクトを作る。
テンプレートからコンソールを選択しておく。


本題であるbitcoinjを追加。
どうやるのが簡単なのか分からんが、「File > Project Structure ...」でModulesを選び、Dependenciesタブから追加した。

  1. 右側の「+」をクリック
  2. 「2. Library...」「From Maven...」
  3. 出てきたダイアログから検索できるので、bitcoinj-coreを検索して、追加
  4. 同じようにして、slf4j-nopを検索して、追加

image

slf4j-nopではなく、slf4j-simpleでもよい。
デバッグ時にはそっちの方が良いのかもしれん。
両方置いておくと、このリストで上にある方が選ばれるようだ。
ただ「こっちを選んだ」みたいなログが出てうっとうしいので、片方だけ見えるようにしておいた方が良かろう。
Scopeを「Test」にしたのは、後でまた検索し直すのが面倒だったので、リストに置いておくためだ。


最後に、適当にコードを書いて実行。
というと、がんばって調べたように見えるが、こちらを読んだだけだ。

bitcoinjの入門 - Bitcoin blog
http://adrenaline2017.hatenablog.com/entry/2017/11/23/000000


オリジナルは、本家のサイトだろうが、日本語になっているだけでありがたい。

Getting started in Java
https://bitcoinj.github.io/getting-started-java

01: package com.hiro99ma.blogpost.bitcoinj_test01; 02: 03: import org.bitcoinj.core.Address; 04: import org.bitcoinj.core.ECKey; 05: import org.bitcoinj.core.NetworkParameters; 06: import org.bitcoinj.kits.WalletAppKit; 07: import org.bitcoinj.params.TestNet3Params; 08: 09: import java.io.File; 10: 11: class BitcoinjTest { 12: private NetworkParameters params; 13: private WalletAppKit wak; 14: 15: BitcoinjTest() { 16: params = TestNet3Params.get(); 17: wak = new WalletAppKit(params, new File("./wallet"), "wak") { 18: @Override 19: protected void onSetupCompleted() { 20: if (wallet().getKeyChainGroupSize() < 1) { 21: wallet().importKey(new ECKey()); 22: } 23: } 24: }; 25: wak.startAsync(); 26: } 27: 28: void exec() { 29: wak.awaitRunning(); 30: Address addr = wak.wallet().currentReceiveAddress(); 31: System.out.println("addr = " + addr.toString()); 32: } 33: } 34: 35: public class Main { 36: public static void main(String[] args) { 37: BitcoinjTest bjt = new BitcoinjTest(); 38: bjt.exec(); 39: } 40: }


着金アドレスをコンソールに出力させるだけだ。
あれこれやりたかったら、Examplesを見るのが早いだろう。
IntelliJの設定が分かったので、私は満足した。。。

[lmdb][win10]もう少しlmdbがうまく動作しない件を調査

以前、こういう記事を書いた。


hiro99ma blog: [lmdb][win10]Windows10 1803でWSLのファイル書込みがうまく動いてない?(2018/05/05)
https://hiro99ma.blogspot.com/2018/05/lmdbwin10windows10-1803wsl20180505.html


今日(2018/06/02)もまだ発生している。
Ubuntu 18.04も試してみたが、同じだ。
だから、これはOSよりもWSL側に何かあるんじゃなかろうかと考えた。

解決できないだろうけど、せっかくなので、もう少し調べてみよう。


前回からgistの内容を変更しているので、再掲しておこう(Revision 3)。

https://gist.github.com/hirokuma/4ea889b2a596c59ef4f862e9df54e469


これを実行させると、初回から3秒くらいまでの間はL.95でエラーが出力されている。

test1(): OK
ERR(95): MDB_BAD_TXN: Transaction must abort, has a child, or is invalid

前回のブログではmdb_put()でエラーになったと書いているが、mdb_get()だな。
勘違いして書いたのか、現象が変わったのか・・・。
mdb_put()がうまくいっていないから、mdb_get()に失敗したといいたかったのかもしれんが、よくわからんな。


まあ、よかろう。
mdb_get()で失敗しているのだ。


まず、実行するディレクトリを変えてみた。
前回動かしたときは、Windowsで作業しているディレクトリだったのだ。
もしかすると。WSLで管理しているディレクトリじゃないとうまくいかない、とかあるのかもしれん。

が、これは関係なさそうだった。
「~/」に移動させて実行したが、結果は同じだ。



次は、失敗したときと成功した時をstraceで眺めた。

失敗時の出力。
どのくらい抜粋すればよいかわからないので、いいところが載ってないかも。

mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0x7f2044f1d000
open("testdb/data.mdb", O_RDWR|O_CREAT, 0644) = 4
fstatfs(4, {f_type=0x53464846, f_bsize=4096, f_blocks=58348607, f_bfree=35725114, f_bavail=35725114, f_files=999, f_ffree=1000000, f_fsid={1, 0}, f_namelen=255, f_frsize=4096, f_flags=1056}) = 0
pread64(4, "\0\0\0\0\0\0\0\0\0\0\10\0\0\0\0\0\336\300\357\276\1\0\0\0\0\0\0\0\0\0\0\0"..., 152, 0) = 152
pread64(4, "\1\0\0\0\0\0\0\0\0\0\10\0\0\0\0\0\336\300\357\276\1\0\0\0\0\0\0\0\0\0\0\0"..., 152, 4096) = 152
mmap(NULL, 1048576, PROT_READ, MAP_SHARED, 4, 0) = 0x7f20440e0000
open("testdb/data.mdb", O_RDWR|O_DSYNC) = 5
fcntl(3, F_SETLK, {l_type=F_RDLCK, l_whence=SEEK_SET, l_start=0, l_len=1}) = 0
write(2, "test1(): OK\n", 12test1(): OK
)           = 12
lseek(4, 16384, SEEK_SET)               = 16384
writev(4, [{"\4\0\0\0\0\0\0\0\0\0\2\0\22\0\302\17\302\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}, {"\5\0\0\0\0\0\0\0\0\0\2\0\22\0\356\17\356\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}, {"\6\0\0\0\0\0\0\0\0\0\2\0\22\0\330\17\330\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}], 3) = 12288
fdatasync(4)                            = 0
pwrite64(5, "\0\0\20\0\0\0\0\0\0\20\0\0\10\0\1\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0"..., 120, 32) = 120
write(2, "ERR(95): MDB_BAD_TXN: Transactio"..., 73ERR(95): MDB_BAD_TXN: Transaction must abort, has a child, or is invalid
) = 73


成功時。

mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0x7f4d72ec0000
open("testdb/data.mdb", O_RDWR|O_CREAT, 0644) = 4
fstatfs(4, {f_type=0x53464846, f_bsize=4096, f_blocks=244189951, f_bfree=103944675, f_bavail=103944675, f_files=999, f_ffree=1000000, f_fsid={1, 0}, f_namelen=255, f_frsize=4096, f_flags=1056}) = 0
pread64(4, "\0\0\0\0\0\0\0\0\0\0\10\0\0\0\0\0\336\300\357\276\1\0\0\0\0\0\0\0\0\0\0\0"..., 152, 0) = 152
pread64(4, "\1\0\0\0\0\0\0\0\0\0\10\0\0\0\0\0\336\300\357\276\1\0\0\0\0\0\0\0\0\0\0\0"..., 152, 4096) = 152
mmap(NULL, 1048576, PROT_READ, MAP_SHARED, 4, 0) = 0x7f4d720e0000
open("testdb/data.mdb", O_RDWR|O_DSYNC) = 5
fcntl(3, F_SETLK, {l_type=F_RDLCK, l_whence=SEEK_SET, l_start=0, l_len=1}) = 0
write(2, "test1(): OK\n", 12test1(): OK
)           = 12
lseek(4, 8192, SEEK_SET)                = 8192
writev(4, [{"\2\0\0\0\0\0\0\0\0\0\2\0\22\0\302\17\302\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}, {"\3\0\0\0\0\0\0\0\0\0\2\0\22\0\356\17\356\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}], 2) = 8192
pwrite64(4, "\n\0\0\0\0\0\0\0\0\0\2\0\24\0\240\17\320\17\240\17\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096, 40960) = 4096
fdatasync(4)                            = 0
pwrite64(5, "\0\0\20\0\0\0\0\0\0\20\0\0\10\0\1\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0"..., 120, 32) = 120
write(2, "test2(): OK\n", 12test2(): OK
)           = 12


失敗straceログは、2回目の実行を載せている。
初回だとディレクトリ作成やファイル作成に違いが出てきてわかりにくいからだ。

文字色を付けたところが、コンソールログ("write"で始まる行)。
背景色を付けたところは、違いがありそうな箇所。


失敗時は、ファイル4へのpwrite64()が発生していない。
fd=4は、最初に開いた方のtestdb/data.mdbだ。。。あれ、fd=5もtestdb/data.mdbだな。
同じファイルをオープンしているのか?

2回目のopen()にはO_DSYNCが指定されていた。
「ファイルに対する書き込み操作は、同期 I/O のデータ完全性完了の要件に基づいて行われる」らしい。
https://linuxjm.osdn.jp/html/LDP_man-pages/man2/open.2.html


・・・などと、違いがあるところばかり気にしていたが、test1()でOKを出力させる前のところでファイル書込みをしてないよね?
mdb_put()に相当する処理がスキップされている??
既にデータがあるから、書き込む前にデータを比較して、同じだからスキップされたのかもしれない。
まあ、ストレージへの書込みはコストが高いので、さもありなん。


そしてもう1つ。
成功する状態でいくつかstraceを取ってみたが、lseek()されるサイズは16384の場合もあったし、違うサイズの場合もあった。
その際、背景色が緑の出力は行われていなかった。。。


何が起きているのか、さっぱりわからんな。


WSLではないLinuxでのstraceを見よう。


mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0x7f10690bc000
open("testdb/data.mdb", O_RDWR|O_CREAT, 0644) = 4
fstatfs(4, {f_type="EXT2_SUPER_MAGIC", f_bsize=4096, f_blocks=7607162, f_bfree=594658, f_bavail=590562, f_files=3840000, f_ffree=2984847, f_fsid={-2044918571, 1650642210}, f_namelen=255, f_frsize=4096, f_flags=4128}) = 0
uname({sysname="Linux", nodename="lntest1", ...}) = 0
pread64(4, "\0\0\0\0\0\0\0\0\0\0\10\0\0\0\0\0\336\300\357\276\1\0\0\0\0\0\0\0\0\0\0\0"..., 152, 0) = 152
pread64(4, "\1\0\0\0\0\0\0\0\0\0\10\0\0\0\0\0\336\300\357\276\1\0\0\0\0\0\0\0\0\0\0\0"..., 152, 4096) = 152
mmap(NULL, 1048576, PROT_READ, MAP_SHARED, 4, 0) = 0x7f106839c000
open("testdb/data.mdb", O_RDWR|O_DSYNC) = 5
fcntl(3, F_SETLK, {l_type=F_RDLCK, l_whence=SEEK_SET, l_start=0, l_len=1}) = 0
write(2, "test1(): OK\n", 12test1(): OK
)           = 12
lseek(4, 16384, SEEK_SET)               = 16384
writev(4, [{"\4\0\0\0\0\0\0\0\0\0\2\0\22\0\302\17\302\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}, {"\5\0\0\0\0\0\0\0\0\0\2\0\22\0\356\17\356\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}, {"\6\0\0\0\0\0\0\0\0\0\2\0\22\0\330\17\330\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}], 3) = 12288
fdatasync(4)                            = 0
pwrite64(5, "\0\0\20\0\0\0\0\0\0\20\0\0\10\0\1\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0"..., 120, 32) = 120
write(2, "test2(): OK\n", 12test2(): OK
)           = 12


fd=4へのpwrite64()呼び出しは、起きたり起きなかったりで、これはWSLと同じだ。
連続して実行するとpwrite64()が呼び出されないようなので、キャッシュに吐き出されるまでは呼び出していないのかもしれん。


WSLで失敗したとき。

lseek(4, 16384, SEEK_SET)               = 16384
writev(4, [{"\4\0\0\0\0\0\0\0\0\0\2\0\22\0\302\17\302\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}, {"\5\0\0\0\0\0\0\0\0\0\2\0\22\0\356\17\356\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}, {"\6\0\0\0\0\0\0\0\0\0\2\0\22\0\330\17\330\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}], 3) = 12288
fdatasync(4)                            = 0
pwrite64(5, "\0\0\20\0\0\0\0\0\0\20\0\0\10\0\1\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0"..., 120, 32) = 120
write(2, "ERR(95): MDB_BAD_TXN: Transactio"..., 73ERR(95): MDB_BAD_TXN: Transaction must abort, has a child, or is invalid
) = 73


Linuxで成功したときの例。

lseek(4, 16384, SEEK_SET)               = 16384
writev(4, [{"\4\0\0\0\0\0\0\0\0\0\2\0\22\0\302\17\302\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}, {"\5\0\0\0\0\0\0\0\0\0\2\0\22\0\356\17\356\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}, {"\6\0\0\0\0\0\0\0\0\0\2\0\24\0\240\17\320\17\240\17\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096}], 3) = 12288
fdatasync(4)                            = 0
pwrite64(5, "\0\0\20\0\0\0\0\0\0\20\0\0\10\0\1\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0"..., 120, 32) = 120
write(2, "test2(): OK\n", 12test2(): OK
)           = 12


lseek()の結果と、pwrite64()の一番最後にある引数は毎回変わるので、Linuxの方は何度か実行して同じような出力を探した。


つまり、straceの結果からでは違いが分からんということだ。


まだだ・・・今のわしには力が足りんのだ。。。

2018/05/26

[c/c++]socketのshutdown()だけでは足りない

socketを使った、サーバっぽいアプリを作っている。

長時間動かしていると、なんか調子が悪い・・・。
メモリの使用量なんかも問題ないし、負荷が高いわけでもない。

なんとなくnetstatで見てみると、大量のFIN_WAIT2が残っていた。
なんだこれは・・・。


原因は、shutdown()だけしかしていないせいだったようだ。
close()したら、解決したようである。


い、言い訳をさせてください!


システムコールの本を読んでいて、shutdown()のところで「close()やプロセス終了でも閉じられるので、shutdown()を省略することもあります」みたいな説明が書いてあったのだ(手元に本がないので、私の記憶では、だが)。

そこを、何を勘違いしたのか「じゃあ、close()を書かずにshutdown()でもいいんだ」と解釈したのである。
冷静に考えれば、close()だけすればいいやん、と思いそうなのだが、そのときの私の心理を解説できるものはどこにもいない。。。



その前も、cURLでcurl_easy_init()とcurl_easy_cleanup()を繰り返していたら、TIME_WAITが大量に残るという現象を味わったばかりだった。
そのときは、接続先が1つしかなかったので、最初にinitしてハンドルをもらってきて、ずっと使い回すことで対応できたようだ。
サイトにも「You can do any amount of calls to curl_easy_perform while using the same easy_handle.」と書いているし、そのやり方でいいんだろうと思っている。

しかし、毎回異なるところへ接続するようだったら、また別のやり方を考えねばならんだろう。


socket関連って、原因に思い当たるノウハウがないと、なかなかつらいですな。

2018/05/20

[git]ブランチを消すとcommit履歴も消える

以前、gitでちまちまcommitするので、Pull Requestしてマージするときにまとめたい話をした。

http://hiro99ma.blogspot.com/2018/05/git-merge-squash.html


merge --squashするときれいになったので、こりゃいいや、と使っていた。

それはよかったのだが、てっきりこう思っていたのだ。

Pull Request用に作ったブランチにcommitがまとまっただけで、ローカルにはcommit履歴が残っている

しかし、ひさびさにcommit履歴を見てみると、squashでまとめたものしか残っていない。。。
あれ、commitの履歴って、全部残っているものではないの??


わからないときは、実験だ。


$ git init
$ touch test.txt
$ git add test.txt
$ git commit -m "first"
$ git checkout -b work/aaa
$ (test.txtを編集)
$ git commit -a -m "hello"
$ (test.txtを編集)
$ git commit -a -m "world"
$ (test.txtを編集)
$ git commit -a -m "morning"

image

$ git checkout master
$ git checkout -b aaa
$ git merge --squash work/aaa
$ git commit -m "squash merge"

image

$ git branch -D work/aaa

image


ああ、消えた!!!


$ git checkout master
$ git merge aaa
$ git branch -d aaa

image


むう、ブランチaaaがあったことすらわからないのか。
reflogには残っているけど、手元に残っているだけだろう。

image


fast-forwardだったから、ログに残っていないのだろうか?


fast-forwardを無効にしてやってみよう。

$ git config merge.ff false
(同じ手順でやっていく)
$ git merge --squash work/aaa
fatal: You cannot combine --squash with --no-ff.

ダメやん。

$ git merge --ff --squash work/aaa

これならできたけど・・・branchを消すと当然同じ結果になった。



そもそも、私は何がしたかったのかというと、以下を何とかしたかったのだ。

  • mergeしたという記録が見えない
    • ログで残すしかない?
  • --squashしたcommitの履歴が残らない
    • squashしたブランチを消さない?


--squashした記録は、ログで残すしかなさそうだ。
上でやった手順では commit -mとコメントを指定したのだが、-m なしで実行するとエディタが起動して、squash対象のcommit logを自動的に埋めん込んでくれていた。

ただ、これはこれで悩ましいな。
commitを頻繁にするのは、細かく履歴を残しているときもあるが、別の環境に移動する際にgit経由でやる場合もあるからだ。
「家に帰るのでcommit」とかも、しばしば。

まあ、ログを編集すれば良いのだけど、それだったら自分で「mergeした」と入れるだけでもよい気がする。


それよりも、問題は2番目だ。
commitはgitのどこかに必ず残っていて、ブランチはcommit idをつなげているだけだと思っていたのだ。
しかし、ブランチを削除してcommit履歴も消えると言うことは、commitはブランチに紐付いているということになる。
今までsquashせずにmergeしていて、commit履歴が全部残っていたので、そう勘違いしていたのだろう。


ブランチを消さなければ残るのだろうが、私はそこまでして履歴を残したいのだろうか?
「この行は何のためにこうしたんだっけ?」という確認をしたいときに見るかもしれないけど、そもそも記憶にないのにcommitログを見ただけでわかるとは思えない。
それよりは、mergeする単位を大きくしすぎないというか、1つの目的のためにmergeするようにしておけばよいのではなかろうか。


issueを上げて対応するためにブランチを作っていたのだけど、複数の問題が絡んでいる場合がある。
そうすると、1つのブランチでの作業が大きくなってしまう。。。

そんなときは、気付いた時点でさらにブランチを作っておけばよいのかもしれん。
あれこれ試して、自分というかチームにあう運用スタイルを作っていくしかなかろう。

2018/05/17

[android][ndk]MbedTLSをビルドしてみよう (4 挫折)

久々に挫折した・・・。
いや、「久々に挫折した」という記事を書きます。


前回からも修正を続け、よくわからないなりにMbedTLSの.aファイルを作ることができた。
それで終わらせれば良かったのだが、もともとは、複数のライブラリを使うライブラリがあるので、それをAndroid Studioからビルドしたい、ということだったのを思い出した。


MbedTLSだけのとき、build.gradleに-Dで設定をすることでビルドができていた。
それをset()で置き換えられるのであればよかったのだが、それは失敗している。

となると、他のライブラリをビルドするときにも、同じbuild.gradleの影響下で行うならば、同じ設定を使うことになる。
それは、まずい。
もしかしたら問題ないのかもしれないが、不安要素は残しておきたくない。

それを回避してできるように、ああでもないこうでもないと数日やっていたのだが、さすがに時間を掛けすぎている。
Android Studioを捨てれば、Makefileでビルドはできているので、そんなに難しいことはないだろう。



という流れで、「今回はこれで挫折して終わらせ、MakefileでAndroid用にビルドする」のが無駄が少なかろうと判断したのだ。
時間ができたらやり直そう、とは思うが・・・まあ、そういうのって、やらんよね。


ただ、まだ回避方法はあると思っている。

たとえば、今はMbedTLSに入っているCMakeLists.txtをそのまま使っているけど、それをコピーして、build.gradleに設定しないと有効にならなかった設定を編集してしまっておく、とか。


なぜやらなかったかといえば、正攻法ではない気がしたからだ。
しかし、正攻法ではない、というのは気分的なもので、実はそうするのが普通なのかもしれん。
ライブラリがアップデートされたときに、コピー&編集したCMakeLists.txtへの反映を忘れそうだから嫌だ、というだけかもしれない。


判断ができないのは、cmakeに詳しくないからだ。
付け焼き刃の知識で逃げようとしているのも、cmakeに詳しくないからだ。

では、cmakeを覚える気力があるかというと・・・無い。
なんだか、暗黙のルールが多い感じがして、気力が湧かないのだ。
まあ、そんなことをいえばmakeだって同じようなものかもしれないが、同じようなものを2つ覚えるには歳を取り過ぎたのだ。


という言い訳をして、今回は挫折した、で終わらせてもらおう。

2018/05/13

[android][ndk]MbedTLSをビルドしてみよう (3)

Android Libraryとして、既にあるCMakeLists.txtを取り込み、static libraryを作ってみる方法は分かったと思う。
まあ、単純なCファイル1つだけだったので、CMakeLists.txtもシンプルだったから問題が出なかったのだろう。

本題であるMbedTLSを取り込んだら失敗するんじゃないかと思うが、MbedTLSはAndroid OSでも動くと書いているので、案外とあっさり動くのかもしれない。

さあ、どちらだ?


はい、前者でしたー。

前回と同じようにcppディレクトリを作って、今回はその中にMbedTLSをcloneしたファイルを置き、ディレクトリの直下にあるCMakeLists.txtを「Link C++ Project with Gradle」で取り込んでみたのだが、その時点でエラーが発生した。

まあ、そうよね。。。

Android Studioに出ているログを見ると、testsあたりで失敗しているようだ。
うん、Arm用にビルドしたものは、たぶんテストできないだろうねぇ。


MbedTLSでライブラリを作るときにはlibraryディレクトリの中しか使わないので、そこにあるCMakeLists.txtを使ってみたがエラーになった。
こっちは、cmake_minimum_requiredがないとか。
では、オプションでMbedTLSをcloneした直下にあるCMakeLists.txtを制御するか、自分でCMakeLists.txtを作ってlibrary/CMakeLists.txtを呼び出すかがよいだろう。


うーん・・・・。
testsを呼び出さないようにするためのオプションは、"MSVC"らしい。
MSVCじゃないのに"MSVC"をセットするのはいやだから、後者にするか。


そう書いて、あれこれ試しながら数時間・・・。
ぜんぜんうまくいかない・・・。

L.138でエラーが出るのだが、私としてはそのルート自体を通さないつもりでいるのだ。
L.125のif文がfalseになるように、呼び出し元のCMakeLists.txtで「set(USE_STATIC_MBEDTLS_LIBRARY OFF)」としているのだが、効果があるようには見えない。


やはり、適当な解釈ではダメなのか。


少し、CMakeLists.txtのことを調べた。

「"MSVC"をセットしないと」と思っていたが、optionという項目はデフォルト値を決めているようだ。
setで設定すれば、そちらが上書きされるようである。

まあ、そう思ったからsetを使ってみたのだが、どうにもダメっぽい。
testsを通る箇所を全部コメントアウトすればライブラリができているから、そこだけのようだ。

今日はここまで。
github

[android][ndk]MbedTLSをビルドしてみよう (2)

前回の復習から。


NDKでMbedTLSをビルドしようとしたが、それ以外にもビルドしたいライブラリがあるので、まずは複数のC/C++ライブラリをビルドする用意をしておきたい。

Android Studioではcmakeを使えるようだし、MbedTLSもcmakeの設定があるので、取り込めばよさそうだ。

そのサンプルが、hello-libsであった。


hello-libsは、pre-builtされたライブラリを使うサンプルになっているが、2箇所変更するだけでpre-builtされたライブラリをビルドするところもやってくれるようになっている。


で、今回は、どうやればいいのかを調べるところからだ。


Android Studioの左下に「Build Variants」があるが、settings.gradleのコメントアウトを消すと"gen-libs"が出てきた。
設定値は"debug"である。


ツリーはこうなっている。
有効にした"gen-libs"が増えているから、settings.gradleが効いたのだろう。

image

このファイルでincludeしているのは、"app"と"gen-libs"で、両方ともフォルダがあり、中にbuild.gradleがある。
ライブラリたちを、同じように専用のフォルダを作ってまとめてしまうか、あるいはライブラリごとにフォルダを作るかだろう。

アイコンがappとは違っているので、そういうフォルダが作れるようだ。
どうも、Module、というもののようである。

image

ここで+アイコンをクリックしても良いのだろうが、ツリー画面のコンテキストメニューに「New>Module」もある。
出てくるウィザード画面はどちらも同じなので、どっちでもよいのだろう。


ウィザード画面を見たが、どれを選べばよいのだろう?
JNIになるからJava Libraryかと思ったが、違う気がする。

そもそも、最後はJNIになるし、MbedTLSも使いたいのだが、MbedTLSをJNI経由で直接扱いたいわけではなく、JNIで使いたいライブラリが中でMbedTLSを使っているだけなのだ。
gen-libsはJavaを持ってないので、同じようになっているはず。
build.gradleの「apply plugin」が"com.android.library"になっているから、Android Libraryを選べばよさそうだ。

作ってみると、アイコンも同じだったので、大丈夫だろう。
ここでは「build-libs」という名前で作った。
モジュール設定画面を見ていっても、testrunnerが入っているくらいで、gen-libsとあまり違いは無かった。


image


デフォルトで「java」というフォルダと、その中にパッケージのフォルダ階層が作られたが、それだけのようだ。

image

gen-libsの方には、cppの中にCMakeLists.txtがある。
build.gradleの中にCMakeLists.txtへのパスが書かれているので、同じようにやれば良いのだろう。
相対パスで書けるので、このフォルダごと別のプロジェクトに持っていってインポートするようなこともできるのかもしれん。


まず、javaディレクトリを削除して、cppディレクトリを作成。
その中に、const char*を返すだけのCソースとヘッダファイルを追加。
cmake関係は後回しにして、まずはjavaの痕跡を消しておきたかったのだが、syncっぽいことをしても消えないし、cppも出てこない。
testrunnerがあるせいかと思って消したが、効果無し。

だが、コンテキストメニューを出してみると「Link C++ Project with Gradle」が出てきた。
これで「Android Studio UIの使用」に書いてあることができるのだ。

image


では、cmakeの作業をせねばなるまい。
私はcmakeに詳しくないので、gen-libsの中にあるgmathのCMakeLists.txtをコピーして、"gmath"と書いてあるところを適当に置き換えた。
ライブラリやヘッダファイルがあった distributionディレクトリへのコピーもこの中でやるようなので、パスを変更する。

そして、作ったCMakeLists.txtを「Link C++ Project with Gradle」でリンクさせる。
CMakeLists.txtを指定するだけなのだが、相対パスへの変換もちゃんとやってくれる。


取りあえずビルドだ、ビルド。
とボタンを押してみるが、バイナリができないし、includeファイルのコピーもされていない。
ただ、distributionディレクトリの中にはディレクトリが追加されている。


そういえば、gen-libsをビルドできるようにしたとき、appのbuild.gradleにも変更を加えたのだった。
コメントを見ると、native libraryのビルドが行われないというissueが出ているらしく、その対応のように見える(今はv3.1.2だから直ってるかも)。
同じように追加すればビルドされるかと思ったのだが・・・ダメだ。
distributionディレクトリの中にディレクトリが追加されたのも、cmakeが行われたからというよりは、リンクする作業を行ったからやってくれただけのようにも見える。
distributionディレクトリの中を削除してビルドし直しても、gmathとgprefしか生成されないからだ。


設定ファイルを見直していくと、build.gradleに"android.defaultConfig.externalNativeBuild"が無いことに気付いた。
gen-libsだと"targets"にライブラリの名を書いているだけなのか?
appの方は"arguments"で、g++だかclangだかに与えるパラメータを書いているように見える。
よくわからんので、"targets"に今回追加したライブラリ名を書いてみた。
が、変わらず。


ツリーのビューを"Packages"に変更すると、追加したライブラリのパッケージ名が違っていることに気付いた。
AndroidManifest.xmlで変更するようだったので、書き換えた。


ここら辺までやると、distributionディレクトリの中に追加したライブラリとヘッダファイルがコピーされていた。
されていたけど、あれこれやりすぎて、どれが有効だったのかが分からん。。。


新規に、hello-libsに自分のライブラリを追加していこう。


  1. Android Studio v3.1.2を起動して、「Import an Android code sample」の"hello-libs"プロジェクトを選択。
  2. メニュー:File > Project Structure
    1. +アイコンをクリック
    2. "Android Library"を選択してNext
    3. 「Application/Library name」に"mklib"、「Package name」に"com.example.buildlibs"を設定して、Finish
    4. Android Studioがぐりぐり動き出すので、OKでダイアログを閉じて安定するまで待つ。
      1. ダイアログを閉じずに待っていたのだが、10分くらいしてもぐりぐりしたままだった
  3. 安定したら、もう一度Project Structureを開いて、左からmklibを選択し、「Dependencies」タブでjunitとtestrunner関係を削除して、OK
  4. ディレクトリ"mklib/src"にある"androidTest"、"test"、"main/java"を削除
  5. mklib/src/mainの中に"cpp"ディレクトリを作り、ソースファイルとCMakeLists.txtを置く
    1. ライブラリ名は"mklibtest"
  6. mklib/build.gradleの編集
    1. android.externalNativeBuildを追加し、cmake.pathにCMakeLists.txtのパスを追加
    2. android.defaultConfig.externalNativeBuildを追加し、cmake.targetsに"mklibtest"を追加
      1. これは、コンテキストメニューの「Link C++ Project with Gradle」から行ってもよい
    3. testInstrumentationRunnerの行を削除
  7. app/build.gradle(これはやらなくてもよいかもしれん)
    1. tasks.whenTaskAddedに、"gen-libs"のまねをして"mklib"を追加
    2. tasks.whenTaskAddedのコメントアウトを戻す
  8. メニュー:Build > Make Project


これで、distribution/mklibtestにヘッダファイルとライブラリがコピーされた。
ライブラリは今回staticにしていて、libmklibtest.a、という名前になった。

ここまでをgithubに上げておこう。


さて、ここまででライブラリはビルドできた。
アプリで使うには、app/build.gradleに追加したり、JNIを作ったりせんといかんが、今回の目的ではないのでよかろう。

次回はMbedTLSをビルドできるようにしたいが、すごく苦労しそうな気がする。

2018/05/12

[android][ndk]MbedTLSをビルドしてみよう (1)

久々にAndroidだが、アプリではなくNDKだ。


LinuxでMbedTLSを使ったアプリを作ったのだが、それをAndroidで動かしたい。
もちろん、私が作っているので、C言語だ。
理想は、画面だけAndroidのJavaなどで作って、機能的な部分はNDK/JNIで今作っているものを流用したい。

その前に、まずMbedTLSをAndroidでビルドできるのか?というところが課題となっている。
なお、私はNDKはほとんど知識がないという前提だ(実際、ないのだが)。


https://tls.mbed.org/features

一番下に移植性のことが書いてあり、Android OSも入っている。
じゃあ、ビルドは簡単かもしれん。


まず、Android Studio v3.1.2でプロジェクトを作る。
Lollipopがデフォルトで出てきたので、そのまま進める。
ただ、C++だけはチェックしておく。

image


ビルドして動かすと、画面の真ん中に「Hello from C++」とだけ表示される。
C++のnative-lib.cppで文字列を返して、それをJavaで出力させているのだ。

ビルドするソースファイルの指定は、CMakeLists.txtでやっている。
cmakeを使うようだ。
Android.mkなんかは、もう使わなくなったのかな?


NDKやcmakeは、インストールされていなければビルドエラーと一緒にリンクも出てきて、それをクリックするとインストールしてくれる。
進化したものだ。。。


MbedTLSもcppフォルダ内にcloneしておこう。
https://github.com/ARMmbed/mbedtls


cloneしたものを"android"でgrepしたが、ヒットしない。。。
ビルドしやすいようなサポートはやってないということか。


新規でC/C++ファイルをAndroid Studioに作成していくのであれば、既存のCMakeLists.txtにファイル名を記載していけばよいのだろう。
しかし、既存のライブラリを取り込むのであれば、何か簡単な方法があるのではないだろうか?

今回はMbedTLSだけだが、それ以外の既存ライブラリを使いたいことの方が多いだろう。
そのとき、全部のライブラリに関するファイル名を列挙するのも嫌だし、そもそもライブラリを1つにまとめることになってしまうのが嫌だ。

既存のライブラリは、その単位で扱いたいものである。


Android StudioのUIを使って、cmakeなりndk-buildなりでビルドできるようにしたプロジェクトをリンクできるそうだ。
https://developer.android.com/studio/projects/add-native-code?hl=JA#link-gradle

ライブラリごとにビルドするスクリプトなどを用意しておけば、それぞれプロジェクトをリンクさせるだけでよい、ということではなかろうか?


Android StudioのAndroidビューから、appなどでコンテキストメニューを表示させると「Link C++ Project with Gradle」という項目が出てきそうなことが書いてあるのだが、メニューにはない。
既にC++が使えるプロジェクトにしているから、出てこないのだろうか?

build.gradleにcmakeの項目があったので、pathをもう1つ追加してみたが、ビルドエラーになった。
なんとなく、1つしか書けないような気がする。


他のCMakeLists.txtを取り込むのは、こちらか?
https://developer.android.com/studio/projects/configure-cmake#include-other-cmake-projects

まねをしようとしたが、何をしたらまねになるのかが分からん。。。



こちらが、複数のライブラリを扱った場合のサンプルのようだ。

https://github.com/googlesamples/android-ndk/tree/master/hello-libs


pre-builtと書いてあるので、既にビルドされたライブラリを取り込むのかと思ったが、ソースファイルもあるな。
どっちを使っているのだろう?


  • hello-libs: app/src/main/cpp/CMakeLists.txt + ソースファイル
    • gen-libs: gen-libs/src/main/cpp/CMakeLists.txt
    • lib_gmath: gen-libs/src/main/cpp/gmath/CMakeLists.txt + ソースファイル(static)
    • lib_gpref: gen-libs/src/main/cpp/gpref/CMakeLists.txt + ソースファイル(shared)
    • log: NDK付属


プロジェクトを開くと、こういう風に見える。

image


組み込むライブラリのソースファイルは見えない。
distributionディレクトリを削除してリビルドしたら、やはりビルドに失敗した。


READMEにも書かれているが、ビルドしたものをdistributionにコピーしているようだ。
もし再生成したいなら、setting.gradle, app/build.gradleを変更すれば良いそうだ。


setting.gradle

include ':app'

// To generate libs used in this sample:
//   1) enable the gen-libs at end of this file
//   2) enable module build dependency in app/build.gradle
//   3) build the app's APK in Android Studio or on command line
//   4) undo step 1) and 2) above
// include ':gen-libs'

app/build.gradleは、一番最後がコメントアウトされていたから、それか?

tasks.whenTaskAdded { task ->
    if (task.name == 'externalNativeBuildRelease') {
        task.dependsOn ":gen-libs:externalNativeBuildRelease"
    } else if (task.name == 'externalNativeBuildDebug') {
        task.dependsOn ":gen-libs:externalNativeBuildDebug"
    }
}


両方を有効にしてビルドし直すと・・・おお、distributionディレクトリが復活している。
次は、これを理解するところから始めよう。

先は長いな・・・。

2018/05/06

git merge --squashによるコミットまとめ

先日、こういう記事を書いた。

クラウド上のVMとのファイル同期はどうやるのがよいのだろう?
http://hiro99ma.blogspot.com/2018/05/vm.html


あれこれ考えたが、私にとってはgitを使った方が無難だという判断に至った。
性格的に、突然大きな変更なんかをやらかしてしまいがち(そして動かなくなる)なので、こまめにgit commitしていく方が安心なのだ。


が、コミットログが乱れるのはよろしくない。
今までは気にもしなかったのだが、そういうお年頃になったのだと思おう。


お仕事用のプロジェクトで、ほぼ自分しかリポジトリを触っていなかったので、直接commit・直接pull request・直接merge、と、やっていたのだ(githubを使っている)。
それを、forkしてからpull requestするタイプにすればよさそうだった。

参考にしたのは、こちら。

慣れてきたらコミットをまとめてPull Requestしよう(git merge --squash) | 株式会社グランフェアズ
https://www.granfairs.com/blog/cto/git-merge-squash


たぶんだが、forkせずに同じリポジトリで作業していたら、それはそれでログが汚れてしまいそうな気がしたのだ。
だ、だよね??

まあ、そうじゃなくてもよいのだ。
プロジェクト用のリポジトリだから、自分は自分のリポジトリで作業して、結果だけを残せば良いのである。


やり方は、リンク先を見た方がわかりやすい。


で、その後だ。

実は・・・forkして作業するというのをほとんどやったことがない。
pull requestがマージされると、fork先が先に進んでしまうけど、どうしたらよいのだか。。。

GitHubでFork/cloneしたリポジトリを本家リポジトリに追従する - Qiita
https://qiita.com/xtetsuji/items/555a1ef19ed21ee42873

ありがたい。


手元にbranchが3つ(upstreamのもの、作業中だったもの、作業中をsquashしたもの)が残り、upstreamはそのままだし、squashしたものもmergeしたからよいのだが、作業中だったものは "git branch -d xxx"では消せず、 "git branch -D xxx"にしなくてはならなかった。
squashされたものの履歴までは追わないのだろう。


しかし、今回はsubmoduleも変わってしまったためか、mergeしてもcommitしていないものが残ってしまった。
commitしてpushしたのだが、当然オリジナルとは別のcommit idになる。


もしかして・・・forkしないでやるべきものなのだろうか?
いや、今まではそうせずにやっていて、commitログが乱れたものになってしまったのだから、それだと期待通りではない。


ずっとローカルで作業できるのであれば、forkせずにいけるような気がする。
だが、私はpushしてしまいたいのだ。
そうなると、第2のローカルという感じで、forkして自分のリポジトリで作業することになると思う。



今日はもうpull requestしたくなるようなものがないので、次回試すしかないな。


pull requestするものができたので、試した。

流れを復習しておこう。

  1. 本体からfork
  2. 本体側でsubmoduleの更新が発生
  3. fork側でpull requestし、本体側でmerge
  4. fork側を本体側に同期させようとして、upstreamをfetchして、メインブランチにmerge
    1. submoduleに差分が出たので、commit


この状態で、fork側からpull requestしたのだが、fork側のsubmode差分によるcommitも対象になってしまった。
おかげで、本体側のcommitログには、submoduleの更新が複数出てきてしまっている。
やはりか。。。


そんなに神経質になるつもりもないのだが、回避できるのであればやりたい。

簡単なのは、手順4をせずに、forkしたものを削除して、新たにforkする、だろう。
本体と同じものになるから、悩むこともない。


upstreamというブランチは、本体側と同じものだ。
それと同じように、手順4でmergeなどせず、mergeしようとしたブランチ自体を捨てて、fetchしたあとのupstreamからブランチを作ってしまえば良いのか?

何か違う、という感触はあるのだが、何がどうダメなのかはわからん。。。


だいたい、forkやらpull requestって、githubやbitbucketの機能で、git自体の機能じゃないよなぁ。
cloneして、remote set-urlで変更するとかか? いや、それじゃ足りないか。。。

使えば使うほど、gitへの理解が足りてないのが身にしみるわ。

2018/05/05

[lmdb][win10]Windows10 1803でWSLのファイル書込みがうまく動いてない?(2018/05/05)

Windows10で、2018年春のアップデートが行われた。
ありがちだが、バグはいろいろあるようだ。

そしてうちでも、そのバグに悩まされることになった。
WSLだ。


https://gist.github.com/hirokuma/4ea889b2a596c59ef4f862e9df54e469


lmdbのmdb_put()がうまく処理できないようなのだ。

MDB_BAD_TXN: Transaction must abort, has a child, or is invalid

最初はmdb_get_cursor()を使って失敗していたのでcursor関連かと思い込んでいたのだが、どうもMDB_txnのチェックでエラーになっているようだ。

https://github.com/LMDB/lmdb/blob/86a90cad721cbdc0bb0a9c3bf15396f5c946ae95/libraries/liblmdb/mdb.c#L7164



そして、DBファイルを残したままであれば、test1()を実行しなければ発生しない。
test1()のmdb_put()をコメントアウトするだけでも大丈夫そうだ。

MDB_txnは毎回MDB_envから取ってきているので、MDB_envに何か残っているということか・・・。


mdb_env_sync()というAPIがあった。
http://www.lmdb.tech/doc/group__mdb.html#ga85e61f05aa68b520cc6c3b981dba5037

何か残っているというならば、ファイルシステムへの書き込みができていないとかだろう。
ただ、ライトキャッシュが吐き出されていなかったとしても、再起動しているわけでも無い状態でOS経由で読み込む分にはキャッシュから読み込んでくれないといかんはずだ。
じゃないと、単に「書込みをしなかった」のと同じになってしまうではないか。。。

ひとまず、test1()とtest2()の間に、以下を追加した。

mdb_env_sync(env, 1);

そして、DBファイルを削除して実行すると・・・動いた。



じゃあ、mdb_env_sync()せずに、コマンドでsyncを実行したらうまく行くのでは?と思ったが、そうでもなかった。
が、syncせずとも、LMDBサンプルを一度実行して失敗した後、5~10秒くらい待ってからもう一度実行すると成功した。


なんじゃそりゃー


そんな感じで、一時的に回避したいなら、強制的に吐き出させるしかなさそうだ。
まいったなぁ。


そして、mdb_env_sync()をmdb_txn_commit()後に行うようにしてみたのだが、作ったサンプル程度であれば動いたのだが、作業でやってるアプリだとダメだった。。。
サンプルでダメだったときはmdb_get()なんかで発生していたのだが、私のアプリだとmdb_txn_commit()で起きてしまう。

MDB_TXN_FINISHEDかMDB_TXN_ERRORが原因のようだが、ファイルの書き込みが疑わしいとなってしまうと、DBとしては何が正しいのかわからんので、WSLが原因なのか、私の実装が悪いのかは、今回の現象が直ってからだな。

2018/05/04

クラウド上のVMとのファイル同期はどうやるのがよいのだろう?

ソースファイルを、ローカルで編集したり、ビルドしたりする。
gitを使っているので、適当なタイミングでpushする。

Linux向けのプログラムなので、ローカルマシンがWindowsではあまり確認がちゃんとできない。
なので、Windowsで実装し、クラウド上のLinuxVMで実行する、ということをしばしば行う。
このとき、どうやってファイルを同期させるのが良いのだろう?
(オープンソースで開発しているので、セキュリティにはそんなに考慮しなくて良い前提)


git経由でやっていたのだが、コミットログが「Linuxで動作確認するためにpush」みたいな感じになってしまい、それで動いたならまだしも、動かないこともしばしば。。。
gitのことを検索すると「コミットログを汚さない方法」みたいなものが結構出てくるのだが、世の中ではそういうやり方をして、なるべく不要なコミットログを残さないように努力しているのだろうか??



あるいは、DropBoxみたいなものを使うという手段もあろう。
Googleドライブだと同期が遅いのでやりづらいのだが、DropBoxは比較的速いので、手元で編集したらすぐにアップされるようだ。
これなら、コミットせずにVMと連携できるし、ファイルを変更するだけでよいから手軽だ。

しかし・・・なんか抵抗がある。
なんかちょっと違うんじゃなかろうか、という、漠然とした思いだけだ。



クラウド上のファイルを正とする、というパターンもありだ。
なんでローカルで編集しているかというと、使い慣れたテキストエディタが使いたいというだけなのだ。
SFTPなんかで編集したいファイルを持ってきて、編集したらアップする、みたいなことはSFTPツールで実現しているものも多い(私はWinSCPでやってる)。
ちょっとした変更だったら、これでもよいのだ。
ただ、複数のファイルを扱ったり、テキストエディタの機能で置換を掛けようとしても、それはできない。

まるまるマウントできると、非常にありがたい。
そういうわけで、今日はAzure Filesを使って、Windowsのネットワークドライブとして接続してみた。
ルータのフィルタリングで445が止められているのに気付かなくて時間がかかったが、困ったのはそれくらいだ(ルータが止めているというのにたどり着くまでが非常に大変だったが)。

WindowsのExplorerから見えるようになって便利なのだが、いくつか問題が。
私の環境で大きい問題は、シンボリックリンク。
CIFSというファイルシステムだからだろうが、シンボリックリンクが使えないようだ。
実行権限なんかは生きているようだから、ファイルコピーすればよいのだけど、そのために作りを変えるのも負けた気がするので避けたい。

また、アクセスがちょっと遅い気がする。
これはVMの方が原因かもしれないので、はっきりしたことは言えない。

あと、ポート445を開けるというのも、ちょっと嫌かも。


いま思いつく方法は、このくらいだ。

なんかいい方法を模索していかねば。。。

2018/04/24

[c]初期化用マクロを用意するとき

protobuf-cを見ているときだったが、構造体の初期化をするのに、関数とマクロの2つが用意されていた。
関数はアドレスを渡せばどこでも初期化できるのだが、マクロは構造体のインスタンス宣言に代入する形でないと使えない。


言葉で書くとわかりづらいので、コードにしておこう。

01: typedef struct {
02:   int a;
03:   const char *b;
04: } aaa_t;
05: 
06: #define INIT_AAA    { 0, NULL }
07: 
08: void init_aaa(aaa_t *val)
09: {
10:    val->a = 0;
11:    val->b = NULL;
12: }


マクロの場合は、構造体の初期化に限定した書き方になるので、宣言時の代入でしか使用できない。
C++であればコンストラクタで済ませられるのだが、Cではどうしようもない。


「じゃあ、初期化関数だけでいいやん」と私は思っていたのだ。
明示的に初期化したのが分かるし、そんなに面倒な書式ではない。


そう思って、自作のライブラリでもそうしていたのだが・・・だんだん嫌になってきた。
例えば、上記のコードを使うと、こうなる。


aaa_t     a;
init_aaa(&a);


大したことは無い。
では、こうなってきたらどうだ。


int k = 0;
aaa_t    a1;
aaa_t    a2;
bbb_t    b;
ccc_t    c;
char *str = NULL;

init_aaa(&a1);
init_aaa(&a2);
init_bbb(&b);
init_ccc(&c);


こういう混在が続いてくると、初期化するものが続いているときには、その場で初期化するようにした方が間違いが少ないんじゃなかろうか、という考えが芽生えてきたのだ。


おじさんでも、芽生えるときは芽生えるのだ。
ただ、それが良い方向かどうかは分からんがね。
「コードリーティング」を読んだ方が良いのかもなぁ。

2018/04/22

[tech]組み込み向けにするのをあきらめるとき

お仕事でプログラムを作っている。

一人で作っているのだが、設計も何もせずにやっている。
よろしくはないのだが、そもそもどういう動きをするようになるのかわからずに作るという、試作の手前のようなものである。


で、最近見直していると、同じようなことをやるのになぜか3つの実装方法をわざわざ使っていることに気付いた。
「同じようなこと」は、データを次々にためておく方法だ。

  • キュー
  • リングバッファ
  • リスト

我ながら「はぁ?」となってしまった・・・。
まあ、キューもリストも似たようなものではあるのだが、「キュー」は自分で実装したのだが、「リスト」はLinuxのリストであるLIST_HEADやLIST_ENTRYを使っている。
作ったのが別の人ならまだしも、私しかいないのに、なぜ。。。


プライオリティがなければ、データの詰め込み方はFIFOかFILOか、キューかスタックか、くらいしかないと思う。
リングバッファは、FIFO型になるのか。


たぶん、私の思考過程からすると、最初に作った「キュー」は、そこまで数が増えないけど、数の予測が付かなかったので、自作したのだろう。
その次がリングバッファになったのは「増えすぎたらキャンセルしてしまえ」と思ったからではなかろうか。

とにかく、メモリサイズがはっきりしない状況が嫌だったのだ。
何とかして、サイズを支配したかったようだ。


最後に作ったのはLinuxのリストを使ったもので、この頃になると自作するのも嫌になったし、サイズを考えるのにも疲れてきたのだろう。
そう、設計どうこうではなく、疲れたのだ。
私らしいといえば、私らしい。


そして、もう1つ。
作っているうちに、こりゃLinuxが動くレベルのマシンじゃないと実装できない、ということに気付いたことだ。
Cortex-Mなどの組み込みソフトにしてしまうことをあきらめたのだ。
これはこれで「疲れた」と表現できるかもしれない。


Raspberry-Pi1くらいあれば動くが、そこは私にとってはもう大きめの組み込みになる。
ストレージを付けたり外付けメモリとかやればCortex-Mでもできなくもないのかもしれないが、そこまでして実現する意味はあまりないだろう。
ハードウェアを安めに仕立ててソフトウェアががんばる、という時代でも無いと思う。
そういうことずっとやってるから、いつまで経っても仕事が減らんのじゃー。


と、それっぽい結論で終わらせようとしたが、実装が3種類もあるのはよろしくないので、どれかにまとめよう。

2018/04/14

[vscode]WSLの配色が直る

こちらの過去記事をご存じだろうか。


[vscode]WSLのターミナルが赤くなった
http://hiro99ma.blogspot.com/2017/12/vscodewsl_4.html


2017年の12月に書いた記事なのだが、これがさっきまで全然解決しなかったのだ。
ColorToolというツールを動かすことで、ようやく直った。


しかし、どういうことだったのだろう?
ColorToolはコンソールの配色を変更するツールのようなのに、vscodeにまで影響があったのか。
あるいは、コンソールに関する色設定がおかしくなっていたのだろうか。
他の環境で発生していないことからすると、後者の方があり得そうだ。

でもなぁ、cmdとかPowerShellなんかは普通だったし、Ubuntuのコンソールもまともだったのだよ。


いろいろ腑に落ちないのだが、新しい解決方法が身についたのは喜べることだ。

[win10]最近あったトラブルたち

昨日のことだった。

会社で使っているWin10 Homeのマシンの調子がよくなかった。
アクションセンター(タスクトレイの右端にある、吹き出しっぽいアイコン)をクリックしても、何も出てこない。
タスクトレイに置いているExplorerのアイコンを右クリックしても、何も出てこない。
スタートメニューはClassic Shellを使っていたから気付かなかったのだが、通常では左クリックしても出てこない。

むう・・・。


イベントビューアを見ると、Cortanaがどうとか。
確か、何かのツールを使ったときに無効化されていたと思うが・・・と見てみると、アンインストールされていた!
通常はアンインストールしようとできないので油断していたのだが、何かをトリガにして消えてしまったようだ。


ネットで検索すると、PowerShellを使ってあれこれやる方法が見つかるのだが、どれをやってもダメ。
昔なら、ストアアプリ系が動かなくてもなんとかなったのだが、いまや設定アプリがストアアプリになっているものが多く、作業できなくもないのだが、非常に不便になることが目に見えていた。


最終的に効果があったのは、Media Creation Toolを使っての再インストール。
私の知っている「再インストール」は、とりあえずWindows周りを再設定し直す、というものだった。
しかし、ネットで検索していると、どうも最近の「再インストール」は、データは削除しないしストアアプリは何とかするけど、それ以外のものは使えなくなる可能性は高いかも、というものが出てきていた。
私が思っていた「クリーンインストール」が、今の「再インストール」になりつつある気配を感じている。


時代が変わったということか。。。
あるいは、レジストリモデルはうまくいかない時代になったと言うことかもしれない(うまくいっていたのかどうかは知らん)。


Media Creation Toolで再インストールするという決断に至るまで、けっこう試したと思う。
wsrreset.exeやトラブルシューティングツールもエラーになるくらいだったから、どうしようもなかったのだろう。
前日までは問題なかったから、Windows Updateをトリガにして何か起こったのか、蓄積していた不具合が一挙に出てきたのか、あるいは・・・。

いや、意味の無い考察はやめよう。



再インストールをMedia Creation Toolでやった結果を残しておこう。
2018/04/13時点のWindows10 Home(Windows Updateは最新)での結果だ。

メディアを作らずに直接再インストールしたが、インストール済みのWindowsアプリ(ストアアプリじゃないやつ)は生き残っていた。
ストアアプリは、起動するようになった。
アクションセンターは、表示されるようになった。
使わないストアアプリはアンインストールしていたのだが、復活していた。Cortanaも戻った。
Shell Newは元に戻った。
Quick Launchにデスクトップ表示などのショートカットが復活した。
ATOKの認証が戻されて、ライセンス入力が必要だった。

全部は試していないのだが、困らないところまでは復帰してくれた。


こういう現象とは別に、以前から起きているものがある。
今回の再インストールをしても直らなかったものだ。


何かというと、Office系のアプリで、画像の挿入を行おうとしてエクスプローラのウィンドウが出たとき、サムネイルがほぼ表示されない、という現象だ。

「ほぼ」が何かというと、一度WindowsのExplorerでサムネイルが表示されているものは、Officeでも表示されているらしい。
Officeで使用されるExplorerにサムネイル作成機能が無い、とも言えるのか。


Office系だけなのかどうか調べたかったのだが、他に画像の挿入を思想なアプリが分からなかった。。。
WireSharkとかWinMergeじゃダメだろうしねぇ。


検索したけど、

  • Explorerの設定がアイコン表示になっている
  • Officeデータをサムネイル表示にしたい

がたくさん出てきて、見つけられなかった。。。


もう1つ。

Edgeを使っている人がいたのだが、「コピー&ペーストをするとウィンドウが閉じる」という話を聞いて、そんなわけなかろう、と操作してもらうと、本当にそうだった・・・。

ウィンドウが閉じるというか、強制終了なのだと思う。
毎回起きるわけではないけど、起きるときには何度やってもダメっぽい。



こういう現象を見ると、ネットで「××がうまくいきません」みたいなQと「○○すればできます。ネットで検索したらすぐ出てきました」というAがよくあり、私も読んで「検索してないのかな?」と思うことがあるのだが、実際目にすると、なかなかそうも行かないパターンもあるので、気をつけねばならんですな。

2018/04/01

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

protobuf-cの使い方を調べている。

前回、ProtobufCBufferSimpleが何か使えるんじゃなかろうかと思ったのだが、packに使うくらいしかなさそうだ。

https://gist.github.com/hirokuma/c79f5c80cfee9db8cc3c968ac4f46819

置き換えてみたのだが、packするサイズは事前に調べられるので、この使い方だと意味は無いな。

repeatedメンバーで個数が増えるときにappendするのがよさそうだが、realloc()で参照するメンバのアドレスを追加し、malloc()でメンバを追加していく方が素直に見える。


protobuf-cを使っているオープンソースが見つけられていなくて、うまい使い方が分からないのだ。
自分で見つけるしかないのか。


サンプルアプリのメモリ解放漏れが気になるので、valgrindで確かめよう。
・・・WSLだと、freeをわざと外しても"All heap blocks were freed -- no leaks are possible"となって信用できんので、ノーマルなUbuntu環境に立て直そう。


git clone https://github.com/google/protobuf.git
cd protobuf
./autogen
make
sudo make install

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

protobufのヘッダがないというエラーになった。。。
しかし、config.logを見ると、-std=c++11 or -std=gnu++11と出ていた。

configure:17261: checking google/protobuf/compiler/command_line_interface.h usability
configure:17261: g++ -c -g -O2  -pthread -I/usr/local/include conftest.cpp >&5
In file included from /usr/include/c++/5/mutex:35:0,
                  from /usr/local/include/google/protobuf/stubs/mutex.h:33,
                  from /usr/local/include/google/protobuf/stubs/common.h:52,
                  from /usr/local/include/google/protobuf/compiler/command_line_interface.h:41,
                  from conftest.cpp:61:
/usr/include/c++/5/bits/c++0x_warning.h:32:2: error: #error This file requires compiler and library support for the ISO C++ 2011 standard. This support must be enabled with the -std=c++11 or -std=gnu++11 compiler options.

では、足してみよう。

./configure CPPFLAGS=-std=c++11
make
...
error: ‘scoped_array’ does not name a type

configureは通ったが、makeでエラーだ。
しかし、WSLでprotobufをビルドしたときにはエラーが出なかったはずだ。

configure:17261: checking google/protobuf/compiler/command_line_interface.h usability
configure:17261: g++ -c -g -O2 -I/home/xxx/prog/clang/protobuf/protobuf-c/../../../cpplang/protobuf/install/include -pthread conftest.cpp >&5
configure:17261: $? = 0
configure:17261: result: yes


何か見落としているのだろうか。。。

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なんかが出てくると、やはりめんどうか。


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