困ったとき | Excel作業をVBAで効率化 https://vbabeginner.net いつものExcel作業はVBAを使えば数秒で終わるかもしれませんよ Sat, 09 Nov 2024 13:34:19 +0000 ja hourly 1 https://wordpress.org/?v=6.6.2 https://vbabeginner.net/wp-content/uploads/2019/02/favicon-150x150.png 困ったとき | Excel作業をVBAで効率化 https://vbabeginner.net 32 32 VBA画面で標準モジュールが開かない場合 https://vbabeginner.net/module-does-not-open/ Sat, 09 Dec 2023 06:41:07 +0000 https://vbabeginner.net/?p=6890 ダブルクリックしても標準モジュールが開かない

VBAのソースコードを見る場合、VBAの画面の左側にある標準モジュールやクラスモジュールをダブルクリックや上のコード表示ボタンを押して開きます。

ところが、ブックが壊れている場合には標準モジュールをダブルクリックしてもコードが表示されません。右クリック→エクスポートの操作でもエクスポートされません。ちゃんと操作しているのにです。他のブックは標準モジュールが開くのに、このブックだけ開きません、なんてことが起きます。

原因はマクロ部分が壊れているためのようですが、何が壊れているのかは分かりません。けどどうにか復旧させたいです。

ここでの内容は私が実際に壊れたVBAありのブックを復旧させたときのメモです。試行錯誤したときのものをまとめました。

以下が手順概要です。
1. Macで開いて別名で保存する。
2. Windowsで開いて、開けるなら標準モジュールをエクスポートする。
3. エクスポートしたファイルの文字化け部分をエディタで直す。
4. 修正した標準モジュールをインポートしなおして保存する。

ブックの状況を確認する

ブック自体が保存できるかをまず確認します。どこかのセルに「aaa」とか入力して保存をすると、以下のダイアログでブックが壊れていることが分かります。

このダイアログが出たときは、大体復旧できません。

何度か続行すると以下のダイアログが出て、xlsbファイルが保存されることがあります。

xlsbファイルにはシート部分の入力データや背景色ぐらいが残っている程度で、VBA関連は一切保存されていません。

というわけで、やはり復旧されません。

Macで開けるならMacで保存しなおすWindowsで開けるようになる(開けるけどまだ直ってない)

ただ、理由は分かりませんが、Windowsで「破損しています」と表示されるブックでも、Macで開くと普通に開けることがよくあります。そこで、Macで開き、どこかのセルに「aaa」とか入力して別名で保存します。上書き保存だと本当に壊れたときに怖いので別名にします。

この別名のブックをWindowsで開くとなぜか開けます。標準モジュールなども表示できるようになります。

これで復旧は「大体」完了です。

しかし直ってない箇所があります。それはWindows特有の文字をMacでは正しく解釈できないという問題です。

なお、Macが用意できない場合は、WindowsPCにLinuxのUbuntuをデュアルブート(1のPCに複数のOSをインストールしている状態)して、UbuntuでLibreOfficeやFreeOfficeを使うと同じような修復作業が行えるかもしれません。試してません。

Macでの保存で円マーク部分の文字コードが変わってしまう

VBAで一番多いWindowsとMac間での文字化けの影響が大きいのが円マークです。

これですね。

このWindowsのパス区切り文字の円マークですが、Macでのブック保存時に文字コードが変わってしまいます。

Windowsの円マークは10進数=92、16進数=0x5cで、Macで変わったあとは10進数で128、16進数で0x80になります。0x80は貨幣ユーロの記号として使われることがあります。Windowsでは元の円マークの0x5cはバックスラッシュと同じ文字コードのため、今回と同様にシステム開発でよく問題になります。

ただ、そのあたりはどうでもいいので、とにかく変わっちゃったものは仕方ありません。いい感じに修正するツールがあるのかもしれませんが、ここでは自力で直す方法を採ります。

Macで変換された0x80文字はVBAの画面では表示上は空文字として扱われます。実際には1バイト0x80があるのに何も入力されていないように見えます。

こんな感じです。本来はダブルクォーテーションの間に円マークがあるのですが、0x80になっているため見えません。

本来は以下が正しい状態です。ちなみに、上のようになっている箇所を行ごと一度削除して、円マークを書き直せば直ります。

標準モジュールやクラスモジュールをエクスポートしてテキストファイル化する

上の絵のように、VBAの画面ではどこに0x80があるのかを探すことが出来ないため、標準モジュールやクラスモジュールを全てエクスポートしてテキストファイルとして見れるようにします。

エクスポートはVBAの画面で標準モジュールやクラスモジュールを右クリックしてエクスポートを選択して、保存先を選べばそこにbasファイルやclsファイルが出力されます。

1ファイルずつエクスポートするのが面倒な場合は「標準モジュール等の一括エクスポート」をご参照ください。

テキストエディタで0x80を円マークに置換する

エクスポートした標準モジュールは.bas、クラスモジュールは.clsの拡張子が付いています。

これらのファイルをバイナリエディタで開きます。ここではImHexを使います。ImHexはフリーの高機能なバイナリエディタです。海外のアプリですが言語設定で日本語対応しています。

ImHexリンク:https://imhex.werwolv.net/

バイナリエディタで元の円マークがあった0x80を16進数で検索します。ImHexの場合は以下のように、左右に16進数とASCII文字が表示されます。

あとは0x80の部分を0x92(Windowsの円マーク)に置換します。

数が多くて大変な場合はテキストエディタで置換しても構いません。ここではサクラエディタを使います。サクラエディタはフリーの高機能エディタです。

サクラエディタのリンク:https://sakura-editor.github.io/

サクラエディタで開いて元々円マークだった場所を調べます。そうすると1文字の点で表示されている箇所があります。

この点の部分を円マークに置換します。

置換後はこんな感じになります。

バイナリエディタで置換しても構いません。

この置換作業をエクスポートした全てのファイルに対して行います。

編集後のbasファイルとclsファイルをインポートしなおす

Macで修復したブックを開き、VBAの画面で標準モジュールとクラスモジュールを全て解放し、サクラエディタで修正したbasファイルとclsファイルをインポートします。

全てのインポートが終わったら保存します。

これで修復完了です。

]]>
マクロの実行をブロックしています表示の対応方法 https://vbabeginner.net/blocking-macro-execution/ Sun, 04 Sep 2022 17:17:35 +0000 https://vbabeginner.net/?p=6508 ダウンロードしたExcelのマクロが動かなくなったら

2022年8月のWindows Updateで、ダウンロードしたExcelなどのマクロが動作しないようになりました。

ブックを開くとこのようなメッセージが表示されることがあります。

「セキュリティ リスク このファイルのソースが信頼できないため、Microsoft によりマクロの実行がブロックされました。 詳細を表示」

これが表示されている場合、そのブックのマクロは動きません。「詳細を表示」ボタンを押しても動きません。動かしたい場合は一度ブックを閉じて、以下に紹介する対応をしてから再度開きなおす必要があります。

ブックをコピペして別ファイルを作ったりしても状況は変わりません。

以下で解決方法を4つ紹介します。

原因:ダウンロードしたことを示す情報が付く → 削除しないといけない

WindowsはインターネットなどのネットワークにあるExcelマクロブック(*.xlsm)をダウンロードした際に、元のファイルに特殊な隠し情報を加えた状態でダウンロードします。

この隠し情報は「Mark of the Web」と呼ばれます。でもこの名前は忘れていいです。

この特殊な隠し情報は「インターネットなどからダウンロードしたから開くと危険かもよ?」ということを示しています。Excelでブックを開いた際に隠し情報があると「マクロが危険」と判定され、それによってマクロの実行がブロックされます

