บั๊กในฟังก์ชั่น Shell( )
กระทู้เก่าบอร์ด อ.Yeadram

 2,842   1
URL.หัวข้อ / URL
บั๊กในฟังก์ชั่น Shell( )

Windows XP Service Pack 2,
Access 2002, Access 2003

สวัสดีครับ วันนี้ก็มีเรื่องมาเล่ากันอีกครั้ง

เรื่องนี้เกิดขึ้นเมื่อ แต่เดิมทีผมจะใช้ฟังก์ชั่นที่เขาตั้งชื่อว่า ShellWait( ) (http://www.mvps.org/access/api/api0004.htm) เพื่อสั่งงานที่เป็น command-line แล้วให้ Access รอจนกว่างานนั้นจะเสร็จจึงทำงานต่อไปได้ (เพราะคำสั่ง Shell( ) ของ VBA เองเป็นแบบ asynchronous คือ Access จะไม่รอว่าคำสั่งที่สั่งไปนั้นจะทำงานเสร็จหรือยัง แต่ Access จะทำงานคำสั่งต่อไปทันทีเลย) แต่ ShellWait( ) มีข้อเสียคือมันจะกิน CPU 100% ทำให้ดูเหมือนว่า Access จะแฮงก์ไปเลยจนกว่า command-line นั้นจะทำงานเสร็จ ผมเลยไปหาว่าจะมีใครแก้ปัญหาจอง CPU ของ ShellWait( ) หรือไม่ ก็ไปพบว่ามีฟังก์ชั่นที่ชื่อ ShellAndWait( ) ตามข่างล่างนี้ ผมเอามาทดลองก็ปรากฏว่าทำงานได้ดี ไม่จอง CPU เลย ตรงตามความต้องการทุกประการ ก็ตัดสินใจเอาไปใส่ในโปรแกรมแล้วติดตั้งให้ลูกค้า

วันต่อมา ... ลูกค้าบอกว่ามีบางเครื่องจะค้างเมื่อทำโปรแกรมนี้ ผมก็สงสัยว่าทำไมทำไม่ได้และแถมเป็นบางเครื่องอีกต่างหาก ก็แน่ใจหล่ะว่าต้องมีปัญหาใน procedure ShellAndWait( ) แต่ยังไม่รู้ว่าจุดไหน จนผมไปหาในเน็ท (http://www.vb-helper.com/howto_shell_get_hwnd.html) เขาบอกว่าคำสั่ง Shell( ) ตั้งแต่ Access 2002 เป็นต้นไป ได้เปลี่ยนค่าที่ส่งคืนมา จากเดิมเป็นค่า ProcessID ไปเป็นค่า True/False แทน ซึ่งต่างจากที่อธิบายใน Help File ของ Access เอง ผลจึงเป็นว่ามันจะลูปไปเรื่อยๆๆๆๆ ไม่สิ้นสุด แต่อย่างที่บอก บางเครื่องเป็น บางเครื่องไม่เป็น ดังนั้นค่าที่ส่งกลับจาก Shell( ) อาจจะไม่แน่นอนเสียแล้ว ไม่ทราบว่าเป็น bug หรือไม่อย่างไร ใครจะใช้ก็คงต้องเสี่ยงนะครับ

สุดท้าย... ก็เลยต้องกลับไปใช้ ShellWait( ) เหมือนเดิม แน่นอนกว่า

ปล. ยังมีการรายงานจากบางเวปด้วยว่าเมื่อสั่งรันโปรแกรมด้วยฟังก์ชั่น Shell( ) ใน Office 2007 แล้ว จะพบข้อความ “Invalid procedure call or argument” ด้วย

'***************** Code for ShellWait **********
'This code was originally written by Terry Kreft.
'It is not to be altered or distributed,
'except as part of an application.
'You are free to use it in any application,
'provided the copyright notice is left unchanged.
'
'Code Courtesy of
'Terry Kreft
Private Const STARTF_USESHOWWINDOW& = &H1
Private Const NORMAL_PRIORITY_CLASS = &H20&
Private Const INFINITE = -1&

Private Type STARTUPINFO
    cb As Long
    lpReserved As String
    lpDesktop As String
    lpTitle As String
    dwX As Long
    dwY As Long
    dwXSize As Long
    dwYSize As Long
    dwXCountChars As Long
    dwYCountChars As Long
    dwFillAttribute As Long
    dwFlags As Long
    wShowWindow As Integer
    cbReserved2 As Integer
    lpReserved2 As Long
    hStdInput As Long
    hStdOutput As Long
    hStdError As Long
End Type

Private Type PROCESS_INFORMATION
    hProcess As Long
    hThread As Long
    dwProcessID As Long
    dwThreadID As Long
End Type

Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal _
    hHandle As Long, ByVal dwMilliseconds As Long) As Long
    
Private Declare Function CreateProcessA Lib "kernel32" (ByVal _
    lpApplicationName As Long, ByVal lpCommandLine As String, ByVal _
    lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
    ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
    ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As Long, _
    lpStartupInfo As STARTUPINFO, lpProcessInformation As _
    PROCESS_INFORMATION) As Long
    
Private Declare Function CloseHandle Lib "kernel32" (ByVal _
    hObject As Long) As Long
    
Public Sub ShellWait(Pathname As String, Optional WindowStyle As Long)
    Dim proc As PROCESS_INFORMATION
    Dim start As STARTUPINFO
    Dim ret As Long
    ' Initialize the STARTUPINFO structure:
    With start
        .cb = Len(start)
        If Not IsMissing(WindowStyle) Then
            .dwFlags = STARTF_USESHOWWINDOW
            .wShowWindow = WindowStyle
        End If
    End With
    ' Start the shelled application:
    ret& = CreateProcessA(0&, Pathname, 0&, 0&, 1&, _
            NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
    ' Wait for the shelled application to finish:
    ret& = WaitForSingleObject(proc.hProcess, INFINITE)
    ret& = CloseHandle(proc.hProcess)
End Sub
'***************** Code End ****************


'***************** Code for ShellAndWait **********
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, _
                                             ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Public Sub ShellAndWait(ByVal strProg As String, ByVal lStyle As VbAppWinStyle)
   Dim ProcessId As Long
   Dim ProcessHandle As Long
   Const Access As Long = &H100000
   
   ProcessId = Shell(strProg, lStyle)
   Do
      ProcessHandle = OpenProcess(Access, False, ProcessId)
      If ProcessHandle <> 0 Then
        CloseHandle ProcessHandle
      End If
      DoEvents
      Sleep 200
   Loop Until ProcessHandle = 0
End Sub
' ----------------------------------------------------------------------------------------------------------------------

1 Reply in this Topic. Dispaly 1 pages and you are on page number 1

1 @R00104
ขอบคุณครับอาจารย์ ได้ความรู้เพิ่มครับ
มันลึกมากเลยนะครับ
ผมไม่เคยรู้มาก่อนเลยว่าเบื้องหลังคำสั่ง shell() ยังมีเรื่องให้เราต้องคิด ต้องติดตามอีกนะ
@ ประกาศใช้งานเว็บบอร์ดใหม่ => บอร์ดเรียนรู้ Access สำหรับคนไทย
แล้วจะใส่ลิ้งอ้างอิงมาที่โพสต์เก่านี้หรือไม่ก็ตามสะดวกครับ
Time: 0.3163s