2017年2月22日水曜日

win64アプリケーションクロスビルドでアイコン・プロパティ・リソースを付ける

Gtk3/Cで書いたベクターグラフィック・エディタのvecterionを、Ubuntu16.04ホストでwin64ターゲットにクロスビルド中。でアイコン・プロパティ・リソースを付けた作業のメモです。


以下のようなコードで実現できます。

# .rcほか、リソースファイルを用意する 

実際に必要なのは```IDI_ICON ICON ~```行だけです。
そのICONこれも実は文字列"ICON"とファイルパス以外は大した意味はないとか、他にもオプションが付けられるとかあるようです。
Win32アプリケーションプログラミングをマスターしたいわけではないので.rcファイルの記述はスルー。
.icoファイルですが、GIMPでいい加減に縮小するだけならいい加減に作れます。PNG透過の正方形画像を、512px, 64px,32px,16pxの順にレイヤ複製->レイヤ縮小を繰り返して、最後に.icoで保存しようとすると、アイコン形式画像を保存するウィザードが出てきてGIMPが良いようにしてくれます。
(64pxは48pxのほうがいいかもしれないけれどしていません。512pxもMacOSX対応にしては中途半端。)


Ubuntu16.04環境ではnautilusもeogもこの.icoを正しく描画できませんでしたが、Windowsではとしてはきちんとアプリケーションアイコンとして表示されたので問題ありません。

```
diff --git a/deploy/win/vecterion.rc b/deploy/win/vecterion.rc
new file mode 100644
index 0000000..ed229c3
--- /dev/null
+++ b/deploy/win/vecterion.rc
@@ -0,0 +1,24 @@
+#include <windows.h>
+
+//ICON
+IDI_ICON1 ICON DISCARDABLE "deploy/win/vecterion_icon.ico"
+
+1 VERSIONINFO
+FILEVERSION     0,0,0,1
+PRODUCTVERSION  0,0,0,1
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "080904E4"
+        BEGIN
+            VALUE "InternalName",        "vecterion_vge"
+            VALUE "OriginalFilename",     "vecterion_vge.exe"
+            VALUE "ProductName",        "vecterion_vge"
+            VALUE "ProductVersion",        "0.0.0.1"
+            VALUE "FileVersion",        "0.0.0.1"
+            VALUE "CompanyName",        "project daisy bell"
+            VALUE "LegalCopyright",        "Michinari.Nukazawa"
+            VALUE "FileDescription", "Vecterion, the true vector graphics editor for linux (and other)."
+        END
+    END
+END
diff --git a/deploy/win/vecterion_icon.ico b/deploy/win/vecterion_icon.ico
new file mode 100644
index 0000000..bf8ade4
Binary files /dev/null and b/deploy/win/vecterion_icon.ico differ
diff --git a/deploy/win/vecterion_icon.xcf b/deploy/win/vecterion_icon.xcf
new file mode 100644
index 0000000..7ba9088
Binary files /dev/null and b/deploy/win/vecterion_icon.xcf differ

```

# Windowsリソースのオブジェクト作成します。

クロスビルドはbashスクリプトにしています。

ここでリソース(.rc テキストのソース)をwindresでオブジェクトに変換します。
Makefileに入れるより楽そうで、やってみると、ビルド時間にほぼ影響がなかったので。
```
+# make resource(icon and other)
+TARGET_ARCH_WIN_RESOURCE=${OBJECT_DIR}/vecterion.res
+x86_64-w64-mingw32-windres ${ROOT_DIR}/deploy/win/vecterion.rc -O coff -o ${TARGET_ARCH_WIN_RESOURCE}
+


 # build app source
 CFLAGS_APPEND=-Wno-missing-include-dirs

 [ -d ${GTK3LIBRARY_DIR}/lib/pkgconfig ]
 export PKG_CONFIG_PATH=${GTK3LIBRARY_DIR}/lib/pkgconfig
-make TARGET_ARCH=win CC=x86_64-w64-mingw32-gcc CFLAGS_APPEND=${CFLAGS_APPEND} INCLUDE_APPEND="-mwindows"
+make TARGET_ARCH=win CC=x86_64-w64-mingw32-gcc CFLAGS_APPEND=${CFLAGS_APPEND} INCLUDE_APPEND="-mwindows" TARGET_ARCH_WIN_RESOURCE=${TARGET_ARCH_WIN_RESOURCE}

```



# リソースのオブジェクトをリンクします。

Windowsクロスビルド以外ではリソースのオブジェクトのパスを空文字にします。```
diff --git a/Makefile b/Makefile
index 4f859f1..0aa22f9 100644
--- a/Makefile
+++ b/Makefile
@@ -71,6 +71,7 @@ $(APP_FILE) : $(OBJECTS)
     $(CC) \
         $^ \
         $(OBJECT_DIR)/version.c \
+        $(TARGET_ARCH_WIN_RESOURCE) \
         $(CFLAGS) \
         $(INCLUDE) \
         -o $(APP_FILE)


```



2017年2月18日土曜日

gtk3アプリケーションのクロスビルド(Windowsターゲット、Linuxホスト)した際のメモ

開発中のGtk3/Cによるベクターグラフィック・エディタであるvecterionの、Windowsx64ターゲット、Linuxホストでクロスビルド作業中。
とりあえず動作するところまでは行きました。



その時のトライアンドエラーというか問題と解決のざっとしたメモです。


Windowsターゲットビルドはvecterionにとってあくまでおまけなのだけれど、Linuxターゲットで開発していてコード中に甘かった箇所がいくつかあり(ex. フォーマット文字列中のsize_tの変換指定を%zuにしていなかったなど)、ついでに修正することができた。


原因を追い切ってはいません。Windows版の優先度はその程度です。

#### 解決方法をメモし忘れたエラー error: unknown type name ‘uid_t’