そのため、この隠し情報を削除してしまえば、マクロは実行できるようになります

削除する方法は2通りあり、ファイルのプロパティを変更する簡単な方法と、Powershellを使って変更するちょっと手を動かしてやる方法があります。

いずれの方法も、ダウンロードした度に実施する必要があるため、ダウンロードする頻度が多い場合は面倒に感じると思います。

解決方法 1. ファイルのプロパティを変更して隠し情報を削除する方法

この方法は一番わかりやすい方法です。

ダウンロードしたファイルをエクスプローラで右クリックしてプロパティを開き、「全般」タブの一番下にある「セキュリティ」の「許可する」のチェックを付けて、OKボタンを押します。これでマクロが使えるようになります。

一度チェックを付けて適用 or OKしたあとは、ダウンロードしたことの隠し情報は削除されるため、プロパティを開いてもこの「セキュリティ」欄自体が無くなるため、チェックを外すことはできません。

先にも書いた通り、この方法はダウンロードしたファイルごとに対応が必要な方法です。

解決方法 2. Powershellで隠し情報を削除する方法

ダウンロードする頻度が多いのであれば以下の方法が一番簡単です。

一度ps1ファイルとbatファイルを用意してしまえば、あとはずっとそれにブックをドラッグするだけの方法です。

ダウンロードしたファイルが多い場合、1つずつプロパティを設定するのは面倒です。多数ある場合は以下のPowershellファイルとbatファイルを用意するとラクに隠し情報を削除できます。

まず、Powershellのファイルを作成します。ファイル名はなんでもいいのですが、ここではunblock.ps1とします。拡張子ps1はPowershellファイルであることを意味します。

1. 新規ファイルとしてunblock.ps1を作成し、メモ帳などのテキストエディタで以下を書いて保存します。

unblock.ps1

foreach ($arg in $args)
{
    Unblock-File -Path $arg
}

 

2. 次に、新規ファイルとしてunblock.batを作成して、上と同様にテキストエディタで以下を書いて保存します。

unblock.bat

powershell.exe -ExecutionPolicy RemoteSigned -File unblock.ps1 %*

 

作成するものは以上です。

あとは、ダウンロードしたブックがあるフォルダにunblock.ps1とunblock.batを置いて、ダウンロードしたブックをunblock.batにドラッグすれば解除されます。複数ブックをドラッグ可能です

解除済みのブックをドラッグしても問題ありません。何も変わらないだけです。

一応ですが、unblock.ps1とunblock.batが何をやっているかというと、ダウンロード時に付けられる隠し情報はPowershellの「Unblock-File」というコマンドに対象のブックを引数で渡すことで削除できるようになっています。

ただ、Powershellのps1ファイルにはドラッグしても処理を行えないため、batファイルを用意してそちらにブックをドラッグして、batファイルからps1ファイルにドラッグされたブックを渡すようにしています。

使い方の見本は以下の通りです。

1. unblock.ps1とunblock.batをブックと同じフォルダに置きます。

2. 隠し情報を解除したいブックを選択します。複数選択可。

3. 選択したブックをunblock.batにドラッグします。これ以降はマクロの実行が可能になります。

解決方法 3. USBメモリにダウンロードして隠し情報が付かないようにする

この方法はプロパティを変更したり設定を変えたりする必要がなく、準備としてUSBメモリを挿しておくだけです。設定は何も変える必要がありませんPCの利用状況(USBメモリを挿しっぱなしの場合とか)によっては一番簡単な方法になります。

マクロの実行がブロックされるのは、ダウンロードする際にWindowsがファイルに隠し情報の「Mark of the Web」が付与されていることが原因ですが、この隠し情報はファイルシステムがNTFSのADSという仕組みを利用しているため付与自体がNTFSに限定されるため、FAT32の場合には付与されません。

FAT32はWindowsの上ではほとんど見かけなくなりましたが、USBメモリなどの外部記憶装置では主流です。

そこでダウンロード先をUSBメモリにすると、ファイルに隠し情報の「Mark of the Web」が付与されず、マクロの実行は可能になります。

USBメモリにダウンロードしたファイルをPCに移動やコピーしてもマクロは実行できます

解決方法 4. 指定フォルダを安全である場所として設定する

この方法は、指定フォルダに置いてあるブックは安全である、という設定をExcelで行いますあまりお勧めしません

Excelには、このフォルダにあるブックは信頼があるとみなす、という設定を行うことが出来ます。「信頼できる場所」という言い方をします。

極端な方法ですが、ダウンロード先が「ダウンロード」フォルダの場合は、C:\Users\[ユーザー名]\Downloads\ フォルダを設定しておくと、ダウンロードしたブックは安全と見なされ隠し情報である「Mark of the Web」が付いていてもそれを無視するため、マクロは実行可能です。

ただ、セキュリティ対策ソフトを未導入の状態でダウンロードフォルダを信頼するのは危険です。

設定方法は以下の通りです。

1. ファイルメニュー→Excelのオプションを開きます。(ファイルメニューを開いて、一番左下に「オプション」とあるのでそれをクリック)

2. 左のトラストセンターを選択し、「トラストセンターの設定」ボタンを押します。

3. 左から「信頼できる場所」を選択し、右下の「新しい場所の追加」をクリックします。

4. 「パス」にフォルダパスを設定します。必要であれば「この場所のサブフォルダーも信頼する」にチェックを付けます。このフォルダのみを信頼する場合にはチェックは付けません。

この「信頼できる場所」の設定には注意点があります

それは、ここで設定したフォルダにあるブックのマクロは実行できますが、ダウンロードしたときの隠し情報である「Mark of the Web」は付与されたままになっているという点です。

「信頼できる場所」に設定されたフォルダでブックを開いた場合は、隠し情報の「Mark of the Web」を無視しているだけで、インターネットなどからダウンロードしたブックである、という情報は残ったままになっています。

そのため、「信頼された場所」のフォルダから別のフォルダに移動やコピーすると、隠し情報の「Mark of the Web」は残ったままのためマクロの実行が出来なくなります

マクロを人に渡したりするつもりであれば、隠し情報は上記の方法で削除しておいた方がよいでしょう。

その他の方法はお勧めしません

上に挙げた方法以外にも、ネットワーク関連の設定をいじってマクロを実行できるようにする方法が複数あり、それらで対応が可能になることはありますが、いずれもお勧めしません

Excelブックにデジタル証明書を付ける方法、hostsファイルを編集する方法、インターネットプロパティでセキュリティレベルを変える方法、などがありますが、いずれも詳しくない人にとっては自分がどういう設定しているのかを理解できないため、VBA以前にPC全体のセキュリティを弱めることになりかねません。

中途半端に知識を入れて設定するぐらいなら、むしろ知らない方が安全です。

例えば上に挙げたデジタル証明書ですが、会社などでデジタル証明書で運用しようとしても、「面倒になってしなくなる」「嫌がられる」「秘密鍵って何?」「難しくてやってられん」「もっと簡単な方法ないの?」などの反応があるのが当たり前で、結局続けられないことが多いでしょう。

どうしてもそれらの設定をしたい方は別サイトを探してみてください。安全のためにここではあえて紹介しません。

Microsoftのヘルプが分かりにくい?

マクロが動かなくなった原因は、Microsoftによるセキュリティの強化、ではありますが、今まで出来ていたことが出来なくなるというのはほとんどの人からすると嫌がらせと感じるでしょう。

