C言語とGTK+3でHello, World!を表示する【ウィンドウ, GUI】
目次
GTK+3でHello, World!
C言語でGUIアプリを作りたいとなった時に、選択肢は色々あります。
その1つが「GTK」というライブラリを使う方法です。
GTKは「GIMP」というドローウィングツールの開発で開発されました。
GIMPはマルチプラットフォームで無料で利用できるドローウィングソフトとして有名です。
GTKにはバージョンがありGTK+3やGTK+4などがあります。
今回はGTK+3でウィンドウを作成し、Hello, World!するプログラムを解説いたします。
表示するウィンドウは以下になります。
ビルド環境
今回はビルド環境としてはWindowsとMSYS2を使っています。
MSYS2を使うとWindowsで実行できるGTKプログラムを作成可能です。
MSYS2の環境ではpacman
というパッケージツールが使えます。
これでGTK+3をインストールします。
MSYS2の端末でpacman -Ss gtk3
でそれっぽいライブラリのリストが表示されますので、環境に合わせたライブラリをインストールします。
インストールはpacman -S ライブラリ名
で行えます。
私の環境ではmingw64/mingw-w64-x86_64-gtk3 3.24.33+19+ga47f81804d-1
を入れています。
また、ビルド用のツールとしてpkg-config
というプログラムも必要です。
これもpacman -Ss pkg-config
でリストが表示されますので、環境にあったやつを入れます。
私の環境ではmingw64/mingw-w64-x86_64-pkgconf 1.8.0-2 (mingw-w64-x86_64-toolchain)
というのを入れています。
gccコンパイラがMinGWの64ビットの場合はmingw64
系になります。
Clangならclang
系になるでしょう。
gccコンパイラはpacman -Ss gcc
で色々表示されます。
私の環境ではmingw64/mingw-w64-x86_64-gcc 11.3.0-1 (mingw-w64-x86_64-toolchain)
を入れています。
Makefile
ソースコードをビルドするためのツールとして「Makefile」があります。
これはビルドのルールを記述してビルドを自動化するための言語です。
適当なMSYS2環境のフォルダにMakefile
という拡張子無しのファイルを作ります。
そして以下を記述します。
# ビルドに使用するコンパイラ CC := gcc # コンパイラのフラグ CC_FLAGS := -Wall -g `pkg-config --cflags gtk+-3.0` # ビルド時にリンクするライブラリ CC_LIBS := `pkg-config --libs gtk+-3.0` # 生成するプログラム名 PROG := helloworld.exe # 現在フォルダ以下の.cファイルをSRCSに格納 SRCS := $(wildcard *.c) # SRCSの拡張子を.oに変更してOBJSに格納 OBJS := $(SRCS:.c=.o) # makeが実行されたPROGを生成する all: $(PROG) # PROGの生成規則 $(PROG): $(OBJS) $(CC) $(CC_FLAGS) -o $@ $^ $(CC_LIBS) # .oファイルの生成規則 %.o: %.c $(CC) $(CC_FLAGS) -o $@ -c $< $(CC_LIBS) # サブコマンド。オブジェクトファイルとプログラムの削除 clean: rm *.o rm $(PROG) # clean識別子をPHONYターゲットに .PHONY: clean
# ビルドに使用するコンパイラ CC := gcc
上記のCC := gcc
という記述はCC
という変数にgcc
という値を入れています。
この変数は$(CC)
で参照することができます。
CC
にはビルドに使うコンパイラの名前を入れておきます。
# コンパイラのフラグ CC_FLAGS := -Wall -g `pkg-config --cflags gtk+-3.0` # ビルド時にリンクするライブラリ CC_LIBS := `pkg-config --libs gtk+-3.0`
上記の記述ではCC_FLAGS
にコンパイラのフラグ、CC_LIBS
にリンクするライブラリを格納しています。
pkg-config
をバッククオートで囲むと、そのpkg-config
の出力結果が展開されます。
pkg-config --libs gtk+-3.0
とやると、コンパイル用のGTK+3のインクルードフォルダの指定が取得されます。
これは例えば以下のような値になります。
$ pkg-config --cflags gtk+-3.0 -IC:/msys64/mingw64/include/gtk-3.0 -IC:/msys64/mingw64/include/pango-1.0 -IC:/msys64/mingw64/include -IC:/msys64/mingw64/include/glib-2.0 -IC:/msys64/mingw64/lib/glib-2.0/include -IC:/msys64/mingw64/include/harfbuzz -IC:/msys64/mingw64/include/freetype2 -IC:/msys64/mingw64/include/libpng16 -IC:/msys64/mingw64/include/fribidi -IC:/msys64/mingw64/include/cairo -IC:/msys64/mingw64/include/lzo -IC:/msys64/mingw64/include/pixman-1 -IC:/msys64/mingw64/include/gdk-pixbuf-2.0 -IC:/msys64/mingw64/include/atk-1.0 -mms-bitfields
pkg-config
を使うとこういう感じでコンパイルオプションを補完してくれます。
上記の-I
というのはgccのショートオプションで、指定したフォルダのヘッダーファイルをコンパイル時に探すようになります。
pkg-config --libs gtk+-3.0
では以下のような出力になります。
$ pkg-config --libs gtk+-3.0 -LC:/msys64/mingw64/lib -lgtk-3 -lgdk-3 -lz -lgdi32 -limm32 -lshell32 -lole32 -Wl,-luuid -lwinmm -ldwmapi -lsetupapi -lcfgmgr32 -lhid -lwinspool -lcomctl32 -lcomdlg32 -lpangowin32-1.0 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -latk-1.0 -lcairo-gobject -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 -lintl
上記では-lgtk-3
などと書かれていますが、-l
というのはライブラリをリンクするgccのショートオプションです。
# 生成するプログラム名 PROG := helloworld.exe
上記では最終的にビルドで生成するプログラム名をPROG
に指定します。
UNIX/Linux系ではhelloworld.out
等になると思います。
# 現在フォルダ以下の.cファイルをSRCSに格納 SRCS := $(wildcard *.c) # SRCSの拡張子を.oに変更してOBJSに格納 OBJS := $(SRCS:.c=.o)
上記ではSRCS := $(wildcard *.c)
で、現在の作業フォルダ以下の.c
な拡張子のファイルをまとめてSRCS
に格納します。
OBJS := $(SRCS:.c=.o)
ではSRCS
のファイルの.c
を.o
に置き換えてOBJS
に保存します。
# makeが実行されたPROGを生成する all: $(PROG)
MakefileはMakefileがある場所でmake
というコマンドを打つとMakefileが実行されます。
その実行されるときに何をビルドするのかを上記のようにall
に指定します。
ここではPROG
をビルドするようにしています。
# PROGの生成規則 $(PROG): $(OBJS) $(CC) $(CC_FLAGS) -o $@ $^ $(CC_LIBS) # .oファイルの生成規則 %.o: %.c $(CC) $(CC_FLAGS) -o $@ -c $< $(CC_LIBS)
上記ではPROG
や.o
ファイルのビルドルールを記述しています。
Makefileの記述ルールは以下のようになります。
生成したいファイル: 依存ファイル ビルドに使うコマンドライン ビルドに使うコマンドライン ...
つまり先ほどの例ではPROG
を生成するにはOBJS
が必要で、そのためのビルド用のコマンドラインは
$(CC) $(CC_FLAGS) -o $@ $^ $(CC_LIBS)
になるということです。
$(CC)
や$(CC_FLAGS)
などは変数の値を出力していますが、$@
は生成したいファイル、つまりPROG
を指し、$^
は指定されているすべての依存ファイル、つまりOBJS
を指します。
-o
オプションは生成ファイル名の指定です。
ライブラリはコマンドラインの後ろの方に指定した方がいいので$(CC_LIBS)
をお尻に付けています。
%.o: %.c
という指定は.o
ファイルを生成したいファイルに指定し、その依存ファイルが.c
ファイルということになります。
このようにパーセンテージの指定を行うとファイル名を省略できます。
この指定はオブジェクトファイルの生成に使いますので、コマンドラインは
$(CC) $(CC_FLAGS) -o $@ -c $< $(CC_LIBS)
上記のように-c
オプションを指定します。
$<
は依存ファイルの一番左のファイルの指定になります。
# サブコマンド。オブジェクトファイルとプログラムの削除 clean: rm *.o rm $(PROG) # clean識別子をPHONYターゲットに .PHONY: clean
上記ではサブコマンドの「clean
」を定義しています。
これは開発者が勝手に定義しているコマンドです。
Makefileはこのように生成したいファイルに識別子を指定すると、make clean
のようにコマンドみたいに実行することができます。
実行すると記述されている以下のコマンドラインが順に実行されます。
rm *.o rm $(PROG)
このようなサブコマンドは.PHONY
に記述しておきます。
.PHONY: コマンド名 コマンド名 ...
のように記述するとそのコマンド名がPHONYターゲットになります。
PHONYターゲットにすると、作業フォルダ以下にclean
ファイルがあってもサブコマンドが実行されるようになります。
C言語によるソースコード
ではC言語とGTK+3を使ってコードを書いていきます。
/* GTK+3でHello, World!を表示するプログラム。 * License: MIT * Since: 2023-10-29 */ #include <gtk/gtk.h> int main(int argc, char *argv[]) { // GTKを初期化 gtk_init(&argc, &argv); // ウィンドウを作成 GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); // ウィンドウのタイトルを設定 gtk_window_set_title(GTK_WINDOW(window), "Hello Window"); // ウィンドウの横幅と高さを設定 gtk_widget_set_size_request(window, 400, 300); // ウィンドウが破棄されたときにgtk_main_quit関数を呼び出すように設定 g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); // ラベルの作成 GtkWidget *label = gtk_label_new("Hello, World!"); // ラベルをウィンドウに配置 gtk_container_add(GTK_CONTAINER(window), label); // ウィンドウ以下のウィジェットを全て表示 gtk_widget_show_all(window); // GTKを実行 gtk_main(); return 0; }
まず以下のコードです。
#include <gtk/gtk.h>
GTK+3を使うには上記のようにgtk/gtk.h
をインクルードする必要があります。
これはpkg-config --cflags gtk+-3.0
でインクルードフォルダを指定しているから可能になっています。
// GTKを初期化 gtk_init(&argc, &argv);
上記ではGTKを初期化しています。
argc
とargv
のアドレスを渡します。
// ウィンドウを作成 GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
上記ではウィンドウを作成します。
GtkWidget
型のポインタになっている点に注意してください。
GTK_WINDOW_TOPLEVEL
はトップレベルのウィンドウを作成するときに指定します。
この他にはポップアップウィンドウを作成するときに指定するGTK_WINDOW_POPUP
などがあります。
// ウィンドウのタイトルを設定 gtk_window_set_title(GTK_WINDOW(window), "Hello Window"); // ウィンドウの横幅と高さを設定 gtk_widget_set_size_request(window, 400, 300);
上記ではウィンドウのタイトルと横幅と高さを指定します。
gtk_window_set_title()
は引数がGtkWindow *
が必要です。
ですのでwindow
をGTK_WINDOW()
でキャストします。
GtkWindow
はGtkWidget
を継承していますので、これら2つはポインタであれば相互キャスト可能です。
C言語で継承? そんなバハマ! と思われた方もいるかもしれません。
C言語では構造体のポインタと型キャストを使うと、継承のような設計をすることができます。
以下は簡単なサンプルコードです。
// ベースとなる構造体 struct Animal { int age; }; struct Bird { struct Animal base; // 継承 int weight; };
上記のようなコードでC言語で継承を実現できます。
gtk_widget_set_size_request()
は引数はGtkWidget *
ですのでそのままwindow
を渡します。
// ウィンドウが破棄されたときにgtk_main_quit関数を呼び出すように設定 g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
上記ではwindow
のシグナルdestory
に、gtk_main_quit
関数を紐づけています。
g_signal_connect()
でウィジェットにコールバック関数を指定できます。
ウィンドウが破棄(destory
)されるとgtk_main_quit()
が呼ばれてGTKが終了する、という感じです。
最後のNULL
はコールバック関数に渡す引数(gtk_main_quit()
)を指定しますが、ここではNULL
にしています。
// ラベルの作成 GtkWidget *label = gtk_label_new("Hello, World!"); // ラベルをウィンドウに配置 gtk_container_add(GTK_CONTAINER(window), label);
ウィンドウの中央に「Hello, World!」という文字列を表示したいので、ラベルを作成します。
上記ではラベルのウィジェットを作成して、それをwindow
に配置しています。
GtkWindow
はGtkContainer
も継承していますので、上記のようにgtk_container_add()
で型キャストしています。
このようにC言語のGTK+3では型キャストを頻繁に行いますのでミスって変な型にキャストしないようにするのが大事です。
// ウィンドウ以下のウィジェットを全て表示 gtk_widget_show_all(window); // GTKを実行 gtk_main();
gtk_widget_show_all()
でwindow
以下のウィジェットを全て表示します。
こうしないとウィンドウが表示されません。
gtk_main()
でGTKを実行します。
おわりに
今回はC言語とGTK+3でHello, World!してみました。
GTKは非常に高速なGUIライブラリで実用的なGUIアプリの構築に適していると言えます。
代表的なアプリは例えばSublimeText3なんかがそうですね。
なにか参考になれば幸いです。