詳細を忘れてしまった。
確かMinGW64のWindowsターゲットクロスビルドであるにも関わらず、pkg-configがLinuxのライブラリをリンクして、起こっていたような気がする。
http://stackoverflow.com/questions/32282270/c99-error-unknown-type-name-pid-t


```
In file included from /usr/lib/x86_64-linux-gnu/glib-2.0/include/glibconfig.h:9:0,
                 from /usr/include/glib-2.0/glib/gtypes.h:32,
                 from /usr/include/glib-2.0/glib/galloca.h:32,
                 from /usr/include/glib-2.0/glib.h:30,
                 from /usr/include/gtk-3.0/gdk/gdkconfig.h:13,
                 from /usr/include/gtk-3.0/gdk/gdk.h:30,
                 from /usr/include/gtk-3.0/gtk/gtk.h:30,
                 from ./include/et_pointing_manager.h:4,
                 from source/et_pointing_manager.c:1:
/usr/include/glib-2.0/glib/gtypes.h: In function ‘_GLIB_CHECKED_ADD_U64’:
/usr/include/glib-2.0/glib/gmacros.h:232:53: error: size of array ‘_GStaticAssertCompileTimeAssertion_0’ is negative
 #define G_STATIC_ASSERT(expr) typedef char G_PASTE (_GStaticAssertCompileTimeAssertion_, __COUNTER__)[(expr) ? 1 : -1] G_GNUC_UNUSED
                                                     ^
/usr/include/glib-2.0/glib/gmacros.h:229:47: note: in definition of macro ‘G_PASTE_ARGS’
 #define G_PASTE_ARGS(identifier1,identifier2) identifier1 ## identifier2
                                               ^
/usr/include/glib-2.0/glib/gmacros.h:232:44: note: in expansion of macro ‘G_PASTE’
 #define G_STATIC_ASSERT(expr) typedef char G_PASTE (_GStaticAssertCompileTimeAssertion_, __COUNTER__)[(expr) ? 1 : -1] G_GNUC_UNUSED
                                            ^
/usr/include/glib-2.0/glib/gtypes.h:422:3: note: in expansion of macro ‘G_STATIC_ASSERT’
   G_STATIC_ASSERT(sizeof (unsigned long long) == sizeof (guint64));
   ^
In file included from /usr/include/glib-2.0/gio/gio.h:46:0,
                 from /usr/include/gtk-3.0/gdk/gdkapplaunchcontext.h:28,
                 from /usr/include/gtk-3.0/gdk/gdk.h:32,
                 from /usr/include/gtk-3.0/gtk/gtk.h:30,
                 from ./include/et_pointing_manager.h:4,
                 from source/et_pointing_manager.c:1:
/usr/include/glib-2.0/gio/gcredentials.h: At top level:
/usr/include/glib-2.0/gio/gcredentials.h:75:1: error: unknown type name ‘uid_t’
 uid_t            g_credentials_get_unix_user      (GCredentials    *credentials,
 ^
/usr/include/glib-2.0/gio/gcredentials.h:79:52: error: unknown type name ‘uid_t’
                                                    uid_t           uid,
                                                    ^
Makefile:60: ターゲット 'object/win/et_pointing_manager.o' のレシピで失敗しました


```

```
 58 export PKG_CONFIG_PATH=${GTK3LIBRARY_DIR}/lib/pkgconfig¬
 59 make TARGET_ARCH=win CC=x86_64-w64-mingw32-gcc CFLAGS_APPEND=${CFLAGS_APPEND} INCLUDE_APPEND="-m    windows"¬


```



#### pkg-configで生成したビルドオプションの[-Werror=missing-include-dirs]


一時的にチェックフラグを解除して回避
最終的にビルドスクリプトを変更し、pkg-config(*.pc設定ファイル)が正しいパスを指すよう修正して解決

```
cc1: error: /srv/win32builder/fixed_3104/build/win64/include/freetype2: そのようなファイルやディレクトリはありません [-Werror=missing-include-dirs]
cc1: error: /srv/win32builder/fixed_3104/build/win64/include/libxml2: そのようなファイルやディレクトリはありません [-Werror=missing-include-dirs]
cc1: error: /srv/win32builder/fixed_3104/build/win64/include/freetype2: そのようなファイルやディレクトリはありません [-Werror=missing-include-dirs]
cc1: all warnings being treated as errors
Makefile:60: ターゲット 'object/win/et_pointing_manager.o' のレシピで失敗しました


```

``` packaging_win64.sh
 42 >-------pushd ${GTK3LIBRARY_DIR}¬
 43 ¬
 44 >-------unzip ${CACHE_DIR}/gtk+-bundle_3.10.4-20131202_win64.zip > /dev/null¬
 45 >-------find -name '*.pc' | while read pc; do sed -e "s@^prefix=.*@prefix=$PWD@" -i "$pc"; done¬
 46 >-------find -name '*.pc' | while read pc; do sed -e "s@/srv/win32builder/fixed_3104/build/win64    @$PWD@g" -i "$pc"; done¬
 47 >-------sed -e "s@Z:/srv/win32builder/fixed_3104/build/win32/@@g" -i "lib/gdk-pixbuf-2.0/2.10.0/    loaders.cache"¬
 48 ¬
 49 >-------popd¬


```




#### mingwがswitch defaultに置いたassert(false)を解釈してくれない

「到達しないソース行」の検出あたりが、linuxのgccとMinGWで違うようで(バージョンの違いかもしれない)、リターンエラーのメッセージが出る。
assert()のラッパを、新規作成したabort()のラッパ関数に置き換えて解決。

```
source/pv_type.c: In function ‘pv_rect_get_edge_point’:
source/pv_type.c:202:1: error: control reaches end of non-void function [-Werror=return-type]
 }
 ^
cc1: all warnings being treated as errors

```

