Unexplained application crashes when using uncom

Started by Quin, October 08, 2024, 09:50:08 AM

Previous topic - Next topic

Quin

I appologize I don't have a more isolated test case, but I've seen this across two of my applications that use COM/Uncom.
If you run this code, hide a couple updates, and then press any key to exit, it crashes. However, if you just press enter without typing anything, it doesn't, which is what leads me to think it's COM/uncom.

#include <wuapi.h>

BCX_Show_COM_Errors(True)

Dim As Object Session, Searcher, SearchResult, Update, Updates
Dim NumUpdates, I
Comset Session = Com("Microsoft.Update.Session")
Comset Searcher = Session.CreateupdateSearcher()
Searcher.ClientApplicationID = "uphide"
Print "Checking for updates..."
Comset SearchResult = Searcher.Search("IsInstalled=0 And IsHidden=0")
NumUpdates = SearchResult.Updates.Count
If NumUpdates = 0 Then
    Print "No updates were found."
    End
End If
Print "Enter the numbers of the updates you want to hide, seperated by spaces, and press enter. Leave blank to exit."
Dim Index = 1
Comset Updates = SearchResult.Updates
For Each Item In Updates
    Dim Title$
    Title$ = Item.Title
    Print Str$(Index, 1) & ": " & Title$
    Index++
Next
Dim Input$
Input Input$
If Input$ = "" Then End
Dim NumSelections
Dim ToHide[100] As String
NumSelections = Split(ToHide, Input$, " ", 0)
For I = 0 To NumSelections - 1
    Dim CurIndex
    CurIndex = Val(ToHide[I])
    If CurIndex <= 0 Or CurIndex > NumUpdates Then Continue
    Comset Update = Updates.Item(CurIndex - 1)
    Dim Title$
    Title$ = Update.Title
    Update.Ishidden = True
    Print "Hid " & Title$
Next
Pause
Uncom(Searcher)
Uncom(Session)
Uncom(SearchResult)
Uncom(Update)
Uncom(Updates)

In order to see this crash in action above Windows 10 1607, you have to go to Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting and make a new DWORD called DontShowUI set to 0.
Is this something I'm doing? I have utterly no idea how to even begin to track this down, so any help would be greatly appreciated.
Thanks!
-Quin.
GitHub

MrBcx

When I comment out Update.Ishidden = True and place a pause after the last UNCOM
statement, it's easy to see that the UNCOM statements have completed without issue.

I suspect it's a permissions issue but I'm not an expert in this area.

I recommend that you try it in vbscript, to learn whether you achieve the desired results.

If it works in vbscript, it should work in BCX without much more effort.


airr

#2
Is it possible that the order of the UNCOM statements matter?  Generally it shouldn't, but.....

AIR.

Edit:  This is what Google's Gemini says about this:

Quote from: GEMINIYes, it's generally recommended to call Uncom statements in the reverse order that the corresponding objects were created.

This ensures that COM objects are released in the correct order, preventing potential memory leaks or other issues. When you release an object, its reference count is decremented. If the reference count reaches zero, the object is destroyed. By releasing objects in the reverse order of creation, you help ensure that all dependencies are properly released before an object is destroyed.

Here's a breakdown of why this is important:

    Dependency Management: If an object depends on another object, the dependent object should be released before the object it depends on. This prevents circular references and ensures that resources are released in the correct order.
    Error Handling: In case of errors or exceptions, releasing objects in reverse order can help prevent memory leaks or other issues that might arise if objects are not released properly.

While the COM system typically handles object destruction automatically, following this recommendation can contribute to more robust and predictable code behavior.

Quin

#3
Hi Kevin,
Thanks for your reply! I however doubt it's a permissions issue, because I observe the same behavior with another BCX app of mine that doesn't require UAC or anything even cloes to it. It's slightly larger, but here it is:

#include <shellapi.h>

Gui "BatteryMeterWndClass"

Const ID_TrayIcon = 101

