VB.NET開発者にクイズです! ちゃんと正解を出せますか?

VB.NETの開発者にクイズです。

是非とも、正解を出して下さい・・・ね。

出せなかった人は、VB.NETの基本から見直すことも必要かもしれませんよ。

 

それでは、クイズです。

先ずは下記のコードをお読みください。

Private Sub BTN_HUTU_Click(sender As Object, e As EventArgs) Handles BTN_FUTU.Click

  Dim l_SideA As Integer

  Dim l_SideB As Integer


  '■A側の変数に1000を代入
  l_SideA = 1000

  '■B側の変数にA側の値を代入
  l_SideB = l_SideA

  '■B側の変数の値を2000に変更
  l_SideB = 2000

  '■A側の変数の値を画面に表示
  MessageBox.Show("l_SideA側の値:" & l_SideA)

  '■B側の変数の値を画面に表示
  MessageBox.Show("l_SideB側の値:" & l_SideB)

End Sub

 

さて、このコードの最後に表示されるメッセージボックスに表示される値は何でしょうか?

なんて問題は、聞くまでもありませんね。

結果は、以下のように表示されます。

res_h_1

res_h_2

 

では、下記のコードの最後に表示される2つのメッセージボックスに表示される値は何でしょうか?

Private Sub BTN_OBJECT_Click(sender As Object, e As EventArgs) Handles BTN_OBJECT.Click

  Dim l_SideA As DataTable

  Dim l_SideA_AddRow As DataRow

  Dim l_SideB As DataRow


  '■A側のオブジェクト変数(DataTableの1行目)に1000を代入
  l_SideA = New DataTable
  l_SideA.Columns.Add("A", GetType(Integer))
  l_SideA_AddRow = l_SideA.NewRow
  l_SideA_AddRow("A") = 1000
  l_SideA.Rows.Add(l_SideA_AddRow)

  '■B側のオブジェクト変数にA型のオブジェクト変数を代入
  l_SideB = l_SideA.Rows(0)

  '■B側のオブジェクト変数の値を2000に変更 
  l_SideB.Item("A") = 2000

  '■A側のオブジェクト変数の値を画面に表示
  MessageBox.Show("l_SideA.Row(0)側の値:" & l_SideA.Rows(0).Item("A"))

  '■B側のオブジェクト変数の値を画面に表示
  MessageBox.Show("l_SideB側の値:" & l_SideB.Item("A"))

End Sub

 

正解を出して下さいよ~~。

(-_-)/~~~ピシー!ピシー!

【正解】はこちらになります。

res_o_1

res_o_2

 

皆さん、正しい正解をだせましたか?

もしかして「最初のメッセージボックス(l_SideA側)の値は、変更されないから初期設定のまま1000だよ。」なんて解答をした人いませんでしたか?

 

 

上記のコードのコメントを書き換えると以下のようになるわけです。

Private Sub BTN_OBJECT_Click(sender As Object, e As EventArgs) Handles BTN_OBJECT.Click

  Dim l_SideA As DataTable

  Dim l_SideA_AddRow As DataRow

  Dim l_SideB As DataRow


  '■A側のオブジェクト変数(DataTableの1行目)に1000を代入
  l_SideA = New DataTable
  l_SideA.Columns.Add("A", GetType(Integer))
  l_SideA_AddRow = l_SideA.NewRow
  l_SideA_AddRow("A") = 1000
  l_SideA.Rows.Add(l_SideA_AddRow)

  '■B側のオブジェクト変数にA型のオブジェクト変数のアドレスを代入
  l_SideB = l_SideA.Rows(0)

  '■B側のオブジェクト変数の値を2000に変更 → 同時にA側のオブジェクト変数も変更されてしまう。
  l_SideB.Item("A") = 2000

  '■A側の値を画面に表示 → B側の値に変更により、A側の値も変更されることを確認
  MessageBox.Show("l_SideA.Row(0)側の値:" & l_SideA.Rows(0).Item("A"))

  '■B側の値を画面に表示
  MessageBox.Show("l_SideB側の値:" & l_SideB.Item("A"))

