◆VBA (VB6)でのWindows Service制御 - Win32 API編 その3

[ 上へ ]

・スタートアップの種類の取得

前ページで準備ができたところでスタートアップの種類の取得のコーディングをします。 Functionプロシージャで、プロシージャ名はGetStartMode、引数はサービス名を受け取る String型、戻り値は ここ で定義したQUERY_SERVICE_CONFIG構造体のdwStartTypeメンバで受け取れる定数です。

    
'スタートアップの種類の取得
'引数 argsName : サービス名
'戻値 QUERY_SERVICE_CONFIG構造体のdwStartModeで受け取ることができる定数
Public Function GetStartMode(ByVal argsName As String) As Long

    Dim vlRet        As Long
    Dim vlHSCManager As Long    'サービス制御マネージャのデータベースハンドル
    Dim vlHService   As Long    'サービスのハンドル
    Dim vbyBuf()     As Byte    'QUERY_SERVICE_CONFIGの結果を受け取るバッファ
    Dim vlSize       As Long    'QUERY_SERVICE_CONFIGの結果を受け取るサイズ
    Dim vtSvcConf    As QUERY_SERVICE_CONFIG


    Do    
        'サービス制御マネージャのデータベースOpen
        vlHSCManager = OpenSCManager("", "ServicesActive", SC_MANAGER_CONNECT)  '(A)
        If vlHSCManager = 0 Then
            vlRet = SERVICE_NONE: Exit Do
        End If

        'サービスOpen
        vlHService = OpenService(vlHSCManager, argsName, SERVICE_QUERY_CONFIG)  '(B)
        If vlHService = 0 Then
            vlRet = SERVICE_NONE: Exit Do
        End If

        '結果を受け取るQUERY_SERVICE_CONFIG構造体のサイズ取得
        ReDim vbyBuf(0)             '(C)
        vlSize = 0     

        If QueryServiceConfig(vlHService, vbyBuf(0), vlSize, vlSize) = 0 And _
                            Err.LastDllError <> ERROR_INSUFFICIENT_BUFFER Then  '(D)
            vlRet = SERVICE_NONE: Exit Do
        End If

        'バッファに結果取得
        ReDim vbyBuf(vlSize)        '(E)
    
        If QueryServiceConfig(vlHService, vbyBuf(0), vlSize, vlSize) = 0 Then   '(F)
            vlRet = SERVICE_NONE: Exit Do
        End If

        'QUERY_SERVICE_CONFIG構造体に受け取ったバッファからコピー
        CopyMemory vtSvcConf, vbyBuf(0), LenB(vtSvcConf)    '(G)

        vlRet = vtSvcConf.dwStartType

        Exit Do
    Loop

    If vlHService <> 0 Then CloseServiceHandle vlHService
    If vlHSCManager <> 0 Then CloseServiceHandle vlHSCManager

    GetStartMode = vlRet
End Function

(A)、(B)はサービスの状態の取得/設定で使いましたので説明は省きます。 ただ、QueryServiceConfigを有効にするために、OpenService関数の第三引数で SERVICE_QUERY_CONFIG定数に変わっています。

Byte型の配列vbyBufに対象サービスのサービス構成パラメータの結果を受け取りますが、 先にも 書きましたように結果を受け入れるためのサイズは この時点ではわからないのでわざとQueryServiceConfig関数を失敗させるために(C)で 第二、第三引数に渡す値を最小に設定します。

(D)でERROR_INSUFFICIENT_ERROR(領域不足)で失敗しますが、第四引数に必要バイト数が 変数vlSizeに返ってきます。このサイズを元に(E)で再度配列のサイズを設定しなおします。

これで結果を第二引数に受け取る準備ができましたので、Byte型配列vbyBufの 先頭アドレスを(F)で渡してあげます。