```
195 >------->-------case PvRectEdgeKind_DownLeft:¬
196 >------->------->-------return (PvPoint){rect.x, (rect.y + rect.h)};¬
197 >------->-------case PvRectEdgeKind_DownRight:¬
198 >------->------->-------return (PvPoint){(rect.x + rect.w), (rect.y + rect.h)};¬
199 >------->-------default:¬
200 >------->------->-------pv_assertf(false, "%d", edgeKind);¬
201 >-------}¬
202 }¬

```
```
200 >------->------->-------pv_abortf("%d", edgeKind);¬
```


#### size_t型のリテラル間違い

size_tを"%lu"のままにしてしまうミス。
修正後、正しい指定の"%zu"を受け付けない。
一時的には、出力を潰して対処した。
最終的に、`-posix` 追加で"%zu"が使えるようになり解決。

```
-               pv_debug("len:%lu '%s'", strlen(src), src);
+               pv_debug("len: '%s'", /*strlen(src),*/ src);

```




#### sscanfの非C11標準(posix標準)リテラルの使用

sscanf()にて、リテラル記述に"%m"が存在しないとされて使えない。
-D_GNU_SOURCE=1 は効果なし。
gcc(MinGW)のバージョンが原因でもないように見えた。
最終的に、`-posix` 追加で解決。


```
source/pv_io.c: In function ‘_new_css_str_maps_from_str’:
source/pv_io.c:450:24: error: unknown conversion type character ‘m’ in format [-Werror=format=]
   if(2 != sscanf(head, " %m[^:;] : %m[^;]", &skey, &svalue)){
                        ^
source/pv_io.c:450:24: error: unknown conversion type character ‘m’ in format [-Werror=format=]


```

http://linuxjm.osdn.jp/html/LDP_man-pages/man3/scanf.3.html


http://stackoverflow.com/questions/11926883/scanf-dynamic-allocation
`I don't see anything about m flag in my copy of C11 (which is, admittedly, a draft).`


http://grokbase.com/t/postgresql/pgsql-hackers/114vq5qrhn/unknown-conversion-m
`Hmm. The error disappears if I use -D__USE_MINGW_ANSI_STDIO=1 or -posix`


#### libxml2の引数型の間違い

関数の引数の型が合わないビルドエラー。
Web上のドキュメントではconst付きで記載されているが、実際にヘッダのソースコードを当たってみると、constなしになっている。
constを外して解決。

```
source/pv_io.c:450:24: error: too many arguments for format [-Werror=format-extra-args]
source/pv_io.c: In function ‘_overwrite_conf_read_svg_from_xmlnode’:
source/pv_io.c:534:32: error: passing argument 1 of ‘xmlGetProp’ discards ‘const’ qualifier from pointer target type [-Werror=discarded-qualifiers]
  xmlChar *xc_fill = xmlGetProp(xmlnode, BAD_CAST "fill");


```

http://xmlsoft.org/html/libxml-tree.html#xmlGetProp

#### libgtk3 for windowsの古いバージョンの未実装関数

gtk3-3.10.4がこの関数を未実装であるためエラー。
同名のラッパ関数を定義、またはターゲットにより関数呼び出しを取り除くマクロを定義して解決。

```
source/et_canvas_collection.c: In function ‘et_canvas_collection_delete_canvases_from_doc_id’:
source/et_canvas_collection.c:250:3: error: implicit declaration of function ‘gtk_notebook_detach_tab’ [-Werror=implicit-function-declaration]
   gtk_notebook_detach_tab(GTK_NOTEBOOK(self->widget_tab), canvas_frame);

```

https://mail.xfce.org/pipermail/xfce4-commits/2016-June/071144.html

```
source/et_layer_view.c:148:2: error: implicit declaration of function ‘gtk_text_view_set_monospace’ [-Werror=implicit-function-declaration]
  gtk_text_view_set_monospace (GTK_TEXT_VIEW(self->text), TRUE);


```

#### libgtkのsvgのローダ

svgが開かない
以前も見ていた不具合。
相対パスへの書き換えとライブラリの追加パッケージングで解決。

```
sed -e "s@Z:/srv/win32builder/fixed_3104/build/win32/@@g" -i "lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"

${GTK3LIBRARY_DIR}/lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-svg.dll
${GTK3LIBRARY_DIR}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache

```


#### Windows環境でのマウスポインタ変更

windowへのポインタを取ろうとして、Windows環境で取れないため、マウスカーソル周りの処理でエラー。
マウスカーソル周りをWindowsターゲットのビルドで無効にして一時的に解決。


 
その後、あらためて確認したらビルドが通りました。ヘッダなどが必要だったのかもしれない。

```
(vecterion_vge.exe:4292): Gdk-CRITICAL **: gdk_screen_get_root_window: assertion 'GDK_IS_SCREEN screen)' failed

(vecterion_vge.exe:4292): Gdk-CRITICAL **: gdk_window_new: assertion 'GDK_IS_WINDOW (parent)' failed

(vecterion_vge.exe:4292): Gdk-WARNING **: gdk_input_wintab_init: gdk_window_new failed

```



#### libgtkで一部のダイアログを出すのに必要なファイルの追加

Save操作時にエラーダイアログ。
パッケージにschemasファイルを追加して解決。

```
GLib-GIO-ERROR**: No GSettings schemas are installed on the system
```

```
 76 mkdir -p ${PACKAGE_DIR}/share/glib-2.0/¬
 77 cp -r ${GTK3LIBRARY_DIR}/share/glib-2.0/schemas ${PACKAGE_DIR}/share/glib-2.0/¬

```

http://stackoverflow.com/questions/28953925/glib-gio-error-no-gsettings-schemas-are-installed-on-the-system




2017年1月29日日曜日

Debian(.deb)パッケージを生成