End Sub

 

私見ですが、これはただのクイズで終わるような問題ではないと考えます。

VB.NETのビギナーであれば、まず間違いなく「最初のメッセージボックスの値は、1000だよ。」と解答してしまうのではないでしょうか?

無理もないかもしれません。コードを見る限りオブジェクト変数のl_SideA側を変更するような命令行を見つけることができませんから・・・

つまり、先輩プログラマが開発したソースコードをビギナープログラマーが誤って読み解いて、そのプログラムに対して誤ったメンテナンス作業を行ってしまう危険性がある考えます。

開発したシステムのライフサイクルとなる数年間(長ければ10年近く)の間、メンテナンス作業を行う度に、この問題記事のような誤った解釈によるコードが書かれることがないか心配してしまいます。

この問題記事の内容は、VB.NETを使用するすべてのプログラマーが認識し、かつ後輩プログラマに対して誤った解釈をしないように教育をしていく必要がある重要な事項と考えます。

 

もし、本記事のクイズに誤った解答を出してしまった方は、今後十分に注意して下さいね。

 

※本記事記載のVB.NETのソースコード(ソリューション一式)のダウンロードはこちらからどうぞ。

ExcelのVBAのFindメソッドで困ったエラーが発生!

ExcelのVBAでの話になります。

RangeオブジェクトのFindメソッドを使用し、指定された値を所持するセルの行位置を取得する処理を組み入れた業務APを開発される際の注意喚起になります。

実際の業務APの内容をお見せすることは出来ないため、デフォルメ化したサンプルAPで説明させていただきます。

※サンプルExcelファイルのダウンロードはこちらからどうぞ。

<サンプルAP仕様>
シート「Sheet1」上の表より、指定された値を所持するセルを探索しその行位置を結果としてメッセージ表示する。

先ずは、シート「Sheet1」の内容になります。
excel_01_00_data

 

そして、当初開発時のソースコード(VBAマクロ)

Sub 一見問題のないように見える書き方()

 Dim l_Row As Long   '探し物の行位置

 With Sheets("Sheet1")

  '■探索範囲に存在する"くじら"を探索
  l_Row = .Range("A:A").Find("くじら").Row

  MsgBox "探し物の行は[" & Format(l_Row, "#,##0") & "]です。"

 End With

End Sub

 

これを実行すると正常に処理されます。
excel_01_01_ok1

 

しかし、下記のソースコードのように探索値を変更してみたところ・・・

Sub 問題が発生します()

 Dim l_Row As Long   '探し物の行位置
 
 With Sheets("Sheet1")

  '■探索範囲に存在しない"ことり"を探索
  l_Row = .Range("A:A").Find("ことり").Row

  MsgBox "探し物の行は[" & Format(l_Row, "#,##0") & "]です。"

 End With

End Sub

 

Findメソッドの実行行で、このように結果がエラーとなります。
excel_01_02_ng1

しかも、withブロック変数の設定が原因とのこと・・・・

見てのとおりwithブロックの書き方に間違いは見られない。

しかし、エラーはwithブロック変数・・・これは困りました。

 

諸々の情報収集と検証をしたところ、原因は本当につまらないことでした。

Findメソッドで条件該当するセルを探索することが出来ないにも関わらず、Rowプロパティを要求したため「Rowプロパティを返したいけど対象のセルがありませんよ!」というエラーだったようです。

つまり、下記のソースコードのようにオブジェクトとメソッドとプロパティの3連続ピリオド結合を止めて、正しく途中経過を判定すれば良いだけだったのです。

Sub 皆様このように書きましょう()

 Dim l_Range As Range   '探し物の結果セル

 With Sheets("Sheet1")

  '■探索範囲に存在しない"ことり"を探索
  Set l_Range = .Range("A:A").Find("ことり")

  '■探索結果を判定する。
  If (Not l_Range Is Nothing) Then
   MsgBox "探し物の行は[" & Format(l_Range.Row, "#,##0") & "]です。"
  Else
   MsgBox "探し物はありませんでした。"
  End If

 End With

End Sub

 

その結果は、このとおり正常に表示されました。
excel_01_03_ok2

 

ExcelのVBAコードを記述する場合、このようにオブジェクト.メソッド.プロパティのように連続したピリオドで結合し、ソースコードの記述を楽したいと思う心情は私も理解します。

しかし、お客様へ納品する業務APとしてVBAコードを記述する際は、このような楽をすべきではありません

今回の記事では、Findメソッドを例として記載しましたが、Findメソッド以外の他のメソッドの場合も考え方は同じです。

 

今回は、このFindメソッドの原因を究明するために、本来なら出さなくてもよいあぶら汗を約1時間も出しながら苦戦することになりました。

「作れば良い」「動けば良い」ではダメなのです。

「保守しやすいか?」「エラーが発生した場合、原因究明しやすいか?」を念頭に置いて、プログラマ諸君にはVBAコードを書いていただきたいものです。

 

(検索キーワード)
オブジェクト変数または With ブロック変数が設定されていません。

VB.NETでExcel編集を行った後でExcelプロセスが消えないトラブル

先日VB.NETでExcelブックにデータを読み書きするプログラムを作っていたのですが、
その際にタスクマネージャにExcelのプロセスが残ったままになる現象が起きてはまって
しまったので備忘録を書いておこうと思います。

【環境】
・Windows 10
・Visual Studio 2013
・Microsoft Office 2013

結論から言うとExcelのオブジェクトを解放してあげればいいのですが、それにはコツがあるのです。

NG例

xlApplication.Workbooks.Item("Sheet1")

'■COMオブジェクトの解放
Call ReleaseCOMObject(xlsApp)

何がいけないかというと「xlsApp.Workbooks.Item(“Sheet1”)」の部分です。
ドット(.)が二つ以上続く操作を行う場合は、COMオブジェクトを解放してもExcelのプロセスが残ってしまいます。
この場合は一つずつ分解して変数に保持しなければいけません。

OK例(サンプルソース)

Imports Microsoft.Office.Interop

Public Shared Sub P_Sample()

'■Excel用
Dim xlApplication As Excel.Application = Nothing
Dim xlWkBooks As Excel.Workbooks = Nothing
Dim xlBook As Excel.Workbook = Nothing
Dim xlSheets As Excel.Sheets = Nothing
Dim xlWkSheet As Excel.Worksheet = Nothing
Dim xlActvSheet As Excel.Worksheet = Nothing
Dim xlRange As Excel.Range = Nothing

Try

'■Excel.Application の新しいインスタンスを生成する
xlApplication = New Excel.Application()

'■アプリケーションの非表示
xlApplication.Visible = False

'■test.xlsmを開く
xlWkBooks = xlApplication.Workbooks
xlBook = xlWkBooks.Open(“D:\test.xlsm”)

'■シートの指定
xlSheets = xlBook.Worksheets

'***********************
' [Sheet1]シート
'***********************
xlWkSheet = xlSheets.Item("Sheet1")
xlWkSheet.Activate()

'■指定エリアの値をクリア
xlRange = xlWkSheet.Range("B7:C59")
xlRange.Value = ""
'■COMオブジェクトの解放
Call ReleaseCOMObject(xlRange)

'■COMオブジェクトの解放
Call ReleaseCOMObject(xlWkSheet)

'***********************
' [Sheet2]シート
'***********************
xlWkSheet = xlSheets.Item("Sheet2")
xlWkSheet.Activate()

'■指定エリアの値をクリア
xlRange = xlWkSheet.Range("B7:C59")
xlRange.Value = ""
'■COMオブジェクトの解放
Call ReleaseCOMObject(xlRange)

'■COMオブジェクトの解放
Call ReleaseCOMObject(xlWkSheet)

'----------------------------------------
' Sheet1シートに社員番号、氏名をセット
'----------------------------------------

xlWkSheet = xlSheets.Item("Sheet1")
xlWkSheet.Activate()

