メインコンテンツへスキップ

フレームワークを使わず純粋なC言語でWindows ToDoアプリを作ってみた!容量なんと278KB!

·3 分
2025/05 C言語 Windows GUI プログラミング 軽量

フレームワークを使わず純粋なC言語でWindows ToDoアプリを作ってみた!容量なんと278KB!

引用元:https://news.ycombinator.com/item?id=43954649

masternight 2025/05/11 22:27:27

Raymond Chenの話とか読むと、win32 GUIプログラミングって面白いんだよね。昔Petzoldの本でいっぱいGUIアプリ作ったのが楽しかった。strcpyとかsprintf使ってるみたいだけど、危ないから長さチェック付きの使った方がいいよ。Win32 APIにはC標準ライブラリの代わりがいっぱいあるから、サイズ減らしたいならZeroMemoryとかCopyMemoryとか使ってみたら? 生Cは大変だけど最初学ぶには良いよ。WTLっていうC++のラッパーもあるから、GUIやりたいなら見てみて。

scripturial 2025/05/11 22:35:49

少なくとも今どきは、strcpyじゃなくてstrncpy使わないと、みんな(AIとか)にずっと言われ続けることになるよ。zig使うとこういう落とし穴少ないんだけど、Cでも大丈夫。

masternight 2025/05/11 22:43:04

へー、で、もしstrncpy()使うなら、今度は僕が「strncpy()は間違った関数だよ」って講釈垂れることになるね。

nly 2025/05/11 23:05:54

strncpyは固定長の文字列フィールドがあるバイナリプロトコルで超完璧なんだ。パディングが必要なんだよね。読むときもstrnlenとかstrncpy使うんだよ。

masternight 2025/05/11 23:14:45

うん、それがstrncpy()の本来の使い方だよ。OPみたいな汎用プログラミングには向いてないね。バッファがいっぱいだとnull終端してくれないから問題になるし、余計なnullも書いちゃう。FreeBSDにはstrlcpy()、Windowsにはstrcpy_s()ってのがあるよ。OSのAPIはnull終端を期待してるから、読むときもnull終端前提で処理した方が安全でエラーも少ない。C++でstd::string使うとか、他の言語使えばいいんだよ。Cは文字列がひどいね。

MortyWaves 2025/05/11 23:55:52

ZeroMemoryとかCopyMemoryが、既存のWindows C stdの関数の代わりに提供されてるのって、何のためにあるの?

codebolt 2025/05/12 06:37:29

memset()とかCopyMemory()の代わりにZeroMemory()があるって話だけじゃなくて、fopen/fread/fcloseじゃなくてCreateFile/ReadFile/WriteFileとか使うべきって点にも同意だね。

raverbashing 2025/05/12 09:48:54

ほとんど同意だけど、win32 GUIプログラミング(この記事みたいの)は正直痛いよね。MFCでもちょっとマシになったくらい。BorlandのDelphiみたいなC++ライブラリの方が断然良かったな。CreateFileはファイルだけじゃなくて、他のものも開く最高のAPIなんだよね。懐かしいな。

mrheosuper 2025/05/12 06:19:10

stringって、最後の要素がNullのバイト配列だと思ってたんだけど? どうやったらNull終端しないstringになるの?

BenjiWiebe 2025/05/12 13:13:54

文字列がNULLで終わるかはプログラマ次第だよ。慣習だけど、最後のバイトを何にするかは自由。単なるバイトの配列なんだ。

hkpack 2025/05/12 11:20:08

Cは書いてないけど、Pascalみたいな文字列ラッパーがなんで流行らないのかずっと気になってたんだ。先頭に長さ情報があって、互換性のためにNULL終端も付けるやつ。

int_19h 2025/05/12 17:06:50

> Borlandの”Delphi like” C++ライブラリはすごい
ってあるけど、実際はDelphiのVCLそのものだったんだ。C++Builderに言語拡張があったのは、Delphiの機能と1:1で合わせるため。DelphiのユニットもC++で使えたし、Delphiのソースもコンパイルできたんだよ。