リポジトリに上げておきました。
https://github.com/MichinariNukazawa/debian_package_c_application_automate_example

公式の「 Chapter 4. Simple Example」を、bashスクリプトで自動化したものです。



以下、エラーメッセージとその対処など、リポジトリに上がっていない情報をいくつか。


## DEBEMAILなどを指定していないと吐くエラー

debmakeなどのDebianパッケージ関連コマンドは、製作者情報を環境変数から読むので、セットしておく必要がある。
(コマンド毎に引数指定しても行けるはずと思いますが、コマンド毎に引数指定を調べるのが面倒だったので、そちらは試していない。)

```
$ debmake
I: set parameters
Traceback (most recent call last):
  File "/usr/bin/debmake", line 28, in <module>
      debmake.main()
        File "/usr/lib/python3/dist-packages/debmake/__init__.py", line 104, in main
        para = debmake.para.para(para)
          File "/usr/lib/python3/dist-packages/debmake/para.py", line 44, in para
              debmail = os.getlogin() + '@localhost'
          FileNotFoundError: [Errno 2] No such file or directory


```
忘れずに環境変数に製作者情報をセットしておく。

```
export DEBEMAIL="michinari.nukazawa@gmail.com"
export DEBFULLNAME="Michinari.Nukazawa"

```


## 秘密鍵のエラー

```
W: debhello: binary-without-manpage usr/bin/hello
Finished running lintian.
Now signing changes and any dsc files...
 signfile debhello_0.0-1.dsc Michinari.Nukazawa <michinari.nukazawa@gmail.com>
 gpg: "Michinari.Nukazawa <michinari.nukazawa@gmail.com>"をスキップします: 秘密鍵が利用できません
 gpg: /tmp/debsign.Fwk1I2Pw/debhello_0.0-1.dsc: clearsign failed: 秘密鍵が利用できません
 debsign: gpg error occurred!  Aborting....
 debuild: fatal error at line 1295:
 running debsign failed
 error:packaging_deb.sh(53) "debuild" ""

```
などと出るが、とりあえずパッケージは出来ている。

サインはDebianLinuxの公式な開発者として配布するときに付けるもので、個人使用のパッケージでは不要とのこと。
.debファイルを直接ダウンロード配布するときもとりあえず無くてもインストールできる模様。
```
debuild -us -uc
```
とする。
(manpageに、このオプションは`dpkg-buildpackage`と共通、みたいなことが書いてある。)






## 動作確認


```
dpkg -i debhello_0.0-1_amd64.deb
hello
sudo dpkg -r debhello

```

以上です。

cross_gtk3_for_win64_in_ubuntu

特に書くことはないですが、リポジトリに上げておきました。

gtk3アプリケーションを、Linux(Ubuntu)上でクロスビルドして、ターゲットのWindows(64bit)のバイナリを生成します。
また、gtk3アプリケーションが動作するよう、gtk3のライブラリを含めてzipにパッケージします。
https://github.com/MichinariNukazawa/cross_gtk3_for_win64_in_ubuntu


詳しい手順はリポジトリを参照してください。

TIPS:
* gtk3のWindowsアプリケーションは -mwidows を指定しないとコマンドラインが立ち上がってきます。
* mingwは、今(Ubuntu16.04)は、 mingw-w64 になっています。
* 日本語ファイルを含める場合は、日本語エンコーディングでzipしなければなりません。
(今回は必要ないのでしていません。)
* 公式のコンパイル済みgtk3ライブラリを使っていますが、いかんせんバージョンが古いのもあってか、ターミナルを見るとけっこうエラーメッセージを吐いています。それでも動くから、今回はそのまま。
 

2016年12月16日金曜日

Vim x C言語で書くプロジェクトの良さと(私的)つらみについて

この記事は Vim Advent Calendar 2016 の16日目の記事です。
15日目の前日は shinespark氏の「Vimでtreeっぽくディレクトリ構成を書きたい、そんなアナタの為のVim Plugin、できてます。」でした。


この前の新Mac騒動でCtrl+]を初めて知った@MNukazawaです。
しかし相変わらずESCを連打しています。


追記 > 投稿してから文字サイズがおかしいことに気づいたのですが、ざっくり対処しておきますので、多少おかしくても勘弁いただければと思います。

まえがき


家でも仕事でも、C言語を書くときは(C言語を書くときに限らず)Vimを使っています。
ライトVimmerにとって、Vimは最高ですが、完璧ではないです。
C言語での開発とVimとの相性は最高ですが、光のあるところには影もあり、以下に書いてあるアレコレのつらみに、私は今も苦しんでいます。

今回挙げた中には、受け入れていかなければならないものや、いかにも井の中の蛙っぽい悩みも含まれており、解決策やベストプラクティスが無いはずがないと思えるものもあります。
「プラグイン忌避を克服しろ」「Vimの基本コマンドくらい覚えろ」等々、厳しくツッコミを入れて頂けると、正直とても嬉しいです。


私は現在、Vecterion(べくてりおん)という名前のベクターグラフィック・エディタを書いています。
今回のVim記事も、VecterionをVimで書いているときの話をするので、Vecterionのスペックをざっくり挙げておきます。
* GUIアプリケーション(ベクターグラフィック・エディタ)
* C言語/GTK3で書いている。
* googletest使用
* GNUMake(生Makefile)でビルド
* 中規模アプリケーション(1人で開発。アプリケーション本体のヘッダとソースが50個ずつ有る)

リリースはまだしていませんが、昨日もリリースブロッカを一つ潰したので、来年のどこかでは...。
Vecterionについては、Vimに関わらない C言語 Advent Calendar 2016 の12日目の記事「Cで書く中規模GUIアプリケーションから得た知見(初稿) 」でざっくり書いたので、よければどうぞ。





つらみ達

# ディレクトリ構成

