Excel *.* Object Libraryについて教えて下さい

解決


初開発  2006-04-04 00:58:40  No: 130870

お世話になります。
関連する内容で2〜3分けて質問させていただきたいことと、内容をうまく
伝えきれるかという部分で長めの質問文になるかもしれませんがお許しください。

このたび極めて小規模ですが初めて納品を目的としたシステムを開発中です。
メンバーは3人で開発環境はWinXP、VB.NET2003なんですけど唯一Excelだけ
1台はExcel2002(私の機械)で残りはExcel2000(他二名)という環境になっています。

1.
Excel2000と2002で参照設定のExcel *.* Object Libraryが9.0と10.0で異なると思いますが
下位互換性がない??みたいなので2000の方で9.0を参照設定したのち残りの二台へ
プロジェクトを渡したんですが2002の機械で参照プロパティの説明を見ると
10.0になっています。その後で2002の機械から2000の2台へプロジェクトを
渡すと参照設定のExcelの部分が黄色△の!マークがついてビルドエラーになります。
仕方なくそれぞれ参照設定を再度9.0にしてもらってるんですが
プロジェクトの参照を下位のバージョンにそろえることはできないのでしょうか?

2.
また、9.0を参照設定にして開発すれば配布先にExcel2000以上があれば
動作すると思っているんですが間違った認識でしょうか?

3.
さらに、テストで2002の機械で作成したサンプルexeを2000の機械で実行した時
"コレクションをサポートしません。"という例外エラーになりました。
まずないらしいんですが万が一配布先がExcel97だった場合、仮に9.0で作成しても・・だと思います。
そこでバージョンが合わない場合やそもそもExcelがない場合は処理ボタンを
押せなくしようと考えていますが、その判定は可能でしょうか?方法があれば教えて下さい。

欲張った質問ですが関連事項なので別スレッドはまずいかなと思った次第です。
是非よろしくお願いします。m(__)m


魔界の仮面弁士  2006-04-04 02:15:13  No: 130871

> プロジェクトの参照を下位のバージョンにそろえることはできないのでしょうか?
できません。Excel 用のタイプライブラリには、ADO のような
互換バージョンのライブラリが提供されていないからです。

> また、9.0を参照設定にして開発すれば配布先にExcel2000以上があれば
> 動作すると思っているんですが間違った認識でしょうか?
間違っています。互換性のある部分と無い部分とがあるためです。

例えば、Workbooks.Open メソッドを見てみましょう。
このメソッドは、97/2000 では引数が13個ですが、2002/2003では15個です。
そもそも、メソッドの定義が異なるので、一方で作ったコードは、
他方の環境では動作しません。

一応 2002/2003には、引数13個版が _Open という別名で用意されているので、
それを使うという逃げ道もあるのですが……環境によって、使用するメソッド名が
異なるので、ソースの共有という面では扱いにくいため、今回は使えないでしょう。

また、そのような互換メソッドが無い場合もありえます。
たとえば、Range オブジェクトの Insert メソッドなどがその例であり、
Excel 2002 が『Function Insert(Shift, CopyOrigin)』 
Excel 2000 が『Function Insert(Shift)』 
Excel 97 が、『Sub Insert(Shift)』という、バージョンごとに
異なった定義になっている上に、互換メソッドも用意されていません。

> という例外エラーになりました。
基本的には、バージョンごとに異なるアセンブリを作るようにしてください。
それが出来ないのであれば、参照設定せずに、レイトバインドで実装しましょう。

> そこでバージョンが合わない場合やそもそもExcelがない場合は処理ボタンを
> 押せなくしようと考えていますが、その判定は可能でしょうか?
CreateObject("Excel.Application") が失敗すれば、Excel が無い(または起動不可)
という事になります。
起動できた場合は、その Excel.Application の Versionプロパティをチェックすれば、
個々の Excel バージョンがわかります。
その


初開発  2006-04-06 03:36:39  No: 130872

魔界の仮面弁士さん、とても親切な回答ありがとうございます。
ほとんどが誤った予測や認識であった事がよく分かりました。根拠のない
思い込みや勘で事をすすめようとしていたことに深く反省しています。