mike_hearn 2025/05/12 09:12:55

WindowsはCに標準化したわけじゃないよ。最初はアセンブリやPascalで、後からC/C++。MicrosoftはCを他の言語と同等に見てて、UNIXみたいに特別扱いしてない。C標準ライブラリもOSじゃなくてコンパイラ付属だった。最近は変わってきてるけど、Windowsは他のOSと違って言語独立性を大事にしてるんだ。WinRTとか見るとわかるよ。

pjmlp 2025/05/13 07:51:45

そうそう、Object Windows Libraryから発展したんだよね。OWLも拡張があって、MFCよりずっと使いやすかった。過去形じゃなくて、今も製品は市場にあってリリースも頻繁だよ。昔ほどじゃないけどね。

donnachangstein 2025/05/12 07:27:34

> fopen/fread/fcloseを使ってる
ToDoリストだよ、ネットワークサービスじゃない。無制限のstrcpyを使ってても何が問題?攻撃の余地はほぼないし、彼は自分のために書いたんだ。HNの細かい批判はやめようぜ。人の作品を素直に見てみようよ。コンピューターは何かをやるためのツールで、たまには見た目悪くてもいいんだ。ここでの批判は”lsをセキュリティのためにRustで”みたいなナンセンスと同じレベルだと思うね。

userbinator 2025/05/11 22:32:10

memset()の代わりにZeroMemory()、memcpy()の代わりにCopyMemory()があるね。MSVCのintrinsicはrep stos/movs命令を使うはずで、これって関数呼び出しよりサイズが小さくなるんだ。

mrheosuper 2025/05/12 13:48:22

Cでは全部バイト配列なんだ。uint32_tも4バイトの配列って言う人もいる。だから慣習が必要なんだよ。文字列は最後にNullがあるバイト配列と定義されてる。Nullがなくなったらもう文字列じゃない。

renewedrebecca 2025/05/12 15:39:39

Pascalは元々、文字列を扱う前に長さを指定する必要があったんだ。これってすごく良いアイデアだけど、当時は使うのが面倒だって思われてたんだよ。

masternight 2025/05/12 08:16:14

無制限のstrcpyを使ってても何が問題?攻撃の余地はほぼないし、彼は自分のために書いたんだ。HNの嫌味な連中からの批判のためじゃない。← これを指摘したのは賢い気分になりたかったわけじゃないし、世界をRustで書き直せとかも思ってないよ。バッファオーバーランとかでデバッグに費やした時間はめちゃくちゃ多いんだ。最初からsaferなAPIがあれば、どれだけ時間を節約できたか。クールなプログラムだし、学ぶのは良いことだけど、不必要な問題は避けた方がいいと思うんだ。

userbinator 2025/05/12 00:52:25

Windows 1.xとか2.xってさ、C89標準より前に出てたんだよね。だからWINAPIの呼び出し規約がCじゃなくてPascalから受け継がれてるのもこれで説明つくわけ。C standard libraryは当時「ただの競合相手」だったってことだね。

donnachangstein 2025/05/12 07:55:15

> freebsdにはstrlcpy()があるって?
strlcpy()はOpenBSDから来たやつで、後からFreeBSDとかSolarisに移植されたんだよ。

int_19h 2025/05/13 20:06:19

OWLって、一部の標準コントロールを独自に描画するから、Win 3.11だとOWLアプリだけ浮いて見えた気がするな。MFCは叩かれがちだけど、結構不当な批判だと思うよ。だって根本的に違う種類のフレームワークなんだもん。MFCは底层APIを使いやすくするラッパーだけど、その核心は隠さない。一方でOWLやVCL(あとVB6とかWinFormsも)は、たとえネイティブウィジェットを使ってても、もっと高レベルで色々やってくれるラッパーなんだ。そういう視点で見ると、MFCへの適切な批判は「やりすぎ」ってことかな—例えばあのドキュメント/ビューの仕組みとか。フレームワーク全体の設計から浮いてる気がしてたんだよね。WTLは、MFCが目指したけど失敗したものを実現した感じ。