中規模アプリケーションは、ヘッダとソースが別パスになる。
=== Vecterionのヘッダ一覧 ==
:~/etaion_vge$ ls include/
et_canvas.h             et_error.h             et_snap.h          pv_bezier.h           pv_io.h
et_canvas_collection.h  et_etaion.h            et_snap_panel.h    pv_cairo.h            pv_render_context.h
et_color_panel.h        et_key_action.h        et_state.h         pv_color.h            pv_render_option.h
et_define.h             et_layer_view.h        et_stroke_panel.h  pv_element.h          pv_renderer.h
et_doc.h                et_mouse_action.h      et_thumbnail.h     pv_element_general.h  pv_rotate.h
et_doc_history.h        et_mouse_cursor.h      et_tool_id.h       pv_element_info.h     pv_stroke.h
et_doc_history_hive.h   et_mouse_util.h        et_tool_info.h     pv_error.h            pv_svg_info.h
et_doc_id.h             et_pointing_manager.h  et_tool_panel.h    pv_file_format.h      pv_type.h
et_doc_manager.h        et_position_panel.h    pv_anchor_point.h  pv_focus.h            pv_vg.h
et_doc_relation.h       et_renderer.h          pv_appearance.h    pv_general.h

===


ソースは source/ 以下にある。

* ヘッダとソースを別パスにすると、Vimでソースヘッダ間の移動が面倒になる

imでソースヘッダ間の移動で有名な 
`:sp %<.h`
 が効かなくなるため、移動が面倒。
仕方なしにヘッダからソースへの移動は、目に付いた適当な関数を叩いて飛んでいる。
というか、ソースへのアクセスもとりあえずヘッダを開いてctagsでソースへ飛び、いつでもctagsのスタックでヘッダへ飛べるようにしておく、という運用をしている。

 * Vimに必須なctagsが絡むと、ディレクトリが深くなりタイプ量が増える

 ctagsは、tagsファイル生成と利用のために、vimのカレントディレクトリを、常にプロジェクトのソース先頭にしておかないと都合が悪い。
そうでなくても、ソースとヘッダの間を行き来することを考えると、Tab補完はあるものの、ディレクトリの分だけタイプ数が増えることを受け入れなければならない。

# 今どきな長めの名前で、インテリセンス無しで書く

 今どきの関数・変数名は、省略を避けるなど、なんとなく長い名前になりがち。
 長い名前をインテリセンス無しで書くのはつらい。
 (名前が長いこと自体は、自分が悪い気がしないでもないが。)
 Vimは最高のテキストエディタだが、最高のIDEであるとは限らない。

 ex. `>-------pv_anchor_point_set_handle(&dst_aps[2], PvAnchorPointIndex_HandlePrev, p1_handle_prev_dst);`

 インテリセンスのプラグインに決定打がない。ついでにVimプラグインはインストールがつらい。


# リファクタリング的な置換ができる命名

「リファクタリング」というほど格好良くなくても、開発していて名前を変えたくなることは多い。

 * Vimのリネームは1ファイル内のみ

プロジェクト内でファイルを跨いだ命名変更がしたいとき、Vimは無力。
 ヘッダ・ソース合わせて100ファイルを超えているので手作業は嫌。
 なので、
`sed -i 's/AA/BB/g' */*.[hc]`
を使っている。

 * sedでリネームしやすい名前を付けている(付けなければならない)

 つまり`sed`しやすい名前を付けなければならない。
 EtElement構造体のオブジェクトの変数は"element", "layer_element"などと名付ける。
リネームしずらいので、"elem"とか"e"とは付けない。
 (というか初期に一部のコードでそういった変数名を付けてしまったので、後で直さないと...)
 関数の引数を変更したいとき、引数に折り返しがあるとsedできなくてつらい。(これはIDEでも難しいだろうけれど)

ファイルの"path"(filepath)と被るのが怖くて、アンカーポイントで構成される線分"path"(ahchorpath)と名づけるのを避けて、変な名前を付けたりしていた。

# ctagsでstatic関数定義へ飛ぶとき

中規模以上のC言語プロジェクトでは、ファイルローカルなstatic関数を定義する。
そしてコーディングルールによったり、その他の都合で、ソースの先頭近くでファイルローカルstatic関数の定義をすることになる。
そしてVimを使うならば、ctagsで関数間をジャンプする。

 * ctagsは最初の候補としてstatic関数の宣言へ飛ぶが、私は定義を見たい

いつもCtrl+]とCtrl+Tを使っている。Vimのタグジャンプ自体はとても便利で多用するので、いつも常に:tsと打って候補一覧を見てから数字キーで飛び先を指定する、なんてことはしたくない。

 * そもそも開発中に関数を追加しながら毎度`:!ctags -R`するのがつらい。

コミット前のコードは、書きかけの関数が消えたり、名前が変わったりが頻繁に起こる。
`ctags -R`を何度か打つことになる。が、ディレクトリに複数のソースファイルを抱えたプロジェクトで、再帰的にソースを探索させるとctagsも2~3秒かかることもある。(いま以上に大きくなったらどうなるか不安)

# インデント整形(gg=G)にコーディング規約を合わせる

 * Vimはデフォルトでインデント整形できる機能が用意されているのがすばらしい

 しかし完璧ではないので、以下のような考慮すべき点がある。

## switch case{}とLinuxコーディング規約の衝突

Linuxコーディング規約よりインデントが1段深い。
8tabを使っていると8文字分の横幅を使われる。
他の開発者に触ってもらうなどを考えると、Vimのデフォルトから.vimrcで変えたくなかった。
一人で開発しているだけなら、そのうち慣れたので大きな問題でもない。

## extern C{} (c++対応)

