質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

Q&A

解決済

3回答

2356閲覧

VBAからWshShell等により他アプリケーションを起動する場合の終了待ちの方法について

xail2222

総合スコア1497

VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

0グッド

0クリップ

投稿2021/04/10 23:36

編集2021/04/11 07:08

前提・実現したいこと

VBAからWshShell等により他アプリケーションを起動する場合に終了待ちを行いたい。
同期呼び出しではVBAが固まってしまうので、非同期で呼び出したいと考えており
終了時にイベント発生というのが理想です。処理が複雑になるのも避けたい感じです。

要件としてはpythonのプログラムを起動し結果を取得するものです。

質問は
今のままで問題ないか、それとも何か良い別の終了待ちの方法はないでしょうか。
という内容です。

ちょっとしたことでも良いので何かあれば回答をお願いします。

発生している問題・エラーメッセージ

現在使っている方法は「試したこと」に記載した内容なのですが
以下の部分

VBA

1 Do While tExex.Status = 0 2 DoEvents 3 Loop

ここのループがCPUをそれなりに使ってしまうので良くないのでは。
と思っている次第です。静かに待ちたいのです。

.Netだと終了のイベントが取れない場合は
Timerのコンポーネントをつかって終了判定をする所だと思います。
1秒間に1回程度の判定であれば、それほど騒がしくないと思うので。

試したこと

VBA

1Public Event EndCommand(ExitCode As Long) 2 3Public Sub Exec(cmd As String) 4 Dim tWsh As IWshRuntimeLibrary.WshShell 5 Dim tExex As WshExec 6 Dim tExitCode As Long 7 Set tWsh = New IWshRuntimeLibrary.WshShell 8 9 ' 処理呼び出し 10 Set tExex = gWsh.Exec(cmd) 11 12 ' 終了待ち 13 Do While tExex.Status = 0 14 DoEvents 15 Loop 16 17 ' 終了イベント呼び出し 18 tExitCode = tExex.ExitCode 19 Set tExex = Nothing 20 RaiseEvent EndCommand(tExitCode) 21 22End Sub

省略したコードですが、これをクラスモジュールに記載し
呼び出し側でWithEventsで終了時の処理を記述
という風にしています。
参照設定はWindows Script Host Object Modelです。

(追記 2021/04/11)
終了時にコールバック関数が呼ばれたりイベントが発生したりというようなものがあればいいのですが
と考えているとTimerがVBAにもあればなんとかなるような気がしたので検索すると
Application.OnTime というのを見つけました。これはこれで使える気がします。

あと
timeSetEventについて記述がある
これは使えるのかどうなのか。
「処理が複雑になるのも避けたい感じです」と言いましたが
少しぐらい複雑でも、それをひとまとめにしてクラスにしたり
決まった関数が呼び出されるようにするというのは許容範囲内だと考えています。

(解決後の追記)
sleep 1ということで静かに待つとまでは言えないですが
cpu負荷がほぼ0%に下がった為、これで運用上は問題ないと判断しました。

Application.OnTimeの方が静かに待つ。という条件には沿う気はしますが
こちらは、とりあえず使い方だけ押さえておくという事にしておきます。

補足情報(FW/ツールのバージョンなど)

Windows10
Office365

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

Application.OnTimeを使った場合の例も調べたので一応回答に記載しておきます。

まず、UserForm1で呼び出すとし、標準モジュールに以下を追加

VBA

1Public Sub TimerCallBack() 2 UserForm1.TimerEvents 3End Sub

UserForm1の方は、CommandButton1を追加して

VBA

1Private mExec As IWshRuntimeLibrary.WshExec 2 3Public Sub TimerEvents() 4 If mExec Is Nothing Then 5 Exit Sub 6 End If 7 If mExec.Status = WshRunning Then 8 Application.OnTime Now + TimeValue("00:00:02"), "TimerCallBack" 9 Exit Sub 10 End If 11 ' ここで終了時の処理 12End Sub 13 14Private Sub CommandButton1_Click() 15 Dim tWsh As IWshRuntimeLibrary.WshShell 16 Set tWsh = New IWshRuntimeLibrary.WshShell 17 Set mExec = tWsh.Exec("notepad.exe") 18 Call TimerEvents 19End Sub

という感じで行けそうでした。

投稿2021/04/11 23:01

xail2222

総合スコア1497

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/04/12 00:19 編集

DoEvents呼ぶ必要が無いので、最初のループの実装よりもこちらの実装の方が良さそうですね。 追記の timeSetEvent は、コールバックが別スレッドで呼ばれた記憶があるのでVBAで使用するのは問題があるかもしれません。
guest

0

ベストアンサー