対応方法が書いてあるMicrosoftのヘルプページはあることにはありますが、相変わらずヘルプの書き方が超絶下手です。読みにくかったり意味が分からず読むのを諦めた人はたくさんいるでしょう。英文のページも読みにくさは同様です。

URLは一応載せておきます。
https://docs.microsoft.com/ja-jp/deployoffice/security/internet-macros-blocked

このページを作ったのも、公式サイトが読みにくくて仕方がないのがきっかけです。

解決しない方法

上に挙げた解決方法の他にも、「これなら解決できるのでは?」と思いつく物がいくつかあると思います。

しかし、以下の対応はいずれも解決できません。

  • ダウンロードしたファイルをコピーしたりファイル名を変えても、ブロックされたままです。
  • ファイルメニュー→オプション→Excelのオプションで「トラストセンター」→「トラストセンターの設定」ボタン→マクロの設定→「VBAマクロを有効にする」 に設定してもブロックされたままです。
  • PC再起動しても、ブロックされたままです。

いずれの場合も、隠し情報の「Mark of the Web」が残っていればマクロは実行できません。

]]>
VBAの関数名や変数名に日本語を使ってよい https://vbabeginner.net/japanese-code/ Wed, 15 Dec 2021 13:58:13 +0000 https://vbabeginner.net/?p=6411 日本語の関数や変数? 自分しか使わないならむしろオススメ

一般的なプログラミング言語はコードを英字で書きますが、VBAは関数名や変数名に日本語を使うことが出来ます。先に書いてしまいますが、日本語でのコーディングは初心者だけでなくむしろ熟練プログラマーに対してもオススメします。

以下は日本語を使ったコードです。引数文字列を1文字ずつイミディエイトウィンドウに出力する関数です。CallTest関数を実行することで1つ目の日本語の関数を呼び出します。コードを見ると、「引数1文字分」という名前の変数が出力されているので、「ループしながら、なんか1文字ずつ出してんのかな?」とぱっと見ただけでも当たりを付けることができます

Sub 日本語の関数例(引数文字列 As String)
    Dim ループカウンター    As Integer
    Dim 文字数              As Integer
   
    文字数 = Len(引数文字列)
    
    For ループカウンター = 1 To 文字数
        Dim 引数1文字分     As String
        
        引数1文字分 = Mid(引数文字列, ループカウンター, 1)
        
        Debug.Print 引数1文字分
    Next
End Sub

Sub CallTest()
    Dim s   As String
    
    s = "ABC123あいう"
    
    Call 日本語の関数例(s)
End Sub

このようにVBAでは日本語を使うことは出来ますが、書籍やネットでのVBAのコードで日本語での関数名や変数名を見ることはほとんどありません。

その理由の一番大きい理由は、日本語を理解できない人には伝わらないためです。英語圏の人に日本語のコードを見てもらうのは酷でしょう。また、日本語の変換入力が面倒だったり、文字コードの問題に遭遇する確率が高くなるのも日本語が避けられる理由として挙げられます。

当サイトでも同じ理由で英語を主体でコードを公開しています。

ただ、自分しか使わないようなコードであれば日本語で書くことは避ける必要はありません。むしろ書くことをオススメします

実際に日本語で書いてみると、利点が見えてきます。

日本語コードと英語コードの比較

上の1つ目の日本語の関数を英訳で書き直すと以下のようになります。

Sub JapaneseFunctionExample(argumentString As String)
     Dim loopCounter As Integer
     Dim NumberOfCharacters As Integer
   
     NumberOfCharacters = Len(argumentString)
    
     For loopCounter = 1 To NumberOfCharacters
         Dim Argument1Character As String
        
         Argument1Character = Mid(argumentString, loopCounter, 1)
        
         Debug.Print Argument1Character
     Next
End Sub

さらに、短縮変数名にすると以下のようになります。当サイトのコードはこんな感じですね。

Sub JapaneseFunctionExample2(a As String)
     Dim i      As Integer
     Dim iLen   As Integer
     
     iLen = Len(a)
     
     For i = 1 To iLen
         Dim c As String
         
         c = Mid(a, i, 1)
         
         Debug.Print c
     Next
End Sub

ぱっと見た感じで、コードが少なくなっているのは分かりますが、処理内容の分かりやすさが上がるかというと、あまりそういうことはないと思われます。むしろ日本語のコメントが欲しくなってきます。

少なくとも、日本語のコードのように変数名から1文字出力しているなー、なんてことは一目では分かりません。ある程度コードを追わないとそこまでは理解できないでしょう。

日本語コードの利点

日本語を扱うことの利点は、「母国語だから読み書きに慣れている」という点です。

アラビア語やスワヒリ語のコードを渡されても困りますよね。でもそれを母国語とする人にとっては読みやすいはずです。日本人にとっての日本語も同じです。

そして、漢字とひらがなの組み合わせは文章をすばやく読める、という利点があります。

アルファベットだけの変数や関数を作る場合、複数の単語を組み合わせて名前を付けることが多いですが、GetSystemData()とかget_system_data()のように単語の区切りを大文字やアンダーバーで見やすさを考慮しなければなりません。しかし、漢字とひらがなはそんな書き方をする必要がありません。

プログラミング言語はどうしても英語を母国語として設計されているため、それに合わせる必要がありますが、自分しか使わないコードであれば自分が理解しやすいようにするのは何も問題ありません

変数を日本語で書けばコメントが不要になる

プログラミングのセオリーが書いてある良書として、書籍「リーダブルコード」や書籍「達人プログラマー」などがあります。

これらの良書には必ずと言っていいほど、以下が挙げられています。

  • コメントは適切に書くこと。

「コメントは適切に書く」というのはいろんな書籍で指摘されていることですが、実はこれに対してぴんとこない人がいます。

どういう意味かというと、「コードを見て分かることをいちいちコメントで書くのはやめなさい」、という話ですが、日本人には伝わりにくいのです。それはなぜでしょうか。実は、これは書籍の筆者が英語圏の人であることが関係しています。

以下のような変数宣言があったとします。1つ目と2つ目は日本語のコメントがあると助けになりますが、3つ目の日本語変数「ループカウンタ」に対して「ループカウンタ」というコメントは不要でしょう。書籍で言っているのはこのことです。日本語で書くとすぐに理解できます。

Dim i               As Integer  '// ループカウンタ
    Dim loopCounter     As Integer  '// ループカウンタ
    Dim ループカウンタ  As Integer  '// ループカウンタ

英語圏の場合は英語のコードが当たり前のため、「(英語で書かれた)変数名を見れば分かるから同じことをいちいち(英語の)コメントは書かなくていい」、という趣旨で書いてあるのですが、日本人は「コードは英語」で「コメントは日本語」を書くため書籍の内容が理解しにくいのです。

ところが、変数自体が日本語になると無駄なコメントが省略できるようになります。これは言い換えると、和訳作業が不要もしくは軽減されるということになります。

このように、日本語を使うことでコードの理解がしやすくなる利点があります。仕事で作成するコードの場合は「日本語はダメ」との制約があるかもしれませんが、自分しか使わないマクロであれば日本語でのコードを自由に作成してもよいと思います。

]]>
「コードの実行が中断されました」の解決方法 https://vbabeginner.net/code-execution-has-been-interrupted/ Wed, 27 Jan 2021 15:05:20 +0000 https://vbabeginner.net/?p=5136 ブレイクポイントがない場所で止まる

VBAを実行すると、ブレイクポイントを貼ってないのになぜか「コードの実行が中断されました」とメッセージボックスが表示され、「デバッグ」ボタンを押すとVBAの画面でコードが止まっている箇所が表示されます。

ところが、表示されているVBAのコードを見ても、ブレイクポイントがありません。ブレイクポイントが無いのにコードが中断しているのです。

