始まりは単純な好奇心から

「HWPファイルを.NETで直接扱えたらいいのに…」

こんな考えをした.NET開発者は私だけではないでしょう。HWPファイルは韓国で公共機関を中心に今でも広く使われている文書形式ですが、.NETエコシステムではこれを適切に扱えるオープンソースライブラリがありませんでした。

これまで.NETでHWPファイルを扱うには、Windows OS限定でアレアハングルをインストールすると付属するHWP ActiveXコントロールのCOMタイプライブラリを呼び出して制御する程度しかありませんでしたが、残念ながらこれさえもサポートが終了し、今は道が閉ざされた状態です。

そんな中、Javaで書かれたhwplibを発見しました。neolord0さんが継続的にメンテナンスしてきたこのライブラリは、HWPファイルの読み書きを完成度高くサポートしていました。

以前なら、このようなライブラリを移植することは、強い使命感と目的意識がなければなかなか手を出せる作業ではありませんでした。しかし、高性能なAIモデルが多く登場し、今なら新しい挑戦ができるのではないかという好奇心がありました。

こうして始まった3週間の旅を共有します。

数字で見るプロジェクト

本格的な話の前に、Javaバージョンのプロジェクトの規模を数字でまとめてみましょう。

項目内容
元プロジェクトneolord0/hwplib (Java)
移植プロジェクトrkttu/libhwpsharp (C#)
ターゲットフレームワーク.NET Standard 2.0, .NET Framework 4.7.2, .NET 8
総コミット数54個
開発期間2025-12-16 ~ 2026-01-08(約3週間)
初期移植コード641ファイル、50,010行

641ファイル、5万行。正直、最初にこの数字を見たときは「これは可能なのか?」という疑問が湧きました。

初日:AIと共に行った大規模コード変換

5万行を1日で?

2025年12月16日、プロジェクトを開始しました。通常の状況であれば、5万行のJavaコードをC#に変換するには数ヶ月かかったでしょう。しかし、AIコーディングアシスタントがゲームチェンジャーでした。

AIにJavaファイルを渡し、C#に変換してもらいました。もちろん、機械的な変換だけでは不十分でした。JavaとC#は似ているように見えますが、微妙な違いが多いからです。

// Java
public byte getValue() { return value; }
public void setValue(byte value) { this.value = value; }
// C#
public byte Value { get; set; }

このようなパターン変換はAIがうまく処理してくれました。しかし、本当の問題は別にありました。

最大の難関:Apache POIからOpenMcdfへ

OLE2複合ドキュメントの沼

HWPファイルはMicrosoftのOLE2複合ドキュメント形式(Compound File Binary Format)に基づいています。簡単に言えば、1つのファイルの中に複数の「ストリーム」がフォルダ構造のように格納されている形式です。

JavaではApache POIライブラリがこの形式を扱いますが、.NETには直接対応するものがありません。代わりにOpenMcdfというライブラリを使う必要がありましたが、残念ながらApache POIとはAPIが完全に異なります。

// Java (Apache POI)
DirectoryEntry root = poiFs.getRoot();
DocumentInputStream stream = root.createDocumentInputStream("Section0");
// C# (OpenMcdf)
CFStorage root = compoundFile.RootStorage;
CFStream stream = root.GetStream("Section0");
byte[] data = stream.GetData();

概念は似ていますが、実装は完全に異なりました。このような違いを理解し、分析しながら調整する過程をAIがうまくこなせるか、正直不安がありました。

HWPファイル内部のデータはzlibで圧縮されています。JavaとC#の解凍ライブラリが微妙に異なる動作をするため、同じデータを入れても結果が異なりました。

数時間かけてバイト単位で比較しながらデバッグした結果、問題の原因を見つけました。zlibヘッダーの処理方法が異なっていたのです。幸いにも、neolord0さんが丁寧に作成してくださったユニットテストのおかげでこのような問題を発見できましたが、ここで私はVisual Studio 2026の「エージェントベースデバッガー」の力を実感しました。

Visual Studio 2026は皆さんもご存知の通り、どのバージョンよりもAIエージェントの強力さを活用できるようにリファクタリングされた製品です。VS Codeのようにエージェントを使ってコードを「自動作成」することに注目しがちですが、今回の移植プロジェクトでユニットテストをデバッグモードで実行して「失敗」が発生すると、デバッガーを開いたまま「何が問題なのか」を即座に把握し、自らブレークポイントを設定して追跡しながら、変数やコールスタックを内部MCPサーバーを通じて収集して対応する姿は非常に興味深いものでした。

もちろん、すべての問題をこのように解決できたわけではありませんが、このようなツールのサポートがなければ、終わりを見ることができなかったかもしれません。

テスト100%達成の喜び

赤から緑へ

元のhwplibプロジェクトには、neolord0さんが作成したテストケースがありました。私にとってこれらのテストは灯台のような存在でした。「とりあえずこれをすべて通過すればいい」という明確な目標がありましたから。

最初にテストを実行したときは惨憺たる結果でした。47個中3個だけ通過。赤いX印が画面を埋め尽くしました。しかし諦めることはできませんでした。一つずつ失敗の原因を追跡し、コードを修正し、再度テストを実行しました。

Sectionパースを修正したら5個追加で通過しました。コントロールパースを修正したら10個が緑に変わりました。テーブル処理、GSO(グラフィック)オブジェクト…まるでパズルのピースを合わせるように一つずつ解決していきました。

そして遂に、その瞬間が来ました。

Tests Passed: 47/47

モニターを穴が開くほど見つめました。本当に47/47なのか何度も確認しました。コーヒーを一杯飲んでもう一度実行してみました。まだ47/47。やっと息ができました。

この瞬間のために走ってきたんだなと思いました。

より多くのユーザーのための旅

「macOSでは動きませんか?」

テストを通過して喜んでいたのも束の間、現実がやってきました。最初の質問は予想通りでした。

「Macでも使えますか?」

しまった、と思いました。私は最初、画像処理にSystem.Drawingを使っていましたが、これはWindows専用と言っても過言ではありません。.NET Core以降、クロスプラットフォームが主流になっているのに、Windowsでしか動かないライブラリなんて…意味が半減する感じでした。

悩んだ末、SkiaSharpに切り替えることにしました。GoogleのSkiaグラフィックエンジンを.NETにラップしたライブラリで、Windows、macOS、Linuxすべてで同じように動作します。画像関連のコードをすべて書き直すのは面倒でしたが、より多くの開発者が使えるなら、その価値はありました。

「うちはまだ.NET Framework 4.7.2なんですが…」

2番目の挑戦はもう少し苦痛でした。企業環境は思ったより保守的です。.NET 8がいくら良くても、レガシーシステムとの互換性のために.NET Frameworkから抜け出せないところが多いのです。

問題は…私が喜んでC# 12の最新構文を思う存分使っていたことです。

// C# 12 - ファイルスコープ名前空間(1行で終わり!)
namespace HwpLib.Reader;

public class HwpReader { }

これを.NET Frameworkで使うには、古い構文に戻す必要があります。

// C# 8互換 - 従来の名前空間(中括弧必須)
namespace HwpLib.Reader
{
    public class HwpReader { }
}

「ただ中括弧を追加すればいいんじゃない?」と思うかもしれません。その通りです。でもそれが582ファイルです。

結果として、51,480行追加、49,701行削除という膨大なdiffが発生しました。GitHubでそのコミットを開くとブラウザが一瞬固まるほどでした。C#言語バージョンも12.0から8.0まで段階的に下げなければなりませんでした。requiredキーワード、initアクセサー、ファイルスコープ名前空間…便利に使っていた機能を一つずつ諦めながら過去に戻る気分でした。

それでも、この苦労のおかげで、今では.NET Framework 4.7.2から.NET 8まで、ほぼすべての.NET環境でlibhwpsharpを使用できるようになりました。

AIとの協業で学んだこと

3週間のプロジェクトを終えて、AIとどのように協業すべきかについて自分なりのノウハウができました。

AIが輝く瞬間

最も印象的だったのは、繰り返し作業での効率性でした。数百個のJavaファイルをC#に変換するのは、人間がやれば数日かかる単純作業です。しかしAIには「これをC#に変換して」と言えば終わりでした。

XMLドキュメントコメントを追加する作業も同様でした。111ファイルに2,000行以上のAPIドキュメントを追加する必要がありましたが、AIに「このクラスが何をするのかコメントで説明して」と言えば、文脈を把握して適切な説明を生成してくれました。

しかし、今回のプロジェクトで本当のゲームチェンジャーはVisual Studio 2026のエージェントベースデバッガーでした。

正直に言うと、AIコーディングアシスタントは今やどこにでもあります。VS Codeにも、JetBrains IDEにも、Webブラウザでも使えます。コード自動補完や生成機能はすでにコモディティ化しています。しかし、Visual Studio 2026は別のところで差別化を図りました。

ユニットテストをデバッグモードで実行して失敗が発生すると、エージェントが自ら動き始めます。「何が問題?」と聞く必要もありません。エージェントが自らブレークポイントを設定し、変数値を追跡し、コールスタックを分析します。内部MCPサーバーを通じてデバッガーの状態をリアルタイムで収集しながら問題の根本原因を探っていく姿は、まるでシニア開発者が隣でペアデバッグをしてくれているようでした。

特にzlib解凍問題を追跡する際にこの機能が輝きました。JavaとC#で同じバイト配列を入れたのに結果が異なる状況。普段なら両方のコードにいちいちブレークポイントを設定し、バイト単位で比較しながら数時間を費やしていたでしょう。しかしエージェントベースデバッガーは「この2つの結果がなぜ異なるのか追跡して」という要求一つで自ら分析を始め、ヘッダー処理方法の違いを指摘してくれました。

これこそがVisual Studio 2026の**moat(堀)**だと思います。コード生成は誰でもできますが、デバッガーとAIの深い統合は、数十年間デバッグツールを作ってきたMicrosoftにしかできない領域です。この機能がなければ、このプロジェクトは3週間ではなく3ヶ月かかったかもしれません。

それでも人間が必要な瞬間

しかし、すべてをAIに任せることはできませんでした。「Apache POIの代わりに何を使うべき?」という質問にAIはいくつかのオプションを提示してくれましたが、実際にどのライブラリがプロジェクトに合うか判断して決定するのは私の役目でした。

特にデバッグはまだ人間の領域だと感じました。zlib解凍結果が異なる問題を追跡する際、AIに「なぜこれが違うの?」と聞いても明確な答えを得るのは難しかったです。結局、バイト配列を直接ダンプし、JavaとC#の動作を比較しながら原因を探らなければなりませんでした。

ペアプログラミングの新しい定義

結論として、AIは最高のジュニア開発者のようでした。言われたことは本当によくこなします。速くて、疲れ知らずで、不満も言いません。しかし「何をさせるか」を決めるのは依然としてシニアの役目です。

ところがVisual Studio 2026のエージェントベースデバッガーは、この公式を少し変えました。コード生成ではAIがジュニアの役割ですが、デバッグではむしろAIがシニアのように振る舞いました。私が「これなぜ動かないの?」と途方に暮れているとき、エージェントが先に仮説を立てて検証し、「ここが問題のようです」と指摘してくれましたから。

従来のペアプログラミングは、一人がコードを書き、もう一人が横でレビューする方式でした。しかしAI時代のペアプログラミングは違います。コード作成はAIが書いて人間がレビューし、デバッグはAIが分析して人間が判断します。役割が状況に応じて柔軟に変わるのです。

このプロジェクトを通じて気づいたのは、AI時代の開発者は、コードを直接書く時間よりも「どのコードを書くべきか」を判断する時間がより重要になったということです。そして良いツールは、その判断をより速く正確にできるよう助けてくれます。Visual Studio 2026はそのようなツールでした。

おわりに

3週間の旅が終わりました。54個のコミット、641個のファイル、5万行以上のコード。

今、libhwpsharpはNuGetに公開されており、誰でも.NETプロジェクトでHWPファイルを扱うことができます。もちろん、まだ道は長いです。実際のフィールドで様々なHWPファイルに出会えば、きっと予期しないエッジケースが出てくるでしょう。

それでも最初の一歩を踏み出したこと、そしてAIと一緒なら一人では手も足も出なかった規模のプロジェクトにも挑戦できることを確認できただけでも、意味のある経験でした。

もし.NETでHWPファイルを扱う必要がある方がいらっしゃれば、ぜひ試してフィードバックをお願いします!

今後の計画

libhwpsharpは始まりに過ぎません。neolord0さんはhwplib以外にもいくつかの有用なプロジェクトを作っており、これらも.NETに移植する予定です。

hwpxlibの移植

最初の目標はhwpxlibです。HWPXはハンコムが新しく導入したオープンドキュメントフォーマットで、従来のHWPのバイナリ形式の代わりにXMLベースの圧縮ファイル構造を使用します。政府機関を中心にHWPXの採用が増えているため、.NETでもこのフォーマットを扱えるようにする必要があります。

幸い、HWPXはXMLベースなので、HWPより移植が容易になると予想されます。OLE2複合ドキュメントの沼でもがいた経験を考えると、ZIP + XMLの組み合わせはほぼリゾート級ですね。

HWP → HWPX変換ツール

2番目の目標は、neolord0さんのHWP to HWPX変換ツールの移植です。レガシーHWPファイルを新しいHWPXフォーマットにマイグレーションする需要は確実にあるでしょう。このツールまで移植されれば、.NETエコシステムで韓国語文書処理の完全なパイプラインが完成します。

この2つのプロジェクトも完成したらブログで共有します。お楽しみに!

技術的詳細

最後に、開発過程で遭遇した主な技術的課題をまとめます。

Apache POI → OpenMcdf移行

区分Java (POI)C# (OpenMcdf)
ディレクトリアクセスDirectoryEntryCFStorage
ストリーム読み取りDocumentInputStreamCFStream
リソース管理close()Dispose() / using

言語間の違いの克服

JavaC#対処方法
byte (符号付き)byte (符号なし)sbyteを使用または変換
getXxx() / setXxx()Property自動変換
Iterator<T>foreachLINQを活用
throws宣言なしtry-catchパターン

感謝の言葉

このプロジェクトはneolord0(パク・ソンギュン)さんのhwplibなしには存在できませんでした。

HWPファイルフォーマットは公式ドキュメントが不足しており、バージョンごとに微妙な違いがあるため、リバースエンジニアリングが難しい領域です。それにもかかわらず、パク・ソンギュンさんは何年もの間hwplibを継続的に開発・メンテナンスしてきました。丁寧に作成されたユニットテスト、明確なコード構造、そしてApache License 2.0での公開のおかげで、この移植プロジェクトが可能になりました。

特に47個のテストケースは、移植過程で灯台のような役割を果たしてくれました。「これさえ通過すればいい」という明確な目標があったからこそ、3週間という短い期間で完成できました。

オープンソースエコシステムは、このようにお互いの肩の上に立って成長します。誰かの黙々とした献身が、また別の誰かに新しい可能性を開いてくれます。パク・ソンギュンさんがJavaエコシステムに植えた種が、今、.NETエコシステムでも芽を出しました。

心から感謝します。🙏

リンク