2017年2月11日土曜日

VB LocalDB とファイル操作 2-2

さて、 前回 の 続き です。
ファイルを操作する為に、 LocalDB のプロセスを、一旦、終了させるコードを考えます。
プロセス終了後の再起動は 元々、 LocalDB の仕組みに組み込まれた もの で賄えます。
つまり、ここでは、再起動させる為の事は 検討しなくても良い 筈です。


前回 VB LocalDB とファイル操作 1-2 の プロセスの動きを表にして見ます。
Process PID PID-parent parent Notice
1st App1 Start
App1.exe 9516 4768 explorer
- sqlservr.exe 5900 9516 App1
2nd LocalDB kill
App1.exe 9516 4768 explorer
3rd App1 Action
App1.exe 9516 4768 explorer
- sqlservr.exe 6272 9516 App1
4th App1 Close
sqlservr.exe 6272 9516 App1   !  Out of Explorer
5th App1 Start
App1.exe 5776 4768 explorer
sqlservr.exe 6272 9516 App1   !  Not Child Of App1
6th App1 Close
sqlservr.exe 6272 9516 App1  App1 Is Unknown

LocalDB 、 複雑です。
でも、 この相手を 判別して 停止(破棄) しないと、 ファイル操作は 叶いません ... 。
そして、 自分(対象アプリ)以外にも、 LocalDB が配下で 動いている可能性もあります。

さて、
検索して見ると分かりますが、sqlserver.exe と書かれた記事が多いです。
でも、Program Files を覗いて見れば ... 。
C:\Program Files\Microsoft SQL Server\110\LocalDB\Binn\sqlservr.exe
C:\Program Files\Microsoft SQL Server\120\LocalDB\Binn\sqlservr.exe
実行ファイル名は sqlservr.exe であって、sqlserver.exe ではありませんでした。
                             うっ、8 文字に合わせてる ... 。
                             LongName と 8.3Name が一致する様にか ... 。