Enum
    ID_TrayCallback = WM_USER + 1
    IDT_CheckBattery
    ID_MenuSettings
    ID_MenuExit
    ID_IntervalLabel
    ID_IntervalField
End Enum

Global Note As NOTIFYICONDATA
Global MainForm As HWND
Global ReportInterval = 10
Global Voice As Object

Sub Formload
    MainForm = Bcx_Form("BatteryMeter", 0, 0, 150, 100)
    Note.cbSize = Sizeof(Note)
    Note.hWnd = MainForm
    Note.uID = ID_TrayIcon
    Note.uFlags |= NIF_MESSAGE | NIF_TIP
    Note.uCallbackMessage = ID_TrayCallback
    ! lstrcpy(Note.szTip, TEXT("Battery Meter"));
    Shell_NotifyIcon(NIM_ADD, &Note)
    Voice = Com("SAPI.SpVoice")
    Dim Power As SYSTEM_POWER_STATUS
    GetSystemPowerStatus(&Power)
    If Power.BatteryFlag = 128 Then ' No system battery
        Voice.Speak "This computer does not appear to have a battery. Exiting..."
        End
    End If
    If Exist(Curdir$ & "\\BatteryMeter.ini") Then ReportInterval = GetPrivateProfileInt("Settings", "Interval", 10, Curdir$ & "\\BatteryMeter.ini")
    Beep(440, 100)
    Voice.Speak "Running"
    SetTimer(MainForm, IDT_CheckBattery, 100, NULL)
End Sub

Begin Events
    Select Case Cbmsg
    Case WM_DESTROY, WM_QUIT, WM_CLOSE
        Uncom(Voice)
        Shell_NotifyIcon(NIM_DELETE, &Note)
        End
        Exit Function
    Case ID_TrayCallback Then
        If Cblparam = WM_LBUTTONDOWN Then
            PostMessage(hWnd, WM_QUIT, 0, 0)
            Exit Function
        Else If Cblparam = WM_RBUTTONDOWN Then
            Dim Menu As HMENU
            Menu = CreatePopupMenu()
            InsertMenu(Menu, -1, MF_BYPOSITION | MF_STRING, ID_MenuSettings, "&Settings")
            InsertMenu(Menu, -1, MF_BYPOSITION | MF_STRING, ID_MenuExit, "E&xit")
            Dim pt As POINT
            GetCursorPos(&pt)
            SetForegroundWindow(hWnd)
            TrackPopupMenuEx(Menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, MainForm, NULL)
        End If
    Case WM_TIMER Then
        Call CheckBattery
    Case WM_COMMAND
        Select Case Cbctl
        Case ID_MenuExit
            PostMessage(hWnd, WM_QUIT, 0, 0)
            Exit Function
        Case ID_MenuSettings
            Bcx_Dialog(SettingsDlgProc, "Settings", MainForm, 0, 0, 300, 150)
            Exit Function
        End Select
    End Select
End Events

Begin Dialog As SettingsDlgProc
    Static As HWND IntervalLabel, IntervalField, SaveBtn, CancelBtn
    Select Case Cbmsg
    Case WM_INITDIALOG
        IntervalLabel = Bcx_Label("&Interval to report battery percentage in", hWnd, ID_IntervalLabel, 5, 5, 30, 5)
        IntervalField = Bcx_Input(Str$(ReportInterval, 1), hWnd, ID_IntervalField, 5, 15, 50, 20)
        SaveBtn = Bcx_Button("&OK", hWnd, IDOK, 100, 10, 30, 30)
        CancelBtn = Bcx_Button("&Cancel", hWnd, IDCANCEL, 100, 50, 30, 30)
        Show(hWnd)
        SetFocus(IntervalField)
    Case WM_COMMAND
        Select Case Cbctl
        Case IDCANCEL
            Closedialog
        Case IDOK
            ReportInterval = Val(Bcx_Get_Text$(IntervalField))
            WritePrivateProfileString("Settings", "Interval", Str$(ReportInterval, 1), Curdir$ & "\\BatteryMeter.ini")
            Closedialog
        End Select
    End Select