あらためまして・・・
今開発でExcelが絡む部分は帳票に出力しない一部のテーブルについての補助機能として
そのテーブルのフィールド名称をCheckedListBoxに表示して必要に応じて選択された項目の値を
Excelに出力するというもので出力の際セルを結合したり罫線をひくetcなど
凝ったことは一切しません。(というかまだできませんが^^;)
ですので9.0で作成しておけばあわよくば以降のバージョンならと目論んでおりました。
コーディング自体は実は始めたばかりで主にここを参考にしました
http://www.bcap.co.jp/hanafusa/dotnet/index.html
過去ログの中では比較的最近のものみたいですが
http://madia.world.coocan.jp/cgi-bin/VBBBS2/wwwlng.cgi?print+200603/06030058.txt
なども先のリンク先の手法に似ていて参考にしました。

前置きが長くなりましたが要するに今のところExcel *.* Object Libraryを
参照設定にする方法しか分からない状態です。

>それが出来ないのであれば、参照設定せずに、レイトバインドで実装しましょう。
であれば今回の場合ある程度は対応がとれるのでしょうか?レイトバインドについては
http://madia.world.coocan.jp/cgi-bin/VBBBS2/wwwlng.cgi?print+200403/04030024.txt
の中で魔界の仮面弁士さんが詳しく回答されているのを拝見してヘルプにある
事前バイディングと遅延バインディングもぼんやりとですが分かりかけてきました。
ですが自分のコードにどう反映させればよいのかイメージできません。
図々しいかと思いますができればその辺りのコツや注意点など教えていただけないでしょうか?


初開発  2006-04-06 03:42:55  No: 130873

参考にはならないと思いますが念のためコードです。
見づらさや幼稚な部分はお許しください。

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim w_DS As New System.Data.DataSet
    Dim w_DT As New System.Data.DataTable
    Dim w_TName As String = "TCM21_GINKO"
    Me.OleDbDataAdapter1.Fill(w_DS, w_TName)
    w_DT = w_DS.Tables(w_TName)
    '
    Dim i, j As Integer
    Dim Col1, Col2 As String
    Dim w_Array() As String
    '
    Dim xlApp As New Excel.Application
    Dim xlBooks As Excel.Workbooks = xlApp.Workbooks
    Dim xlBook As Excel.Workbook = xlBooks.Add
    Dim xlSheets As Excel.Sheets = xlBook.Worksheets
    Dim xlSheet As Excel.Worksheet = CType(xlSheets.Item(1), Excel.Worksheet)
    Dim xlCells As Excel.Range = xlSheet.Cells
    Dim xlRange1, xlRange2 As Excel.Range
    '--------------------------------------------------------------------------------
    With Me.CheckedListBox1

        ReDim w_Array(.CheckedItems.Count - 1)

        For i = 0 To w_DT.Rows.Count - 1

            w_Array.Initialize()

            For j = 0 To .CheckedItems.Count - 1
                w_Array(j) = w_DT.Rows(i)(.CheckedItems.Item(j).ToString).ToString
            Next

            xlRange1 = CType(xlCells(i + 1, 1), Excel.Range)
            Col1 = xlRange1.Address(False, False)
            System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange1)

            xlRange1 = CType(xlCells(i + 1, .CheckedItems.Count), Excel.Range)
            Col2 = xlRange1.Address(False, False)
            xlRange2 = xlSheet.Range(Col1 & ":" & Col2)
            xlRange2.Value = w_Array

            System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange1)
            System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange2)
        Next

        xlApp.Visible = True
    End With
    '--------------------------------------------------------------------------------
    System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange1)
    System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange2)
    System.Runtime.InteropServices.Marshal.ReleaseComObject(xlCells)
    System.Runtime.InteropServices.Marshal.ReleaseComObject(xlSheet)
    System.Runtime.InteropServices.Marshal.ReleaseComObject(xlSheets)
    System.Runtime.InteropServices.Marshal.ReleaseComObject(xlBook)
    System.Runtime.InteropServices.Marshal.ReleaseComObject(xlBooks)
    System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp)
End Sub


