質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.45%

  • C#

    9290questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

  • Excel

    1975questions

    Excelは、マイクロソフト社が開発しているデータ集計や分析を行う表計算ソフトの一つです。文書作成や表計算、資料作成などの多彩な機能を備えており、統合パッケージであるMicrosoft Officeに含まれています。

  • Windows Forms

    183questions

    Windows Forms(WinForms)はMicrosoft .NET フレームワークに含まれる視覚的なアプリケーションのプログラミングインターフェイス(API)です。WinFormsは管理されているコードの既存のWindowsのAPIをラップすることで元のMicrosoft Windowsのインターフェイスのエレメントにアクセスすることができます。

C#でエクセルファイルのデータ取得時にメモリ不足が発生する

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 3,361

yoshin

score 29

困っていること

WindowsFormアプリケーションにてエクセルファイルを選択して、
指定したファイルのデータをDataTableにセットする機能を実装しています。
メモリ不足のエラーが発生するのですが、原因がわからず困っています。

発生している問題・エラーメッセージ

ファイル選択ボタンをクリックしてエクセルファイルのデータを取得しています。
同じファイルを選択して複数回実行したときに、
1~6回目は処理が成功するのですが、7回目でエラーが発生します。
※ファイルのサイズは522KB(65537行,256列)です。
なお、上記の問題はVisual Studioのビルドのプラットフォームターゲットをx86とし、
64bitのマシン環境で実行すると発生します。
※事情によりプラットフォームターゲットをx86にしています。
プラットフォームターゲットをAny CPU にすると発生しないことまではわかりました。

【エラー発生箇所】
DaoExcelFileクラスのReadFileメソッド

【エラーメッセージ】
プログラムを続行するための十分なメモリがありませんでした

