简体   繁体   中英

OLE Automation - Ghost Excel Instance in Task Manager

I am using wxAutomationObject to export data to MS Excel. I have created a few helper classes ExcelApp, ExcelWorkbook etc... all of which inherits from wxAutomationObject .

Briefly, ExcelApp is as follows:

class ExcelApp :public wxAutomationObject
{

    public:

        ExcelApp(WXIDISPATCH* dispatchPtr = NULL);

        ~ExcelApp() = default;

        
        void Quit();

        std::unique_ptr<ExcelWorkbook> CreateWorkbook(bool Visible = true);

        std::vector<std::unique_ptr<ExcelWorkbook>> GetOpenedWorkbooks();

        long GetNumberofOpenedWorkbooks() const;


    private:
        wxAutomationObject m_App;   
};

The constructor is implemented as follows:

ExcelApp::ExcelApp(WXIDISPATCH* dispatchPtr):wxAutomationObject(dispatchPtr)
{
        if (!m_App.GetInstance("Excel.Application"))
            throw std::exception("An instance of Excel object could not be created.");
}

And to create a workbook, I used to following code:

std::unique_ptr<ExcelWorkbook> ExcelApp::CreateWorkbook(bool Visible)
{
        auto wb = std::make_unique<ExcelWorkbook>();

        bool suc = m_App.GetObject(*wb, "Workbooks.Add");

        if (!suc)
            return nullptr;


        if (Visible)
            m_App.PutProperty("Visible", true);

        
        return std::move(wb);
}

This whole OLE implementation is part of a dynamic menu. Part of the code for the event handler for menu is:

void MyFrame::OnExportToExcelButtonHandler(wxCommandEvent& event)
{
    
        auto item = static_cast<wxMenu*>(event.GetEventObject());

        std::unique_ptr<ExcelWorkbook> xlsWB ((ExcelWorkbook*)event.GetEventUserData());

        
        bool CreateNewWB = false;
    
        try
        {
            

            //Export either the entire workbook or the active worksheet to a new Workbook
            if (xlsWB == nullptr)
            {
                auto xlsApp = ExcelApp(); 

                xlsWB = std::move(xlsApp.CreateWorkbook());

                CreateNewWB = true;
            }

In terms of exporting data and formatting, everything works fine. However, after closing the created Workbooks Excel.exe still remains in the taskbar list. I wonder what I could be missing?

By the way, I tried the very basic sample shipped with wxWidgets and there seems to remain no ghost instances of Excel after quitting. I

I am using wxWidgets 3.1.4 on Windows 10 using VS 2019.

EDIT 1:

The ribbon button that generates the dynamic menu:

void MyFrame::OnExportToExcelButtonClicked(wxRibbonButtonBarEvent& event)
{
    auto excel = sys::win32::ole::ExcelApp();


        auto PrepareMenu = [&](wxMenu* Menu)
        {

            try
            {
                

                wxMenuItem* item1 = Menu->Append(wxID_ANY, "New Excel Workbook");
                item1->SetBitmap(wxArtProvider::GetBitmap(wxART_NEW));

                Bind(wxEVT_COMMAND_MENU_SELECTED, &MyFrame::OnExportToExcelButtonHandler, this, item1->GetId());


                auto wbs = excel.GetOpenedWorkbooks();

                if (wbs.size() > 0)
                {
                    for (int i = 0; i < wbs.size(); ++i)
                    {
                        auto ExcelWB = std::move(wbs[i]);

                        wxMenuItem* item1 = Menu->Append(wxID_ANY, ExcelWB->GetFullName());

                        Bind(wxEVT_COMMAND_MENU_SELECTED, &MyFrame::OnExportToExcelButtonHandler, this, item1->GetId(), wxID_ANY, ExcelWB.release());

                        
                    }
                }

            }
}

The problem seems to originate from the following line:

Bind(wxEVT_COMMAND_MENU_SELECTED, &MyFrame::OnExportToExcelButtonHandler, this, item1->GetId(), wxID_ANY, ExcelWB.release());

The ExcelWB pointer is now owned by the wxWidgets system and therefore a reference remains and thus the ghost Excel.exe remains.

To solve it, in the following procedure I added:

void MyFrame::OnExportToExcelButtonHandler(wxCommandEvent& event)
{
    Unbind(wxEVT_COMMAND_MENU_SELECTED, &MyFrame::OnExportToExcelButtonHandler, this, event.GetId(), wxID_ANY, xlsWB.get());

But the problem still remains. I am not sure how to properly get rid of the pointer that is now owned by the menu. I redesigned some parts with shared_ptr and it did not help.

You already may be doing that but just to be sure.

When debugging such things, make sure the application instance is always visible so you can see when it for example gets stuck waiting on a user input, eg, asking to save a modified workbook, preventing it from quitting.

I have written wxWidgets-based MS Excel automation wrapper wxAutoExcel (it uses a shared instead of unique pointer for Excel objects) and I have never encountered application ghosts, except when I terminated my application when debugging, without disposing Excel objects.

BTW, I do not get why your ExcelApp both derives from wxAutomationObject and has wxAutomationObject as a member variable ( m_App ). What is the IDispatch used in the ExcelApp ctor for?

Finally, it seems like I solved the issue, using the following steps:

  1. Instead of a menu , now a dialog is used; therefore, the menu does not own the pointer anymore.
  2. All wxAutomationObject inheriting objects are passed around via std::unique_ptr.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM