You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

wxWidgets C++程序执行文件下载功能时崩溃问题求助

wxWidgets+cURL下载程序崩溃问题修复

问题现象

开发基于wxWidgets的C++下载应用,使用cURL处理下载请求,点击安装按钮后程序立即关闭,调试器捕获到如下异常:

异常抛出在 0x00007FFC285301E8 (wxbase32ud_vc_custom.dll) 中的 WindowsInstaller.exe: 0xC0000005: 写入位置 0x00000080FDFDFDFD 时发生访问违例。

核心错误分析

  1. WriteCallback类型转换完全错误
    代码中设置CURLOPT_WRITEDATA为文件指针file,但WriteCallback里强行把userp转换成wxThreadEvent*,直接导致对非法内存地址的写入操作,触发访问违例。同时WriteCallback的设计完全偏离职责——它应该将cURL获取到的字节写入本地文件,而非发送UI事件。

  2. 进度捕获方式错误
    要实现下载进度更新,不能篡改WriteCallback,应使用cURL专门的进度回调CURLOPT_XFERINFOFUNCTION(旧版本可用CURLOPT_PROGRESSFUNCTION)。

  3. 线程悬空引用问题
    下载线程的lambda捕获了局部变量downloader的引用(&downloader),但downloader是OnInstallButtonClick函数内的局部对象,函数执行完毕后该对象会被销毁,线程后续访问该引用会触发未定义行为。

  4. 缺少cURL全局初始化
    未调用curl_global_init(CURL_GLOBAL_ALL)初始化cURL全局环境,导致cURL无法正常工作。

  5. 未绑定线程事件处理函数
    定义了ID_UPDATE_PROGRESS等线程事件,但NewFrame中没有绑定对应的处理函数,事件无法被处理。

修复后的完整代码

#include <wx/wx.h>
#include <iostream>
#include <fstream>
#include <curl/curl.h>
#include <thread>
#include <functional>

class FileDownloader {
public:
    using ProgressCallback = std::function<void(double)>;

    FileDownloader(ProgressCallback progressCb) : m_progressCb(std::move(progressCb)) {}
    ~FileDownloader() = default;

    static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
        size_t totalSize = size * nmemb;
        FILE* file = static_cast<FILE*>(userp);
        return fwrite(contents, size, nmemb, file);
    }

    static int ProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
        (void)ultotal;
        (void)ulnow;
        auto progressCb = static_cast<ProgressCallback*>(clientp);
        if (dltotal > 0 && progressCb) {
            double progress = static_cast<double>(dlnow) / dltotal * 100.0;
            (*progressCb)(progress);
        }
        return 0;
    }

    bool DownloadFile(const std::string& url, const std::string& destination) {
        CURL* curl = curl_easy_init();
        if (!curl) return false;

        FILE* file = fopen(destination.c_str(), "wb");
        if (!file) {
            curl_easy_cleanup(curl);
            return false;
        }

        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);
        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
        curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback);
        curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &m_progressCb);
        // 禁用SSL验证(仅测试用,生产环境需开启)
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);

        CURLcode res = curl_easy_perform(curl);

        fclose(file);
        curl_easy_cleanup(curl);

        return res == CURLE_OK;
    }

private:
    ProgressCallback m_progressCb;
};

class WindowsInstaller : public wxApp {
public:
    bool OnInit() override;
    int OnExit() override;
};

wxIMPLEMENT_APP(WindowsInstaller);

class NewFrame : public wxFrame {
public:
    NewFrame();

private:
    void OnHello(wxCommandEvent& event);
    void OnExit(wxCommandEvent& event);
    void OnAbout(wxCommandEvent& event);
    void OnInstallButtonClick(wxCommandEvent& event);
    void OnSetButtonClick(wxCommandEvent& event);
    void OnCheckBoxClicked(wxCommandEvent& event);
    void OnUpdateProgress(wxThreadEvent& event);
    void OnDownloadComplete(wxThreadEvent& event);
    void OnDownloadError(wxThreadEvent& event);
    void OnEnableInstallButton(wxThreadEvent& event);

    wxButton* install_button;
    wxButton* set_location;
    wxTextCtrl* textCtrl;
    wxCheckBox* accept_checkbox;
    wxGauge* gauge;
};

