ユーニックス総合研究所

  • home
  • archives
  • c-gtk3-hello-world

C言語とGTK+3でHello, World!を表示する【ウィンドウ, GUI】

  • 作成日: 2023-10-28
  • 更新日: 2023-12-24
  • カテゴリ: C言語

GTK+3でHello, World!

C言語でGUIアプリを作りたいとなった時に、選択肢は色々あります。
その1つが「GTK」というライブラリを使う方法です。

GTKは「GIMP」というドローウィングツールの開発で開発されました。
GIMPはマルチプラットフォームで無料で利用できるドローウィングソフトとして有名です。

GTKにはバージョンがありGTK+3やGTK+4などがあります。
今回はGTK+3でウィンドウを作成し、Hello, World!するプログラムを解説いたします。

表示するウィンドウは以下になります。

関連記事
C言語でcharをintに変換する方法
C言語でenumをtypedefして使う【列挙型】
C言語でforeachマクロを実装する方法
C言語でnull判定する方法【NULL, 比較】
C言語でできることを解説!C言語歴16年の開発者が語る
C言語でオブジェクト指向する【単一継承の方法】
C言語でグローバルに関数を使う方法
C言語でシャローコピーとディープコピーを実装する

ビルド環境

今回はビルド環境としては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を初期化しています。
argcargvのアドレスを渡します。

    // ウィンドウを作成  
    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 *が必要です。
ですのでwindowGTK_WINDOW()でキャストします。

GtkWindowGtkWidgetを継承していますので、これら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に配置しています。
GtkWindowGtkContainerも継承していますので、上記のように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なんかがそうですね。
なにか参考になれば幸いです。