見出し画像

Windowsアプリ、どの言語で書く?

私は現在、クラウド製品のインフラ側の作業を主に担当しています。
仕事としてWindowsアプリケーションを新規に作成することはありません。
そのため、最近のWindowsアプリケーション開発がどのように行われているのか、完全に浦島太郎状態です。

久しぶりにWindowsアプリケーションを書くとしたら?というお題で最近の開発環境を調べてみようという回です。
結論を先に書くと、C#(F#)か、Python、またはJavaしかなさそうでした。

日常的にWindowsアプリケーションを書いている人からすると「何言ってんの?」って感じでしょうが、とりあえずお付き合いください。

ただ、Windowsアプリケーションといっても機能は様々なので、どういうものを作るのかによって選択も異なるはずです。
ここではSVF Cloud Agentをターゲットとして考えることにしましょう。

SVF Cloud Agentの構成

SVF Cloud Agent(以下、Agent)の機能については、「ダイレクトプリントの話」で簡単に触れています。

Agent作成時、どういうプログラミング言語を使用するか?については深く考察する時間がありませんでした。
Javaで書かれた既存製品があったので、そのパーツを流用することを考えて、Javaが無難かなと。
既存製品でも同様ですが、印刷に関する最下層の部分は当時のJavaでは実現できず、一部JNI(Java Native Interface)を使用しています。
また、Agentには、Agent自体の設定を行うためのGUI画面があり、JavaFXを利用して書かれています。
それと、Agent自身のアップデートを行うため、Agent本体以外にAgentを起動するランチャーがあり、この部分はC言語で書かれています。

構成図

パーツと言語

考慮すべき点

JNI(Java Native Interface)で記述している処理が実現できるか?
SVF Cloudが行なっている、Windowsスプーラーを経由する印刷はJavaだけでは実現できないため、Win32APIを呼んでいます。
同等の機能をWin32APIを呼び出さずに実現できる言語があればそれが望ましいです。
言語の機能だけで実現できない場合は、何らかの方法でWin32APIを呼び出すことができる言語である必要があります。

GUIによる設定画面を作成できるか?
Agent自身の設定を行ったり、プリンターの検索/登録を行うためのGUI画面が必要です。
元々AgentはWindows/Mac/Linuxのマルチプラットフォームで動作するように作られましたが、現在はWindowsしかサポートしていないため、Windowsで動作できれば良く、他の環境で動作する必要はありません。

ランチャーの機能が実現できるか?
ランチャーはAgent本体と同じ言語である必要はありませんが、

  • Agent本体を起動/停止できること

  • Windowsサービスに登録できること

の2つが実現できれば良いです。

言語を使用している開発者数は十分か?
何か問題があったときに、ネットで検索すると類似した問題で困っている人がいたり、解決方法があったりしますが、これは使用している人が多い言語ならではです。
誰も使っていない言語だと、何も見つからない可能性があります。
また、使用者がたくさんいるとか、使用者が増えていっている言語は今後も続いていく可能性が高いですが、使用者が減っている言語はメンテナンスされなくなり、廃れてしまう可能性があります。

Agent本体の記述に使えそうな言語

まずは、Agent本体を作成するのに適した言語を考えましょう。

  • Java + Swing

  • C/C++ + (Win32 or MFC)

  • C# + 何か

  • F# + 何か

  • Electron

  • Python + 何か

  • Zig + 何か

  • Rust + 何か

  • Go + 何か

他にもあるとは思いますが、とりあえずこれくらい。

それぞれの言語について

Java + Swing
現状と同じです。言語は変えないということです。
ただ、JavaFXが削除されてしまったので、OpenJFXを使用するか、Swingで書き直す必要があります。
OpenJFXは今後のサポートが不安です。また、通常のJREには含まれていないので、配布も面倒です。
そのためSwingを使うことになると思います。

C/C++ + (Win32 or MFC)
機能的にはまったく問題ないですが、流石にこれはないかな。
実際に作業できる人が少ないだろうし、今後はメンテナンスできる人がどんどん減っていきそうなので。

