Author Topic: Stand Alone App to kill AutoCAD on Fatal Error  (Read 2189 times)

0 Members and 1 Guest are viewing this topic.

jparts

  • Guest
Stand Alone App to kill AutoCAD on Fatal Error
« on: December 27, 2012, 07:16:26 AM »
Hi All,

I'm trying to write a stand alone application that monitors the state of autocad. We are running autocad on a server to generate pdf/dxf/dwg files fully automatically. The jobs for autocad on this batchserver are generated automatically from our PLM system and picked up automatically by an addin in autocad. The problem however is keeping autocad alive without user interference. The biggest problem I'm facing is to kill AutoCAD when it runs into a Fatal Error  :evil: . If autocad now runs into a fatal error the whole process of creating pdf/dxf or dwg files is now stopped until someone manually closes autocad and restart it.

So I hope someone here can help me how to "catch" a fatal error in autocad.

Thanks alot!

Joris

This is what I've got so far...:

Code: [Select]

Imports System
Imports System.Runtime.InteropServices
Imports Autodesk.AutoCAD.Interop

Public Class Form1

    Private Property acApp As AcadApplication = Nothing
    Private Property acDoc As AcadDocument = Nothing
    Private Const progID As String = "AutoCAD.Application.18"

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Me.lblText1.Text = "Checking for AutoCAD Dialogs"
        Me.btnStart.Text = "Start"
        Me.btnStart.Enabled = True
        Me.Update()
    End Sub

    Private Sub btnStart_Click(sender As System.Object, e As System.EventArgs) Handles btnStart.Click
        Select Case Me.btnStart.Text
            Case "Start"
                Me.btnStart.Text = "Stop"
                acApp = Nothing
                acDoc = Nothing
                bgw.RunWorkerAsync()
            Case "Stop"
                Me.btnStart.Text = "Start"
                bgw.CancelAsync()
        End Select
    End Sub

    Private Sub bgw_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgw.DoWork
        Do
            If (bgw.CancellationPending) Then
                e.Cancel = True
                Exit Do
            End If
            Try
                acApp = CType(Marshal.GetActiveObject(progID), AcadApplication)
                acDoc = acApp.ActiveDocument
                'catch dialogs that require user input and close autocad
                AddHandler acApp.BeginModal, AddressOf CloseAutoCAD

                'catch a fatal error and close autocad
                '...?
                '...?
                '...?

            Catch ex As System.Exception
                acApp = Nothing
                acDoc = Nothing
            End Try
            Do While acDoc IsNot Nothing
                If (bgw.CancellationPending) Then
                    e.Cancel = True
                    RemoveHandler acApp.BeginModal, AddressOf CloseAutoCAD
                    acApp = Nothing
                    acDoc = Nothing
                    Exit Do
                End If
                If Process.GetProcessesByName("acad").Count = 0 Then
                    Exit Do
                End If
                Threading.Thread.Sleep(500)
            Loop
            Threading.Thread.Sleep(500)
        Loop
    End Sub

    Public Sub CloseAutoCAD()
        Dim prs = Process.GetProcessesByName("acad")
        For Each pr As Process In prs
            pr.Kill()
        Next
        Do
        Loop Until Process.GetProcessesByName("acad").Count = 0
    End Sub

End Class
« Last Edit: December 27, 2012, 09:47:23 AM by jparts »

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Stand Alone App to kill AutoCAD on Fatal Error
« Reply #1 on: December 27, 2012, 10:51:03 AM »
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

jparts

  • Guest
Re: Stand Alone App to kill AutoCAD on Fatal Error
« Reply #2 on: December 28, 2012, 06:04:43 AM »
Hi Keith,

Thanks for the tip!

However this method doesn't seem to work, because when a fatal error occurs the process still returns that the program is running i.s.o. not responding.

