R.Aokiをフォローする

【C#】.NET FrameworkでExcelを扱う

バックエンド

今回は.NET Framework (C#)回となります。
業務で技術調査を行っていた際に触れる機会があり、せっかくなので行った内容をまとめました。

今回必要だった主な要件は以下です。
1. テンプレートファイルを読み込めること。
2. 図形(テキストボックス含む)の保存ができること。
3. マクロの使用不可。

主に問題だったのが1と2の両立です。
元々はPythonライブラリのopenpyxlを使用していたものの図形の保存に対応しておらず、代替手段が必要でした。
その他ライブラリ等探してみたものの、図形の保存が不可または図形の保存はできるが新規作成のみ(テンプレートファイル読み込み不可)ばかりでした。

使用技術

・.NET Framework (C#)
・Visual Studio

使用技術について

.NET FrameworkとはWindowsアプリケーションの作成に用いられるソフトウェア開発フレームワークです。
今回はC#を使用しましたが、その他にF#やVisual Basicも使用可能となっています。

(引用:https://dotnet.microsoft.com/ja-jp/learn/dotnet/what-is-dotnet-framework)

同じフレームワーク内で別の言語が使用できるというのが不思議でしたが、どうやら一度言語に依存しない共通中間言語 (CIL)へのコンパイルを挟むことで可能にしているようでした。

使用例

以下が今回使用したコードになります。
テンプレートファイルを読み込み、コピーを作成。
テキスト・画像・図形の追加を行って、プロセスの開放を行っています。

using Microsoft.Office.Core;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using core = Microsoft.Office.Core;

namespace Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("start");
            Application xlApp = new Application();
            Workbooks xlBooks = xlApp.Workbooks;
            Workbook templateBook = xlBooks.Open(@"C:\~~~\\Book1.xlsx");
            var fileName = "BookCopy1.xlsx";
            templateBook.SaveCopyAs(fileName);
            templateBook.Close(false);
            Workbook xlBook = xlBooks.Open(@"C:\~~~\\" + fileName);
            Excel.Worksheet xlSheet = xlBook.Sheets["sheet1"];
            Excel.Range xlRange = xlSheet.Cells;
            Excel.Range xlCells = xlSheet.Cells[1, 1];
            Excel.Shapes xlShapes = xlSheet.Shapes;

            try
            {
                // 保護解除
                xlBook.Unprotect();
                xlSheet.Unprotect();

                // 書き込み
                xlCells.Value = "保護解除&書き込み";
                Console.WriteLine("Write success!!");

                // 画像
                xlShapes.AddPicture(
                    @"C:\~~~\\test.png",
                    MsoTriState.msoCTrue,
                    MsoTriState.msoCTrue,
                    5,
                    5,
                    100,
                    100
                );
                Console.WriteLine("Picture success!!");

                // テキストボックス
                xlShapes.AddTextbox(
                    core.MsoTextOrientation.msoTextOrientationHorizontal,
                    100,
                    100,
                    100,
                    100
                );
                Console.WriteLine("TextBox success!!");

                // 保護
                xlBook.Protect();
                xlSheet.Protect();

                // 保存
                xlBook.Save();
                Console.WriteLine("All success!!");
            } catch
            {
                Console.WriteLine("Error!!");
            } finally
            {
                // 解放処理
                Marshal.FinalReleaseComObject(xlShapes);
                Marshal.FinalReleaseComObject(xlCells);
                Marshal.FinalReleaseComObject(xlRange);
                Marshal.FinalReleaseComObject(xlSheet);
                xlBook.Close(false);
                Marshal.FinalReleaseComObject(xlBook);
                Marshal.FinalReleaseComObject(templateBook);
                xlBooks.Close();
                Marshal.FinalReleaseComObject(xlBooks);
                xlApp.Quit();
                Marshal.FinalReleaseComObject(xlApp);

                // 上記でプロセスが解放されない場合は強制的にkill
                foreach (var p in Process.GetProcessesByName("EXCEL"))
                {
                    p.Kill();
                }
            }
        }
    }
}

解説

今回の実装ではCOM参照を使用していますが、この方法だといくつか問題があります。
1つめはExcelが必須という点です。
COM参照を使用しているため、裏でExcelを起動してファイルの読み込みや書き込みを行っています。
自身のPC上で行う分にはExcelをインストールすればよいのですが、実際にシステムで使用するとなるとサーバー上にExcelをインストールする必要があります。

2つめはプロセスの解放漏れについてです。
COM参照を使用した場合、各オブジェクト(ブック、シート、セルなど)の開放が必要となります。厄介なことに一括で解放できるわけではなく、触れたすべてのオブジェクトに対して明示的に指定しなければなりません。
解放漏れが起きるとプロセスにずっと居座ることになってしまいます。
全てのプロセスを解放してもバックグラウンドでプロセスが居座ることがあり、そこも不安要素です。

※記載したコードでも最終的に無理やり開放しています。

上記の点から、よほどのことが無い限りは各種ライブラリの使用をお勧めします。

おわりに

今回は.NET Framework (C#)でのExcel操作についてまとめてみました。
openpyxlやphpSpreadsheetなどのライブラリを使用することは多々ありますが、図形の保存ができないというのは知りませんでした。。。

ここまでご覧いただきありがとうございました。