* extern C{}がVimの自動インデントで引っかかる。ヘッダ全体にインデントが一段かかってしまう。

 googletestでテストを書こうとするとこれに悩む。
 googletest側でextern C{}することで解決。
 CヘッダがC++に考慮するのが間違いと思うことにする。
 せいぜいC++テストコード内のinclude行だけの問題になる。




以上です。



あとがき

今更ですが、「つらみ」ってこういう使い方で良かったのでしょうかね?

もうそろそろ「ライトVimmerの世界 - 驚きのプラグイン無し生活 -」みたいな記事を書いたほうが良い気がしています。
それよりVimコマンドを覚えたほうが有意義そうですが。

水の中の魚は水のことを意識することがありません。Vimを使ってC言語でコードを書いていると、ctagsの有り難みをつい忘れてしまいがちです(Windows上のAtomでソースを開いたときに思い出す)。
それは必ずしも悪いことではないのだと思います。が、快適なVimコーディングも、使い続けていると細かいところでつらみが意識されるようになるのも、自然なことで、だからこそ先達たちは多用なVimプラグインを生み出してきたのかもしれません。


この記事は Vim Advent Calendar 2016 の16日目の記事でした。
17日目の明日はanekos氏の「Vim の下着の話」です。

2016年12月12日月曜日

Cで書く中規模GUIアプリケーションから得た知見(初稿)

この記事は C言語 Advent Calendar 2016 の12日目の記事です。
11日目の前日はegtra氏の「配列でないオブジェクトに対するポインタ演算」でした。


@MNukazawaといいます。今、ベクターグラフィック・エディタをC言語で書いています。
Vecterion(べくてりおん)という名前です。
VecterionはGtk3/C言語で書いています。
現在進行形ですが、今日はVecterion開発の知見というか、Vecterionで使っているちょっと大きめのイディオムを、いくつか紹介したいと思います。

なおこの記事で言っている「中規模」ですが、
* ワンソース(ほぼmain.cだけ)に収まるアプリケーションを小規模
* 多人数で開発しなきゃ作れないアプリケーションを大規模
として、コマンドラインスクリプトよりは高度なことをしているけれど、コード量としては一人で収まる。
小規模と大規模の間くらい、程度の意味です。

小規模は、(語弊があるが)catやechoのような、ワンパスのコマンドラインスクリプト程度の機能。
大規模は、GIMP, LibreOfficeのようなGUIエディタや、Linuxカーネルなど。



なんというか、中規模コードの良い例を見つけることができなかったので書きました。
どなたかオススメのプロジェクトをご存知の方は教えてください。


開発中プロジェクトでまだ悩み中(本当は良くない)なので、まとまりきらない内容になっていますが、あなたが中規模プロジェクトを書く際に、一部でも参考になれば幸いです。

# ソース構成

## ソース構成

struct型の定義と一緒に、メンバ関数・構造体を操作するUtility関数を同じヘッダに置いている。
CヘッダとCソースは原則的に一対一対応させている。

これらのソース構成は、ビルド速度的には最善ではないが、関数の置き場所がわかりやすく、プロジェクト全体の読みやすさに繋がる。

"#define"でない方法で定数 を定義している。
===
 static const PvPoint PvPoint_Default = {0, 0};¬
===
static const は入れ子に宣言定義(?)できないので、その時は諦めて#defineを使う。


Gtk的な関数名の付け方を、そのままソースの名前にしている。
===
:~/etaion_vge$ ls include/
et_canvas.h             et_error.h             et_snap.h          pv_bezier.h           pv_io.h
et_canvas_collection.h  et_etaion.h            et_snap_panel.h    pv_cairo.h            pv_render_context.h
et_color_panel.h        et_key_action.h        et_state.h         pv_color.h            pv_render_option.h
et_define.h             et_layer_view.h        et_stroke_panel.h  pv_element.h          pv_renderer.h
et_doc.h                et_mouse_action.h      et_thumbnail.h     pv_element_general.h  pv_rotate.h
et_doc_history.h        et_mouse_cursor.h      et_tool_id.h       pv_element_info.h     pv_stroke.h
et_doc_history_hive.h   et_mouse_util.h        et_tool_info.h     pv_error.h            pv_svg_info.h
et_doc_id.h             et_pointing_manager.h  et_tool_panel.h    pv_file_format.h      pv_type.h
et_doc_manager.h        et_position_panel.h    pv_anchor_point.h  pv_focus.h            pv_vg.h
et_doc_relation.h       et_renderer.h          pv_appearance.h    pv_general.h

===



# コーディング

## ソース(関数・変数etc)のネーミング

sed, grep, vim, ctags, が効く名前を付けるべき。今はGtk3ライクな名前を付けている。
 vim上で一意性のある名前にするかは悩ましい(悩んでる)
 ctags対応には一意な名前を付ける。変に同名(static関数とか?)を付けなければ大丈夫。

特に開発中のプロジェクトは、実装により適切な名前が別にあることがわかったりするので、関数ローカルの一時変数名まで、置き換えを意識して付けるようにしている。
つまり、同名を避けることで、
 sed -i s/AA/BB/g */*.[hc]
 grep -r AA */*.[hc]

が効くように名前を付ける。
例えば、"Doc"を"Document"に直すときに、「ほぼ一括置換」くらいの手間で置き換えられる状態を目指している。
===
 11 struct EtDoc;¬
 12 typedef struct EtDoc EtDoc;¬
 13 ¬
 14 typedef int EtCallbackId;¬
 15 typedef void (*EtDocSlotChange)(EtDoc *doc, gpointer data);¬
 16 ¬
 17 ¬
 18 ¬
 19 EtDoc *et_doc_new();¬
 20 EtDoc *et_doc_new_from_vg(const PvVg *vg);¬
 21 void et_doc_delete(EtDoc *);¬
 22 EtDocId et_doc_get_id(EtDoc *self);¬
 23 char *et_doc_get_new_filename_from_id(EtDocId doc_id);¬

===