「は?」と思い、再度実行しても、また同じ場所で止まります。「いやいや、ブレイクポイント無いんですけど。なんで止まっちゃうの?」と不思議な事象を見ることになります。

この事象の原因は、過去の自分が貼ったブレイクポイントが内部的に残っているためです。通常、ブレイクポイントはデバッグ中に止めたい場所に設定します。そして、止める必要がなくなれば解除します。これが普通の使い方です。

しかし、「コードの実行が中断されました」のメッセージボックスが表示された場合は、見た目はブレイクポイントは見えないのにVBAの内部情報としてはブレイクポイントとして保持されたままになっており、「非表示のブレイクポイント」とも言うような状況に陥っています。

解決方法は「非表示のブレイクポイント」を消せばいい、ということになります。

解決方法

解決方法は以下の通りです。

  1. 「コードの実行が中断されました」のメッセージボックスの「デバッグ」ボタンを押します。
  2. VBAの画面がアクティブになり、ブレイクポイントがない行が表示されます。
  3. VBAの画面がアクティブの状態で、Ctrl キー + Pause/Break キーを2回(2回でまた止まる場合は1回)押します。見た目上は何も起きません。
  4. 実行ボタンを押して続行させるか、リセット(停止)ボタンを押して処理を止めます。

これで直るはずです。PCやExcelの再起動は必要ありません。

これでも直らない場合はPCの再起動をしてください。

発生原因

この事象が発生する原因は、Ctrlキー + Pause/Break キーで強制的に中断しているためです。

上にも書いていますが、ブレイクポイントの情報は見た目のブレイクポイントのマーク●の情報と、内部的にどこにブレイクポイントが設定されているか、という情報があります。通常のデバッグ時にブレイクポイントの設定と解除というVBA画面上での操作であれば、見た目情報と内部情報が一致しますが、Ctrlキー + Pause/Break キーで強制的に中断すると、見た目情報と内部情報に乖離が起きることがあります。

ただ、Ctrlキー + Pause/Break キーを押すと必ずこの事象が発生するわけではありません。

一般的に、Ctrlキー + Pause/Break キーを押すのは、なんらかの無限ループやそれに類する長い処理を止めたい場合に使います。言い方を変えれば、普通じゃない状態のときの緊急避難としての操作です。この操作はVBAの処理に対して割り込みを行うための操作で、「無理やりでいいからVBAを止めろ」という要求になります。

しかし、そんなことを言われても、Windowsがその割り込みを許可できない場合や、EscキーやCtrlキー + Pause/Break キーを押しながらタスクバーをいろいろクリックしまくって無理やり止める(タスク切り替えや処理停止のメッセージを連続で投げまくる)、なんてことをすると、VBA側で割り込みメッセージを適切に処理できず、本件の事象が発生します。

Windowsのイベント処理はWindowsプログラミングの中でも面倒かつ複雑な部類で、この事象は古いExcelでも発生していたため、多分ですがVBAの既知のバグです。まれにしか起きないのと修正が大変なので放置されているのかもしれません。

やらない方がいいこと

Application.EnableCancelKey というプロパティがあります。これは、外部のキー操作による割り込みをどのように扱うかを指定するプロパティです。

「Application.EnableCancelKey = xlDisabled」 と書くと、EscキーやCtrlキー + Pause/Breakキーを押された場合に、VBAの処理を中断しなくなります。なので、本件の事象の根本原因である「Ctrl + Pause/Breakで非表示のブレイクポイントが発生する」ということがなくなります。

ただ、このプロパティを使うことは本末転倒で、むしろ、マクロ利用者による強制割り込みを拒否したいなどのよほどの意図的な目的がない限りは使わない方がいいです。

「もしかしたらCtrl + Pause/Breakキーを押すと、コードの実行を中断しました、と出るかもしれない。そのときは見えないブレイクポイントが発生してしまい、VBAを実行する度に止まってしまう。なので、そうならないようにCtrl + Pause/Breakキーは処理しないようにしよう」という考え方は目的を誤ってます。

なので、本件事象の解決のために「Application.EnableCancelKey = xlDisabled」を書くのは目的を間違ってますのでやめましょう。

]]>
VBAのクラス名やオブジェクト名の大文字小文字が壊れた場合 https://vbabeginner.net/classname-objectname-corrupted/ Fri, 13 Nov 2020 17:25:21 +0000 https://vbabeginner.net/?p=4866 いつのまにかRangeがrangeやRANGEに?

ExcelでのVBAで間違いなく一番使われるクラスと言えば、Rangeクラスです。

こんな書き方をして使います。

Sub TestSub()
    Dim r   As Range
    
    Set r = Range("A1")
End Sub

ところがあるとき急に、「あれ? つづりがへんじゃね?」と気が付くことがあります。

こんな感じ。

Dim r   As range

Rangeが小文字の「range」になっていたりすることが。それとは逆に大文字になることもあります

Dim r   As RANGE

よく見てみると、その変数だけでなくそのブックのすべてのコードのRangeクラスの大文字小文字が壊れてしまっています。以下はすべて小文字になった例です。

Sub TestSub()
    Dim r   As range
    
    Set r = range("A1")
End Sub

Sub TestSub2()
    Dim r   As range
End Sub

この現象はRangeクラスに限らず、どのクラスでも発生します。FileSystemObjectクラスが小文字のfilesystemobject、Fileクラスがfile、RegExpクラスがregexpのようになることがあります。

なぜこんなことになるのでしょうか?

クラス名が壊れる原因は?

このようにクラス名が壊れる原因は、そのブックのどこかの変数定義や引数定義で、以下のようにクラス名と同じ名前で変数名に小文字で「range」と書いた場所があるためです。

以下のようなコードですね。変数名を小文字でrangeと書いてしまい、それに釣られてクラス名も小文字になってしまいます。

Dim range   As range

小文字ではなく大文字に壊れることもあります。

Dim RANGE   As RANGE

他には関数の引数定義の場合もあります。

Sub TestSub3(RANGE As RANGE)

あとは、これをどうやって直すか、です。

そもそも、変数名にクラス名と同じ名前で大文字と小文字を変えたからといって、それに引きずられてクラス名の大文字小文字が変わってしまうVBAの仕様というかバグがダメなんですが、こういうのはVBAあるあるで、もうこれはどうしようもないので、以下の直し方で直すしかありません。

壊れたクラス名の直し方

壊れたクラス名を直すには2段階必要になります。

  1. クラス名を付けた変数定義で、変数名を正しいクラス名で書き直します。変数名を直すことでクラス名も一緒に直ります。
    (変数定義が「Dim range As range」であれば「Dim Range」に書き直します。これで変数名が修正されたと同時にクラス名のrangeがRangeに直り、「Dim Range As Range」となります。)
  2. 変数名にクラス名を付けたままだと変数名とクラス名を混同するため、それを避けるため、変数名を変更します
    (「Dim Range As Range」を「Dim r As Range」などに書き直します。それ以降のコードで「Range.Value」などで利用している箇所があればすべて変更後の変数名「r.Value」などに書き直します。)

この2段階の順序を踏まずに、1をすっとばして2のいきなり変数名をクラス名とは違う名前にしても、クラス名は壊れたままになります。また、変数名を直さずにクラス名を正しく書き直しても直らずに元の間違った記述に戻ってしまいます。

誤って付けた変数名を正しいクラス名で書き直して、それから、変数名をクラス名とは違う名前にしないと、正しく直りません。

クラス名が壊れる範囲