kevin_thibedeau 2025/05/12 04:20:01

標準でmemsetとかmemcpyはインラインコードに置き換えても良いことになってるんだ。パフォーマンス上げるために非標準の拡張機能を使う必要なんてないんだよ。

BarryGuff 2025/05/12 08:08:56

> win32 guiプログラミングに惹かれるものがある
マジで同意だわ。AlomWare Toolboxっていう超優秀なPCアプリを使ってて、これこそまさにWin32設計の極致って感じなんだ(https://www.alomware.com/images/tab-automation.png)。あんなに機能多いのに、たった3MBくらいしか容量ないのもそれが理由。これもフレームワーク一切なし、実行ファイル一つだけ。ソフトウェアが全部まだこうだったら良いのにって思うよ。

int_19h 2025/05/12 16:57:04

公平に言うと、CreateFileとかってfopenよりずっと記述が冗長だよね。

sirwhinesalot 2025/05/12 14:30:44

2バイトじゃ足りないね、普通は長さのためにsize_t分のバイトを使うことが多いかな。でも、utf-8っぽい感じで、長さの最初のバイトのあるビットパターンを見れば、長さ自体が何バイト使ってるかわかる、みたいなこともできるかもね。

int_19h 2025/05/12 16:56:10

それは可能な規約の一つにすぎないし、特段良い規約ってわけでもないね。

int_19h 2025/05/12 16:45:49

ほとんどのライブラリ(この場合はWin32含む)のAPIに渡すには、やっぱり0終端文字列が必要だよ。

Suppafly 2025/05/12 21:59:02

> 世界全部をRustで書き直すっていう考えには全く賛同しないね。
良い心がけだよ。だってRustの人達って、「XをRustで書き直す」のがRustのほぼ全てだって指摘すると、めちゃくちゃ怒るからね。

rcarmo 2025/05/11 22:33:54

俺もそれにはすげー時間かけたよ。正直、ネイティブコードでネイティブUI開発するの、懐かしいし、できなくなって寂しいんだよね。

もっとコメントを表示(1)
electroly 2025/05/11 20:07:12

CreateWindow()でいちいちコントロール作るより、昔ながらの方法だと.rcファイルにダイアログリソース置いてたんだよ(Visual Studioには今もエディタある)。で、CreateWindow()じゃなくてCreateDialog()使う。これだと全部勝手に作ってくれるんだ。さらにアプリマニフェスト足せば、モダンなUIスタイルとか高DPI対応もできちゃうぜ。

userbinator 2025/05/11 22:09:13

その方法だと、コントロール間の自動タビングとか、いくつかのキーボードショートカットも勝手についてくるんだ。サイズ変更は手動でやる必要があるけどね、でもそれは簡単で、コードも数百バイトもあれば十分だよ。

pjmlp 2025/05/12 06:46:55

UNIX以外だとC標準ライブラリはOSじゃなくてコンパイラベンダーが提供してたんだ。だから昔はいろんな会社(Borland, Microsoftとか)のがあった。Windows 10以降はUniversal C Runtimeもあるけどね。

belter 2025/05/11 21:58:24

Petzold見てみろって昔は言ってたもんだよ…

kazinator 2025/05/12 01:31:30

でもこの記事のアプローチは、FFIがある言語なら移植しやすいんだ。リソースファイルとかDLLいらないし。リソースファイルって良いAPIじゃないしね。高レベル言語でCreateWindow呼ぶなら、リソースみたいなDSLをメタプログラミングで作るのもありかも。

int_19h 2025/05/12 17:12:30

”リソースDLL”なんていらないよ。コンパイルした.rcファイルはバイナリに直接リンクされるんだ。MinGW含むWin32 Cツールチェインなら何でもできる。APIも悪くないと思う。GPも言ってたけど、メリットたくさんあるよ。高DPI対応とかね。.rcファイルは”ダイアログユニット”使うから、CreateWindowのピクセルと違ってDPI独立なんだ。

kazinator 2025/05/13 05:14:27

もし”バイナリ”が自分でビルドしたりリンクしたりしてない、既成の言語ランタイムだったらどう? 俺が考えてるのはそっちのケースなんだ。この記事みたいにCreateWindow直接呼ぶプログラムは、そういう状況にうまく合うんだよね。

kazinator 2025/05/13 20:09:22

もしアプリが.rcからコンパイルした.resファイルをインクルードできる.exeにリンクする言語じゃない場合は?その言語は.exeランタイムは持ってるかもしれないけど、それにリンクできるわけじゃない。その場合でも.rcファイルを使いたいなら、そこからリソースDLLを作って動的にロードしたいと思うんじゃないかな。(ローカライズ可能な文字列のためにリソースDLLがある理由の一つなのはわかってるよ。それはGUIレイアウトリソースを静的にリンクできるプログラムでも意味がある使い方だね。)

int_19h 2025/05/14 11:42:00

あ~、言いたいことわかったよ。でも記事の文脈(C言語Win32アプリ)では.rcファイルを避ける理由はないかな。
君が説明してるケースでも、リソースDLLを使わなくてもCreateDialogIndirectを使えばDLGITEMTEMPLATE構造体の配列からダイアログを作れるよ。コンパイル済みのダイアログも basically その配列のダンプだと思うんだ。

tonyedgecombe 2025/05/12 07:11:17

”Visual Studioにはそれを視覚的にやるダイアログエディタがまだある”
彼らはgccを使ってるよ。

electroly 2025/05/12 14:30:05

それは関係ないよ。.rcファイルを作るのにVisual Studioを使うことはできる。このテクニックはMinGWベースのプロジェクトでもすごくうまくいくんだ。重要なのはVisual Studioに.rcダイアログエディタがあるってこと。

int_19h 2025/05/12 17:45:52

VS以外にも視覚的なダイアログ編集を組み込んだWindows用の.rcエディタはいくつかあるよ。Pelles Cはまだ定期的にアップデートされてる例の一つだね。

electroly 2025/05/12 20:44:10

Pelles Cのヒントありがとう。ちょうど起動してみたんだけど、ちょっと clunky だね(Visual Studioもそうだけど)。でも.rcダイアログリソースを読み込んで編集できたよ。Visual Studioの古いダイアログエディタは最近はけっこう crashy だから、代替があるのは素晴らしいね。

broken_broken_ 2025/05/11 19:21:19

俺も前に Linux でアセンブリで似たようなこと(2 KiB以下)やってたよ:https://gaultier.github.io/blog/x11_x64.html
純粋な C で動的にリンクすれば、Linux でも簡単に 20 KiB 以下に収まるよ。Windows はもっとシンプルだろうけど。
とにかく、この努力には敬礼!記事の最後に書いたリンクオプションを試してみるといいよ、サイズ小さくなるはず。

johnisgood 2025/05/12 08:25:24

えっと、俺のちょっと拡張した TUI (ncurses) TODO プログラムは 15K だね。Linux。スタティックリンクじゃないけど。musl で ncurses をビルドするとこまではまだやってない。

eviks 2025/05/11 19:54:36

”no frameworks”
なるほどね:スケーリングされた dpi でフォントがぼやける、Tab サポートがない、テキストフィールドで Ctrl-A でテキストを選んだり modern な frameworks が提供してた他の stuff ができなかったり、行を追加するとエラー、とかとか…
”modern”
どういう点が?

Dwedit 2025/05/11 20:12:47

DPI設定の例だよ!
このコードはWindowsのバージョン見てDPI関係の関数を動的に呼び分けてるんだって。
XPとか古いのだと何も呼ばないみたい。
https://github.com/Dwedit/GameStretcher/blob/master/Stretche

scq 2025/05/11 22:35:14

DPIはアプリのマニフェストでも設定できるよ。
プログラムでやるよりこっちがおすすめらしい。
詳しくはこちら!
https://learn.microsoft.com/en-us/windows/win32/hidpi/settin

Tringi 2025/05/11 23:55:02

テーマとかGDIとか使うともうちょっと複雑になるよ。
前に自分で作ったDPI対応の例を貼っとくね!
https://github.com/tringi/win32-dpi
WindowsのDPIサポートはXP以降色々変わったけど、
アプリ開発者がやらかしたのを直すためって感じかな。
ちゃんとやればXPでも250%DPIで綺麗に表示できるよ!
https://x.com/TheBobPony/status/1733196004881482191/photo/1

sargstuff 2025/05/11 21:18:32

あー、user32:みたいなWindows API関数って、
「純粋なC」とは違うフレームワークなんじゃないの?

ghewgill 2025/05/11 22:26:28

コロンはC++じゃないよ。
それは特定のDLL(user32とか)に入ってるWindows API関数を指すときの書き方。
古いWindowsには無い関数だから、
DLLを動的に読み込んで関数のアドレス取ってきて呼び出してるんだ。
だからどのDLLにあるか知る必要があるんだよ。

jchw 2025/05/11 21:39:33

ぶっちゃけ、Windows自体が
オブジェクト指向なUIフレームワークって言えなくもないよね。

sargstuff 2025/05/12 03:30:50

OSなしで起動できる組み込みプログラムなら近いかもだけど、
それも結局BIOSとかドライバーに依存するよね。
今のハードウェアで現実的に『純粋なC』プログラムって
ほぼ無理じゃないかなー。
知識も労力もヤバすぎる。
仮想環境でも結局ABIインターフェースあるから、
ソースコードレベルで完全に純粋ってのは難しいと思うな。

userbinator 2025/05/11 20:09:13

『現代的』っていうのは、
必要以上にデカいのに機能不足って意味でしょ?
(キミが言う足りない機能の多くは、
特にコントロール間のタブ移動とか、
ちょー簡単に追加できるけどね。)

card_zero 2025/05/11 20:14:07

フォントスケーリングはSetThreadDpiAwarenessContext(-4)で
直る(有効になる)と思うよ。
-4に相当する定数がなんて名前かは知らんけど。

AaronAPU 2025/05/11 18:21:10

6502プログラマーだった俺の中の何かが、278KBが軽量だって言われて死んでるよ。

abbeyj 2025/05/11 21:03:49

ビルド再現しようとしたら、gitの設定とかで最初うまくいかなかったんだ。でもMinGW使って試したら、最初から102KBになったよ。記事の278KBよりだいぶ小さいね。作者さん、どんな環境や設定でやったか教えてほしいな。GCCのオプション(-Os, -Oz, -flto, -s)で試したら、もっと小さく47KBにもできたよ。EXEサイズだけなら、まだまだ小さくできる余地あると思う。

jedimastert 2025/05/11 21:43:16

> 作者さんが別のツールチェーンとか設定使ってるのかな?
デバッグシンボル付けてコンパイルしてるのかなって思ったんだけど。素のC言語でどれだけ変わるか分からないけど、まず最初に思いついたのはそれ。

tecleandor 2025/05/12 08:44:37

どこかタイプミスだと思う。リポジトリとかリリースだと27KBって書いてあるよ(278じゃない)。

debugnik 2025/05/12 09:26:34

昨日投稿した時点のv0.1リリースは278KBだったんだよ。でも9時間前の最新リリースv0.3では、-Osと-sオプション付けて、さらにUPXで圧縮して27KBになってるんだ。

もっとコメントを表示(2)
abbeyj 2025/05/11 23:30:43

俺もmingw使ったのに、違う結果になったんだよね。たぶんバージョンが違うか、MinGWのディストリビューションが違うか、32ビットか64ビットかの問題か、リンクしてるCRTが違うのかも。作者さんの詳細がないと、よく分からないな。

jcelerier 2025/05/11 19:16:38

多くはプラットフォームとか実行ファイルフォーマットによるものだよ。スタックトレースの情報とか、動的リンクのインフラとか、例外処理のテーブル(C言語でも例外がC関数を通過する場合に必要)とか、そういうのがないと、もっとずっと軽量にできるんだ。

userbinator 2025/05/11 20:05:16

no dynamic linking infrastructureみたいなのはWindowsならタダで付いてくるし、例外処理テーブルも純粋なCなら必須じゃないよ。SEHだってそんなに必要ないし。

nottorp 2025/05/11 18:26:47

デモシーンの大会に「64kb TODOアプリ」部門とか作ってもらうよう請願してみる?

jackjeff 2025/05/11 19:53:57

正直、意外とデカいなってびっくりしたよ。もっと小さいか、半分くらいアイコンに取られてるかと思った。昔、こういうの書いてた時はもっと小さかった気がするんだ。MinGwのせいかな?

mhd 2025/05/11 19:34:09

これ聞くと、急にassemblyでのwin32プログラミングが流行った頃を思い出すね。たぶん、sharewareダウンロードがどんどんデカくなったこと(MFCの暗黒時代だったね)への反動だったんだろうな。昔のPalm Pilot 68kプログラミングと合わせると、あれが非レトロコンピューティングのasmの最後の盛り上がりだった気がするよ。

Borg3 2025/05/11 19:01:48

へへ:) ああ、書くのがもっと簡単なやつ作ったよ。しかも小さいんだ、15kBのquickrun.exe :)
C言語で、純粋なWin32 APIだよ。バイナリを小さくするハックは何もしてない、Mingw32コンパイラだよ。これはエイリアスでアプリをすぐ起動できるGUIアプリさ。