I found a solution anyway (thanks to http://cjwdev.wordpress.com/2010/06/03/get-visible-windows/ ). With this method I can catch the fatal error by the title of the window that appears ("AutoCAD Error Aborting").

If anybody is interested here is my working code:

Code: [Select]
Imports System
Imports System.Diagnostics
Imports System.Runtime.InteropServices
Imports Autodesk.AutoCAD.Interop

Public Class Form1

    Private Property acApp As AcadApplication = Nothing
    Private Property acDoc As AcadDocument = Nothing
    Private Const progID As String = "AutoCAD.Application.18"

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Me.lblText1.Text = "Checking for AutoCAD Dialogs"
        Me.btnStart.Text = "Start"
        Me.btnStart.Enabled = True
        Me.Update()
    End Sub

    Private Sub btnStart_Click(sender As System.Object, e As System.EventArgs) Handles btnStart.Click
        Select Case Me.btnStart.Text
            Case "Start"
                Me.btnStart.Text = "Stop"
                acApp = Nothing
                acDoc = Nothing
                bgw.RunWorkerAsync()
            Case "Stop"
                Me.btnStart.Text = "Start"
                bgw.CancelAsync()
        End Select
    End Sub

    Private Sub bgw_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgw.DoWork
        Do
            If (bgw.CancellationPending) Then
                e.Cancel = True
                Exit Do
            End If
            Try
                acApp = CType(Marshal.GetActiveObject(progID), AcadApplication)
                acDoc = acApp.ActiveDocument
                'catch dialogs that require user input and close autocad
                AddHandler acApp.BeginModal, AddressOf CloseAutoCAD
            Catch ex As System.Exception
                acApp = Nothing
                acDoc = Nothing
            End Try

            Do While acDoc IsNot Nothing
                If (bgw.CancellationPending) Then
                    e.Cancel = True
                    RemoveHandler acApp.BeginModal, AddressOf CloseAutoCAD
                    acApp = Nothing
                    acDoc = Nothing
                    Exit Do
                End If

                'catch a fatal error and close autocad
                Dim Windows = GetWindows()
                If Windows.Count <> 0 Then
                    Dim ErrorFound As Boolean = False
                    For Each Window As ManagedWindow In Windows
                        Select Case Window.Title.ToUpper
                            Case "AUTOCAD ERROR ABORTING"
                                ErrorFound = True
                            Case "FATAL ERROR"
                                ErrorFound = True
                            Case "AUTOCAD"
                                ErrorFound = True
                            Case "NETWORK LICENSE ERROR"
                                ErrorFound = True
                        End Select
                    Next
                    If ErrorFound = True Then
                        CloseAutoCAD()
                    End If
                End If

                If Process.GetProcessesByName("acad").Count = 0 Then
                    Exit Do
                End If

                Threading.Thread.Sleep(500)
            Loop
            Threading.Thread.Sleep(500)
        Loop
    End Sub

    Public Sub CloseAutoCAD()
        Dim prs = Process.GetProcessesByName("acad")
        For Each pr As Process In prs
            pr.Kill()
        Next
        Do
        Loop Until Process.GetProcessesByName("acad").Count = 0
    End Sub

    Public Shared Function GetWindows() As List(Of ManagedWindow)
        Dim WindowList As New List(Of ManagedWindow)
        Dim ListHandle As GCHandle = GCHandle.Alloc(WindowList)
        Try
            NativeAPI.EnumWindows(New NativeAPI.EnumWindowsProc(AddressOf EnumWindowsCallBack), GCHandle.ToIntPtr(ListHandle))
        Finally
            ListHandle.Free()
        End Try
        Return WindowList
    End Function

    Private Shared Function EnumWindowsCallBack(ByVal handle As IntPtr, ByVal lParam As IntPtr) As Boolean
        If NativeAPI.IsWindowVisible(handle) Then
            Dim TitleBuilder As New System.Text.StringBuilder(NativeAPI.GetWindowTextLength(handle) + 1)
            Dim WndList As List(Of ManagedWindow) = DirectCast(GCHandle.FromIntPtr(lParam).Target, List(Of ManagedWindow))
            NativeAPI.GetWindowText(handle, TitleBuilder, TitleBuilder.Capacity)
            Dim ProcessID As Integer = -1
            NativeAPI.GetWindowThreadProcessId(handle, ProcessID)
            Dim ClassNameBuilder As New System.Text.StringBuilder(255)
            NativeAPI.GetClassName(handle, ClassNameBuilder, ClassNameBuilder.Capacity)
            WndList.Add(New ManagedWindow(handle, TitleBuilder.ToString, Process.GetProcessById(ProcessID), ClassNameBuilder.ToString))
        End If
        Return True
    End Function

End Class


Public Class ManagedWindow

    Private _Title As String = String.Empty

    Public Property Title() As String
        Get
            Return _Title
        End Get
        Set(ByVal value As String)
            _Title = value
        End Set
    End Property

    Private _Handle As IntPtr

    Public Property Handle() As IntPtr
        Get
            Return _Handle
        End Get
        Set(ByVal value As IntPtr)
            _Handle = value
        End Set
    End Property

    Private _OwningProcess As Process

    Public Property OwningProcess() As Process
        Get
            Return _OwningProcess
        End Get
        Set(ByVal value As Process)
            _OwningProcess = value
        End Set
    End Property

    Private _ClassName As String = String.Empty

    Public Property ClassName() As String
        Get
            Return _ClassName
        End Get
        Set(ByVal value As String)
            _ClassName = value
        End Set
    End Property


    Public Sub New()
    End Sub


    Public Sub New(ByVal Hwnd As IntPtr, ByVal TitleText As String, ByVal Owner As Process, ByVal NativeClassName As String)
        _Handle = Hwnd
        _Title = TitleText
        _OwningProcess = Owner
        _ClassName = NativeClassName
    End Sub


    Public Overrides Function ToString() As String
        If OwningProcess Is Nothing Then
            Return Handle.ToString & " —- " & ClassName & " —- " & Title
        Else
            Return Handle.ToString & " —- " & ClassName & " —- " & Title & " —- " & OwningProcess.ProcessName
        End If
    End Function

End Class

Public Class NativeAPI
    <UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)> _
    Public Delegate Function EnumWindowsProc(ByVal hwnd As System.IntPtr, ByVal lparam As System.IntPtr) As Boolean

    <DllImportAttribute("user32.dll", EntryPoint:="EnumWindows")> _
    Public Shared Function EnumWindows(ByVal lpEnumFunc As EnumWindowsProc, <MarshalAsAttribute(UnmanagedType.SysInt)> ByVal lParam As IntPtr) As <MarshalAsAttribute(UnmanagedType.Bool)> Boolean
    End Function

    <DllImportAttribute("user32.dll", EntryPoint:="IsWindowVisible")> _
    Public Shared Function IsWindowVisible(<InAttribute()> ByVal hWnd As System.IntPtr) As <MarshalAsAttribute(UnmanagedType.Bool)> Boolean
    End Function

    <DllImportAttribute("user32.dll", EntryPoint:="GetWindowTextW")> _
    Public Shared Function GetWindowText(<InAttribute()> ByVal hWnd As System.IntPtr, <OutAttribute(), MarshalAsAttribute(UnmanagedType.LPWStr)> ByVal lpString As System.Text.StringBuilder, ByVal nMaxCount As Integer) As Integer
    End Function

    <DllImportAttribute("user32.dll", EntryPoint:="GetWindowTextLengthW")> _
    Public Shared Function GetWindowTextLength(<InAttribute()> ByVal hWnd As System.IntPtr) As Integer
    End Function

    <DllImportAttribute("user32.dll", EntryPoint:="GetWindowThreadProcessId")> _
    Public Shared Function GetWindowThreadProcessId(<InAttribute()> ByVal hWnd As System.IntPtr, <OutAttribute()> ByRef lpdwProcessId As Integer) As UInteger
    End Function

    <DllImportAttribute("user32.dll", EntryPoint:="GetClassNameW")> _
    Public Shared Function GetClassName(<InAttribute()> ByVal hWnd As System.IntPtr, <OutAttribute(), MarshalAsAttribute(UnmanagedType.LPWStr)> ByVal lpClassName As System.Text.StringBuilder, ByVal nMaxCount As Integer) As Integer
    End Function
End Class