3

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;
}
  • You know, of course, that it is generally a pretty bad idea to create a thread at the click of a button, right? try clicking it in rapid succession... – rlc May 05 '11 at 23:16
  • Yes, this is a simple demonstration. :) I could disable spawning new threads until any previously spawned ones completed using a Thread manager. – William Whispell May 05 '11 at 23:21
  • @Ronald: Of course, it's easy to stop that sort of abuse too... – Matthew Scharley May 05 '11 at 23:21
  • No repro with the given code, don't see a problem either. – Hans Passant May 05 '11 at 23:27
  • @Hans Can you think of anything that might be specific to my machine/environment that is causing this? Compiling with Express 2010? Windows 7 Home Edition? etc.? – William Whispell May 05 '11 at 23:32
  • Are you build in "release" configuration? – Piotr May 05 '11 at 23:40
  • Yes I build in release and execute the executable found in the release folder. – William Whispell May 05 '11 at 23:42
  • What kind of crash? Details? – sean e May 05 '11 at 23:45
  • It crashes as soon as the button is clicked. This is the output: RepErr.exe has stopped working. Windows can check online for a solution. If I show details: Problem signature: Problem Event Name: BEX Application Name: RepErr.exe Application Version: 0.0.0.0 Application Timestamp: 4dc3370b Fault Module Name: StackHash_0a9e Fault Module Version: 0.0.0.0 Fault Module Timestamp: 00000000 Exception Offset: 00000000 Exception Code: c0000005 Exception Data: 00000008 OS Version: 6.1.7600.2.0.0.768.3 – William Whispell May 05 '11 at 23:55
  • Additional Information 1: 0a9e Additional Information 2: 0a9e372d3b4ad19135b953a78882e789 Additional Information 3: 0a9e Additional Information 4: 0a9e372d3b4ad19135b953a78882e789 – William Whispell May 05 '11 at 23:56
  • BEX is buffer-overflow error: http://stackoverflow.com/questions/51690/vista-bex-error – sean e May 06 '11 at 00:27
  • Are you building with debug symbols? – sean e May 06 '11 at 00:28
  • Any runtime assertions when running a debug build? – sean e May 06 '11 at 00:29
  • RepErr.cpp 1>RepErr.cpp(3): error C2855: command-line option '/clr:pure' inconsistent with precompiled header 1>RepErr.cpp(3): warning C4651: '/D_M_CEE_PURE=001' specified for precompiled header but not for current compile – William Whispell May 06 '11 at 01:46
  • So I set /clr it compiles and runs in debug.. – William Whispell May 06 '11 at 02:14
  • I can run the executable that I compile with Debug without any runtime errors - The only difference in my compile options are the _DEBUG and NDEBUG – William Whispell May 06 '11 at 02:24

1 Answers1

5

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.

Bradley Grainger
  • 27,458
  • 4
  • 91
  • 108
  • Thank you very much! My first attempt at the work around failed because I was foolishly placing the static s_dummy var at file scope and not within the Form1 class. Moved it and fixed. Thanks for the heads up on the Thread class too. :) – William Whispell May 07 '11 at 03:01