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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C#

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

Windows Forms

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

Q&A

解決済

3回答

14646閲覧

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

yoshin

総合スコア39

C#

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

Windows Forms

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

1グッド

1クリップ

投稿2017/05/19 10:01

編集2017/05/23 00:31

###困っていること
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

miiiiiiiisuke👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hihijiji

2017/05/22 08:09

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

2017/05/22 09:54

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

2017/05/23 00:14 編集

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

2017/05/23 00:17 編集

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

回答3

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 04:04

編集2017/05/23 04:11
hihijiji

総合スコア4150

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yoshin

2017/05/23 04:54

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

0

ベストアンサー

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

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

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

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

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

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

投稿2017/05/23 02:29

haru666

総合スコア1591

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

haru666

2017/05/23 02:46

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

2017/05/23 03:14

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

2017/05/23 04:45

なるほど、2インスタンス連続実行すると起きますね。 先にいったように大きなデータを1度に取ろうとしないことです。 CSVに変換して直接ファイルを読む、別のライブラリを使う、何がし逐次読み込みに適した形に変換してください。
yoshin

2017/05/23 04:55

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

2017/05/23 05:15

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

2017/05/23 05:24

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

2017/05/29 02:43

回答が遅くなり大変申し訳ありません。 NPOIを使用して、ファイルを読み込んだところエラーを回避できることは確認できました。 ただ、今回の案件では10万行以上のデータを読み込み・書込みする可能性もあり、 そういった場合、Microsoft.Office.Interop.Excel 、NPOIどちらを使うにしても問題がありそうです。 エクセルを使わずにCSVで読み書きするする等、根本的な見直しが必要になりそうです。 とはいえ、もともとエラーが発生していたところの解決はできましたので、解決済とさせていただきます。
haru666

2017/05/30 02:02

エクセル10万行の処理は相当大変ですからね… 下記に同じような質問がありました。 https://stackoverflow.com/questions/15338199/read-a-big-excel-document 同じ条件で検索するとOLEDBを使う方法をしばしば見かけますね。 ただどちらの方法にしても、このセル数だと1回につき数秒かかりますね… 10万行で…書き込みもある、というのは、前提条件として結構問題かもしれませんね。
guest

0

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

投稿2017/05/19 10:52

Zuishin

総合スコア28660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yoshin

2017/05/20 01:05

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

2017/05/22 07:07

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

2017/05/22 10:03

1 回目の実行で例外がスローされました。 場所は GetSheet() 内の return です。 System.Reflection.TargetInvocationException: '呼び出しのターゲットが例外をスローしました。' "インデックスが無効です。 (HRESULT からの例外:0x8002000B (DISP_E_BADINDEX))"
yoshin

2017/05/23 00:32

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

2017/05/23 00:45

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

2017/05/23 02:40

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

2017/05/23 03:39

プロセスメモリグラフを確認してみました。 1回目 データ取得前:39MB    データ取得時:617MB 1回目 データ取得前:105MB    データ取得時:622MB 1回目 データ取得前:109MB    データ取得時:686MB 1回目 データ取得前:174MB    データ取得時:752MB 1回目 データ取得前:239MB    データ取得時:817MB 1回目 データ取得前:304MB    データ取得時:エラー派生 徐々にメモリが増えている状態です。
Zuishin

2017/05/23 03:43

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

2017/05/23 03:45

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

2017/05/23 04:56

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問