成功したら、 ここ で説明したようにvbyBufからvtSvcConf構造体にメモリコピーする操作をします。 CopyMemoryの定義 を見ると分かるように第一、第二引数は参照渡しなので、それぞれ構造体名、配列の先頭の変数名を 指定するとそのアドレスが渡ることになります。コピーするサイズは この図 で説明したようにQUERY_SERVICE_CONFIG型のサイズ ですからLenB(vtSvcConf)を設定します。

そして目的のdwStartTypeの値を取得して、最後に各ハンドルを閉じます。

・おまけ

せっかく苦労して文字列のサービス構成パラメータを取得したので、これも出力してみましょう。 ポインタから文字列を取得するにはlstrcpyを使う ことは前に書きました。 Win32Api.txt からコピーしましょう。

Private Declare Function lstrcpy Lib "kernel32" Alias "lstrcpyA" _
        (ByVal lpString1 As String, _
         ByVal lpString2 As String) As Long

lstrcpyのC言語の定義は以下のようです。

LPTSTR lstrcpy(
    LPTSTR   lpString1,   // コピー先のバッファ
    LPCTSTR  lpString2    // コピー元の文字列
);

これから第一引数は、コピー先のメモリ領域の先頭アドレス、コピー元の文字列が 格納されているメモリ領域の先頭アドレスです。ここで第二引数は、前項目のQUERY_SERVICE_CONFIG 構造体のインスタンスvtSvcConfのメンバ、例えばlpBinaryPathNameを指定することになりますが、 ここはポインタのLong型に変更していたので>String型では不味いのでLong型に変更します。

Private Declare Function lstrcpy Lib "kernel32" Alias "lstrcpyA" _
        (ByVal lpString1 As String, _
         ByVal lpString2 As Long) As Long

それでは、GetStartModeプロシージャに変更を加えます。

'スタートアップの種類の取得
'引数 argsName : サービス名
'戻値 QUERY_SERVICE_CONFIG構造体のdwStartModeで受け取ることができる定数
Public Function GetStartMode(ByVal argsName As String) As Long


    Dim vlRet        As Long
    Dim vlHSCManager As Long     'サービス制御マネージャのデータベースハンドル
    Dim vlHService   As Long 'サービスのハンドル
    Dim vbyBuf()     As Byte 'QUERY_SERVICE_CONFIGの結果を受け取るバッファ
    Dim vlSize       As Long 'QUERY_SERVICE_CONFIGの結果を受け取るサイズ
    Dim vtSvcConf    As QUERY_SERVICE_CONFIG

    Dim vsValue      As String
    Dim vvData       As Variant
    Dim i            As Long

    Do
        'サービス制御マネージャのデータベースOpen
        vlHSCManager = OpenSCManager("", "ServicesActive", SC_MANAGER_CONNECT)   '(A)
        If vlHSCManager = 0 Then
            vlRet = SERVICE_NONE: Exit Do
        End If

        'サービスOpen
        vlHService = OpenService(vlHSCManager, argsName, SERVICE_QUERY_CONFIG)  '(B)
        If vlHService = 0 Then
            vlRet = SERVICE_NONE: Exit Do
        End If

        '結果を受け取るQUERY_SERVICE_CONFIG構造体のサイズ取得
        ReDim vbyBuf(0)          '(C)
        vlSize = 0

        If QueryServiceConfig(vlHService, vbyBuf(0), vlSize, vlSize) = 0 And _
                            Err.LastDllError <> ERROR_INSUFFICIENT_BUFFER Then  '(D)
            vlRet = SERVICE_NONE: Exit Do
        End If

        'バッファに結果取得
        ReDim vbyBuf(vlSize)      '(E)
    
        If QueryServiceConfig(vlHService, vbyBuf(0), vlSize, vlSize) = 0 Then   '(F)
            vlRet = SERVICE_NONE: Exit Do
        End If

        'QUERY_SERVICE_CONFIG構造体に受け取ったバッファからコピー
        CopyMemory vtSvcConf, vbyBuf(0), LenB(vtSvcConf)     '(G)

        vvData = Array(vtSvcConf.lpBinaryPathName, "lpBinaryPathName", _
                       vtSvcConf.lpDependencies, "lpDependencies", _
                       vtSvcConf.lpDisplayName, "lpDisplayName", _
                       vtSvcConf.lpLoadOrderGroup, "lpLoadOrderGroup", _
                       vtSvcConf.lpServiceStartName, "lpServiceStartName")

        For i = 0 To UBound(vvData) Step 2
            vsValue = Space(255)
            lstrcpy vsValue, CLng(vvData(i))
            Debug.Print vvData(i + 1) & " : " & vsValue
        Next i

        vvData = Array(vtSvcConf.dwErrorControl, "dwErrorControl", _
                       vtSvcConf.dwServiceType, "dwServiceType", _
                       vtSvcConf.dwStartType, "dwStartType", _     
                       vtSvcConf.dwTagId, "dwTagId")

        For i = 0 To UBound(vvData) Step 2
            Debug.Print vvData(i + 1) & " : " & vvData(i)
        Next i
  
        vlRet = vtSvcConf.dwStartType

        Exit Do
    Loop

    If vlHService <> 0 Then CloseServiceHandle vlHService
    If vlHSCManager <> 0 Then CloseServiceHandle vlHSCManager

    GetStartMode = vlRet