End Dialog

Sub CheckBattery
    Static LastReported = 0
    Dim Percentage
    Dim Power As SYSTEM_POWER_STATUS
    GetSystemPowerStatus(&Power)
    Percentage = Power.BatteryLifePercent
    If Percentage % ReportInterval = 0 And LastReported <> Percentage Then
        Dim Frequency = 260 + ((Percentage - 10) / (100 - 10)) * (440 - 260)
        Beep(Frequency, 700)
        Dim ToSpeak As String
        If Percentage = 100 Then
            ToSpeak$ = "Battery is full"
        Else
            ToSpeak$ = "Battery " & Str$(Percentage, 1) & "%"
        End If
        Voice.Speak ToSpeak$
        LastReported = Percentage
    End If
End Sub

If I right click and press exit in the context menu, it crashes. It however doesn't happen if I left click on the icon directly to exit. This is utterly baffling to me. :(
-Quin.
GitHub

Quin

Quote from: airr on October 08, 2024, 05:47:22 PM
Is it possible that the order of the UNCOM statements matter?  Generally it shouldn't, but.....

AIR.

Edit:  This is what Google's Gemini says about this:

Quote from: GEMINIYes, it's generally recommended to call Uncom statements in the reverse order that the corresponding objects were created.

This ensures that COM objects are released in the correct order, preventing potential memory leaks or other issues. When you release an object, its reference count is decremented. If the reference count reaches zero, the object is destroyed. By releasing objects in the reverse order of creation, you help ensure that all dependencies are properly released before an object is destroyed.

Here's a breakdown of why this is important:

    Dependency Management: If an object depends on another object, the dependent object should be released before the object it depends on. This prevents circular references and ensures that resources are released in the correct order.
    Error Handling: In case of errors or exceptions, releasing objects in reverse order can help prevent memory leaks or other issues that might arise if objects are not released properly.

While the COM system typically handles object destruction automatically, following this recommendation can contribute to more robust and predictable code behavior.
Hi Airr,
Thanks as well for your reply! I tried moving them around, but it didn't seem to help. My other application also only has one Uncom statement and it exhibits the same behavior.
-Quin.
GitHub

Quin

Part of me wonders if this dialog is semi-unreliable, when I have it enabled and build my project with Pelles-C in BED I get two errors about Pelles ISO C compiler having stopped working that I have to close before my app finishes building. Maybe there's a reason they turn it off by default now, although I am still incredibly curious what BCX is doing in these cases that's causing it to happen.
-Quin.
GitHub

MrBcx

Quinn,

I'm having trouble following your focus.

The real problem that I see is that Update.Ishidden = True simply does not work
in a BCX COM app, that's why I suggested you try it in a vbscript. 



Quin

Quote from: MrBcx on October 09, 2024, 03:34:31 PM
Quinn,

I'm having trouble following your focus.

The real problem that I see is that Update.Ishidden = True simply does not work
in a BCX COM app, that's why I suggested you try it in a vbscript.
Kevin,
That line does work in a BCX console app, you just need to run it as administrator. My application linked against a manifest to request UAC, and it always worked like it should. Only after the pause would it crash.
-Quin.
GitHub

MrBcx

Quote from: Quin on October 09, 2024, 04:12:00 PM
Kevin,
That line does work in a BCX console app, you just need to run it as administrator.
My application linked against a manifest to request UAC, and it always worked like it should.
Only after the pause would it crash.

You must have the magic touch because those steps don't work on my machine (Windows 11 pro)

C'est la Vie ...

Quin

Interesting. What about if you download it from here and try? https://quinbox.xyz/files/uphide.exe
This is baffling to me. It's as if something after the pause is making it crash, because after I press a key, it makes my screen reader lock up for about a second then I get the crash dialog.
-Quin.
GitHub