C#
UIに何を使うかは置いといて、本体をC#で書くことについて。
C#からWin32APIを呼び出すのは普通にできるので、JNIでやっている部分は呼び出せるはず。
Agentを書いた頃は、SNMPとかLPRのライブラリは存在しなかったので自作のJavaモジュールでやっているけれど、今ならライブラリがありそう。

C# + WPF
C#で書くとして、GUI部分を何で書くか?
WPF(Windows Presentation Foundation)はまだ現役らしい。
Microsoftとしては、WPFと後述のWinUIの両方を推していくらしい。

C# + WinUI(WinUI 3)
最新らしいけど、使えないと言っている人も多い。
AgentのUIはすごくシンプルだから、たぶん大丈夫だと思う。
問題は、WPFにしてもWinUIにしても、私自身はまったく経験がないということです。

F# + 何か
簡潔に記述できて可読性が高いC#?
C#用のライブラリはそのまま使えるので、C#同様に使えそう。
F#使ってる人はどれくらいいるんでしょうか?

Electron
マルチプラットフォームである必要はないのだけれど、Windowsアプリケーションの開発について調べると出てくるので。
GUIは書けるけど、印刷処理などの低レベル処理が難しい。
あと、出来上がったAgentを配布するときに困りそう。
いくつかElectronで書かれたツールを使用しているけれど、不安定な印象がある。終了しようとすると落ちるとか。

Python
UIに何を使うかは置いといて、本体をPythonで書くことについて。
PythonはWin32APIを呼ぶためのライブラリがあるので、JNIでやっている部分は呼び出せるはず。
C#の場合と同じく、SNMPのライブラリは存在する。ライセンスは調べていない。
また、手順は少し面倒だが、exe化することができるので配布は問題ない。

Python + Tkinter
AgentくらいのUIなら問題なく作れそう。

Python + Tkinter以外のUIライブラリ
作るのは問題なさそうだけど、配布するにはライセンスがきつそう。
使用する場合はライセンスの確認は入念にする必要がある。
WindowsらしいUIにしたい場合は良さそうなんだけどねえ。

Zig + 何か
この記事を書こうと思ったきっかけはこれです。
ZigというC言語が担っていた場所を置き換えることができそうな言語。
C言語の便利さはそのまま、C言語特有のバグが発生しないように言語側でいろいろ考えられている。
まだ新しい言語なので、この先どうなるかわからないため、製品に使える状態ではないが、ちょっとしたツールなんかを書いてみるのは良さそう。
GUIライブラリもいくつか出始めているので使ってみようかな?くらい。

Rust + 何か
全体をRustで書くかどうかはともかく、C言語の代替として使えるかもしれない。ランチャー部分だったり、最下層のDLLだったり。
実際に使ったことがないのでなんとも言えませんが、なんか学習コストが高そうなイメージがあります。
C言語よりも未来があると思いますか?錆びつきませんか?

Go + 何か
Rustを出したならGoも出さないとダメですよね。Rustほどの性能は出ないけれど、Agentなら高速である必要はないのでぜんぜん大丈夫。
言語自体は簡単に使えそうなイメージ。

というわけで

現状だと、Agent本体はC#かF#で書いて、GUIはWPFかWinUI、それかPythonとTkinterの組み合わせくらいか。
Rust/Goも使えそうだけど、GUIライブラリの選択が必要。
これらは一度使ってみないとわからないな。
ランチャーの方はC言語のままか、Rustか?Zigはまだちょっと早い。

ちょっと書いてみましょう

と言ってもAgentはサクッと書けるほど小さくはないので、ここでは最小限の構成のものでやってみましょう。

  • GUI画面がある

  • Win32APIを呼び出す

  • タスクトレイに常駐して、メニューからGUI画面を呼び出す

を満たすものにしましょう。

C#

さて、ちょっくら開発環境でも作りますか。と思ったら、C# Dev Kitはライセンスが必要なんですね。
今回は商用プログラムではないけれど、会社のPCだし使うのはやめておきましょう。

Python + Tkinter

とりあえず、C#は一旦保留してPythonでやってみましょう。
基本的な開発はMacで行いました。プログラムはWin32APIを呼び出すので、実行するにはWindows環境が必要です。

