AutoCADのLISPで、Excelのデータを取得する

# AutoCAD から、Excel のデータを読む
AutoCAD のカスタマイズ用言語？の AutoLISP は、AutoCAD で作図をする際の便利ツールを組むのには手軽で良いのですが、VLA オブジェクトを使用する事で、ActiveX 経由で Excel を操作できたりします。
当然ですが **Excel がインストールされている**必要があります。
## VLA の基本的な使い方
[こちら](https://qiita.com/tamarinn_x/items/b81c5840e42ca171a98d#%E6%98%94%E3%81%AA%E3%81%8C%E3%82%89%E3%81%AE%E3%82%84%E3%82%8A%E6%96%B9-2%E6%AC%A1%E5%85%83%E9%99%90%E5%AE%9A)でも記載しましたが、LISP から AutoCAD VBA のコマンドを扱うのに使用できます。
先に示したリンク先の例では、LISP のジオメトリック関数 `inters` では、直線同士の交点しか算出できませんが、VBA の `IntersectWith` なら、円や円弧、スプライン等の曲線との交点も算出できますが、そのためにわざわざ VBEditor で dvb ファイルを作って `vl-vbaload` でロードして `(command "vbarun" 〜` とか面倒な事はしたくないので、VLA オブジェクトを使って、VBA の `IntersectWith` を利用しているわけです。

https://qiita.com/tamarinn_x/items/b81c5840e42ca171a98d#%E6%98%94%E3%81%AA%E3%81%8C%E3%82%89%E3%81%AE%E3%82%84%E3%82%8A%E6%96%B9-2%E6%AC%A1%E5%85%83%E9%99%90%E5%AE%9A

この様に、LISP から手軽？に AutoCAD の VBA のメソッドやプロパティにアクセスしたり、他のアプリケーションを ActiveX 経由で操作するのに、使用できます。
## 実際の使い方
では、実際に使い方を確認してみましょう。前提として
+ Microsoft Office の VBA がある程度わかる
+ AutoLISP の基礎が分かる

事を前提に記載しています。
## アプリケーションオブジェクトの作成
Access や Word の VBA から Excel を起ち上げる、あるいは VBA を実行している Excel アプリとは別のプロセスの Excel を起動したい場合は、下記の様に記載すると思います。
```VB:VBA から Excel を起動
Set objApp = CreateObject("Excel.Application")
```
また、起動済みの Excel を取得するという場合は、下記のようにすると思います。
```VB:VBA から、起動済み Excel を取得
Set objApp = GetObject(, "Excel.Application")
```
これらを VLA & ActiveX で行うと、下記の様になります。
```LISP:VLA を利用して Excel を起動
(setq objapp (vlax-create-object "Excel.Application"))
```
```LISP:VLA を利用して、起動済み Excel を取得
(setq objapp (vlax-get-object "Excel.Application"))
```
既に起動している場合は、その起動済みアプリケーションオブジェクトを返し、起動していない場合は新たに起動してそのアプリケーションオブジェクトを返す、下記の様な関数もあります。
```LISP:VLA を利用して、起動済み Excel があればそれを取得、無ければ起動して Excel アプリを取得
(setq objapp (vlax-get-or-create-object "Excel.Application"))
```
[アプリケーションやその他VLAオブジェクトは、使用が終わったら開放(後述)が必要です。](#vla-オブジェクトの開放)
## プロパティの取得
Excel の VBA では、各オブジェクトにはオブジェクトモデルを使用してアクセスすると思います。
例えば、アクティブブックの1つ目のシートの「A1」セルの内容を取得する場合、下記のように記載すると思います。
```VB:Activebook の1つ目の Worksheet の「A1」セルの値を取得
varVal = ActiveWorkbook.Worksheets(1).Range("A1")
```
いきなり Range オブジェクトの指定までで終わっていますが、正しくは下記のように書きます
```VB:Activebook の1つ目の Worksheet の「A1」セルの値を取得
varVal = ActiveWorkbook.Worksheets(1).Range("A1").Value
```
`.Value` を省略した場合、代入先の変数の型や `Set` によるオブジェクト代入かどうかによって、Range オブジェクトを返すか、省略時のデフォルトである Value プロパティを返すかが決まります。
その時の書き方で塩梅良く対応してくれる、VBA の良い所でもあり、悪い所でもあります。
これを AutoLISP の VLA で記載したらどうなるか？
```LISP:VLA を利用して、Activebook の1つ目の Worksheet の「A1」セルの値を取得
(setq
  obj_book (vlax-get-property objapp 'ActiveWorkbook) ;アクティブワークブックを取得
  obj_sheet (vlax-get-property (vlax-get-property obj_book 'Worksheets) 'Item 1) ;最初のシートを取得
  obj_rng (vlax-get-property obj_sheet 'Range "A1") ;A1セルのRangeオブジェクトを取得
  var_val (vlax-get-property obj_rng 'Value) ;A1セルの値を取得
);setq
(vlax-release-object obj_book) ;使わなくなった Book オブジェクトのの開放
(vlax-release-object obj_sheet) ;使わなくなった Sheet オブジェクトのの開放
(vlax-release-object obj_rng) ;使わなくなった Range オブジェクトのの開放
(vlax-variant-value var_val) ;値がバリアント型なので、変換する
```
大変長くなってしまいました。既にExcelアプリケーションオブジェクトは取得済みで、`objapp` に格納されている前提です。
Excel の VBA では、アクティブワークブックを取得するには `Set obj_Book = objapp.ActiveWorkbook` と記載すればよいですし
最初のワークシートを `Set obj_sheet = objapp.ActiveWorkbook.Worksheets(1)` で取得できます。
この様に VBA では、オブジェクトモデルに従って、「.」で繋げて指定できますが、LISP(VLA)で同じ様に記載しようとすると `(setq obj_sheet (vlax-get-property (vlax-get-property (vlax-get-property objapp 'ActiveWorkbook) 'Worksheets) 'Item 1))` となります。な、長い・・・
この様に
Exce の VBA で `変数 = オブジェクト.プロパティ(引数)` で取得するもの
　↓
LISP(VLA)で `(setq 変数 (vlax-get-property オブジェクト 'プロパティ 引数))` で取得できる
という事なのです。
## Item について
先ほどの例では、最初のシートを選択する際、Excel VBA では `Set obj_sheet = obj_book.Worksheets(1)` の様に記載しましたが、LISP(VLA) では `(setq obj_sheet (vlax-get-property (vlax-get-property obj_book 'Worksheets) 'Item 1))` の様に記載しました。
これは、VBA でも
`Set obj_sheet = obj_book.Worksheets(1)`
を正確に記載すると
`Set obj_sheet = obj_book.Worksheets.Item(1)`
となるためです。
Rangeオブジェクト に Value を記載しなくても Value が取得出来たりと、表記で色々省略できる VBA ですが、VLA から利用する際は**省略できない**ものが多いので注意が必要です。
この辺の話は、後述の Cells でも絡みます。
## バリアント型について
VBA には、「バリアント型」という何でも入れれる便利な(そしてトラブルにもなりがちな)データ型があります。
型を指定せずに宣言した変数は、バリアント型になります。
LISP(VLA) では、バリアント型の変数内に入っているデータを、LISP で扱えるデータ型にして取得する `vlax-variant-value` という関数があります。
格納されている値のデータ型を取得する `vlax-variant-type` という関数もあります。
先の例でも `(setq var_val (vlax-get-property obj_rng 'Value))` で取得したセルの内容を、`(vlax-variant-value var_val)` で型固定の値に変換しています。
LISP 自体が、変数に型を指定しないため、どうしてこのような構造なのかよく分かりませんが・・・
同様に、**VBA の配列** ⇔ **LISP の LIST** を変換する関数もあります(ここでは触れません)。
## Cells プロパティでセルの値を取得
今迄のプロパティ取得の方法から、行番号、列番号を指定して、Cells プロパティで値を取る手法も、想像がついたかと思います。
```LISP:VLA を利用して、Activebook の1つ目の Worksheet の「A1」セルの値を取得(ダメな方法)
(setq
  obj_book (vlax-get-property objapp 'ActiveWorkbook) ;アクティブワークブックを取得
  obj_sheet (vlax-get-property (vlax-get-property obj_book 'Worksheets) 'Item 1) ;最初のシートを取得
  obj_rng (vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1) ;A1セルのRangeオブジェクトを取得
  var_val (vlax-get-property obj_rng 'Value) ;A1セルの値を取得
);setq
(vlax-release-object obj_book) ;使わなくなった Book オブジェクトのの開放
(vlax-release-object obj_sheet) ;使わなくなった Sheet オブジェクトのの開放
(vlax-release-object obj_rng) ;使わなくなった Range オブジェクトのの開放
(vlax-variant-value var_val) ;値がバリアント型なので、変換する
```
実は、上記方法では、<font color="Red">**上手く取得できません！**</font>
`(vlax-get-property obj_rng 'Value)` の箇所でエラーになります。
これは、`(vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1)` が返すのが、**オブジェクトを値として持つバリアント型の戻り値だから**です。
そのため、下記のように記載する必要があります。
```LISP:VLA を利用して、Activebook の1つ目の Worksheet の「A1」セルの値を取得
(setq
  obj_book (vlax-get-property objapp 'ActiveWorkbook) ;アクティブワークブックを取得
  obj_sheet (vlax-get-property (vlax-get-property obj_book 'Worksheets) 'Item 1) ;最初のシートを取得
  obj_rng (vlax-variant-value (vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1)) ;A1セルのRangeオブジェクトを取得
  var_val (vlax-get-property obj_rng 'Value) ;A1セルの値を取得
);setq
(vlax-release-object obj_book) ;使わなくなった Book オブジェクトのの開放
(vlax-release-object obj_sheet) ;使わなくなった Sheet オブジェクトのの開放
(vlax-release-object obj_rng) ;使わなくなった Range オブジェクトのの開放
(vlax-variant-value var_val) ;値がバリアント型なので、変換する
```
取得した `(vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1)` はセル範囲を示す Range オブジェクトを格納したバリアント型の戻り値です。
これを `(vlax-variant-value (vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1))` とする事で、Range オブジェクトを取得できます。
## プロパティの値を指定(セルに値を書き込む)
プロパティに値を設定するには、VBAだと下記のように記載すると思います。
```VB:セルに値を記入する
ActiveWorkbook.Worksheets(1).Cells(1, 1).Value = varVal
```
これを LISP(VLA) で実現するには、プロパティの値を取得するのに `vlax-get-property` を使用したように、プロパティ値を設定するには `vlax-put-property` を使用します。
実際に、下記の様に記載してみると・・・
```LISP:VLA を利用して、セルに値を記入する(ダメな例)
(setq
  obj_book (vlax-get-property objapp 'ActiveWorkbook) ;アクティブワークブックを取得
  obj_sheet (vlax-get-property (vlax-get-property obj_book 'Worksheets) 'Item 1) ;最初のシートを取得
  obj_rng (vlax-variant-value (vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1)) ;A1セルのRangeオブジェクトを取得
);setq
(vlax-put-property obj_rng 'Value (vlax-make-variant varVal))
;オブジェクト開放は省略
```
これでは<font color="red">**動きません**</font>。
セルに値を書き込むには、何故か `Value` プロパティは使用できず、`Value2` を使用しなければなりません。
そのため、下記のように記載する必要があります。
```LISP:VLA を利用して、セルに値を記入する
(setq
  obj_book (vlax-get-property objapp 'ActiveWorkbook) ;アクティブワークブックを取得
  obj_sheet (vlax-get-property (vlax-get-property obj_book 'Worksheets) 'Item 1) ;最初のシートを取得
  obj_rng (vlax-variant-value (vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1)) ;A1セルのRangeオブジェクトを取得
);setq
(vlax-put-property obj_rng 'Value2 (vlax-make-variant varVal))
;オブジェクト開放は省略
```
さて、ココには他にも新しく `vlax-make-variant` が使われています。
`(vlax-get-property obj_rng 'Value)` でセルの値を取得した際に、`vlax-variant-value` を使って取得したバリアント型の値を LISP で扱える値に変換したように、セルに値を書き込む際には、その値をバリアント型にする必要があります。
その為に、`(vlax-make-variant varVal)` でバリアント型の値に変換しています。
## メソッドの実行
VBA に限らず、オブジェクトに対する操作として、プロパティの取得・設定のほかに、メソッドの実行があります。
```VB:Worksheet の「A1」セルの値をクリア
obj_rng.ClearContents
```
上記の様な、obj_rng の Range 範囲をクリアするメソッドは、LISP(VLA) では下記の様になります。
```LISP:VLA を利用して、Worksheet の「A1」セルの値をクリア
(vlax-invoke-method obj_rng 'ClearContents)
```
## VLA オブジェクトの開放
変数に格納するなどして得たVLAオブジェクトは、開放しないとメモリを占有し続けます。
例えば、ワークブックオブジェクトは `(vlax-invoke-method obj_book 'Close :vlax-false)` で閉じる事が出来ますし、
例えば、アプリケーションオブジェクトは `(vlax-invoke-method objapp 'Quit)` で終了する事が出来ます。
しかし、それだけでは obj_book 変数や objapp 変数が使用していたメモリは、解放されません。
これらを解放するためには
```LISP:VLA オブジェクトの開放
(vlax-release-object obj_book)
(vlax-release-object objapp)
```
等として、メモリを解放する必要があります。
## ワークシートの最終行を得る (副題：プロパティか？メソッドか？)
obj_sheet 変数に格納されたワークシートの最終行を取得する場合、色々なやり方がありますが
```VB:Worksheet の最終行を取得
lngEndRow = obj_sheet.Cells.SpecialCells(xlCellTypeLastCell).Row
```
私は上記の様に SpecialCells を使った方法を用います。
さて、これを LISP(VLA) で記載すると・・・
```LISP:VLA を利用して、Worksheet の「A1」セルの値をクリア(ダメな例)
(setq
  obj_cells (vlax-get-property obj_sheet 'Cells)
  obj_scells (vlax-get-property obj_cells 'SpecialCells 11)
  lngEndRow (vlax-get-property obj_scells 'Row)
);setq
;オブジェクト開放は省略
```
こうなると思ったんですよ。でも<font color="red">**実際は動きません**</font>。
何故なら、`SpecialCells` は、**プロパティじゃなくてメソッド**だから。
だから、下記のようにする必要があります。
```LISP:VLA を利用して、Worksheet の「A1」セルの値をクリア
(setq
  obj_cells (vlax-get-property obj_sheet 'Cells)
  obj_scells (vlax-invoke-method obj_cells 'SpecialCells 11)
  lngEndRow (vlax-get-property obj_scells 'Row)
);setq
;オブジェクト開放は省略
```
これを見た時に、私の中で「プロパティとメソッドの区別」が良く分からなくなりました・・・