魔界の仮面弁士  2006-04-06 06:01:00  No: 130874

> ですので9.0で作成しておけばあわよくば以降のバージョンならと目論んでおりました。
.NETの場合、話はそう単純な物では無かったりします。
あわよくば、ではなく、全バージョンを用意して、メソッド定義の違いや、
実際の動作をしっかりとチェックしておきましょう。
(ただし、同一PCに、複数バージョンのExcelを入れるのは NG ですけれどね)

Excel VBA / VB6 / VBScript 等であれば、COMオブジェクトの解放処理が自動化されているため、
省略可能引数の個数や、戻り値の有無などを気にせずに済むのですが、これが .NET の場合は、
ご存知のように、オブジェクトの解放のために ReleaseComObject の呼び出しが求められるため、
それを意識したコードを書かなければならないからです。

たとえば、先の
> Excel 2002 が『Function Insert(Shift, CopyOrigin)』 
> Excel 2000 が『Function Insert(Shift)』 
> Excel 97 が、『Sub Insert(Shift)』という、バージョンごとに
を例にとれば、Range.Insert を呼ぶ場合でも、

  Dim VersionText As String = xlApp.Version
  If (VersionTextがExcel97を示す文字列だったら) Then
    oRange.Insert(xlShiftDown)
  Else
    Dim R As Object = oRange.Insert(xlShiftDown)
    System.Runtime.InteropServices.Marshal.ReleaseComObject(R)
  End If

のような対応が求められたりするわけです。
少なくとも、自分が使用しているメソッドの定義だけでも確認の必要があるかと。

>>それが出来ないのであれば、参照設定せずに、レイトバインドで実装しましょう。
> であれば今回の場合ある程度は対応がとれるのでしょうか?

対応は可能ですが、参照設定が使えないという事になるので、Excelで使用する
定数等を自分で Const 定義しなければならないなど、手間は増えます。
Option Strict On モードの場合は、CallByName の併用も必要でしょう。

また、提示されたコードを見る限り、現在の解放手順も見直す必要がありそうです。
具体的には、ReleaseComObject の戻り値を意識する必要がある、という事です。

たとえば、
  Dim Hs As Excel.HPageBreaks = oWorksheet.HPageBreaks
  Dim H As Excel.HPageBreak = Hs.Add(oRange)  '改ページ挿入
というコードをレイトバインドにした場合には、
  Dim Hs As Object = oWorksheet.HPageBreaks
  Dim H As Object = Hs.Add(oRange)  '改ページ挿入
のように書く事になるわけですが、この場合、Hs.Add 時に oRange の
参照カウントが増加してしまうので、Marshal.ReleaseComObject(oRange) を、
2回呼び出さねばならなかったりします。
http://madia.world.coocan.jp/cgi-bin/VBBBS2/wwwlng.cgi?print+200512/05120042.txt

ついでに、この辺りも参照してみてください。
先月末、別の掲示板であったやりとりです。
http://f57.aaa.livedoor.jp/~jeanne/bbs/faq.cgi?mode=al2&namber=2899


魔界の仮面弁士  2006-04-06 06:13:29  No: 130875

> 参考にはならないと思いますが念のためコードです。

実際に実行確認まではしてはいませんが…ざっと見た限りでは、
この程度であれば、9.0 〜11.0 間の違いは意識せずに済むと思いますよ。
(12.0 でもいけるかどうかはわかりませんけど)

ただ、9.0の参照設定のままで動作するのかどうかまでは、実際に試してみないとわかりませんので、
複数バージョンに対応させるつもりならば、参照設定は使わない事をお奨めします。

もしも、参照設定したいのであれば、各バージョンごとに別のexeとして作成するのが無難かと。


初開発  2006-04-07 04:45:48  No: 130876

度々の親切な回答本当にありがとうございますm(__)m
遅くなりましたが現状報告いたします。

まず、今の9.0参照の事前バインディング?(提示コード)の状態で
>この程度であれば、9.0 〜11.0 間の違いは意識せずに済むと思いますよ。