End Function

・スタートアップの種類の変更

スタートアップの種類の変更はChangeServiceConigを使います。いつものように Win32Api.txtからコピーします。

Private Declare Function ChangeServiceConfig Lib "advapi32.dll" Alias _
                                                    "ChangeServiceConfigA" _
                (ByVal hService As Long, _
                 ByVal dwServiceType As Long, _
                 ByVal dwStartType As Long, _
                 ByVal dwErrorControl As Long, _
                 ByVal lpBinaryPathName As String, _
                 ByVal lpLoadOrderGroup As String, _
                 lpdwTagId As Long, _
                 ByVal lpDependencies As String, _
                 ByVal lpServiceStartName As String, _
                 ByVal lpPassword As String, _
                 ByVal lpDisplayName As String) As Long

引数がたくさんあってややこしそうですが、第一引数のhService、第十引数の lpPassword以外はQUERY_SERVICE_CONFIG構造体のメンバで、変更するのはdwStartTypeのみです。 このうち、lpdwTagIdだけは参照渡しです。MSDNの解説には

============================================================================
[出力]タグ値を受け取る変数へのポインタを指定します。このタグ値は、lpLoadOrderGroup パラメータで指定されたグループで一意の値です。既存のタグを変更しない場合は、NULLを指定します。 ============================================================================
とあります。Long型の変数を宣言して、この引数に設定するようですが、変更しないのでNULL、つまり0を 設定したいのですが、参照渡しではメモリのアドレスが0を渡さなければならなくてやりづらいので、 値渡しで0を渡すように変更します。

最後の引数lpDisplayNameですが、スタートアップの種類の取得で書いたように、 結果を受け取るQUERY_SERVICE_CONFIGのそのメンバはポインタのLong型で受けているので、 そのままLong型の値を渡せるようにString型からLong型に変更します。

Private Declare Function ChangeServiceConfig Lib "advapi32.dll" Alias _
                                                    "ChangeServiceConfigA" _
                (ByVal hService As Long, _
                 ByVal dwServiceType As Long, _
                 ByVal dwStartType As Long, _
                 ByVal dwErrorControl As Long, _
                 ByVal lpBinaryPathName As String, _
                 ByVal lpLoadOrderGroup As String, _
                 ByVal lpdwTagId As Long, _
                 ByVal lpDependencies As String, _
                 ByVal lpServiceStartName As String, _
                 ByVal lpPassword As String, _
                 ByVal lpDisplayName As Long) As Long