どう言う事になるかと言えば、
Process ID を取得する為に、
Process.GetGetProcessesByName で sqlserver.exe を検索しても、LocalDB には辿り着きません。
更に、
名前 は Process Name (実行ファイル名 拡張子なし) ですので、注意ですね。 :( 。

msdn のヘルプにも記載があります。
  
https://msdn.microsoft.com/en-us/library/z3w4xdc9(v=vs.110).aspx Remarks 参照。
A processName can be specified for an executable file that is not currently running on the local computer, ...
中略 <omit>
The process name is a friendly name for the process, such as Outlook, that does not include the .exe extension or the path.

では、 お約束のコード。

検索しまくり! のコードなので、 その点はご容赦を。
また、 実際の Project から抜粋したコードですので、 これ単独では用をなしませんし、試せません。
是非、 LocalDB が稼働する状態のものに 組み込んで見て下さい。
尚、 一部、 クラスを流用させて戴きました。  Original : C# > VB.net に変換。
作者の tezaurismosis さまに感謝!。
                         記載の Code 内にも 記事の URL と お名前 とを貼ってあります。
                         ロシア語の QA サイトの様ですが、 ロシア語は全く分かりません ... 。

LocalDB のプロセスを取得し、
その親プロセスが残っていれば、 その名前を自アプリと照合し、 削除します。
親プロセスが消滅していれば、 その内容は確認出来ないので、 不本意ながら、 全て 削除です。
コード上では、 自プロセス名 ("MyApp" 赤字) をハードコーディング していますが、
汎用的にするなら、 p_parentName.StartsWith(My.Application.Info.AssemblyName) に書き替えて下さい。

Imports System.Runtime.InteropServices
Imports System.Diagnostics

Public Class Form1

    Private flagCompared As Integer = 0

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    End Sub

    Private Sub ButtonX_Click(sender As Object, e As EventArgs) Handles ButtonX.Click
        DoCopyTo()
    End Sub

    Private Sub DoCopyTo()
        Me.Cursor = Cursors.WaitCursor
        LocalDB_ProcessEnding()
        Me.Cursor = Nothing
        'Do Copy_
        ' *
        ' *        Add Your Code
        ' *
    End Sub

    Private Sub LocalDB_ProcessEnding()
        'LocalDBプロセスは、アプリケーションの子プロセスとしてスタート
        'このプロセスに最後に接続してから数分後、プロセスを閉じるためプロセスをシャットダウン
        '実行モード:SQL Server Compactはin-proc DLL、LocalDBは分離プロセス
        ' RefURL:http://sqlazure.jp/r/sql-server/233/

        'LocalDB Process Name "sqlservr" (sqlservr.exe)   Parent Name "MyApp" Only Kill

        'Get (MyApp) LocalDB Process
        Dim ps As System.Diagnostics.Process() = _
            System.Diagnostics.Process.GetProcessesByName("sqlservr")
        'System.Diagnostics.Process.GetProcessesByName("MyApp")

        'Closing
        For Each p As System.Diagnostics.Process In ps
            Dim procID As Integer = p.Id
            Dim p_parentID As Integer = GetPPID(procID)

            Try
                'Alive Parent Process
                Dim p_parentName = Process.GetProcessById(p_parentID).ProcessName
                If p_parentName.StartsWith("MyApp") Then         ' On VS : MyApp.vshost
                    'p.CloseMainWindow()
                    p.Kill()
                End If
            Catch ex As ArgumentException
                'Already Killed Parent Process > All LocalDB Close !!!
                p.Kill()
            End Try

            p.WaitForExit(10000)

        Next
    End Sub

    Private Function GetPPID(ByVal procID As Integer) As Integer
        Dim pProc As Process = myProcessEx.GetParentProcess(procID)
        If pProc Is Nothing Then
            Return -1
        Else
            Return pProc.Id
        End If
    End Function

End Class


NotInheritable Class myProcessEx

    'This Class Notice !!!
    'Original Code : on C#  >  Modify : Error No Catch, Return Nothing
    'Written by : tezaurismosis 18.12.2013, 23:06
    'Ref.URL : http://www.cyberforum.ru/csharp-beginners/thread1044660.html
    'Convert To VB.net : by http://codeconverter.sharpdevelop.net/SnippetConverter.aspx

    Private Sub New()
    End Sub

    'inner enum used only internally
    <Flags> _
    Private Enum SnapshotFlags As UInteger
        HeapList = &H1
        Process = &H2
        Thread = &H4
        [Module] = &H8
        Module32 = &H10
        Inherit = &H80000000UI
        All = &H1F
        NoHeaps = &H40000000
    End Enum

    'inner struct used only internally
    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
    Private Structure PROCESSENTRY32
        Const MAX_PATH As Integer = 260
        Friend dwSize As UInt32
        Friend cntUsage As UInt32
        Friend th32ProcessID As UInt32
        Friend th32DefaultHeapID As IntPtr
        Friend th32ModuleID As UInt32
        Friend cntThreads As UInt32
        Friend th32ParentProcessID As UInt32
        Friend pcPriClassBase As Int32
        Friend dwFlags As UInt32
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=MAX_PATH)> _
        Friend szExeFile As String
    End Structure

    <DllImport("kernel32", SetLastError:=True, CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
    Private Shared Function CreateToolhelp32Snapshot(<[In]> dwFlags As UInt32, <[In]> th32ProcessID As UInt32) As IntPtr
    End Function

    <DllImport("kernel32", SetLastError:=True, CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
    Private Shared Function Process32First(<[In]> hSnapshot As IntPtr, ByRef lppe As PROCESSENTRY32) As Boolean
    End Function

    <DllImport("kernel32", SetLastError:=True, CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
    Private Shared Function Process32Next(<[In]> hSnapshot As IntPtr, ByRef lppe As PROCESSENTRY32) As Boolean
    End Function

    <DllImport("kernel32", SetLastError:=True)> _
    Private Shared Function CloseHandle(<[In]> hObject As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    ' get the parent process given a pid
    Public Shared Function GetParentProcess(pid As Integer) As Process
        Dim parentProc As Process = Nothing
        Dim handleToSnapshot As IntPtr = IntPtr.Zero
        Try
            Dim procEntry As New PROCESSENTRY32()
            procEntry.dwSize = CType(Marshal.SizeOf(GetType(PROCESSENTRY32)), UInt32)
            handleToSnapshot = CreateToolhelp32Snapshot(CUInt(SnapshotFlags.Process), 0)
            If Process32First(handleToSnapshot, procEntry) Then
                Do
                    If pid = procEntry.th32ProcessID Then
                        parentProc = Process.GetProcessById(CInt(procEntry.th32ParentProcessID))
                        Exit Do
                    End If
                Loop While Process32Next(handleToSnapshot, procEntry)
            Else
                Throw New ApplicationException(String.Format("Failed with win32 error code {0}", Marshal.GetLastWin32Error()))
            End If
        Catch ex As Exception
            'Throw New ApplicationException("Can't get the process.", ex)      'Return Nothing
        Finally
            ' Must clean up the snapshot object!
            CloseHandle(handleToSnapshot)
        End Try
        Return parentProc
    End Function
End Class

これで、 LocalDB が動いている環境でも、一時停止の上、ファイル操作が可能になりました。
注意点は、 他の 親プロセスが消滅した LocalDB が稼働していた場合 不都合が無いか? です。  一応確認を。,



0 件のコメント:

コメントを投稿