bool WindowsInstaller::OnInit() {
    // 初始化cURL全局环境
    curl_global_init(CURL_GLOBAL_ALL);

    NewFrame* frame = new NewFrame();
    frame->Show();
    return true;
}

int WindowsInstaller::OnExit() {
    // 清理cURL全局环境
    curl_global_cleanup();
    return wxApp::OnExit();
}

enum {
    ID_Hello = 1,
    ID_Install_Button = 2,
    ID_Set_Location = 3,
    ID_Accept_CheckBox = 4,
    ID_UPDATE_PROGRESS = 5,
    ID_DOWNLOAD_COMPLETE = 6,
    ID_DOWNLOAD_ERROR = 7,
    ID_ENABLE_INSTALL_BUTTON = 8
};

NewFrame::NewFrame()
    : wxFrame(nullptr, wxID_ANY, "Hello World", wxDefaultPosition, wxSize(700, 500), wxDEFAULT_FRAME_STYLE & ~(wxRESIZE_BORDER | wxMAXIMIZE_BOX)) {
    wxMenu* menuFile = new wxMenu;
    menuFile->Append(ID_Hello, "&Hello...\tCtrl+H", "Help string shown in status bar for this menu item");
    menuFile->AppendSeparator();
    menuFile->Append(wxID_EXIT);

    wxMenu* menuHelp = new wxMenu;
    menuHelp->Append(wxID_ABOUT);

    wxMenuBar* menuBar = new wxMenuBar;
    menuBar->Append(menuFile, "&File");
    menuBar->Append(menuHelp, "&Help");

    SetMenuBar(menuBar);
    CreateStatusBar();
    SetStatusText("Welcome to wxWidgets!");

    wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);

    textCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY | wxHSCROLL);
    textCtrl->SetBackgroundColour(*wxWHITE);
    textCtrl->SetForegroundColour(*wxBLACK);
    textCtrl->SetFont(wxFont(wxFontInfo(10)));

    wxString licenseText = "Copyright 2023 Author\n"
        "Permission is hereby granted, free of charge, to any person obtaining a copy of this software "
        "and associated documentation files(the “Software”), to deal in the Software without restriction, "
        "including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, "
        "and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do "
        "so, subject to the following conditions:\n"
        "The above copyright notice and this permission notice shall be included in all copies or substantial "
        "portions of the Software.\n"
        "THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR "
        "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS "
        "FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS "
        "OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, "
        "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN "
        "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.";
    textCtrl->SetValue(licenseText);

    mainSizer->Add(textCtrl, 1, wxEXPAND | wxALL, 20);

    accept_checkbox = new wxCheckBox(this, ID_Accept_CheckBox, "I accept the license agreement");
    mainSizer->Add(accept_checkbox, 0, wxALIGN_LEFT | wxALL, 10);

    gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL);
    mainSizer->Add(gauge, 0, wxEXPAND | wxALL, 20);

    install_button = new wxButton(this, ID_Install_Button, "Install");
    set_location = new wxButton(this, ID_Set_Location, "Set Location");

    install_button->Enable(false);

    mainSizer->AddStretchSpacer();
    mainSizer->Add(install_button, 0, wxALIGN_CENTER | wxALL, 10);
    mainSizer->Add(set_location, 0, wxALIGN_CENTER | wxALL, 5);

    SetSizerAndFit(mainSizer);

    // 绑定基础事件
    Bind(wxEVT_MENU, &NewFrame::OnHello, this, ID_Hello);
    Bind(wxEVT_MENU, &NewFrame::OnAbout, this, wxID_ABOUT);
    Bind(wxEVT_MENU, &NewFrame::OnExit, this, wxID_EXIT);
    Bind(wxEVT_BUTTON, &NewFrame::OnInstallButtonClick, this, ID_Install_Button);
    Bind(wxEVT_BUTTON, &NewFrame::OnSetButtonClick, this, ID_Set_Location);
    Bind(wxEVT_CHECKBOX, &NewFrame::OnCheckBoxClicked, this, ID_Accept_CheckBox);
    // 绑定线程事件处理函数
    Bind(wxEVT_THREAD, &NewFrame::OnUpdateProgress, this, ID_UPDATE_PROGRESS);
    Bind(wxEVT_THREAD, &NewFrame::OnDownloadComplete, this, ID_DOWNLOAD_COMPLETE);
    Bind(wxEVT_THREAD, &NewFrame::OnDownloadError, this, ID_DOWNLOAD_ERROR);
    Bind(wxEVT_THREAD, &NewFrame::OnEnableInstallButton, this, ID_ENABLE_INSTALL_BUTTON);

    SetInitialSize(wxSize(700, 500));
}

