为什么我的多线程C ++ .NET应用程序只有在视觉工作室之外执行的崩溃?我的、多线程、应用程序、视觉

2023-09-06 14:19:09 作者:归隐

我创建使用托管和非托管code复制我的问题非常简单的C ++ .NET应用程序。

当用户点击一个按钮,一个新的线程应该产卵,并做一些耗时的任务,同时呼吁回到我的主线程与状态更新。

这code编译并成功地从内部的视觉工作室防爆preSS 2010年。也就是说,当我点击播放按钮一侧执行,我的项目建立并执行没有崩溃。但是,如果我去发布文件夹中的可执行文件的生活和运行的应用程序崩溃,一旦被点击的按钮。我编译使用/ clr和发布模式。

我创建一个表单并添加一个按钮。这就是$ C $下Form1.h如下:

 的#pragma一次

#包括core.h
#包括< WINDOWS.H>
#包括< process.h>

命名空间RepErr {

    使用命名空间系统;
    使用命名空间系统:ComponentModel;
    使用命名空间系统::集​​合;
    使用命名空间系统:视窗:表格;
    使用命名空间系统:数据;
    使用命名空间系统::绘图;

    使用命名空间系统:运行:: InteropServices;

    INT X;

    ///<总结>
    ///为Form1摘要
    ///< /总结>
    公众参考Form1类:公共系统:视窗:形式:表
    {
    上市:
        Form1的(无效)
        {
            的InitializeComponent();
            //
            // TODO:添加构造函数code在这里
            //
        }

    保护:
        ///<总结>
        ///清理所有正在使用的资源。
        ///< /总结>
        〜Form1中()
        {
            如果(部件)
            {
                删除组件;
            }
        }
    私人:系统:视窗:形式:按钮^按钮1;

    保护:

    私人:
        ///<总结>
        ///必需的设计变量。
        ///< /总结>
        系统:: ComponentModel ::集装箱^组件;

的#pragma区域Windows窗体设计器生成的code
        ///<总结>
        ///设计器支持所需的方法 - 不要修改
        ///此方法的code编辑器的内容。
        ///< /总结>
        无效的Ini​​tializeComponent(无效)
        {
            这 - >按钮1 =(gcnew系统:视窗:形式:按钮());
            这 - > SuspendLayout();
            //
            //按钮1
            //
            这 - > button1->位置=系统::绘画::点(104,62);
            这 - > button1->名称= L按钮1;
            这 - > button1->尺寸=系统::绘画::尺寸(75,23);
            这 - > button1->的TabIndex = 0;
            这 - > button1->文字= L按钮1;
            这 - > button1-> UseVisualStyleBackColor = TRUE;
            这 - > button1->点击+ = gcnew系统::事件处理程序(这一点,和放大器; Form1中::的button1_Click);
            //
            // Form1中
            //
            这 - > AutoScaleDimensions =系统::绘画::的SizeF(6,13);
            这 - > AutoScaleMode =系统:视窗:形式:AutoScaleMode ::字体;
            这 - >的ClientSize =系统::绘画::尺寸(276,160);
            这 - >控制 - >添加(这 - >按钮1);
            这 - >名称= LForm1的;
            这 - >文字= LForm1的;
            这 - > ResumeLayout(假);

        }
的#pragma endregion
    私人:系统:无效的button1_Click(系统:对象^发件人,系统:: EventArgs的^ E){
                 核心* 01 =新的核心();
                 无符号uiThread1ID;
                 HANDLE hth1 =(HANDLE)_beginthreadex(NULL,0,核心:: ThreadStaticEntryPoint,O1,CREATE_SUSPENDED,和放大器; uiThread1ID);
                 ResumeThread(hth1);
             }

上市:

    静态无效*回调(INT smallIndex){
        X = smallIndex;
        无效* dtpage = NULL;
        返回dtpage;
    }

    委托无效* myCALLBACKDelegate(INT smallIndex);

    静态的GCHandle GCH;

    //静态构造函数,在这里初始化委托
    静态Form1中(){
        myCALLBACKDelegate ^ FP = gcnew myCALLBACKDelegate(回调);
        GCH =的GCHandle ::的Alloc(FP);
        formCallback =的static_cast< myCallBack函数>(元帅:GetFunctionPointerForDelegate(FP).ToPointer());
    }

};
}
 
VB.NET 简单多线程