実行中の画面をGIFアニメーションにしたのですが、どうしても貼り付けることができませんでした。ごめんなさい。
なので見辛いですが画像だけ貼ります。
コマンドラインで、python tkinter-sample.py という感じでプログラムを実行します。
タスクトレイにアイコンが表示されるので、アイコンを右クリックします。

アイコンに表示されたメニュー

「画面起動」をクリックすると、ウインドウが表示されます。

起動された画面

「プリンター一覧を取得」ボタンを押すと、Windowsに登録されているプリンターのリストが表示されます。

プリンター一覧

あとは、右上の❌ボタンでウインドウを閉じて、アイコンを右クリックして「終了」をクリックすると、プログラムが終了します。

Pythonプログラムのコード

import sys
import tkinter as tk
from tkinter import ttk
from PIL import Image
from pystray import Icon, MenuItem, Menu

# グローバル変数
tree = None

class TaskTray:
    def __init__(self, image):
        image = Image.open(image)
        menu = Menu(
                    MenuItem('画面起動', self.runGUI),
                    MenuItem('終了', self.exitProgram),
                )
        self.icon = Icon(name='test', title='テストだよ', icon=image, menu=menu)

    def runGUI(self):
        window()
    
    def exitProgram(self, icon):
        self.icon.stop()

    def runProgram(self):
        self.icon.run()

def on_click():
    system_type = sys.platform
    if system_type != 'win32':
        tree.insert(parent='', index='end', values=(1, 'ごめんなさい。取得できません。'))
    else:
        import win32print
        printers = win32print.EnumPrinters(win32print.PRINTER_ENUM_LOCAL, None , 1 )
        i = 1
        for printer in printers:
            tree.insert(parent='', index='end', values=(i, printer[2]))
            i += 1

def window():
    global tree

    root = tk.Tk()
    root.title('設定画面')
    root.geometry('300x400')
    root.minsize(width=300, height=400)

    frame = ttk.Frame(root, padding=10, width=600, height=300, borderwidth=5, relief='flat')
    frame.pack(expand=1)

    button = ttk.Button(frame, text='プリンタ一覧を取得', command=on_click)
    button.grid(column=1, row=0, padx=5, pady=5)

    tree = ttk.Treeview(frame)
    tree['columns'] = ('No', 'Name')
    tree.column('#0',width=0, stretch='no')
    tree.column('No', anchor='w', width=50)
    tree.column('Name', anchor='w', width=200)
    tree.heading('#0',text='')
    tree.heading('No'  , text='No.'     , anchor='w')
    tree.heading('Name', text='プリンタ名', anchor='w')
    tree.grid(column=1, row=2, padx=5, pady=5)

    root.mainloop()

def main():
    system_tray = TaskTray(image="testicon.ico")
    system_tray.runProgram()

if __name__ == "__main__":
    main()

と、ちゃんと動くプログラムを書き上げたように書いていますが、このプログラムはバグだらけです。例えば、ウインドウを閉じる前に「終了」をクリックするとおかしな状態になります。

tkinterでUIを書いたこととがある人ならわかると思いますが、ここでmainloopを呼び出してはいけません。
tkinterはいろいろ面倒なので、他のUIツールを使った方が良さそうですね。

あと、何でもできるPythonですが、EMF印刷を簡単に実現できるライブラリは見つかりませんでした。ここだけはC言語で書いて、外部DLLなどにする必要がありそうです。
いっそのこと、Python用のモジュールを作ってしまうのもありですが。

C言語はメンテナンスできる人が減っているので使いたくないという話をしましたが、すでに破綻していますね。
C言語を使える技術者はまだまだ必要なようです。
ほとんどのことは他の言語でできるんだけど、どうしてもできない部分はC言語になってしまうんだなあと思います。
この「C言語部分を置き換えられるか?」も次回以降考えますか。

最後に

C#の開発環境が用意できていないので、今回はここまでとします。
次回は、C#などその他の開発環境でやってみたいと思います。






この記事が気に入ったらサポートをしてみませんか?