なお、クラス名が壊れる範囲ですが、ブックに含まれる標準モジュールやシートやクラスモジュールなどが対象になります。

そのため、例えばBook1.xlsmでRangeクラスが「range」と小文字になっていても、ブックとしては別である個人用マクロブック(Personal.xlsb)や別のブックでは正しく「Range」と表示されます。

このことから、クラス名が壊れた場合は、そのブックの中でそのクラス名を検索すればよいため、VBAの画面で検索画面を開き、対象欄で「カレント プロジェクト」のラジオボタンを付けて検索すればおかしくなった原因の変数定義か引数が見つかります。

変数名にクラス名と同じ名前を付けちゃダメ

そもそもの原因は、変数名にクラス名を付けようとしていることが原因です。

それで動作するので問題はないように感じますが、そのような勘違いを起こすようなコードは書くべきではありません。

本件の問題を起こすのはJava言語をメインに使っている方が多いのではないかと思っています。あくまでも推測ですが。

その理由ですが、Java言語の場合は「クラス名は大文字で始まり、変数名は小文字で始まる」との命名規則を設けていることが結構あります。結構あるというよりもほとんど業界標準に近い扱いです。そして、VBAで言う変数名にはクラス名を小文字で始まる形でそのまま使うことが多々あります。

これはVBAと異なりJava言語で変数を扱う場合はプリミティブ型(intとかcharとかの単純な型)かクラス型のどちらかを使うことが必須で、業務ロジックを書く場合はクラスが必須になり、またクラスも多数あることからクラス名と一緒にした方が分かりやすいという点があります。

Java言語は大文字と小文字が厳密に別で扱われるため、その命名規則で問題ありませんが、その慣習をVBAにも適用しようとすると、大文字と小文字が同一視されるVBAでは問題になります。

Java言語の感覚では「Range range = new Range()」と書いて、Rangeクラスのインスタンスrangeを作成、というよくある書き方をしますが、その考え方をVBAに適用すると、本件のようなクラス名の破壊が起こります。

]]>
VBAの変数名はどう書くのがよいか(非プログラマー向け) https://vbabeginner.net/howto-write-variablenames/ Tue, 10 Nov 2020 15:24:42 +0000 https://vbabeginner.net/?p=4854 変数名が変

VBAに限った話ではありませんが、プログラミング言語では「変数」を必ず使います。

VBAは他のプログラミング言語と異なり、本職プログラマーではない人も使う機会が多いプログラミング言語のため、一般的なプログラマーが書かないようなコードを書くことがあるようです。

何度か「ネットで調べながら会社で使うマクロを作りました」というものを渡されて目にしたことがありますが、これらを見てすぐに気になるのが「変数名」、変数の名前です。

例えばこういうabcの「何も考えてない系」。

Sub AAAA()
    Dim a
    Dim b
    Dim c
    Dim aa
    Dim aaa

それとか、このような「変数の意味を勘違いしている系」。

Sub BBBB()
    Dim hensu1
    Dim hensu2
    Dim hensu3

なぜ、こんな変数名が出てくるのでしょうか。

思った通りに動いてくれればOK

上記のような変数名は「初心者プログラマーあるある」で、周囲の指導や自身の経験から改善していきますが、プログラマーで無い人にとってはそのようなものは余計なおせっかいに過ぎません。当然こういう話はすっ飛ばされます。目的は思った通りに動けばそれでよく、いちいちプログラミングの作法やらコーディングルールやらを知る動機付けがまず無いでしょう。

それどころか、そういう作法的なものを見たところで嫌になるだけです。まったく生産性がありません。さらに言えば、VBAは変数の強制はデフォルトではありません。なので、そもそもDimでの変数宣言すら必要ありません。

そもそもプログラミングに縁が無かった人にとっては「変数」という言葉自体が馴染みがないわけで、しかも、=で右辺から左辺にコピーする、という概念も理解しがたいでしょう。理解しにくいものをいちいち理解する必要なし! と実にあっさりと「変数とはなんぞや?」→「=の左に書く奴」と解決(?)し、上記のような変数名が生まれるわけです。「変数」以外にも聞いたこともないような言葉や横文字のオンパレードを浴びせられると、もういいや、となるのもわかります。

本職プログラマーであれば「初心者のうちは分からなくてもそのうち分かるようになる」というような何の助言にもなってないアドバイスを先輩等々から受けて嫌々ながらでも横文字を浴びて時間の経過で慣れていきますが、それをプログラマーでない人に要求するのは、ちょっとなー、と。

これは、プログラマーが顧客業務の仕様を把握しても業務自体を習得しないため、業界慣習や言葉の違いで問題を発生させているのと同じ図式です。

じゃあ、変数名はどう書けばいいのか

では、プログラマーで無い人がよくわからん変数にどういう名前を付ければいいのか、ということですが、明快な答えがあります

それは、「指摘されたら直す」です。大事なのは「指摘されたら」です。指摘されなかったら直さなくていいです。変数名がおかしくてもちゃんと動いていればOKですし。

指摘することができる人は、その理由を説明できることでしょう。プログラマーで無い人はそのときに「プログラマーとして」レベルアップすればいいです。もちろんその指摘を無視しても構いません。プログラマーとしてのレベルアップはしませんが、元の職業のレベルは下がりませんから。繰り返しますがVBAが動けばOKですし。

で、私だったらどう指摘するか、という話ですが、難しい話はせず以下の2つに絞ってます。

  1. a、b、cはやめましょう。でも意味がある1文字ならOK。「引数argumentのa」「Workbookのb」「Characterのc」とか。
  2. セットする値の名前をつけましょう。”セルの文章”であれば「cell_no_bunsyou」でも「cellValue」でも「Sentence」でもお好みで。

こう書くと、「ローマ字ダメ」とか「1文字変数ダメ」とか「なんちゃら記法で書け」とかいろいろ意見があるのは分かりますが、対象は「Excelでラクをしたい人」です。決して「プログラミングで苦労している人」ではありません。既に苦労している人にさらにややこしいことを言うのは鬼です。

あと、3つも4つも指摘されると、どんなに親切な言葉であってもうんざりします。2つが限度。本職のプログラマーであればこの2つだけでも劇的に改善することはわかって頂けると思います。

当サイトで使っている変数名とコード見本

ちなみに当サイトのサンプルコードの変数名は1文字とかの短いものが結構あります。ほとんどの場合はこれで事足ります。

以下は短い変数名を使っていますが、その理由はそれぞれのコメントに書いた通りで、大体が変数のデータ型の頭文字です。

Sub VariableName()
    Dim r       As Range            '// 単一セルのRangeオブジェクト(Rangeのr)
    Dim s       As String           '// 処理で使う文字列(Stringのs)
    Dim i       As Integer          '// ループカウンタ(Index、Integer、Iteratorのi)
    Dim wb      As Workbook         '// Excelのワークブックオブジェクト(Workbookのwb)
    Dim sht     As Worksheet        '// Excelのワークブックのシートオブジェクト(Worksheetのsht)
    Dim ar()    As Variant          '// 配列(Arrayのar)
    Dim d       As Double           '// 小数点を持つ値や大きい値(Doubleのd)
    Dim v       As Variant          '// Variant型と明示する変数(Variantのv)
    
    '// アクティブセルのRangeオブジェクトを取得
    Set r = ActiveCell
    
    '// アクティブセルの文字列を取得
    s = r.Value
    
    '// ループカウンタを設定
    i = 0
    
    '// アクティブブックのWorkbookオブジェクトを取得
    Set wb = ActiveWorkbook
    
    '// アクティブブックの一番左のシートのWorksheetオブジェクトを取得
    Set sht = wb.Worksheets(1)
    
    '// 小数点を持つ値を取得
    d = Timer
    
    '// 小数点でタイマー値の整数部と小数部を分割
    v = Split(CStr(d), ".")
    
    '// 配列の要素数を指定
    ReDim ar(UBound(v))
    
    '// Split結果の配列をループ
    For i = 0 To UBound(v)
        '// Split結果を配列にコピー
        ar(i) = v(i)
    Next
