Home > database >  libcurl is apparently crashing the application
libcurl is apparently crashing the application

Time:07-05

Config:
Windows 11 x64
Visual Studio Project: CLR Empty Project (.NET Framework)
.NET Framework: 4.6.2
libcurl: curl-7.83.1 windows build (libcurl-vc-x86-release-static-ipv6-sspi-schannel)
Project architecture: Release x86

Well, I'm trying to report a download progress in my Windows Forms application using libcurl however there are some issues with a pointer from the managed into the unmanaged code apparently when dealing with System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate, here's my files:

Program.cpp:

#include "MyForm.h"
[System::STAThread]
int main(array<System::String^>^ args) {
    try {
        System::Windows::Forms::Application::SetUnhandledExceptionMode(System::Windows::Forms::UnhandledExceptionMode::ThrowException);
        System::Windows::Forms::Application::EnableVisualStyles();
        System::Windows::Forms::Application::SetCompatibleTextRenderingDefault(false);
        DownloadProgressTest::MyForm updater;
        System::Windows::Forms::Application::Run(% updater);
        return 0;
    }
    catch (System::Exception^ exception) {
        System::Console::WriteLine(exception->ToString());
        return -1;
    }
}

downloader.h:

#pragma once
#define CURL_STATICLIB
#include "curl/curl.h"
#include <string>
class Downloader final {
public:
    static size_t writeData(void* ptr, size_t size, size_t nmemb, FILE* stream) {
        size_t written = fwrite(ptr, size, nmemb, stream);
        return written;
    }

    static void download(System::Delegate^ progressFunction) {
        CURL* curl = curl_easy_init();
        if (curl) {
            const char* URL = "https://download.visualstudio.microsoft.com/download/pr/8e396c75-4d0d-41d3-aea8-848babc2736a/80b431456d8866ebe053eb8b81a168b3/ndp462-kb3151800-x86-x64-allos-enu.exe";
            const wchar_t* fileName = L"D:\\Backup\\Downloads\\dotnet462.exe";
            FILE* fp;
            _wfopen_s(&fp, fileName, L"wb");
            if (fp) {
                System::IntPtr progressFunctionPointer = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(progressFunction);

                curl_easy_setopt(curl, CURLOPT_URL, URL);
                curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl");
                curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData);
                curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
                curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE);
                curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progressFunctionPointer);

                CURLcode res = curl_easy_perform(curl); // This line crashes the application.

                if (res == CURLE_OK) {
                    System::Console::WriteLine("Download completed.");
                }
                else {
                    System::Console::WriteLine("Couldn't download file.");
                }

                std::fclose(fp);
            }
            else {
                System::Console::WriteLine("Couldn't open file.");
            }

            curl_easy_cleanup(curl);
        }
        else {
            System::Console::WriteLine("Couldn't initialize curl.");
        }
    }
};

MyForm.h (relevant part only):

public: delegate int DelegateProgressChanged(void* clientPointer, double totalToDownload, double nowDownloaded, double totalToUpload, double nowUploaded);

public: int progressChanged(void* clientPointer, double totalToDownload, double nowDownloaded, double totalToUpload, double nowUploaded) {
    double percentage = (nowDownloaded / totalToDownload) * 100;
    System::Console::WriteLine("I will print this before application crashes.");
    return 0;
}

public: System::Void startDownload() {
    Downloader::download(gcnew DelegateProgressChanged(this, &MyForm::progressChanged));
}

public: System::Void MyForm_Shown(System::Object^ sender, System::EventArgs^ e) {
    System::Threading::Tasks::Task::Run(gcnew System::Action(this, &MyForm::startDownload));
}

As you can see on the following libcurl documentation my delegate and progressChanged functions fit the function signature: libcurl documentation.

My code works fine when providing a pointer directly from an unmanaged function, however I need to report the progress in a System::Windows::Forms::ProgressBar from my application so that's why I need it to be managed.

It creates the download file in the specified directory (incomplete file though), prints out the "I will print this before application crashes." then application gets crashed at the line CURLcode res = curl_easy_perform(curl); and I couldn't catch any exception not even in the Debug build but the following error from Visual Studio is displayed:

Exception thrown at 0x38AD0002 in DownloadProgressTest.exe: 0xC0000005: Access violation executing location 0x38AD0002.

Original Location: C:\Windows\SysWOW64\kernel32.dll

What's going on here? Am I dealing in the wrong way with pointers? Is there a workaround for this?

EDIT

Thanks john for his comment, I managed to fix the problem but partially, now I'm getting the following exception when updating my System::Windows::Forms::ProgressBar:

percentage value is: -2147483648
percentage value is: -2147483648
percentage value is: -2147483648
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentOutOfRangeException: Value of '-2147483648' is not valid for 'Value'. 'Value' should be between 'minimum' and 'maximum'.
Parameter name: Value
   at System.Windows.Forms.ProgressBar.set_Value(Int32 value)
   at DownloadProgressTest.MyForm.BeginInvokeMeProgressChanged(Int32 percentage) in C:\Users\censored\source\repos\DownloadProgressTest\DownloadProgressTest\MyForm.h:line 94
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   at System.Delegate.DynamicInvokeImpl(Object[] args)
   at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry tme)
   at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(Object obj)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry tme)
   at System.Windows.Forms.Control.InvokeMarshaledCallbacks()
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
   at System.Windows.Forms.Form.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
   at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
   at System.Windows.Forms.Application.Run(Form mainForm)
   at main(String[] args) in C:\Users\censored\source\repos\DownloadProgressTest\DownloadProgressTest\Program.cpp:line 9

Since the progress function is inside a System::Threading::Tasks::Task this is how I'm updating the progress bar:

public: System::Void BeginInvokeMeProgressChanged(int percentage) {
    this->progressBar1->Value = percentage;
    this->label1->Text = "Download progress: "   percentage   "%";
}
public: int progressChanged(void* clientPointer, double totalToDownload, double nowDownloaded, double totalToUpload, double nowUploaded) {
    int percentage = (int)((nowDownloaded / totalToDownload) * 100);
    System::Console::WriteLine("percentage value is: "   percentage);
    this->BeginInvoke(gcnew System::Action<int>(this, &MyForm::BeginInvokeMeProgressChanged), percentage);
    return 0;
}

It looks like BeginInvoke messes up the percentage variable inside the libcurl unmanaged progress function, is there a workaround for this?

CodePudding user response:

As john mentioned on his comment, I fixed it up by doing it at the top of my MyForm.h file:

[System::Runtime::InteropServices::UnmanagedFunctionPointerAttribute(System::Runtime::InteropServices::CallingConvention::Cdecl)]
delegate int DelegateProgressChanged(void* clientPointer, double totalToDownload, double nowDownloaded, double totalToUpload, double nowUploaded);

then declaring that delegate as a member in order to avoid it gets garbage collected hence an exception is thrown:

private: DelegateProgressChanged^ delegateProgressChanged;

And to fix up the messed up percentage value I simply did the following:

public: int progressChanged(void* clientPointer, double totalToDownload, double nowDownloaded, double totalToUpload, double nowUploaded) {
    if (nowDownloaded > 0 && totalToDownload > 0) {
        int percentage = (int)((nowDownloaded / totalToDownload) * 100);
        this->BeginInvoke(gcnew System::Action<int>(this, &MyForm::BeginInvokeMeProgressChanged), percentage);
    }

    return 0;
}

The above solution fixes everything up, thank you.

  • Related