該当のソースコード

    public partial class Form1 : Form
    {
        private UserInfoFile userFile;

        public Form1()
        {
            InitializeComponent();
        }
        //ファイル選択ボタンクリック
        private void button1_Click(object sender, EventArgs e)
        {
            var selectFileFormat = "Excel";
            //ユーザーファイルを選択してデータを読み込み
            FileReadWriteController.ReadUserInfoFile(selectFileFormat, out userFile);
        }
        //ファイルからデータを読み込み、書込みをコントロールするクラス
        public static class FileReadWriteController
        {
            ///ユーザー情報ファイルを選択して読み込み、
            ///読み込んだデータをユーザー情報ファイルオブジェクトのDataTabaleにセットする
            public static bool ReadUserInfoFile(
                string selectFileFormat, out UserInfoFile userInfoFile)
            {
                OpenFileDialog ofd = new OpenFileDialog();
                if (ofd.ShowDialog() == DialogResult.OK)
                {
                    userInfoFile = new UserInfoFileExcel();
                    userInfoFile.FilePath = ofd.FileName;
                    userInfoFile.ReadData();
                    return true;
                }
                else
                {
                    userInfoFile = null;
                    return false;
                }
            }
        }
        //ファイルの抽象クラス
        abstract public class MyFile
        {
            public string FilePath;
            public DataTable FileData;
            public MyFile()
            {
                FileData = new DataTable();
            }
            public abstract void ReadData();
        }
        //ユーザー情報情報ファイルの抽象クラス
        abstract public class UserInfoFile : MyFile
        {
            //省略
        }
        //Excel形式のユーザー情報ファイル
        public class UserInfoFileExcel : UserInfoFile
        {
            public override void ReadData()
            {
                using (var excelApp = new DaoExcelFile())
                {
                    excelApp.ReadFile(FilePath, FileData, "Info");
                    excelApp.Quit();
                }
            }
        }
        //Excelクラス
        public class DaoExcelFile : IDisposable
        {
            private object xlsApplication = null;
            private object xlsBooks = null;
            protected object XlsApplication
            {
                get
                {
                    // 存在しない場合は作成する
                    if (xlsApplication == null)
                    {
                        Type classType = Type.GetTypeFromProgID("Excel.Application");
                        xlsApplication = Activator.CreateInstance(classType);
                    }
                    return xlsApplication;
                }
            }

            // Workbooksオブジェクト
            protected object Workbooks
            {
                get
                {
                    if (xlsBooks == null)
                    {
                        xlsBooks = XlsApplication.GetType().InvokeMember("Workbooks", BindingFlags.GetProperty, null, XlsApplication, null);
                    }
                    return xlsBooks;
                }
            }
            //Dispose
            public void Dispose()
            {
                ReleaseComObject(xlsBooks);
                ReleaseComObject(xlsApplication);
            }

            // COMオブジェクトのリリース
            public void ReleaseComObject(object target)
            {
                try
                {
                    if ((target != null))
                    {
                        System.Runtime.InteropServices.Marshal.ReleaseComObject(target);
                    }
                }
                finally
                {
                    target = null;
                }
            }

            // Excel終了
            public void Quit()
            {
                XlsApplication.GetType().InvokeMember("Quit", BindingFlags.InvokeMethod, null, XlsApplication, null);
            }

            // 対象ファイルの対象シートのデータをデータテーブルにセットする
            public void ReadFile(string filePath, DataTable fileData, string sheetName)
            {
                object book = null;
                object sheets = null;
                object sheet = null;
                object cells = null;
                object cella = null;
                object cellb = null;
                object targetRange = null;
                object[,] obj1 = null;

                try
                {
                    book = GetOpenFile(filePath);
                    sheets = GetSheets(book);
                    sheet = GetSheet(sheets, 1);
                    int lastRow = 65537;
                    int lastColumn = 256;
                    cells = GetCells(sheet);
                    cella = GetCell(cells, 1, 1);
                    cellb = GetCell(cells, lastRow, lastColumn);
                    targetRange = GetRange(sheet, cella, cellb);

                    //*********ここでエラーが発生する
                    //*********エラー内容:"プログラムを続行するための十分なメモリがありませんでした"
                    //オブジェクトの2次元配列へセット
                    //以降この配列にアクセスすることで処理を高速化
                    obj1 = (System.Object[,])targetRange.GetType().InvokeMember("Value2", BindingFlags.GetProperty, null, targetRange, null);
                }
                finally
                {
                    //オブジェクトの解放
                    ReleaseComObject(targetRange);
                    ReleaseComObject(cellb);
                    ReleaseComObject(cella);
                    ReleaseComObject(cells);
                    ReleaseComObject(sheet);
                    ReleaseComObject(sheets);
                    ReleaseComObject(book);
                }
            }

            // Excelファイルを開いてオブジェクトを返す
            public object GetOpenFile(string xlsFilePath)
            {
                object[] parameters = new object[15];
                parameters[0] = xlsFilePath;
                parameters[1] = Type.Missing;
                parameters[2] = Type.Missing;
                parameters[3] = Type.Missing;
                parameters[4] = Type.Missing;
                parameters[5] = Type.Missing;
                parameters[6] = Type.Missing;
                parameters[7] = Type.Missing;
                parameters[8] = Type.Missing;
                parameters[9] = Type.Missing;
                parameters[10] = Type.Missing;
                parameters[11] = Type.Missing;
                parameters[12] = Type.Missing;
                parameters[13] = Type.Missing;
                parameters[14] = Type.Missing;
                object openFile = new object();
                openFile = Workbooks.GetType().InvokeMember("Open", BindingFlags.InvokeMethod, null, Workbooks, parameters);
                return openFile;

            }

            // Sheetsを取得
            public object GetSheets(object book)
            {
                return book.GetType().InvokeMember("Worksheets", BindingFlags.GetProperty, null, book, null);
            }
            // シートインデックスからSheetを取得
            public object GetSheet(object sheets, int index)
            {
                object[] parameters = new object[1];
                parameters[0] = index;
                return sheets.GetType().InvokeMember("Item", BindingFlags.GetProperty, null, sheets, parameters);
            }
            // Cellsを取得
            public object GetCells(object sheet)
            {
                return sheet.GetType().InvokeMember("Cells", BindingFlags.GetProperty, null, sheet, null);
            }
            // Cellを取得
            public object GetCell(object cells, int row, int column)
            {
                object[] parameters = new Object[2];
                parameters[0] = row;
                parameters[1] = column;
                return cells.GetType().InvokeMember("Item", BindingFlags.GetProperty, null, cells, parameters);
            }
            // Rangeを取得
            public object GetRange(object sheet, object cell1, object cell2)
            {
                object[] parameters = new Object[2];
                parameters[0] = cell1;
                parameters[1] = cell2;
                return sheet.GetType().InvokeMember("Range", BindingFlags.GetProperty, null, sheet, parameters);
            }
        }
    }

試したこと

COMオブジェクトが解放されていないのかとも思いましたが、
ファイル選択ボタンのクリック時の処理が終了すれば、
タスクマネージャーにEXCEL.EXEプロセスは残っていない状態にはなっています。