stevekemp 2025/05/11 19:52:26

今夜は自分で書いたエミュレータのデバッグに時間使ってるんだ。Z80プロセッサと64kのRAMで動くシステムをエミュレートしてるやつ。
時々さ、立ち止まって色々どう変わったか見るよ。でもサイズの変更に対して、俺たちはたくさんの進歩をしてきたってことだと思うんだ。

kazinator 2025/05/13 05:18:54

90年代初頭にNethackが900kb以上の実行ファイルになるのを見て、雷に打たれたような衝撃を受けたのを覚えてるよ。

webprofusion 2025/05/12 02:03:54

なぜかMSVCじゃなくてGCCでビルドされてるし、最適化も有効になってないみたいだね(速度とかサイズのための)。

p0w3n3d 2025/05/11 20:18:17

ABIの問題もあるし、8bitとかから64bitアーキテクチャになったんだ。ポインタ1つが8倍も長くなってる。文句言うんじゃなくて、ただ感心しようぜ。これはアートだよ。

tonyarkles 2025/05/11 19:51:14

それ、5 1/4インチのフロッピーディスクにギリギリ入るくらいじゃん!

fx1994 2025/05/12 07:44:31

Win10/Win11の”新しい”電卓開いてみろよ。まるで別のOSを読み込んでるみたいだぞ…マジで無駄。

alternatex 2025/05/12 16:01:30

多分JIT-ingしてるのかな?ネイティブにコンパイルされてたら起動は一瞬なはず。どうやってコンパイルしてるのか全然わかんない。

toxi360 2025/05/11 19:23:27

やあみんな、このアプリは試してみたり、ちょっと楽しむために作っただけなんだ、ハハ。でもコメントにある通り、こういうのはC++とか他の言語でもっと賢くやれただろうね、アハハ。

tomtomtom777 2025/05/11 19:59:32

これって、俺が30年前に初めてWindowsプログラムを作るときにexactlyどうやって学んだかと全く同じやり方だよ。ただ、C++コンパイラを使ったけどね。なんでかわからないけど、C++コンパイラでCスタイルのコードを書くのが、Windows APIのドキュメントの使い方だったと思うんだ。Microsoftは単にC++がCの改良されたスーパーセットだから、Cスタイルのコードでも使うべきだっていう考え方に行ったんだと思うよ。

記事一覧へ

海外テックの反応まとめ
著者
海外テックの反応まとめ
暇つぶしがてらに読むだけで海外のテックニュースに詳しくなれるまとめサイトです。