xlRange = xlWkSheet.Range("C1”)
xlRange.Value = “001” ‘社員番号
'■COMオブジェクトの解放
Call ReleaseCOMObject(xlRange)

xlRange = xlWkSheet.Range("D1”)
xlRange.Value = “佐藤” ‘氏名
'■COMオブジェクトの解放
Call ReleaseCOMObject(xlRange)

'■COMオブジェクトの解放
Call ReleaseCOMObject(xlWkSheet)

'■Excelを上書保存して閉じる
xlBook.Close(True)

'■COM オブジェクトの参照カウントを解放する
Call ReleaseCOMObject(xlSheets)
Call ReleaseCOMObject(xlWkBooks)
Call ReleaseCOMObject(xlBook)

xlApplication.Quit()
Call ReleaseCOMObject(xlApplication)

Exit Sub
'**エラー処理 ******************************************
Catch ex As Exception

'■エラーメッセージとログ出力、強制終了
Messagebox.Show “処理に失敗しました。”

Finally

'■Excelを保存
If Not xlBook Is Nothing Then
xlBook.Close(True)
End If

'■COM オブジェクトの参照カウントを解放する
Call ReleaseCOMObject(xlRange)
Call ReleaseCOMObject(xlWkSheet)
Call ReleaseCOMObject(xlSheets)
Call ReleaseCOMObject(xlWkBooks)
Call ReleaseCOMObject(xlBook)

If Not xlApplication Is Nothing Then
xlApplication.Quit()
End If

Call ReleaseCOMObject(xlApplication)
End Try
End Sub

'■====================================================■
'■ COMオブジェクトの解放
'■====================================================■
Public Shared Sub ReleaseCOMObject(Of T As Class)(ByRef objCom As T)

'オブジェクトが空の場合処理を抜ける
If objCom Is Nothing Then
Return
End If

Try
'パラメタがCOMオブジェクトかチェック
If System.Runtime.InteropServices.Marshal.IsComObject(objCom) Then

'Runtime Callable Wrapperの解放を行う
Dim cntRCW As Integer = System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objCom)

If cntRCW <> 0 Then
'解放しきれていない場合、メッセージを出力
MessageBox.Show("解放エラー")
End If

End If

Finally
objCom = Nothing
End Try

End Sub

一つの操作ごとにCOMオブジェクトを解放しています。
また、最後にCOMオブジェクトを解放だけではなく、「Excel.Application」の「Quit」と
Excel.Workbook」の「Close」を必ず行って下さい。

COMオブジェクトの解放が煩わしという人には「ClosedXML」や「EPPlus」といったライブラリを
使ったExcelファイルの読み書きを行う方法もありますよ。

【VB】クラスライブラリ(.dll)の作成・使用方法② dllの配置場所を変更する

前回の続きです。
前回でクラスライブラリの作成&利用方法は備忘録として書きましたので、ここでは作成したexeファイルとdllファイルのフォルダ配置方法(インストール方法)について追加で覚え書きします。

通常、作成したexeファイルとdllファイルは同じフォルダに置かれています。~SampleProject\SampleProject\bin\Debugのフォルダを見てみると、SampleLibrary.dllとSampleProject.exeが一緒になって置かれています。

~SampleProject\SampleProject\bin\Debugのフォルダに、dllとexeが一緒に置かれている

独自に作成したクラスライブラリを使用する場合、exeを起動させるためには、作成したdllが同じフォルダに置かれてある必要があります。SampleLibrary.dllを移動なり削除なりさせると、SampleProject.exeが正常に起動しなくなってしまいます。
ここで前回の②-(6)で設定した『ローカルコピー』の設定項目について触れますが、あれは、exeの作成と同時にdllを同じフォルダにコピーするという設定です。この『ローカルコピー』がFalseだと、独自に作成したdllがコピーされず、exeが正常に起動しませんので、ここは常にTrueにしておくといいでしょう。

以上のことから、dllとexeは同じフォルダに置いておくのが基本です。ですが時には、この二つを別々のフォルダに分けたいという場合もあるかもしれません。プロジェクト内のあるファイルを設定することで、exeファイルが置かれたフォルダよりも下のフォルダからdllを読み込めるようになります。

1)exeのあるフォルダの中に、『DLL』というサブフォルダを作成し、そこにSampleLibrary.dllを移動させます。ここからdllを読み込むように設定を行ってみます。(この状態でexeを起動してみても、dllが同じフォルダにないのでエラーになります)