ここのループがCPUをそれなりに使ってしまうので良くないのでは。

と思っている次第です。静かに待ちたいのです。

CPU負荷減らしたいだけなら、Sleep挟めばいいんじゃないですかね。

投稿2021/04/11 04:35

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xail2222

2021/04/11 04:39

回答ありがとうございます。選択肢の一つかと思います。 ただ、sleepの間Excelが止まってしまうので どうしたものかと思っていたのです。 Excelが止まっていいならgWsh.Runで実行して同期処理で呼び出せばいいと感じています。
退会済みユーザー

退会済みユーザー

2021/04/11 05:15

Sleep(1)なら殆ど影響はないと思いますが。
xail2222

2021/04/11 05:18

なるほど。意味があるのかないのかよくわかってないですが試してみます。
xail2222

2021/04/11 05:22

おお!なぜだかわからないですがsleep 1でcpuの負荷が劇的に減りました。 0%近いです。 なぜなのでしょう。ループする回数が減るのでしょうか。 でも、これで十分な感じがします。
退会済みユーザー

退会済みユーザー

2021/04/11 05:24

ループを全力で回すと、OSに全く処理が返されないのでCPUリソースを使い果たします。 環境によっては、最悪熱暴走します。
xail2222

2021/04/11 05:27

一応DoEvebts挟んでたので処理は返していると思うのですが sleep 1することで、更にExcelが待機してOSが楽になるということでしょうか。
退会済みユーザー

退会済みユーザー

2021/04/11 05:37 編集

> 一応DoEvebts挟んでたので処理は返していると思うのですが DoEventsはメッセージループのキューの処理をしにいくだけで、別にOSに処理を返す訳ではないです。 > sleep 1することで、更にExcelが待機してOSが楽になるということでしょうか。 まあそういうことです。現在のCPUはとんでもなく高速なので、1msもあればかなりの処理をしてしまうのです。ループを回している間も、「ループを回す」という処理をし続けているのです。
xail2222

2021/04/11 05:42 編集

ん? https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/doevents-function 「DoEvents はオペレーティング システムに制御を渡します。 オペレーティング システムがキュー内のイベントの処理を終了し、SendKeys キューのすべてのキーが送信された後、制御が戻されます。」 とあったのでOSに処理を返してはいると思うのですが sleep 1 だと何が違うのか… http://officetanaka.net/excel/vba/tips/tips116.htm にも書いてありましたが、なぜだかがワカラナイ… >現在のCPUはとんでもなく高速なので、1msもあればかなりの処理をしてしまうのです。 なるほど。ちょっとでも十分ってことなのですね。 ただ、sleep 1だけだとOSに制御を渡すだけで、Excelの方の処理はしてくれないから sleep 1 DoEvents と二つすることで、Excelも使えるし、CPUの負荷も下がるという事になるのですね。
退会済みユーザー

退会済みユーザー

2021/04/11 05:52 編集

DoEventsはOSに制御を渡した後に即キュー処理をしにいき、キュー処理⇒元の処理に戻るという動きになるから、CPUは休む暇がないのです。Sleep中は、本当に何もしないのです。
xail2222

2021/04/11 07:00

sleep 1だからすぐ戻ってくるので効果がないと初めは思ってましたが ループの処理の方が1msよりも圧倒的に早いため止まってる時間の方が長いということで 効果があると解釈しました。 よくわかる解説ありがとうございました。
guest

0

Win API を使えば、外部プログラム終了待ち処理を実現できます。

外部プログラムの実行と処理待ち(APIを利用した同期処理)|Excel VBA

投稿2021/04/11 00:41

TanakaHiroaki

総合スコア1063

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xail2222

2021/04/11 04:26

回答ありがとうございます。 提示のURLのものは、終了まで待つかVBAの側から問い合わせて終了しているかを確認するものかと思います。 確かに非同期ではありますが、WshShell.Execと同じ待ち方(ループで待つ)以外に何か終了を判定する方法があるでしょうか。
TanakaHiroaki

2021/04/11 04:36

外部プログラム終了をイベントとして検出する方法でしたら、わからないです。
xail2222

2021/04/11 04:43

特にイベントである必要はないのですが、コールバック関数でもいいのだけど 質問に提示した例のようなループ以外で待てる方法はないのかなと考えています。
xail2222

2021/04/11 05:00

「VBA実行中にExcelを使用できなくなることが問題」 はい、大部分の問題がそれだと思っています。 確かに別Excel起動する手法がありますね。 altで別Excel立ち上がるのは知りませんでした。 これはこれで、色々役に立つ情報だと思います。 ありがとうございます。 ただ、同時に開いていたExcelの方は操作できなくなるのでまだ少し調べたいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問