EXCEL

VisualStudio2005からEXCELを使用する機会があったのだがえらい苦労した。
苦労した原因は実際に起動されるエクセルがそれぞれのローカルマシンにinstallされたEXCELということだ。そしてバージョンは悲しいことにEXCEL97,2000,2002,2003と混在していた。
そのためVSで[参照の追加]-[COM]で[Microsoft Excel xx.x Object Library]を選択しようものなら、他人のマシンでは実行はおろかコンパイルさえ通らなくなってしまう。つまりバージョン問題を解決するには[参照の追加]によるearly bindingではなくリフレクションを使ったlate bindingが必要というわけだ。
で開発手順としてはまずearly bindingでプログラムを作ってどんな関数が存在するかを把握しておいてから、それをlate bindingに書き直すという方法をとった。つまづいたのは 

  • get_Item()などの小文字で始まる関数をリフレクションで呼び出せない→代わりにインデクサを呼び出すようにした
  • インデクサの呼び出し方→Item
  • Open関数は古いバージョンだと引数が多すぎるとエラーになってしまう→引数が少ない分にはエラーにならないので必要最小限の引数だけ渡すようにした
  • EXCELプロセスが消えない→COMオブジェクトの解放忘れ

などなど。
以下ソースコード

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Collections;

namespace Excel1
{
    /// 
    /// LateBindingを使用したExcelクラス
    /// EXCEL97,2000,2003でテスト
    /// 
    public class ExcelLateBinding : System.IDisposable
    {
        object _apl = null;
        object _workbook = null;
        object _worksheet = null;
        List _disposeList = new List();
        bool _isQuit = true;

        public ExcelLateBinding()
        {
            _apl = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
            _disposeList.Add(_apl);
            _apl.GetType().InvokeMember("DisplayAlerts", BindingFlags.SetProperty, null, _apl, new object { false });
            _apl.GetType().InvokeMember("Visible", BindingFlags.SetProperty, null, _apl, new object { false });

        }
        public void Dispose()
        {
           _apl.GetType().InvokeMember("CutCopyMode", BindingFlags.SetProperty, null, _apl, new object{0});
           if (_isQuit) { _apl.GetType().InvokeMember("Quit", BindingFlags.InvokeMethod, null, _apl, null); }
            foreach (object o in _disposeList)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(o);
            }
        }

        public bool IsQuit
        {
            get { return _isQuit; }
            set { _isQuit = value; }
        }
        public bool Visible
        {
            get { return (bool)_apl.GetType().InvokeMember("Visible", BindingFlags.GetProperty, null, _apl, null); }
            set { _apl.GetType().InvokeMember("Visible", BindingFlags.SetProperty, null, _apl, new object { value }); }
        }
        public bool DisplayAlerts
        {
            get { return (bool)_apl.GetType().InvokeMember("DisplayAlerts", BindingFlags.GetProperty, null, _apl, null); }
            set { _apl.GetType().InvokeMember("DisplayAlerts", BindingFlags.SetProperty, null, _apl, new object { value }); }
        }
        public string DefaultFilePath
        {
            set { _apl.GetType().InvokeMember("DefaultFilePath", BindingFlags.SetProperty, null, _apl, new object { value }); }
            get { return _apl.GetType().InvokeMember( "DefaultFilePath", BindingFlags.GetProperty, null, _apl, null).ToString() ; }
        }
        public string Version
        {
            get
            {
                return _apl.GetType().InvokeMember("Version", BindingFlags.GetProperty, null, _apl, null).ToString();
            }
        }
        public void Add(string templateFilePath)
        {
            object workbooks = _apl.GetType().InvokeMember("Workbooks", BindingFlags.GetProperty, null, _apl, null);
            _disposeList.Add(workbooks);
            _workbook = workbooks.GetType().InvokeMember("Add", BindingFlags.InvokeMethod, null, workbooks, new object { templateFilePath });
            _disposeList.Add(_workbook);
            object sheets = _workbook.GetType().InvokeMember("Sheets", BindingFlags.GetProperty, null, _workbook, null);
            _disposeList.Add(sheets);
            _worksheet = sheets.GetType().InvokeMember("Item", BindingFlags.GetProperty, null, sheets, new object { 1 });
            _disposeList.Add(_worksheet);
        }

        public void Open(string filePath)
        {
            object workbooks = _apl.GetType().InvokeMember("Workbooks", BindingFlags.GetProperty, null, _apl, null);
            _disposeList.Add(workbooks);
            _workbook = workbooks.GetType().InvokeMember("Open", BindingFlags.InvokeMethod, null, workbooks, new object { filePath });
            _disposeList.Add(_workbook);
            object sheets = _workbook.GetType().InvokeMember("Sheets", BindingFlags.GetProperty, null, _workbook, null);
            _disposeList.Add(sheets);

            _worksheet = sheets.GetType().InvokeMember("Item", BindingFlags.GetProperty, null, sheets, new object { 1 });
            _disposeList.Add(_worksheet);
        }
        public void SetCell(int row, int col, string str)
        {
            object cells = _worksheet.GetType().InvokeMember("Cells", BindingFlags.GetProperty, null, _worksheet, null);
            _disposeList.Add(cells);
            cells.GetType().InvokeMember("Item", BindingFlags.SetProperty, null, cells, new object { row, col, str });
        }

        public object GetCell(int row, int col)
        {
            object cells = _worksheet.GetType().InvokeMember("Cells", BindingFlags.GetProperty, null, _worksheet, null);
            _disposeList.Add(cells);
            object range = cells.GetType().InvokeMember("Item", BindingFlags.GetProperty, null, cells, new object { row, col });
            _disposeList.Add(range);
            return range;
        }

        public object GetRow(int row)
        {
            object result = _worksheet.GetType().InvokeMember("Range", BindingFlags.GetProperty, null,
                _worksheet, new object { GetCell(row, 1), GetCell(row, 256) });
            _disposeList.Add(result);
            return result;
        }
        public void SelectCell(int row, int col)
        {
            object cell = GetCell(row, col);
            cell.GetType().InvokeMember("Select", BindingFlags.InvokeMethod, null, cell, null);// ここはDisposeは不要
        }

        public void Save(string filePath)
        {
            _workbook.GetType().InvokeMember("SaveAs", BindingFlags.InvokeMethod, null, _workbook, new object { filePath });
        }

        /// 
        /// ここはEXCELのバージョンによって使用できる定数が増えるかも...
        /// 
        public enum XlPasteType : int
        {
            xlPasteValues = -4163,
            xlPasteFormats = -4122,
            xlPasteAll = -4104,
        }

        public static void CopyRange(object range_from, object range_to, XlPasteType pasteType)
        {
            range_from.GetType().InvokeMember("Copy", BindingFlags.InvokeMethod, null, range_from, null);
            object[] args = new object[4];
            args[0] = (int)pasteType;
            args[1] = -4142;//Excel.XlPasteSpecialOperation.xlPasteSpecialOperationNone
            args[2] = false;
            args[3] = false;
            range_to.GetType().InvokeMember("PasteSpecial", BindingFlags.InvokeMethod, null, range_to, args);
        }
    }
}