補足情報(言語/FW/ツール等のバージョンなど)

C#
NET Framework4.0
Microsoft Visual Studio 2015

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • hihijiji

    2017/05/22 17:09

    COM参照を使ったり、Reflection を多用したりとあえて茨の道を進んでいるように見えるのですが、何か事情があるのでしょうか?

    キャンセル

  • caf2for4

    2017/05/22 18:54

    まずはobj1のオブジェクトやbyte数を周回毎に確認してみて、何が増えているのか確認してみるのはいかがでしょうか?OfficeTools for VSはそこそこ不具合や謎仕様もあるのでなんとも言えないところがありますが。。

    キャンセル

  • yoshin

    2017/05/23 09:13 編集

    Excel2003以降のバージョンに対応するため、COM参照、Reflectionを使っています。
    外部のライブラリを使った方が良かったのかもしれないと今は後悔していますが。。。

    キャンセル

  • yoshin

    2017/05/23 09:15 編集

    エラー発生箇所にてにブレークポイントをはってobj1のを確認するとnullになってることは確認しています。
    byte数の確認とはなにか他の見方があるのでしょうか?
    初心者の質問で申し訳ありません。

    キャンセル

回答 3

checkベストアンサー

0

65537行, 256列に適当な値(1)を挿入したら戻るのに時間は30秒ほどかかったものの、

Windows10(64bit)
メモリ16GB
プラットフォームx86
Office(x86)

の状態でメモリが足りないなんてことにはならなかったですね。

ただ、戻り値のオブジェクト配列はLargeObjectHeap(LOH)になるんじゃないですかね。
こんな大きな範囲の二次元配列に一気にアクセスしようなんてのが不健康な気がしますね。

私はLOHを扱うコードを幸いにして書く環境にいないため対処方法や検出方法が分かりませんが、これをもし本番のコードで再帰的に呼び出してたら、組み合わせによってはメモリ断片化の原因になってOutOfMemoryExceptionが起きることもある…のかもしれません。
LOHはOutOfMemoryExceptionの原因になるという話をしばしば聞くので、必然的に扱わなくてはならない場合を除いては、作らないのが一番です。
Excel側の読み込みやマーシャリングにかかるコストを考えると不健康なメモリの使用量だなーとも思いますが、一番の問題はこれかなぁ…と。。純粋なメモリ不足ではないと思います。
(更にDataTableにコピーするんですよね?)

もう少し粒度の細かい読み込みに分割して逐次処理にしてみてはどうでしょうか。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/23 11:46

    そもそもほんとにForm1にサンプルコード貼り付けて手元でエラー起きます…?

    キャンセル

  • 2017/05/23 12:14

    回答ありがとうございます。
    サンプルコードでエラーが起こることは確認済みです。

    キャンセル

  • 2017/05/23 13:45

    なるほど、2インスタンス連続実行すると起きますね。
    先にいったように大きなデータを1度に取ろうとしないことです。

    CSVに変換して直接ファイルを読む、別のライブラリを使う、何がし逐次読み込みに適した形に変換してください。

    キャンセル

  • 2017/05/23 13:55

    回答ありがとうございます。
    ライブラリの使用を検討します。

    キャンセル

  • 2017/05/23 14:15

    まだ治ったとは限らないのに…

    手元でCSVに変換して読み込んだ場合はGCのプロセスメモリの使用量が著しく下がっていたので、OLEとLOHの組み合わせ問題かもしれませんね。

    キャンセル

  • 2017/05/23 14:24

    すいません。
    たしかに解決していないのに解決済にするのはまずかったです。
    まずはライブラリを試してみます。

    キャンセル

  • 2017/05/29 11:43

    回答が遅くなり大変申し訳ありません。
    NPOIを使用して、ファイルを読み込んだところエラーを回避できることは確認できました。

    ただ、今回の案件では10万行以上のデータを読み込み・書込みする可能性もあり、
    そういった場合、Microsoft.Office.Interop.Excel 、NPOIどちらを使うにしても問題がありそうです。

    エクセルを使わずにCSVで読み書きするする等、根本的な見直しが必要になりそうです。

    とはいえ、もともとエラーが発生していたところの解決はできましたので、解決済とさせていただきます。

    キャンセル

  • 2017/05/30 11:02

    エクセル10万行の処理は相当大変ですからね…
    下記に同じような質問がありました。

    https://stackoverflow.com/questions/15338199/read-a-big-excel-document

    同じ条件で検索するとOLEDBを使う方法をしばしば見かけますね。
    ただどちらの方法にしても、このセル数だと1回につき数秒かかりますね…

    10万行で…書き込みもある、というのは、前提条件として結構問題かもしれませんね。

    キャンセル

