・スタートアップの種類の取得の前準備 1
スタートアップの種類の取得は
VB2005の事例
の時のようにレジストリを検索してもいいのですが、
APIにはそれを取得する関数が用意されています。
QueryServiceConfigがそれです。
第一引数はサービスのハンドル、
第二引数は結果を受け取るバッファ、
第三引数は第二引数のバッファが必要とするバイト数、
第四引数は第三引数で指定したバイト数が不足だった場合などに必要なサイズを受け取る変数です。
この関数の第二引数に QUERY_SERVICE_CONFIG構造体 を指定して、そのメンバであるdwStartTypeに該当サービスのスタートアップの種類が格納される仕様です。
とりあえず、 Win32Api.txt からこの関数と構造体をコピーしましょう。
ここで注目してほしいのは QueryServiceConfig の第四引数です。ここは第二引数に指定した QUERY_SERVICE_CONFIG構造体 に入りきらないことによって関数が失敗したときに、いったい どれだけのバイト数が必要かを受け取るためにあります。 QUERY_SERVICE_CONFIG構造体 のサイズは第三引数で指定しますから、ここは
Dim vtSvcConf As QUERY_SERVICE_CONFIG
QueryServiceConfig(xxxx, xxxxx, LenB(vtSvcConf), xxx)
とすれば入りきらないことはないし、第四引数は必要ないように思われます。 もし、仮に第二引数に前回のSERVICE_STATUS構造体を指定するのでしたら、 LenB(SERVICE_STATUS構造体のインスタンス)で問題ないです。しかし、ここに指定するのは QUERY_SERVICE_CONFIG 型です。では、この2つの構造体はどう違うのでしょう?
2つの構造体をよくみてみてください。SERVICE_STATUS構造体のメンバはすべてLong型です。
Long型は4バイト(32ビット)固定です。したがって、そのサイズはメンバ数7 X 4 バイト =28バイト
で一定です。
一方、
QUERY_SERVICE_CONFIG
はString型が入っています。ということは、String型の各メンバは
どれだけの文字列が格納されるかわからない可変長ですから、サイズは一定ではないです。
ということで
QUERY_SERVICE_CONFIG
型に
結果を受け入れるためのサイズ
は不定ということになります。
このサイズを受け取るのが
QueryServiceConfig
の第四引数ということになり、受け取った
サイズを再度
QueryServiceConfig
の第三引数に指定して、必要な情報を第二引数に受け取る
ことになります。
ここで誤解しないでほしいのですが、先ほど QUERY_SERVICE_CONFIG 型の結果を受け入れるための サイズは不定と書きましたが、 QUERY_SERVICE_CONFIG型のサイズ は36バイトで一定です。何を言っている??と思われるかもしれませんが、 これを理解するのはC言語の知識が必要になってきます。
・ポインタ
最初に C言語でのAPIの宣言を確認しなければならないときがあると書きましたが今がそのときです。 VBA(VB6)なのにC言語を理解しなければならないのは苦痛ですが、APIを使う以上避けられないです。
C言語での QUERY_SERVICE_CONFIG の定義は
typedef struct _QUERY_SERVICE_CONFIG { DWORD dwServiceType; DWORD dwStartType; DWORD dwErrorControl; LPTSTR lpBinaryPathName; LPTSTR lpLoadOrderGroup; DWORD dwTagId; LPTSTR lpDependencies; LPTSTR lpServiceStartName; LPTSTR lpDisplayName; } QUERY_SERVICE_CONFIG, *LPQUERY_SERVICE_CONFIG; |
となっています。VBA(VB6)とはまったく違うので戸惑うかもしれませんが、 注目してほしいのはVBA(VB6)でString型で定義したメンバの型はC言語ではLPTSTRという型で 定義されているということです。この型はANSIとUNICODEの文字コードの違いを吸収するために このようになっているのですが、要はchar型、または、wchar_t型へのポインタです。 これまた分からない型かと思いますので以下をご覧ください。
23番地
I | L | i | k | e | V | B | A | \0 |
7番地
23番地 |
↑
これがポインタ(名前がlpBinaryPathName)
上記はコンピュータのメモリを模式的に描いたもので1マスが1バイトとします
(ポインタは4バイト)。コンピュータはメモリをそのアドレス(住所のようなもの)として
1バイトずつ管理しています。このアドレスを頼りに様々な処理をします。
ポインタは変数です。変数というと何かを入れる器ですが、例えばVBAのLong型は整数を入れる
4バイトの器です。ではポインタは何を入れる器かというと、上記の図からわかるようにメモリの
アドレス(住所)を入れる器です。アドレスをいれますが、上記の例で23番地という情報だけでは
そのアドレスにどんな情報が入っているかわからないので「char型への」と型情報が付加されます。
文字列は23番地から始まることがわかりますが、どこで終わるかわかりません。 その終わりを示すのがNULL文字(\0)です。ですから、型と始まりの番地さえ分かれば文字列が 取得できるのです。
QUERY_SERVICE_CONFIG のlpBinaryPathNameを例にすると、7番地の変数名が「lpBinaryPathName」で、この変数が指し示す 23番地以降NULL文字までに文字列が入っているということになります。
話を元に戻しますが、 QUERY_SERVICE_CONFIG 型の結果を受け入れるためのサイズ は23番地以降のメモリを含めたサイズであり、 QueryServiceConfig の第四引数で受け取るサイズでもあります。一方、 QUERY_SERVICE_CONFIG型のサイズ は文字列がどんなに長くなっても(23番地以降のメモリがどんなに増えても)7番地に入っている ポインタの値は「23番地」で4バイト(32ビット)固定です。ということで QUERY_SERVICE_CONFIG型のサイズ は一定ということになります。
・QueryServiceConfigのDeclare宣言の変更
QueryServiceConfig の第三引数に指定する、サービス構成の結果を受け取るためのサイズは 先ほど書いたように不定(わからない)ので、まず、第二、第三引数に小さな値を設定してわざと エラーを起こし、第四引数に必要サイズを取得する必要があります。そして再度、取得したサイズを QueryServiceConfig の第三引数に指定して、第二引数に結果を受け取ります。
ところが、第二引数の型は QUERY_SERVICE_CONFIG 型、正確にはQUERY_SERVICE_CONFIGへのポインタ(QUERY_SERVICE_CONFIGのインスタンスの先頭アドレス)です。 これは、C言語での QueryServiceConfig の宣言を見ればわかります。
BOOL QueryServiceConfig( SC_HANDLE hService, // サービスのハンドル LPQUERY_SERVICE_CONFIG lpServiceConfig, // バッファ DWORD cbBufSize, // バッファのサイズ LPDWORD pcbBytesNeeded // 必要なバイト数 );
前項でQUERY_SERVICE_CONFIG型のサイズ は一定で実際に文字列が格納されている メモリサイズが考慮されていませんと書きました。つまり、
Dim vtSvcConf As QUERY_SERVICE_CONFIG QueryServiceConfig(xxxx, vtSvcConf, xxxx, xxxx)
とした場合、vtSvcConfのString型のメンバは、実際には文字列か格納されている領域の 先頭アドレスだけが格納されているのですが、そのアドレス先に文字列を入れるだけの領域が 確保されていないということです。それでは、
Dim vtSvcConf As QUERY_SERVICE_CONFIG With vtSvcConf .lpBinaryPathName = Space(500) .lpDisplayName = Space(500) ・ ・ End With QueryServiceConfig(xxxx, vtSvcConf, xxxx, xxxx)
このように文字列の領域を確保しておけばいいじゃないかという話になります。 しかしこれもうまくいきません。QUERY_SERVICE_CONFIG型のサイズ分の 確保された領域(vtSvcConf)とSpace関数で確保された文字列を格納するための領域がバラバラで 連続しているとは限らないからです。Space関数を発行した時点でVBAは、メモリ上のどこかに5 00の領域をつくってその先頭アドレスをlpBinaryPathNameなどにいれているのですが、 その新たに作った領域はどこかわからないですし、vtSvcConfの領域につづいて作られるわけではないのです。 QueryServiceConfig の第二引数で要求しているのは連続した 結果を受け入れるためのサイズの 先頭アドレス(ポインタ)です。
ではどうするかというと第二引数の型をByte型として宣言し直します。最初の QueryServiceConfig の第四引数で受け取った必要バイト数分のByte型の配列を確保してそのポインタを第二引数 に設定してやります。こうしてやれば、連続した領域へのポインタを QueryServiceConfig の第二引数に指定できます。
Private Declare Function QueryServiceConfig Lib "advapi32.dll" Alias _
"QueryServiceConfigA" _
(ByVal hService As Long, _
lpServiceConfig As Byte, _
ByVal cbBufSize As Long, _
pcbBytesNeeded As Long) As Long
|
ところでVBA(VB6)でどうやってポインタを指定するの?と感じるかもしれませんが、 知らない間にポインタを設定しているんです。それが参照渡しです。
Sub main() Dim b As Byte b = 1 '(A) func(b) '(B) Debug.Print b '(C) End Sub Sub func(arg As Byte) '(D) arg = arg + 1 End Sub
(A)で変数bに1を代入して、(B)でbを渡しています。ここでVBA(VB6)はデフォルトで 参照渡しなので、funcの引数は1という値を受け取っているのではなく1という値が格納されている 変数bのアドレスを渡しています。もし、1という値が渡っているのでしたら(C)は1が出力されるはず ですが、実際には2になります。正確にはポインタと参照は違うのですがその説明は割愛します。
ですから、例えば、結果を受け取る必要サイズが357バイトだとすると
Dim b(356) As Byte QueryServiceConfig(xxxx, b(0), 357, xxxx)
とすればByte型配列の先頭アドレスが渡って結果をbの配列に受け取ることができます。
・QUERY_SERVIC_CONFIG構造体のメンバの型の変更
受け取った結果はByte型の配列です。これを QUERY_SERVICE_CONFIG構造体 のインスタンスに代入しないといけないのですが、その前にどのようにデータが格納されているかを説明します。
便宜上、 QUERY_SERVICE_CONFIG のメンバをdwStartType、lpBinaryPathName、dwServiceType、lpDisplayNameの4つのみと仮定します。
Dim b(必要サイズ数) As Byte
として QueryServiceConfig で結果を受け取ったメモリの状態が上図のような感じなります。赤枠の 部分がQUERY_SERVICE_CONFIG型のサイズになります。
この図からメモリ操作で先頭の16バイトを
QUERY_SERVICE_CONFIG構造体
のインスタンスにコピーしてあげれば、
QUERY_SERVICE_CONFIG
の情報として取り出せます。このメモリ操作はAPIの
CopyMemoryを使います。
しかし、lpBinaryPathNameとlpDisplayNameはアドレスが入っているだけです。アドレスは32ビットの
正の整数なので、これをString型としてVBAで定義すると値が受け取れないので、String型はLong
として定義しなおします。
でもLong型でアドレスを受け取ってもそのアドレスが指し示す実際のデータはどうやって取り出すの? という疑問がわいてくるかと思います。これはC言語のstrcpyという関数のAPI版があるのでそれを 使わせてもらいます。(だだし、今回必要なのはLong型のdwStartTypeなのでおまけとして後ほど 書きたいと思います)
・スタートアップの種類の取得の前準備 2
実装前の最後の準備として QueryServiceConfigと QUERY_SERVICE_CONFIG構造体 以外の必要な関数・定数の宣言をします。
ここ でわざとエラーを起こしたときにそのエラーコードを識別するための 定数ERROR_INSUFFICIENT_BUFFERの定義をします。
MSDNの QUERY_SERVICE_CONFIG構造体 のdwStartTypeメンバの解説にある設定できる定数一覧を定義します。( Win32Api.txtにはないです)
OpenServiceの第三引数に指定する定数は QueryServiceConfig 関数が使えるようにする定数を設定しないといけないのでそれを定義します。
ここ で必要なメモリ操作のためにCopyMemoryを宣言します。
では Win32Api.txtからコピーしましょう。
ずいぶん前置きが長くなりましたが、ここまでが理解できないとこの次に記述する VBAのコードの意味がまったく理解できないはずです。ということで次回はいよいよスタートアップの 種類の取得のための実装を書いていきたいと思います。