他のString型のメンバはどうなの?ということになりそうですが、これらの値は 変更しない場合NULL(0)を与えてやればよいので、vbNullStringを渡せばいいでしょう。 (受け取ったQUERY_SERVICE_CONFIGの値をつかわないということです)

あと、OpenServiceの第三引数でChangeServiceConfigが変更できるように設定する 定数を設定し、もうひとつ、ChangeServiceConfigのdwServiceType、dwErrorControl引数に 変更しないよう設定する定数SERVICE_NO_CHANGEを定義します。

'OpenService用定数
Private Const SERVICE_CHANGE_CONFIG = &H2

'ChangeServiceConfig用定数
Private Const SERVICE_NO_CHANGE = &HFFFF

それでは実装します。

'スタートアップの種類変更
'引数 argsName : サービス名
'     arglStartMode : 変更するスタートアップの種類(Auto, Manual, Disable)
'戻値 成功 True, 失敗 False
Public Function ChangeStartMode(ByVal argsName As String, _
                                ByVal arglStartMode As Long) As Boolean
    Dim vyRet        As Boolean
    Dim vlHSCManager As Long
    Dim vlHService   As Long
    Dim vbyBuf()     As Byte
    Dim vlSize       As Long
    Dim vtSvcConf    As QUERY_SERVICE_CONFIG

    Do
        vlHSCManager = OpenSCManager("", "ServicesActive", SC_MANAGER_CONNECT)
        If vlHSCManager = 0 Then
            vyRet = False: Exit Do
        End If

        vlHService = OpenService(vlHSCManager, argsName, SERVICE_QUERY_CONFIG _
                                                      Or SERVICE_CHANGE_CONFIG)
        If vlHService = 0 Then
            vyRet = False: Exit Do
        End If

        ReDim vbyBuf(0)
        vlSize = 0
        If QueryServiceConfig(vlHService, vbyBuf(0), vlSize, vlSize) = 0 And _
                                Err.LastDllError <> ERROR_INSUFFICIENT_BUFFER Then
            vyRet = False: Exit Do
        End If

        ReDim vbyBuf(vlSize)
        If QueryServiceConfig(vlHService, vbyBuf(0), vlSize, vlSize) = 0 Then
            vyRet = False: Exit Do
        End If

        CopyMemory vtSvcConf, vbyBuf(0), LenB(vtSvcConf)        '(A)

        If ChangeServiceConfig(hService:=vlHService, _
                                dwServiceType:=SERVICE_NO_CHANGE, _
                                dwStartType:=arglStartMode, _
                                dwErrorControl:=SERVICE_NO_CHANGE, _
                                lpBinaryPathName:=vbNullString, _
                                lpLoadOrderGroup:=vbNullString, _
                                lpdwTagId:=0&, _
                                lpDependencies:=vbNullString, _
                                lpServiceStartName:=vbNullString, _
                                lpPassword:=vbNullString, _
                                lpDisplayName:=vtSvcConf.lpDisplayName) = 0 Then
            vyRet = False: Exit Do    
        End If

        vyRet = True

        Exit Do
    Loop

    If vlHService <> 0 Then CloseServiceHandle vlHService
    If vlHSCManager <> 0 Then CloseServiceHandle vlHSCManager

    ChangeStartMode = vyRet
End Function

(A)までは、スタートアップの種類の取得とほぼ同様です。ChangeServiceConfigの各引数に、 変更しない場合は、SERVICE_NO_CHANGEまたはvbNullStringを与えて、dwStartTypeだけ変更しています。

・最後に

これでAPIを使ったサービスの制御について終わりますが、他のAPIを使用する際にも 応用が利くように前提知識も少し書いたつもりです。いずれにしてもAPIはC言語で書かれているので メモリ周りを意識しながらのコーディングは避けられないと個人的には思いますが、VBA(VB6)は もともとそういうことを意識しなくてもプログラムできるように作られた言語のひとつだと思いますので、 無理して使う必要もないかもしれません。

次は、APIを使った方法からみると天国のようなやり方を書いてみます。