简体   繁体   中英

Why does my multithreaded C++ .NET application only crash when executed outside of visual studios?

I have created a very simple C++ .NET application using both managed and unmanaged code to replicate my problem.

When the user clicks a button a new thread should spawn and do some time-consuming tasks while calling back to my main thread with status updates.

This code compiles and successfully executes from within side of Visual Studios Express 2010. That is, when I click the "play" button, my project builds and executes without crashing. However, if I go to the Release folder where the executable lives and run it the application crashes once the button is clicked. I am compiling with /clr and in Release mode.

I create a form and add one button. This is what the code for Form1.h looks like:

#pragma once

#include "core.h"
#include <Windows.h>
#include <process.h>

namespace RepErr {

    using namespace System;
    using namespace System::ComponentModel;
    using namespace System::Collections;
    using namespace System::Windows::Forms;
    using namespace System::Data;
    using namespace System::Drawing;

    using namespace System::Runtime::InteropServices;

    int x;

    /// <summary>
    /// Summary for Form1
    /// </summary>
    public ref class Form1 : public System::Windows::Forms::Form
    {
    public:
        Form1(void)
        {
            InitializeComponent();
            //
            //TODO: Add the constructor code here
            //
        }

    protected:
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        ~Form1()
        {
            if (components)
            {
                delete components;
            }
        }
    private: System::Windows::Forms::Button^  button1;

    protected: 

    private:
        /// <summary>
        /// Required designer variable.
        /// </summary>
        System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        void InitializeComponent(void)
        {
            this->button1 = (gcnew System::Windows::Forms::Button());
            this->SuspendLayout();
            // 
            // button1
            // 
            this->button1->Location = System::Drawing::Point(104, 62);
            this->button1->Name = L"button1";
            this->button1->Size = System::Drawing::Size(75, 23);
            this->button1->TabIndex = 0;
            this->button1->Text = L"button1";
            this->button1->UseVisualStyleBackColor = true;
            this->button1->Click += gcnew System::EventHandler(this, &Form1::button1_Click);
            // 
            // Form1
            // 
            this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);
            this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
            this->ClientSize = System::Drawing::Size(276, 160);
            this->Controls->Add(this->button1);
            this->Name = L"Form1";
            this->Text = L"Form1";
            this->ResumeLayout(false);

        }
#pragma endregion
    private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
                 core *o1 = new core();
                 unsigned  uiThread1ID;
                 HANDLE hth1 = (HANDLE)_beginthreadex(NULL, 0, core::ThreadStaticEntryPoint, o1, CREATE_SUSPENDED, &uiThread1ID);
                 ResumeThread( hth1 );
             }

public:

    static void* callback(int smallIndex) {
        x = smallIndex;
        void* dtpage = NULL;
        return dtpage;
    }

    delegate void* myCALLBACKDelegate(int smallIndex);

    static GCHandle gch;

    //static constructor, initialize delegate here
    static Form1() {
        myCALLBACKDelegate^ fp=gcnew myCALLBACKDelegate(callback);
        gch = GCHandle::Alloc(fp);
        formCallback = static_cast<myCALLBACK>(Marshal::GetFunctionPointerForDelegate(fp).ToPointer());
    }

};
}

This is the code for core.h:

`#pragma once

#include <vcclr.h>
#include "Form1.h"

extern "C" { 
    typedef void* (__stdcall *myCALLBACK)(int smallIndex);
}

// static pointer to managed function
myCALLBACK formCallback;

public class core {
public:

    core() {}

    static unsigned __stdcall ThreadStaticEntryPoint(void *pThis) {
        core *pCr = (core*)pThis;
        pCr->doCall();
        return 1;
    }

    void doCall() {
        formCallback(1);
    }

};

#pragma endregion

Why does this application crash outside of Visual Studios? Do I need to have certain dll or .NET files in the same directory as the executable?

Thank you, William

If I change the warning level to highest verbosity level the compiler outputs:

1>c:\users\bill\documents\visual studio 2010\projects\reperr\reperr\Form1.h(107): warning C4434: a static constructor must have private accessibility; changing to private access
1>c:\users\bill\documents\visual studio 2010\projects\reperr\reperr\Form1.h(87): warning C4100: 'e' : unreferenced formal parameter
1>c:\users\bill\documents\visual studio 2010\projects\reperr\reperr\Form1.h(87): warning C4100: 'sender' : unreferenced formal parameter
1>RepErr.cpp(9): warning C4100: 'args' : unreferenced formal parameter
1>RepErr.cpp(19): warning C4339: '_TP_POOL' : use of undefined type detected in CLR meta-data - use of this type may lead to a runtime exception
1>RepErr.cpp(19): warning C4339: '_TP_CLEANUP_GROUP' : use of undefined type detected in CLR meta-data - use of this type may lead to a runtime exception
1>RepErr.cpp(19): warning C4339: '_TP_CALLBACK_INSTANCE' : use of undefined type detected in CLR meta-data - use of this type may lead to a runtime exception
1>RepErr.cpp(19): warning C4339: '_ACTIVATION_CONTEXT' : use of undefined type detected in CLR meta-data - use of this type may lead to a runtime exception
1>  Generating Code...
1>c:\Users\Bill\documents\visual studio 2010\Projects\RepErr\RepErr\RepErr.cpp : warning C4710: '__clrcall RepErr::Form1::~Form1(void)' : function not inlined
1>c:\users\bill\documents\visual studio 2010\projects\reperr\reperr\form1.h(28): warning C4710: 'void __clrcall RepErr::Form1::InitializeComponent(void)' : function not inlined
1>c:\users\bill\documents\visual studio 2010\projects\reperr\reperr\reperr.cpp(16): warning C4710: '__clrcall RepErr::Form1::Form1(void)' : function not inlined
1>  .NETFramework,Version=v4.0.AssemblyAttributes.cpp
1>  RepErr.vcxproj -> c:\users\bill\documents\visual studio 2010\Projects\RepErr\Release\RepErr.exe
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

This is RepErr.cpp:

// RepErr.cpp : main project file.

#include "stdafx.h"
#include "Form1.h"

using namespace RepErr;

[STAThreadAttribute]
int main(array<System::String ^> ^args)
{
    // Enabling Windows XP visual effects before any controls are created
    Application::EnableVisualStyles();
    Application::SetCompatibleTextRenderingDefault(false); 

    // Create the main window and run it
    Application::Run(gcnew Form1());
    return 0;
}

The program is crashing because formCallback is NULL (and thus core::doCall dereferences a NULL pointer). formCallback is NULL because the Form1 static constructor that initialises it is never run.

You can demonstrate this by adding the following line to the Form1 static constructor:

static Form1() {
    // ... other initialisation ...
    MessageBox::Show(((int) formCallback).ToString("x8"));
}

In Debug builds (or Release run under the VS debugger), a MessageBox will be shown with the value of the function pointer. In a Release build (not under the debugger), this dialog isn't shown because the static constructor isn't run.

The static constructor isn't run because the type is marked BeforeFieldInit . This means that "the type's initializer method is executed at, or sometime before, first access to any static field defined for that type". If there is no access of any static field, then the static constructor is not required to run (and in a Release build, it doesn't).

As per this Connect issue , this is by design. The workaround is to access a static field of your type in the instance Form1 constructor, which will force the static constructor to run, which will initialise formCallback correctly:

static int s_dummy;

public:
    Form1()
    {
        // force static constructor to run now
        s_dummy = 0;

        InitializeComponent();
    }

Alternatively (and what I would recommend), use the Thread class to create a new managed thread; this will avoid the need for the managed delegate, the GCHandle, the formCallback global function pointer, and the static constructor. From that managed thread, you could call into native C++ if you need to execute unmanaged code.

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