0

ReadFile() 内の try-catch 内をすべてコメントアウトし、空の Excel ファイルを 5 回開きましたが、問題ありませんでした。
つまり、一番肝心なコードがありません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/20 10:05

    回答いただきありがとうございます。
    せっかく見ていただいたのに肝心なコードを省いてしまい申し訳ありませんでした。
    今コードを更新する環境にないので、環境整い次第コードを再更新します。
    その時にまた見ていただけると助かります。
    よろしくお願いいたします。

    キャンセル

  • 2017/05/22 16:07

    【該当のソースコード】を更新しました。
    また【発生している問題・エラーメッセージ】に自分なりにわかったことも追記しました。
    ビルドのプラットフォームターゲットがなにかしらの原因かもしれないとは思うのですが。。。

    キャンセル

  • 2017/05/22 19:03

    1 回目の実行で例外がスローされました。
    場所は GetSheet() 内の return です。

    System.Reflection.TargetInvocationException: '呼び出しのターゲットが例外をスローしました。'
    "インデックスが無効です。 (HRESULT からの例外:0x8002000B (DISP_E_BADINDEX))"

    キャンセル

  • 2017/05/23 09:32

    申し訳ありません。
    GetSheet()が特定のシート名のシートを取得するようになっていたので、
    インデックスが1のシートを取得するように変更しました、

    キャンセル

  • 2017/05/23 09:45

    20 回開いてみましたが、異常ありませんでした。IDE のプロセスメモリグラフもちゃんと落ちています。
    そちらでグラフの動きはどうでしょう?

    キャンセル

  • 2017/05/23 11:40

    回答ありがとうございます。
    プロセスメモリグラフを確認してみます。
    結果をまたご報告します。お手数おかけして申し訳ありません。

    キャンセル

  • 2017/05/23 12:39

    プロセスメモリグラフを確認してみました。

    1回目 データ取得前:39MB    データ取得時:617MB
    1回目 データ取得前:105MB    データ取得時:622MB
    1回目 データ取得前:109MB    データ取得時:686MB
    1回目 データ取得前:174MB    データ取得時:752MB
    1回目 データ取得前:239MB    データ取得時:817MB
    1回目 データ取得前:304MB    データ取得時:エラー派生

    徐々にメモリが増えている状態です。

    キャンセル

  • 2017/05/23 12:43

    DataTable を使わないこのソースで上記のようになるのであれば、Excel 側の問題の可能性もありますね。
    エラーメッセージも OS のメッセージかもしれません。
    http://tomippe.jp/knowledge/2005/08/post_7.html を試してみてはいかがでしょう?

    キャンセル

  • 2017/05/23 12:45

    ただ、Excel や OS 側の問題であればプロセスメモリがなぜ増えるのかという説明はつきませんが。

    キャンセル

  • 2017/05/23 13:56

    今回はライブラリの使用を検討してみようと思います。
    お手数おかけして申し訳ありませんでした。
    いろいろと回答いただき本当にありがとうございました。

    キャンセル

0

今からでも、NPOIなどの.NETライブラリに切り替えることをお勧めします。
Microsoft.Office.Interop.Excel は様々な問題を抱えています。

  1. 大本の Microsoft Excel Object Library, Microsoft Office Object Library 自体にバグが幾つもある。
  2. Microsoft.Office.Interop.Excel はセキュリティソフトなどの外的要因の影響を受けやすいらしい。
  3. .NET環境からのCOM参照はめんどくさい。

等です。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/23 13:54

    ありがとうございます。
    ライブラリを使うことを検討してみます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.45%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • C#

    9290questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

  • Excel

    1975questions

    Excelは、マイクロソフト社が開発しているデータ集計や分析を行う表計算ソフトの一つです。文書作成や表計算、資料作成などの多彩な機能を備えており、統合パッケージであるMicrosoft Officeに含まれています。

  • Windows Forms

    183questions

    Windows Forms(WinForms)はMicrosoft .NET フレームワークに含まれる視覚的なアプリケーションのプログラミングインターフェイス(API)です。WinFormsは管理されているコードの既存のWindowsのAPIをラップすることで元のMicrosoft Windowsのインターフェイスのエレメントにアクセスすることができます。