End Sub

]]>
Alt+F11キーでVBE(VBA画面)が開かない場合の対応 https://vbabeginner.net/not-open-alt-f11/ Thu, 13 Aug 2020 14:49:22 +0000 https://vbabeginner.net/?p=4829 Alt + F11キーが無反応?

今までAlt + F11キーでVBAの画面を開いて編集を行っていたのに、「ある時」から急に無反応になることがあります。Excelを再起動しても、PCを再起動しても直らない。CtrlキーやShiftキーはちゃんと動作するのに、、、

この現象は他のアプリケーションでショートカットの設定されていることが原因です。

そして、ほとんどの場合がグラフィックボードのNVIDIA社のGeForce(ジーフォース)と呼ばれるGPUを導入したことが原因です。グラフィックボードが無かったPCにGeForceを追加した場合や、GeForceがあるPCに引っ越した場合に発生します。

GeForceには「GeForce Experience」という設定アプリがあり、その中で設定されているショートカットでAlt + F11キーが初期割り当てされていることがあります。そのためそちらが優先され、Excel上でのAlt + F11キーが無反応になります。

対応方法

上にも書いてますが、GeForceでのショートカットキーの割り当てを外してしまえば解決します。手順は以下になります。

  1. タスクバーの通知領域から「NVIDIA」のアイコン(緑色をしてます)を右クリックし、「GeForce Experience」を選択します。
  2. GeForce Experience画面が開いたら、右上にある歯車の形をした「設定」を押します。
  3. 画面中央あたりに「ゲーム内のオーバーレイ」と書いてある箇所に「設定」ボタンがありますので押します。
  4. 上から3番目あたりに「キーボードショートカット」があるのでそれを押します。
  5. どこかに「Alt + F11」と書いてある箇所があるので、「Alt + F11」と書いてある箇所をクリックしてハイライト表示にしたあとに、他のショートカット(例えば「Alt + Ctrl + F11」キーなど)を入力して、戻る→完了→×ボタンを押して設定画面を閉じます。

あとは「GeForce Experience」を閉じて、Excel画面上で「Alt + F11」キーでVBA画面が開くことを確認します。

GeForceではない場合

GeForceのショートカット設定ではない場合は、他のアプリケーションで「Alt + F11」を設定している箇所があると思われます。

ショートカットの重複を調べるツールなどがありますので、そちらで調べてみてください。

どうにもこうにもならない場合

そんなことがあるのか分かりませんが、どうにもこうにもAlt + F11キーが動いてくれない場合は、Excelのリボンの開発タブからVisual Basicを押してVBA画面を表示します。

「開発」タブはExcelの初期状態では表示されていません。

ファイルメニュー→オプション→リボンのユーザ設定、を開いて、右側にある「開発」のチェックを付けると開発タブが表示されるようになります。

]]>
VBAでWin32API(WindowsAPI)を64bit対応する方法 https://vbabeginner.net/win32api-64bit/ Mon, 10 Aug 2020 15:14:54 +0000 https://vbabeginner.net/?p=4816 64bit対応が必要な条件

Excel 2007までは32bit版のみでしたが、Excel2010から32bit版と64bit版の2つになりました。Excel2010当時は32bit版のインストールが推奨されていましたが、2020年現在主流のOffice 365では64bit版が一般的になっています。

ただ、違うPCや違うバージョンのExcelを使うとしても、過去に作成したブックを新しいバージョンでもそのまま使うことは普通にあります。そのような場合に、32bit版のExcelでWin32APIを使っているVBAのコードやブックを64bit版のExcelでそのまま使用しようとすると以下のようなエラーメッセージが表示されます。

「このプロジェクトのコードは、64ビットシステムで使用するために更新する必要があります。Declareステートメントの確認および更新を行い、次にDeclareステートメントにPtrSafe属性を設定してください。」

要するに、”Declare Function 関数名”と書いてたものに「PtrSafe」を付けて”Declare PtrSafe Function 関数名”と書け、と言ってるわけですが、日本語がおかしいのでまあ読み取れませんね。また、PtrSafeを付けて終わりではなく、引数の型が変わっている場合があるためそれにも対応が必要です。

以下で対応方法の詳細を説明します。

細かいことはいいから変換方法教えて!な方

エラーメッセージに書いてあるPtrSafeを付けるだけでは実際にはダメで、正しくは以下の3ステップで対応できます。

  1. Win32API_PtrSafe.TXTを入手する。下記入手方法がめんどいので当サイトからダウンロードできるようにしてます。
    ダウンロード「Win32API_PtrSafe.TXT(676KB)(Microsoftサイトからの入手方法は「VBAでWin32APIを使う方法と定義一式」をご参照ください。)
  2. エラーが発生しているWin32APIの関数名をWin32API_PtrSafe.TXTで検索する。
  3. 関数名で検索すると複数見つかるので、Declearが先頭に書いてある行の内容をコピーして、VBAの該当行部分に置き換える。

以下のような感じになります。PtrSafeが付いて、変数の型が変わる部分があります。

‘// 32bit版から64bit版へ変更
‘ Declare Function DrawMenuBar Lib “user32” (ByVal hwnd As Long) As Long
Declare PtrSafe Function DrawMenuBar Lib “user32” (ByVal hwnd As LongPtr) As Long

VBAでコードが書ける人への説明はこれで十分だと思います。VBAが書けない方は以下を。

VBAとか分かりません、という初心者向け

「VBAは分からないがとにかく動くようにしたい。」

こういう方は少なくないと思います。ネットで落としてきたコードや仕事のマクロとかですね。

そういう場合は上の説明だけだとわからないと思うので、もう少し丁寧に説明します。

  1. 最初に、上に書いているWin32API_PtrSafe.TXTを入手してメモ帳などのテキストエディタで開いておきます。後で使います。
  2. そしてExcelを起動します。起動したらAltキー+F11キーを押してください。そうすると「Microsoft Visual Basic for Applications」というタイトルが付いた画面が表示されます。これがVBAの画面です。既にエラーダイアログが表示されていればこの画面が表示されている場合があります。
  3. 次に、VBAのコードを書く画面を開きます。具体的には、VBA画面の左上にある「プロジェクト」のところに「VBAProject (ブック名)」があり、その中に「Microsoft Excel Objects」→「Sheet1」や「ThisWorkbook」がありますので、どちらでもいいのでダブルクリックして右側に表示させます。ここではSheet1を開いています。
  4. 右側にウィンドウが開いたら、Ctrl + Fキー(もしくは編集メニュー→検索)を押して検索ダイアログを表示し、以下の画像のように検索する文字列に「Declare」と入力し、対象に「カレントプロジェクト」を選択して、「次を検索」ボタンを押します。
  5. すると、Win32APIを使用している箇所が見つかります。
  6. 例えば、「Private Declare Function GetWindowLong Lib “user32” Alias “GetWindowLongA” (ByVal hwnd As Long, ByVal nIndex As Long) As Long」という行が見つかったとします。ここでの関数名は「Function」と書かれた右側の部分になりますので、例では「GetWindowLong」が関数名になります。
  7. そうしたら、関数名「GetWindowLong」を事前に開いておいたWin32API_PtrSafe.TXTで検索します。
  8. GetWindowLongをメモ帳で検索すると以下の4行が見つかります。
    ' Window field offsets for GetWindowLong() and GetWindowWord()
    
    Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongPtrA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
    
    Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
    
    Declare PtrSafe Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As Long
  9. この中で関数名として完全に一致しているもので、かつ、行の先頭に「Declare」と書かれている行が対象になるため、見つかった4つの一番下の内容を採用します。
    Declare PtrSafe Function GetWindowLong Lib “user32” Alias “GetWindowLongA” (ByVal hwnd As LongPtr, ByVal nIndex As Long) As Long
  10. あとは、元のGetWindowLongの行をWin32API_PtrSafe.TXTの内容で置き換えます。
  11. 置き換えの際に、元の行の先頭に「Private」や「Public」などが付いていることがあります。その場合は置き換え後の先頭にも元と同様に付けます。
    Private Declare PtrSafe Function GetWindowLong Lib “user32” Alias “GetWindowLongA” (ByVal hwnd As LongPtr, ByVal nIndex As Long) As Long