void NewFrame::OnExit(wxCommandEvent& event) {
    Close(true);
}

void NewFrame::OnAbout(wxCommandEvent& event) {
    wxMessageBox("This is a wxWidgets Hello World example", "About Hello World", wxOK | wxICON_INFORMATION);
}

void NewFrame::OnHello(wxCommandEvent& event) {
    wxLogMessage("Hello world from wxWidgets!");
}

void NewFrame::OnInstallButtonClick(wxCommandEvent& event) {
    if (!accept_checkbox->IsChecked()) {
        wxMessageBox("Please accept the license agreement.", "Error", wxOK | wxICON_ERROR);
        return;
    }

    wxString url = "https://releases.ubuntu.com/22.04.2/ubuntu-22.04.2-desktop-amd64.iso";
    wxString destination = "F:/ubuntu-22.04.2-desktop-amd64.iso";

    install_button->Disable();
    gauge->SetValue(0);

    // 按值捕获变量,避免悬空引用
    std::thread downloadThread([this, url, destination]() {
        auto progressCb = [this](double progress) {
            wxThreadEvent* progressEvent = new wxThreadEvent(wxEVT_THREAD, ID_UPDATE_PROGRESS);
            progressEvent->SetInt(static_cast<int>(progress));
            wxQueueEvent(this, progressEvent);
        };

        FileDownloader downloader(std::move(progressCb));
        bool success = downloader.DownloadFile(url.ToStdString(), destination.ToStdString());

        if (success) {
            wxThreadEvent* completeEvent = new wxThreadEvent(wxEVT_THREAD, ID_DOWNLOAD_COMPLETE);
            wxQueueEvent(this, completeEvent);
        } else {
            wxThreadEvent* errorEvent = new wxThreadEvent(wxEVT_THREAD, ID_DOWNLOAD_ERROR);
            wxQueueEvent(this, errorEvent);
        }

        wxThreadEvent* enableEvent = new wxThreadEvent(wxEVT_THREAD, ID_ENABLE_INSTALL_BUTTON);
        wxQueueEvent(this, enableEvent);
    });

    downloadThread.detach();
}

void NewFrame::OnSetButtonClick(wxCommandEvent& event) {
    wxMessageBox("Button clicked!", "Button", wxOK | wxICON_INFORMATION);
}

void NewFrame::OnCheckBoxClicked(wxCommandEvent& event) {
    install_button->Enable(accept_checkbox->IsChecked());
}

void NewFrame::OnUpdateProgress(wxThreadEvent& event) {
    int progress = event.GetInt();
    gauge->SetValue(progress);
    SetStatusText(wxString::Format("Downloading: %d%%", progress));
}

void NewFrame::OnDownloadComplete(wxThreadEvent& event) {
    wxMessageBox("Download completed successfully!", "Success", wxOK | wxICON_INFORMATION);
    SetStatusText("Download completed");
}

void NewFrame::OnDownloadError(wxThreadEvent& event) {
    wxMessageBox("Download failed!", "Error", wxOK | wxICON_ERROR);
    SetStatusText("Download failed");
}

void NewFrame::OnEnableInstallButton(wxThreadEvent& event) {
    install_button->Enable(true);
}

关键修改说明

  1. 修复WriteCallback:恢复其写入文件的本职功能,正确转换userp为FILE*并调用fwrite。
  2. 添加进度回调:使用CURLOPT_XFERINFOFUNCTION捕获下载进度,通过回调函数发送UI事件更新进度条。
  3. 解决线程悬空引用:线程lambda按值捕获必要变量,避免引用局部对象。
  4. 初始化cURL全局环境:在OnInit中调用curl_global_init,OnExit中调用curl_global_cleanup。
  5. 绑定线程事件处理函数:新增OnUpdateProgress等函数,处理进度更新、完成、错误等事件。
  6. 优化事件发送:直接向当前NewFrame实例发送事件,而非依赖wxTheApp->GetTopWindow(),更可靠。

内容的提问来源于stack exchange,提问作者Ethan James

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 12:49:58