airr

Quin,

I ran your Battery app through WinDBG (first time using it) and got this when I tried exiting via the menu (Left OR Right click)

(3a98.1ad4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
USER32!GetMessageBoxFontForDpi+0x5c:
00007fff`e48736ec 0f29442430      movaps  xmmword ptr [rsp+30h],xmm0 ss:00000000`00dff0a8=000000000000000000000000096abba0


No issues when I just click on the tray icon itself.

AIR.

Quin

Airr,
Thanks for testing and confirming, and for the debugger output. Your results mirror mine, left clicking the icon doesn't cause a crash, only exiting through the menu does.
I have utterly no idea what that exception means, it turns out you can spend hours and hours in WinDBG breaking open propriotary asset storage mechanisms and still be utterly baffled by its output.
I wonder if something is slightly busted in the menu code, as I did see another issue with this program in one case where the settings dialog wouldn't open and instead kept the keyboard focus in the menu. It was on Windows 10 which I sadly don't have any laptops to test on.
-Quin.
GitHub

airr

Hi again, Quin.

I changed FormLoad to this:
Sub Formload
    MainForm = Bcx_Form("BatteryMeter", 0, 0, 150, 100)
    Note.cbSize = Sizeof(Note)
    Note.hWnd = MainForm
    Note.uID = ID_TrayIcon
    Note.uFlags |= NIF_MESSAGE | NIF_TIP
    Note.uCallbackMessage = ID_TrayCallback
    Note.szTip$ = "Battery Meter"
    ' ! lstrcpy(Note.szTip, TEXT("Battery Meter"));
    Shell_NotifyIcon(NIM_ADD, &Note)
    Voice = Com("SAPI.SpVoice")
    Dim Power As SYSTEM_POWER_STATUS
    GetSystemPowerStatus(&Power)
    If Power.BatteryFlag = 128 Then ' No system battery
        Voice.Speak "This computer does not appear to have a battery. Exiting..."
        UNCOM(Voice)
        End
    End If
    If Exist(Curdir$ & "\\BatteryMeter.ini") Then ReportInterval = GetPrivateProfileInt("Settings", "Interval", 10, Curdir$ & "\\BatteryMeter.ini")
    Beep(440, 100)
    Voice.Speak "Running"
    UNCOM(Voice)
    SetTimer(MainForm, IDT_CheckBattery, 100, NULL)
End Sub


And CheckBattery to this:

Sub CheckBattery
    Static LastReported = 0
    Dim Percentage
    Dim Power As SYSTEM_POWER_STATUS
    Dim Voice As Object
    GetSystemPowerStatus(&Power)
    Percentage = Power.BatteryLifePercent
    If Percentage % ReportInterval = 0 And LastReported <> Percentage Then
        Dim Frequency = 260 + ((Percentage - 10) / (100 - 10)) * (440 - 260)
        Beep(Frequency, 700)
        Dim ToSpeak As String
        If Percentage = 100 Then
            ToSpeak$ = "Battery is full"
        Else
            ToSpeak$ = "Battery " & Str$(Percentage, 1) & "%"
        End If
        Voice = Com("SAPI.SpVoice")
        Voice.Speak ToSpeak$
        UNCOM(Voice)
        LastReported = Percentage
    End If
End Sub


And the crashing stopped.

Don't have an explanation for it, best guess is that it's a scoping issue with the Voice object corrupting something...

AIR.

Quin

Airr,
You're indeed correct, thanks for your testing! However this does bafel me, and I'd ideally like to find a different solution. This is definitely not how you were meant to use COM objects...
Maybe making it a global would help?
-Quin.
GitHub

airr

The Voice Object is already a global.

Going back to your original code, I tried this:

        Case ID_MenuExit
            If IsObject(Voice) Then UNCOM(Voice)
            PostMessage(hWnd, WM_QUIT, 0, 0)
            Exit Function


And the crashing went away...

AIR.