あとは他にもDeclareと書かれている箇所がないかを検索して探して、同様に対応します。

単に書き換えるだけでよいのであればこれで終わりです。

#IFでの分岐はしない方がよい

32bit版と64bit版が混在する環境の場合に、#IFを使ってWin32APIの記述を32bit版用と64bit版用に分けて書くことができます。こんな感じです。

'// 64bit版
#If VBA7 And Win64 Then
    Declare PtrSafe Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As Long
'// 32bit版
#Else
    Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
#End If

しかし、こういうことをしなければならないような状況に陥っているのならば、その状況自体が問題です。そして#IFでの分岐なんかやめてしまった方がいいです。

「VBA7定数で分岐できるならいいのではないか」とか「会社で使っているPCのOSやExcelのバージョンがバラバラなので仕方ない」とかの話もよく聞く話ですが、そういう状況であればVBAだけの話に留まらず色んなところに問題が派生していくため、Win32APIのバージョン分岐を入れたのでOK、なんて話で済むことはまずありません。むしろそういう場合はWin32APIを使うことで他の足かせになる恐れがあります。

なので、#IF分岐をするぐらいならVBAでのWin32APIの利用は64bit化に限定するか、そもそもWin32APIを利用しない判断をした方がいいです。その方がVBAが足かせにならずに64bit化対応の影響範囲も少なくなります。

2010年のOffice2010で64bit版が登場してから10年が経過し、そのOffice2010は2020/10/13にサポートが切れます。それを過ぎても32bit版を使っている方はとても多くいらっしゃいます。おそらく10年後、20年後でもWindowsXPやWindows7で32bit版を使っている方はいるでしょう。

ただ、その状態がその時々における標準仕様と言えるかというと、おそらくそうではないでしょう。

数年前までは「64bit版をインストールするといろいろ面倒だから32bit版を入れましょう」という話がいたるところにありました。その当時は周辺環境なども64bit対応への過渡期だったためそういう理屈も一理ありますが、2020年現在では64bitは当たり前の話で「32bit版を入れましょう」なんて話も聞かなくなりました。実際32bit版のPCなんて中古や在庫物で扱っているのがほとんどで、「Windows7 + 32bit + 新品PC」とかがあったとしても新品であっても最新ではありません。

そういうわけで、個人的にはWin32APIのバージョン分岐はしないことをお勧めします。

]]>
VBAで変数の宣言を強制すべきかどうか https://vbabeginner.net/whether-force-variable/ Wed, 14 Aug 2019 08:12:17 +0000 https://vbabeginner.net/?p=4418 変数の宣言を強制する設定

VBA画面のオプション画面の編集タブで「変数の宣言を強制する」のチェックを付けると、モジュールの先頭に「Option Explicit」が記述され、VBAの処理で使う変数のDim宣言などが必須になります。チェックを付けずにモジュールの先頭に「Option Explicit」を記述しても同じ意味になります。

新規ブックのVBAの初期状態ではチェックはついていません。よって、初期状態では変数の宣言はしなくてよい状態になっています。

そのため、Dim変数の宣言が強制されていなければ以下のような変数宣言がないコード(変数宣言をコメントアウト)も許可されます。

Sub testSub()
    '// 変数宣言をコメントアウト
    '// Dim a As Integer
    '// Dim b As Integer
    '// Dim r As Range

    '// 変数aに3を代入
    a = 3
    
    '// 変数bにa+1の結果を代入
    b = a + 1
    
    '// 変数rにA1セルのRangeオブジェクトを設定
    Set r = Range("A1")
    
    '// A1セルに変数bの値を設定
    r.Value = b
End Sub

上のコードでは変数をa,b,rと3つ使っていますが、「変数の宣言を強制する」にチェックを付けていない場合はDimステートメントは使ってなくてもエラーにならず正しく動作します。

ただ、上のコードのように変数宣言を省略することは避けた方がよいです。

変数の強制はした方がいい

変数の強制をした方がいいかどうかについてですが、VBAに限って言えば強制した方がよいでしょう。

正確に言うと、変数宣言を必ず行うために、「変数の宣言を強制する」にチェックを付けた方がよいです。

VBAは「初心者用」とか「簡単」とか言われがちで、その視点で書かれている書籍やサイトも少なくありません。その流れで変数宣言を省略する話が出てくることがあるようなのですが、「初心者用」であることと「変数宣言を省略する」ことに相関関係はありません。

なぜ変数宣言をした方がいいのか

変数宣言はした方がいいのにはもちろん理由があります。

それは、変数宣言している方がしていない場合よりも有利だからです。

「変数の宣言を強制する」にチェックを付けると存在しない変数などのチェックが効きますが、それは副次的な話でしかありません。本質的なことをコードに書く際に、変数チェックを機械的にしてくれる、という手助けをしてくれるに過ぎません。

本質的に必要なのは、そのコードが読みやすいか、拡張や保守しやすいか、シンプルであるか、意図を明確に伝えているか、やりたいことを充足しているか、などです。そこに初心者かどうかは関係ありません。初心者であっても可能な限りよりよいコードを書いた方がいいです。

変数宣言をすることは「ソースコードを書くルール」の一つに過ぎませんが、コーディングルールをきちんと決めて守ることが、後で見てもわかりやすかったり機能追加などの変更がしやすかったりと、結果としてわかりやすいコードになります。

変数宣言の位置

変数宣言を行う位置は、その変数を使う前であれば関数の先頭でも変数を使う直前でもどこでも構いません。当サイトで紹介しているコードの変数宣言は説明に都合がいいため関数の先頭にまとめて書いています。

ただ、ループカウンタなど変数の利用範囲が限定されている場合は使う直前に宣言した方がいい場合もあります。

上のコードの変数宣言を利用の直前に書いた場合は以下のようになります。

Sub testSub2()
    '// 変数aに3を代入
    Dim a As Integer
    a = 3
    
    '// 変数bにa+1の結果を代入
    Dim b As Integer
    b = a + 1
    
    '// 変数rにA1セルのRangeオブジェクトを設定
    Dim r As Range
    Set r = Range("A1")
    
    '// A1セルに変数bの値を設定
    r.Value = b
End Sub

VBAとシステムハンガリアン記法

変数の書き方で、当サイトでは変数の命名はシステムハンガリアン記法(変数の先頭に変数型などを示す1字を付ける書き方)で書いているコードが結構あります。sFilePath(Stringのs)とかiCount(Integerのi)とかの変数です。このように書いているのには理由があります。

それはVBAが良くも悪くも古い言語だからです。

