为什么我的多线程 C++ .NET 应用程序仅在视觉工作室之外执行时才会崩溃?
我使用托管和非托管代码创建了一个非常简单的 C++ .NET 应用程序来复制我的问题。
当用户单击按钮时,应该生成一个新线程并执行一些耗时的任务,同时回调我的主线程并进行状态更新。
此代码在 Visual Studios Express 2010 内部编译并成功执行。也就是说,当我单击“播放”按钮时,我的项目将生成并执行而不会崩溃。但是,如果我转到可执行文件所在的 Release 文件夹并运行它,则单击按钮后应用程序就会崩溃。我正在使用 /clr 并在发布模式下进行编译。
我创建一个表单并添加一个按钮。这是 Form1.h 的代码:
#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());
}
};
}
这是 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
为什么此应用程序在 Visual Studio 之外崩溃?我是否需要将某些 dll 或 .NET 文件与可执行文件放在同一目录中?
谢谢你, William
如果我将警告级别更改为最高详细级别,编译器将输出:
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 ==========
这是 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;
}
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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
程序崩溃是因为
formCallback
为 NULL(因此core::doCall
取消引用 NULL 指针)。formCallback
为 NULL,因为初始化它的Form1
静态构造函数永远不会运行。您可以通过将以下行添加到
Form1
静态构造函数来演示这一点:在 Debug 版本(或在 VS 调试器下运行的 Release)中,将显示一个包含函数指针值的 MessageBox。在发布版本中(不在调试器下),不会显示此对话框,因为静态构造函数未运行。
静态构造函数未运行,因为该类型被标记为 BeforeFieldInit。这意味着“该类型的初始化方法在首次访问为该类型定义的任何静态字段时或之前执行”。如果没有任何静态字段的访问权限,则不需要运行静态构造函数(在发布版本中也不需要)。
根据此 Connect 问题< /a>,这是设计使然。解决方法是在实例
Form1
构造函数中访问您类型的静态字段,这将强制静态构造函数运行,从而正确初始化formCallback
:或者(以及什么)我建议),使用 Thread 类创建一个新的托管线程;这将避免对托管委托、GCHandle、formCallback 全局函数指针和静态构造函数的需要。如果需要执行非托管代码,可以从该托管线程调用本机 C++。
The program is crashing because
formCallback
is NULL (and thuscore::doCall
dereferences a NULL pointer).formCallback
is NULL because theForm1
static constructor that initialises it is never run.You can demonstrate this by adding the following line to the
Form1
static constructor: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 initialiseformCallback
correctly: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.