## C99(C11)を使う

変数の宣言、ただしgotoエラー処理と相性が悪い...
stdbool.hのbool型
const使う
安全な文字列操作 snprintf, strlcpy, g_strdup_printf
 strlcpy, g_strdup_printfは、それが無い環境では互換関数を書いたりしている。

# 未定義動作を避ける

最近は浸透してきた?
未定義動作の存在の認知度ってどのくらいなのだろう。
clang、本の虫、JPCERT CC、
 http://blog-ja.intransient.info/2011/05/c-13.html
 https://cpplover.blogspot.jp/2014/06/old-new-thing.html
 https://www.jpcert.or.jp/sc-rules/c-pre00-c.html

## ヘッダはシステム、自分ソースの順

ヘッダを書き間違えた時にとんでもないエラーメッセージが出ることがあるので、ヘッダはシステムヘッダを上に書く。
// と言いつつ、.h:.c一対一対応のコードはCソースの一番上に対応ヘッダファイルを書いているが。
===
  6 ¬
  7 #include <gtk/gtk.h>¬
  8 #include <gdk/gdk.h>¬
  9 #include <stdbool.h>¬
 10 #include "pv_element_general.h"¬
 11 #include "pv_color.h"¬
 12 #include "pv_stroke.h"¬
 13 #include "pv_appearance.h"¬
 14 ¬
===


## typedef enum定数

例えばアイコンをIDで管理して実体を引く関数を書くなら、get_icon_from_id(int icon_id)よりは(IconId icon_id)のほうが良いかと。
内部的にはint的なモノなのであくまで読みやすくするだけの糖衣。
コンパイラチェックが得られるとは期待しない。
===
 62 // ** ElementKind定数¬
 63 ¬
 64 typedef enum _PvElementKind{¬
 65 >-------PvElementKind_NotDefined,¬
 66 >-------/* special element document root */¬
 67 >-------PvElementKind_Root,¬
 68 >-------/* complex element kinds (group) */¬
 69 >-------PvElementKind_Layer,¬
 70 >-------PvElementKind_Group,¬
 71 >-------/* simple element kinds */¬
 72 >-------PvElementKind_Curve,¬
 73 >-------PvElementKind_Raster, /* Raster image */¬
 74 ¬
 75 >-------/* 番兵 */¬
 76 >-------PvElementKind_EndOfKind,¬
 77 }PvElementKind;¬
 78 ¬
===


## class的C記法 typedef struct & classメンバ的Utility関数

Gtkベースな、C++ classメンバなんて、C関数self引数の糖衣構文に過ぎない的思想。
最初はthisと名づけていたら、googletest側でC++予約語と衝突して、selfにリネームしなければならなくなった。
===
168 PvElement *pv_element_new(const PvElementKind kind)¬
169 {¬
170 >-------PvElement *self = (PvElement *)malloc(sizeof(PvElement));¬
171 >-------pv_assert(self);¬
172 ¬
173 >-------self->parent = NULL;¬
174 >-------self->childs = NULL;¬
175 ¬
176 >-------const PvElementInfo *info = pv_element_get_info_from_kind(kind);¬
177 >-------pv_assertf(info, "%d", kind);¬
178 >-------pv_assertf(info->func_new_data, "%d", kind);¬
179 ¬
180 >-------self->data = info->func_new_data();¬
181 >-------pv_assertf(self->data, "%d", kind);¬
182 ¬
183 >-------self->color_pair = PvColorPair_Default;¬
184 >-------self->stroke = PvStroke_Default;¬
185 ¬
186 >-------self->kind = kind;¬
187 ¬
188 >-------self->etaion_work_appearances = pv_appearance_parray_new_from_num(NUM_WORK_APPEARANCE + 1);¬
189 >-------pv_assert(self->etaion_work_appearances);¬
190 ¬
191 >-------return self;¬
192 }¬
193 ¬
===


## 分岐の方法

switch構文が読みやすくて好き > VimのデフォルトとLinuxでインデントルール衝突しているがまあそれはそれ。
switch文は縦横サイズを食うので、サンプルコードは略す。

## Info型とget_info_from_kind()関数