現在主流のオブジェクト指向型のプログラミング言語はクラスを多用します。プリミティブ型と言われる単純な数値や文字列を格納する変数型を完全に排除している言語もあります。

そのような言語ではいろんなクラスを1字で示すことが困難で、そこにシステムハンガリアン記法を採用しようとしても「似たようなクラス名があった場合にどうしたらいいのか」という話に必ずなります。その状態でのシステムハンガリアン記法はむしろ邪魔で害悪な場合が多くなります。

ですが、VBAはJavaや.NETのようにクラスを多用しません。RangeやWorksheetやFileSystemObjectなど、使うクラスは限定されます。その場合、システムハンガリアン記法でも衝突することがほとんどありません。そういう理由から当サイトではシステムハンガリアン記法を採用しています。

Javaだけ経験したプログラマーが古い言語も新しい言語も一緒くたにして「システムハンガリアン記法は断固反対」「読みにくい」などと言う現場を見たことがありますが、開発状況によって何が便利なのかは異なりますので、単純にシステムハンガリアン記法がダメということにはなりません。

ちなみに私自身はVBAやC言語以外の言語を使う場合はシステムハンガリアン記法は使わず、アプリケーションハンガリアン記法(変数の性格を識別子にする記法)を使っています。

]]>
VBAのRedim Preserveは本当に遅いのか https://vbabeginner.net/redim-preserve-really-slow/ Mon, 22 Apr 2019 17:15:42 +0000 https://vbabeginner.net/?p=4276 動的配列の領域確保は事前に行うと速い

VBAの動的配列は領域の確保をRedimステートメントで上限値を指定する方法と、領域の拡張を行うRedim Preserveステートメントを指定する方法があります。

よく言われるのが、「領域の再確保(Redim Preserve)は遅いので、先に要素数を調べて領域の確保は最初に1回だけにしましょう」という話です。

これ自体はその通りで、Redim Preserveステートメントを行うと、拡張後の領域を探して元の領域から新しい領域にデータがコピーされて以降は新しい領域を利用する、といういくつもの段階があることから、時間が掛かる処理のため避けられるなら避けた方が処理速度の向上につながります。

では、Redimを事前にやっておくとどれぐらい速いのでしょうか? また、遅いと言われるRedim Preserveステートメントは、どれぐらい遅いのでしょうか?

Redimは常に高速

以下のコードは100万件のデータ領域を事前にRedimステートメントで確保します。

Sub RedimTest()
    Dim ar()
    Dim iCount
    Dim i
    Dim a, b
    
    a = Timer
    
    iCount = 1000000
    ReDim ar(iCount)
    
    For i = 0 To iCount
        ar(i) = "aaaaaaaaaaaaaaa"
    Next
    
    b = Timer
    
    Debug.Print b - a & "秒"
End Sub

100万件の領域を確保したあとにループしていますが、処理時間は0.1秒前後です。

これぐらいであれば体感では全くわからず、高速です。

Redim Preserve自体はそんなに遅くない。ただし、データ型未指定だと遅い。

PCの性能によって結果は異なると思いますが、指定回数ループしながらRedim Preserveを行う以下のコードで、ループ回数を変更しながらどれぐらい処理速度に変化があるのかを計測してみました。

データ型指定をしない場合

以下は配列のデータ型を指定しない場合です。2行目の配列は暗黙のデータ型のVariantが指定されます。

Sub RedimPreserveTest()
    Dim ar()
    Dim iCount
    Dim i
    Dim a, b
    
    a = Timer
    
    ReDim ar(0)
    
    iCount = 10000
    For i = 0 To iCount
        ReDim Preserve ar(i)
        ar(i) = "aaaaaaaaaaaaaaa"
    Next
    
    b = Timer
    
    Debug.Print b - a & "秒"
End Sub

結果は以下の通りになりました。

回数 秒数
10000 0.00293 0.0000000
20000 0.006104 0.0031738
30000 0.008057 0.0019531
40000 0.011963 0.0039062
50000 0.024048 0.0120850
60000 0.036987 0.0129395
70000 0.055054 0.0180664
80000 0.072021 0.0169678
90000 0.116943 0.0449219
100000 0.116943 0.0000000
200000 0.468018 0.3510742
300000 1.17395 0.7059324
400000 2.069946 0.8959960
500000 3.290039 1.2200930
600000 4.525024 1.2349850
700000 6.385986 1.8609620
800000 8.122925 1.7369390
900000 10.24707 2.1241450
1000000 12.69812 2.4510500
1100000 15.27002 2.5719000
1200000 18.21704 2.9470200
1300000 21.28796 3.0709200
1400000 24.75098 3.4630200
1500000 28.31104 3.5600600
1600000 32.23096 3.9199200
1700000 36.30798 4.0770200
1800000 40.74902 4.4410400
1900000 45.46997 4.7209500
2000000 50.19495 4.7249800

ずいぶん遅いですね。

このグラフの通り、10万件ぐらいまでは1秒未満程度ですが、そこを超えると処理秒数が上がっていきます。

100万件では20秒近くまで掛かっています。

データ型指定をする場合

以下は配列のデータ型としてString型を指定しています。上のコードとはその点のみが異なります。

Sub RedimPreserveTest()
    Dim ar() As String
    Dim iCount
    Dim i
    Dim a, b
    
    a = Timer
    
    ReDim ar(0)
    
    iCount = 10000
    For i = 0 To iCount
        ReDim Preserve ar(i)
        ar(i) = "aaaaaaaaaaaaaaa"
    Next
    
    b = Timer
    
    Debug.Print b - a & "秒"
End Sub

データ型を指定した場合の結果は以下のようになりました。

回数 秒数
10000 0.001953 0.0000000
20000 0.004883 0.0029297
30000 0.006836 0.0019531
40000 0.009033 0.0021973
50000 0.010986 0.0019531
60000 0.013916 0.0029297
70000 0.015869 0.0019531
80000 0.018066 0.0021973
90000 0.020996 0.0029297
100000 0.022949 0.0019531
200000 0.083984 0.0610352
300000 0.158936 0.0749511
400000 0.271973 0.1130372
500000 0.405029 0.1330566
600000 0.641113 0.2360840
700000 0.8479 0.2067871
800000 1.049805 0.2019046
900000 1.268799 0.2189940
1000000 1.628174 0.3593750
1100000 1.930908 0.3027340
1200000 2.226074 0.2951660
1300000 2.64917 0.4230960
1400000 3.01709 0.3679200
1500000 3.516113 0.4990230
1600000 3.896973 0.3808600
1700000 4.306885 0.4099120
1800000 4.865967 0.5590820
1900000 5.471191 0.6052240
2000000 5.967041 0.4958500

データ型を指定するとかなり速くなっています。

Redim Preserveよりも代入時の暗黙の型変換の方が時間がかかる

上の結果をまとめると以下のようになります。

条件 秒数
最初に要素数を指定する 約0.1秒
データ型ありでのRedim Preserve 約6秒
データ型なしでのRedim Preserve 約50秒

やはり、処理の開始時に配列の要素数を指定して領域を確保するのは0.1秒と高速です。Redim Preserve は配列のデータ型を指定しておけば、そこそこの速さですが、指定しなかった場合はかなり遅くなります。

問題なのはRedim Preserveによる領域の再確保にかかる時間よりも、配列の各要素に代入する際の暗黙の型変換の方が時間が掛かっていることです。

結論として、事前に配列の要素数が分かるのであれば最初に領域を確保しておくのが一番高速で、そうではなくループ内でRedim Preserveで領域を拡張する場合は、配列のデータ型を適切に指定しておいた方がよい、ということになります。

]]>