サブフォルダ『DLL』に、dllファイルを移動させる

2)Visual Studioのソリューションエクスプローラーで、SampleProjectの下にApp.configというファイルがあるので開きます。

App.config

3)ファイルの中身はxmlです。ここに、「DLLというサブフォルダにクラスライブラリがあるよ」という内容を書き込んであげれば、アプリケーションはその場所からdllを読み込んでくれるようになります。
App.configを、以下のように書き換えます。

――――――――――――――――――――――――――――――――――
<configuration>
<startup>
<supportedRuntime version=”v4.0″ sku=”.NETFramework,Version=v4.5″ />
</startup>

 <runtime>
<assemblyBinding xmlns=”urn:schemas-microsoft-com:asm.v1″>
<probing privatePath=”DLL”></probing>
</assemblyBinding>
</runtime>

</configuration>
――――――――――――――――――――――――――――――――――

App.configをこのように書き換える

<probing privatePath=”DLL”></probing>の部分が、dllを読み込むサブフォルダを指定するところです。この場合は、『DLL』というサブフォルダからdllを読み込む、という意味になります。
フォルダ名を;で区切ることで、複数のフォルダを指定することができます。

<probing privatePath=”DLL1;DLL2″></probing>

と書けば、DLL1とDLL2という二つのフォルダからdllを読み込みます。
また、

<probing privatePath=”DLL1/DLL2″></probing>

と “/” で区切れば、DLL1の中にあるDLL2というフォルダから読み込む、という風にサブフォルダのサブフォルダを指定することもできます。

以上の方法で、dllとexeを別々のフォルダに分けることが可能です。ただしこの方法では、必ずexeがあるフォルダより下のフォルダにdllを置く必要があります。それ以外の場所(例えばexeがあるフォルダの上)にあるdllファイルは、この方法では読み込むことができませんし、Windowsアプリケーションを作る上では推奨されないでしょう。
独自作成したdllは、exeと同じフォルダか、そのサブフォルダ(先述のApp.Configを使用)から読み込むようにしましょう。

2013年12月26日 | カテゴリー : VB.NET開発 | 投稿者 : youchin

【VB】クラスライブラリ(.dll)の作成・使用方法

このところ雑記ばかりが続きましたので、久し振りに開発関係の備忘録をば。
汎用可能な関数群をクラスライブラリ(.dll)化しておくと、他のプロジェクト(他のプログラム)からでも手軽に関数を参照利用することができるようになります。プロジェクトごとにいちいち関数を記述する必要がなくなるので、プログラミングの労力を軽減したり、コードを簡略化する効果などが期待できます。
今回はVisual Studio(VB)にてこのクラスライブラリを作成・利用する方法を検証しましたので、備忘録としてまとめておきます。

環境:Windows7, Visual Studio Express 2012Visual Basic )

――――――――――――――――
①クラスライブラリ(.dll)の作成
――――――――――――――――

1)Visual Studioのメニューの『ファイル』→『新しいプロジェクト』から、『クラスライブラリ』というテンプレートを選択して、『SampleLibrary』の名前でプロジェクトを作ります。(プロジェクト名はあくまサンプルなので、自由なものでOK)

クラスライブラリ用のプロジェクトを作成

2)SampleLibraryの下にあるClass1.vbというvbファイルに、クラスライブラリ化する関数を記述していきます。(以後の話をわかりやすくするため、ここではClass1.vbをCommon.vbとリネームしておきます)

『SampleLibrary』プロジェクトの下に、『Class1.vb』ができている

3)クラスライブラリ化するコードの記述します。ここでは文字列を返すだけの単純なものとします。
――――――――――――――――――――――――――――――――――――――――
Function returnString() As String
Return “クラスライブラリ化のサンプルプログラムです。”
End Function
――――――――――――――――――――――――――――――――――――――――