>ただ、9.0の参照設定のままで動作するのかどうかまでは
とりあえず普通に動作するようです。
ただ11.0や8.0?(Excel97)でテストできる環境が近くにないので9.0と10.0のみです。
ですがこれはあくまで動作テストのみとして終わりにし、ここはやはり
>複数バージョンに対応させるつもりならば、参照設定は使わない事をお奨めします。
にすることにします。まずは提示コードの処理のまま・・・

'レイトバインド(Option Strict On 時はCallByName併用)
'Dim oExcel As Object = CreateObject("Excel.Application")
'Dim oBooks As Object = CallByName(oExcel, "Workbooks", CallType.Get)
'Dim oBook As Object = CallByName(oBooks, "Add", CallType.Method)
'Dim oSheets As Object = CallByName(oBook, "Worksheets",CallType.Get)
'Dim oSheet As Object = CallByName(oSheets, "Item", CallType.Get, 1)
'Dim oCells As Object = CallByName(oSheet, "Cells", CallType.Get)
'Dim oRange1, oRange2 As Object

'レイトバインド(Option Strict Off)
Dim oExcel As Object = CreateObject("Excel.Application")
Dim oBooks As Object = oExcel.Workbooks
Dim oBook As Object = oBooks.Add
Dim oSheets As Object = oBook.Worksheets
Dim oSheet As Object = oSheets.Item(1)
Dim oCells As Object = oSheet.Cells
Dim oRange1, oRange2 As Object
(以降、変数名以外は提示コードと同処理)

といった感じで修正しました。魔界の仮面弁士さんの教えを正しく理解しているかは疑問ですが…(汗)
あとCallByNameについて調べるのも大変でしたが、"."を打つと出てくる
入力補助的なメンバ一覧とかも出ないんですね。入力ミスが・・

次に最初に質問したままになっていたバージョンチェック等の件ですが
処理中にタイトルバーにてMe.Text = oExcel.Versionと試したところ
"9.0"や"10.0"という文字列を返してくれるようですね。(ということは2003なら"11.0"、97なら"8.0"?てことかと)
今回の場合は9.0〜11.0の間か判定すればいい…??不明。

あとはExcelがない機械は周りにないためテストできていませんが
>CreateObject("Excel.Application") が失敗すれば
というのはエラーで判断するのかなと思いますが記述の仕方が思いつかない状態です。
Try〜Catch等もあまり使用したことがないもので

>また、提示されたコードを見る限り、現在の解放手順も見直す必要がありそうです。
これが一番色々と拝見しました。たしかに"COMオブジェクトの解放"や"プロセスが終了しない"
といった質問が過去・現在たくさんあり、注意すべき点だと実感しました。
今回の場合は参照カウントが増加することはなさそう(?)とは思いますが
参考先のリンクのようにReleaseComObjectをサブルーチンにするなどもう少しきれいなコードにしようと思います。

現状報告は以上です。なにかご指摘があればお願いします。
解決チェックを付ける前にもう少し勉強しようと思います。


魔界の仮面弁士  2006-04-07 11:04:41  No: 130877

> (ということは2003なら"11.0"、97なら"8.0"?てことかと)
数字とは限りません。アルファベットを含む事もあります。
http://support.microsoft.com/kb/232652/en-us

> というのはエラーで判断するのかなと思いますが
正解です。Try Catch でどうぞ。


初開発  2006-04-12 20:21:26  No: 130878

魔界の仮面弁士さん、この度はたくさんのご指導や参考リンクなど本当にありがとうございました。
今回覚えたことも、だいぶ頭の中で整理できてきましたので解決チェック付けさせていただきます。

あ、それと
>正解です。Try Catch でどうぞ。
の方もコード追加しました。Excelがない環境でのテストはまだできていない状態なので
Catchした時のエラー番号?やメッセージ(ex.Message)?がどんなものなのか検証はできてないですが・・・
あと提示コードの処理にも Try Catch を追加してFinallyで必ず
ReleaseComObject用のサブルーチンをCallするように修正してみました。

まだまだ問題はあるかもしれませんが頑張っていきたいと思います。
ありがとうございました。


※返信する前に利用規約をご確認ください。




  


  このエントリーをはてなブックマークに追加