不満はあるが、Info型をKind,Id,Indexのいずれかで引く方式を使っている。
記事参照 「Cに欲しい機能 インデックス番号付き構造体配列」
===
1739 ¬
1740 const PvElementInfo _pv_element_infos[] = {¬
1741 >-------{PvElementKind_Root, "Root",¬
1742 >------->-------.func_new_data>->------->------->-------= _func_group_new_data,¬
1743 >------->-------.func_free_data>>------->------->-------= _func_group_free_data,¬
1744 >------->-------.func_copy_new_data>---->------->-------= _func_group_copy_new_data,¬
1745 >------->-------.func_write_svg>>------->------->-------= _func_group_write_svg,¬

1757 >------->-------.func_get_rect_by_anchor_points>>-------= _func_notimpl_get_rect_by_anchor_points,¬
1758 >------->-------.func_set_rect_by_anchor_points>>-------= _func_notimpl_set_rect_by_anchor_points,¬
1759 >------->-------.func_get_rect_by_draw>->------->-------= _func_notimpl_get_rect_by_draw,¬
1760 >------->-------.func_apply_appearances>>------->-------= _func_nop_apply_appearances,¬
1761 >-------},¬
1762 >-------{PvElementKind_Layer, "Layer",¬

===

## データ構造の生成というか確保というか

malloc,freeで生成・削除し、ポインタで保持する。
構造体の中身を見せたくなければ、Cのimplイディオムのように、ヘッダに宣言のみ書く方法で定義を隠す。

可変長配列は、ポインタ配列をポインタポインタで確保して使うことで実現している。
Cを使っているので、可変長配列が使いたければmalloc,freeと仲良くするしかない(mallocの速度については必要になるまでは忘れる)。

 size_t *_get_parray_num()トリックで個数を取るとイテレートがやりやすい。
 for(int i = 0; i < (int)num; i++) が定型文になりつつある。
===
 170 >-------int num = pv_general_get_parray_num((void **)elements);¬
 171 >-------for(int i = 0; i < num; i++){¬
 172 >------->-------const PvElement *element = elements[i];¬
 173 >------->-------const PvElementInfo *info = pv_element_get_info_from_kind(element->kind);¬
 174 >------->-------et_assertf(info, "%d", element->kind);¬
 175 ¬
 176 >------->-------PvRect rect = info->func_get_rect_by_anchor_points(element);¬
 177 ¬
 178 >------->-------if(0 == i){¬
 179 >------->------->-------rect_extent = rect;¬
 180 >------->-------}else{¬
 181 >------->------->-------rect_extent = pv_rect_expand(rect_extent, rect);¬
 182 >------->-------}¬
 183 >-------}¬
 184 ¬
 185 ¬
 

===

## エラー返り値

NULLまたはbool falseに統一。
それ以外が必要なら最終引数に "~bool *is_error)"
引数が不正な場合、Vecterionはabort()するが、PhotonVectorはエラーを返す方針。
===

  8 #define et_assert(hr) \¬
  9 >-------do{ \¬
 10 >------->-------if(!(hr)){ \¬
 11 >------->------->-------fprintf(stderr, "et_assert: %s()[%d]:'%s'\n", __func__, __LINE__, #hr); \¬
 12 >------->------->-------assert(hr); \¬
 13 >------->-------} \¬
 14 >-------}while(0);¬
 15 ¬
 16 #define et_assertf(hr, fmt, ...) \¬
 17 >-------do{ \¬
 18 >------->-------if(!(hr)){ \¬
 19 >------->------->-------fprintf(stderr, "et_assertf: %s()[%d]: "fmt"\n", \¬
 20 >------->------->------->------->-------__func__, __LINE__, ## __VA_ARGS__); \¬
 21 >------->------->-------assert(hr); \¬
 22 >------->-------} \¬
 23 >-------}while(0);¬
 24 ¬
 25 // Caution: depend gcc¬ 33 #define et_error(fmt, ...)  \¬
 34 >-------fprintf(stderr, "error: %s()[%d]: "fmt"\n", __func__, __LINE__, ## __VA_ARGS__)¬ 37 #define et_debug(fmt, ...)  \¬
 38 >-------fprintf(stdout, "debug: %s()[%d]: "fmt"\n", __func__, __LINE__, ## __VA_ARGS__)¬
 

===


...という感じです。
本当はもっといろいろありますし、どうしてこれが良さそうか、という理由も、いずれ書こうと思っています。
また、ここに書いたのとは別に Vim (その2) Advent Calendar 2016 にVimでCアプリケーションを開発する話を書く予定です。そちらもよろしくお願いします。

この記事は C言語 Advent Calendar 2016 の12日目の記事でした。
13日目の明日はyashi氏の「The Meson Build System」です。楽しみですね。

2016年10月28日金曜日

GdkPixbufを高速にリサイズ(cairo)


GdkPixbufのサイズ変更を、元画像500x500px から 出力画像100x100~10x10pxで行うと、どうやらとても遅くなるようです。



GDK_POINTER_MOTION_HINT_MASK を使わずに motion-notify-eventをそのまま再描画に投げている私も悪いのですが。
とはいえ GDK_POINTER_MOTION_HINT_MASK はGtk3で思った通りにならず、アプリケーションがフリーズするようになっていました。

将来的には描画前のレンダリングをマルチスレッド化するつもりではいます。しかしシングルスレッドだと競合などを考えずに済み楽であるため、どうにもならなくなるまではシングルスレッドで行くつもりです。

というわけで、単純にリサイズ機能を高速化して対応。このあたりも、cairo任せにするといろいろオブジェクトを確保するコストを考えてもずっと早いのではないかというカンが当たった感じです。


なお、 gdk_pixbuf_scale_simple()をそのまま、画質を GDK_INTERP_HYPER からバイリニア指定に変えるのは試してみましたが、ほとんど効果なしでした。

なお画質は不明。見た目にわからないですし悪くないですが、アプリケーションが将来的に画質を指定するようになったら、リサイズ機能を見直そうと思います。

呼び出し例は以下。
====
/*

 
GdkPixbuf *pb = gdk_pixbuf_scale_simple(

 

 

 
pixbuf,

 
 
 
(int)w, (int)h,
 
 
 
GDK_INTERP_HYPER);
*/

 
GdkPixbuf *pb = _pv_copy_new_pixbuf_scale(
 
 
 
pixbuf,
 
 
 
(int)w, (int)h);
====

実装は以下。
====
static GdkPixbuf *_pv_copy_new_pixbuf_scale(GdkPixbuf *pb_src, double w_dst, double h_dst)
{
 
cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w_dst, h_dst);
 
pv_assert(surface);
 
cairo_t *cr = cairo_create (surface);
 
pv_assert(cr);

 
double w_src = gdk_pixbuf_get_width(pb_src);
 
double h_src = gdk_pixbuf_get_height(pb_src);

 
cairo_matrix_t m = {
 
 
w_dst/w_src, 0,
 
 
0, h_dst/h_src,
 
 
0, 0,
 
};
 
cairo_set_matrix(cr, &m);

 
gdk_cairo_set_source_pixbuf (cr, pb_src, 0, 0);
 
cairo_paint (cr);

 
GdkPixbuf *pb = gdk_pixbuf_get_from_surface(surface, 0, 0, w_dst, h_dst);
 
pv_assert(pb);

 
cairo_surface_destroy (surface);
 
cairo_destroy (cr);

 
return pb;
}
====

作ったPixbufは呼び出し元で開放しましょう。
以上です。