サンプルプログラムを作成

4)メニューの『ビルド』→『ソリューションのビルド』を選択し、ビルドを行う。エラーがなければ、クラスライブラリ化(dllファイルの作成)は無事完了です。
エクスプローラーでプロジェクトを確認してみると、~SampleLibrary\SampleLibrary\bin\Debugのフォルダに、『SampleLibrary.dll』というファイルができあがっていると思います。これが、クラスライブラリです。

――――――――――――――――――――――――――――――
②作成したクラスライブラリを、他のプロジェクトから利用する
――――――――――――――――――――――――――――――

1)クラスライブラリを利用するプロジェクトを作成します。テンプレートを『コンソールアプリケーション』にして、『SampleProject』という名前でプロジェクトを作成。(プロジェクト名はあくまでサンプルなので以下略)

クラスライブラリを利用するプロジェクトを作成

2)今度はプロジェクト下にModule1.vbというファイルが作られるので、SampleProject.vbとリネームしておきます(まあサンプルなのでここまでやる必要はないのですが、名前は統一されていた方が気分がいいですよね……^^;)

3)ソリューションエクスプローラーにあるボタンから、『すべてのファイルを表示』というボタンをクリックします。すると『参照設定』という項目が表示されるので、ここに「さっき作ったSampleLibrary.dllを参照するよ」という設定を加えてやれば、晴れて先ほど作成したクラスライブラリを自由に利用できるようになります。

『すべてのファイルを表示』をクリック『参照設定』という項目が表示されるようになる

4)『参照設定』を右クリックし、『参照の追加』を選択します。なにやらたくさん項目が出てきますが、今回は関係ないので、とりあえず右下にある『参照』ボタンをクリック。するとファイルを選択するダイアログが出てきますので、~SampleLibrary\SampleLibrary\bin\Debugのフォルダにあるdllファイルを選択します。

『参照』ボタンをクリックし、作成したdllファイルを選択

5)『ブラウズ』の『最近使用したファイル』のところに、選択したdllファイルが表示されます。チェックボックスにチェックが入っているのを確認して、『OK』を押せば、参照設定のところに『SampleLibrary』が追加されます。

選択したdllファイルが表示される『参照設定』にdllファイルが追加される

6)追加されたSampleLibraryのプロパティで、『ローカルコピー』という項目がTrueになっているかどうか確認しておきましょう。あとで説明しますが、独自に作成したクラスライブラリを使用するために必要な設定です。

『ローカルコピー』がTrueになっていることを確認しておく

さてここまで済みましたら、晴れてクラスライブラリ化した関数を使用できます!
使用法は、VBで他の関数を使う時となんら変わりません。①では、SampleLibraryのプロジェクトの下にCommonというクラスを作成し、そこに関数を記述しました。なので、まずはSampleLibrary.Commonのオブジェクトを作成。そしてオブジェクトを通して関数呼び出し。これだけです。
それでは図のような簡単なプログラムを作成しまして、『開始』ボタンをポチッと。

クラスライブラリを利用するサンプルプログラム

――――――――――――――――――――――――
Module SampleProject
Sub Main()
Dim obj As New SampleLibrary.Common
Console.WriteLine(obj.returnString())
System.Threading.Thread.Sleep(3000)
End Sub
End Module
――――――――――――――――――――――――

クラスライブラリ内の関数から受け取った文字列が、コンソールに3秒間だけ表示されます。クラスライブラリの関数を使用できていることが確認できます。

①-(3)で作成した関数から返された文字列が、コンソールに表示される

ちょっと長くなってきましたので、今回はひとまずここまで。
次回、これにより~SampleProject\SampleProject\bin\Debugのフォルダに作成されたexeファイルとdllファイルのフォルダ配置方法(インストール方法)について、改めて続きを書きたいと思います。
②-(6)で触れた『ローカルコピー』についても、その時に……ということで。
それでは。

2013年12月24日 | カテゴリー : VB.NET開発 | 投稿者 : youchin