这是在$ C $下core.h:

 一次`指令#pragma

#包括< vcclr.h>
#包括Form1.h

为externC{
    无效的typedef *(__stdcall * myCallBack函数)(INT smallIndex);
}

//静态指针管理功能
myCallBack函数formCallback;

公共类核心{
上市:

    核心() {}

    静态无符号__stdcall ThreadStaticEntryPoint(无效* pThis){
        核心* PCR =(核心*)pThis;
        PCR-> doCall();
        返回1;
    }

    无效doCall(){
        formCallback(1);
    }

};

的#pragma endregion
 

为什么视觉工作室之外的这个应用程序崩溃?我需要在同一目录下的可执行文件的某些DLL或.NET文件?

感谢您, 威廉

如果我改变警告级别设置为最高的详细级别的编译器输出:

  1> C:\用户\票据\文档\ Visual Studio 2010的\项目\ reperr \ reperr \ Form1.h(107):警告C4434:静态构造函数必须有专用可访问;更改为私接
1> C:\用户\票据\文档\ Visual Studio 2010的\项目\ reperr \ reperr \ Form1.h(87):警告C4100:E:未引用的形参
1> C:\用户\票据\文档\ Visual Studio 2010的\项目\ reperr \ reperr \ Form1.h(87):警告C4100:发件人:未引用的形参
1> RepErr.cpp(9):警告C4100:'ARGS:未引用的形参
1> RepErr.cpp(19):警告C4339:'_TP_POOL:使用CLR元数据中检测到未定义的类型 - 使用这种类型可能会导致运行时异常
1> RepErr.cpp(19):警告C4339:'_TP_CLEANUP_GROUP:使用CLR元数据中检测到未定义的类型 - 使用这种类型可能会导致运行时异常
1> RepErr.cpp(19):警告C4339:'_TP_CALLBACK_INSTANCE:使用CLR元数据中检测到未定义的类型 - 使用这种类型可能会导致运行时异常
1> RepErr.cpp(19):警告C4339:'_ACTIVATION_CONTEXT:使用CLR元数据中检测到未定义的类型 - 使用这种类型可能会导致运行时异常
1>生成code ...
1> C:\用户\比尔\文档\ Visual Studio 2010的\项目\ RepErr \ RepErr \ RepErr.cpp:警告C4710:'__clrcall RepErr :: Form1中::〜Form1的(无效)':函数不内联
1> C:\用户\票据\文档\ Visual Studio 2010的\项目\ reperr \ reperr \ form1.h(28):警告C4710:'无效__clrcall RepErr :: Form1中::的InitializeComponent(无效)':函数不内联
1> C:\用户\票据\文档\ Visual Studio 2010的\项目\ reperr \ reperr \ reperr.cpp(16):警告C4710:'__clrcall RepErr :: Form1中:: Form1的(无效)':函数不内联
1> .NETFramework,版本= v4.0.AssemblyAttributes.cpp
1> RepErr.vcxproj  - > C:\ Users \用户账单\文档\ Visual Studio 2010的\项目\ RepErr \发布\ RepErr.exe
==========生成:1成功,0失败,0上升到最新,0跳过==========
 

这是RepErr.cpp:

  // RepErr.cpp:主项目文件。

#包括stdafx.h中
#包括Form1.h

使用命名空间RepErr;

[请将STAThreadAttribute]
INT主(数组<系统::字符串^> ^参数)
{
    //启用Windows XP视觉效果创建的任何控件之前
    应用:: EnableVisualStyles();
    申请:: SetCompatibleTextRenderingDefault(假);

    //创建主窗口,并运行它
    申请::运行(gcnew Form1中());
    返回0;
}
 

解决方案

该计划是崩溃,因为 formCallback 为NULL(因而核心:: doCall 取消引用一个NULL指针)。 formCallback 为NULL,因为 Form1中静态构造函数初始化它永远不会运行。

您可以通过添加以下行 Form1中静态构造函数证明这一点

 静态Form1中(){
    // ...等初始化...
    消息框::显示(((int)的formCallback)的ToString(X8));
}
 

在调试版本(或释放在VS调试器中运行),一个MessageBox将显示与函数指针的值。在发布版本(而不是在调试器),此对话框不显示,因为静态构造函数是不能运行。

静态构造函数无法运行,因为该类型被标 BeforeFieldInit 。这意味着式的初始化方法是在执行,或之前的某个时候,首先进入该类型定义的任何静态字段。如果有任何静态字段的任何访问,那么静态构造函数是不是需要运行(在一个发布版本,事实并非如此)。

按this连接问题,这是由设计。解决方法是访问实例 Form1中构造你的类型的静态字段,这将迫使静态构造函数运行,这将初始化 formCallback 正确的:

 静态INT s_dummy;

上市:
    Form1中()
    {
        //力静态构造函数现在运行
        s_dummy = 0;

        的InitializeComponent();
    }
 

或者(和我会建议),请使用线程类创建一个新的管理线;这将避免需要被管理的委托,对的GCHandle的 formCallback 全局函数指针,静态构造函数。从这个托管线程,你可以打电话到本地C ++,如果你需要执行非托管code。

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.