none
MessageBox表示時のヒープ破壊例外について

    質問

  • MessageBox表示時のヒープ破壊例外についてお教えください。
    開発環境は、Visual C++ 2010でCLIでプログラミングしています。

    以下に事象の発生するコードを記載させて頂きます。

    Form1(void)
    {
     InitializeComponent();

     this->m_Timer = gcnew System::Timers::Timer(); //タイマー
     this->m_Timer->Interval = 10; //1000=1秒
     this->m_Timer->AutoReset = true; //指定した間隔が経過するたびに Timer で Elapsed イベントを発生させる
     this->m_Timer->Elapsed += gcnew System::Timers::ElapsedEventHandler(this, &Form1::MyClock); //イベントハンドラ
     this->m_Timer->Start();
    }

    BOOL    m_bThreadTimeout;
    System::Timers::Timer^  m_Timer;  //タイマー

    private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e)
    {
     m_bThreadTimeout = TRUE;
    }

    private: void MyClock(System::Object^ obj, System::Timers::ElapsedEventArgs^ args)
    {
     if( m_bThreadTimeout == TRUE ){
      m_bThreadTimeout = FALSE;

      UINT uType = MB_ICONINFORMATION;
      ::MessageBox(0, "AAAAAAAAAAAAA\0", "AAA\0", uType);
     }
    }

    上記コードにてbutton1をクリックして、button1_Click()が処理された後にタイマー処理でMyClock()内にてMessageBoxが表示されますが、その際に以下の例外が発生します。

    Windows によって TestApp.exe でブレークポイントが発生しました。
    ヒープが壊れていることが原因として考えられます。TestApp.exe または読み込まれた DLL にバグがあります。
    あるいは、TestApp.exe がフォーカスを持っているときに、ユーザーが F12 キーを押したことが原因として考えられます。
    可能であれば、出力ウィンドウに詳細な診断情報が表示されます。

    VisualStudio上では、Debug/Releaseビルド共に事象が発生します。
    Debug/Releaseビルドにてビルドしたexeをエクスプローラから直接実行した場合は、事象が発生しません。
    プロセスにアタッチした場合も事象は、発生しません。

    また、例外処理上の中断設定はすべてOffしています。

    この例外をキャッチしない設定や、オプション等ご存知の方はお教えください。
    よろしくお願いします。

    2017年11月23日 14:58

回答

  • Windows 7 でご質問のプログラムを動かしたところ再現しました。メインスレッド以外から MessageBox の呼び出しをするのはもしかすると安全ではないのかもしれません。下記のようにプログラムを修正したところ正常に動作しました。

    #pragma once
    
    #pragma comment(lib, "User32.lib")
    #include <Windows.h>
    
    namespace MyFormsApp {
    
    	using namespace System;
    	using namespace System::Windows::Forms;
    
    	/// <summary>
    	/// Form1 の概要
    	/// </summary>
    	public ref class Form1 : public System::Windows::Forms::Form
    	{
    	public:
    		BOOL m_bThreadTimeout;
    		System::Timers::Timer^ m_Timer;  //タイマー
    		delegate void MyCallback();
    
    		Form1(void)
    		{
    			InitializeComponent();
    
    			this->m_Timer = gcnew System::Timers::Timer(); //タイマー
    			this->m_Timer->Interval = 10; //1000=1秒
    			this->m_Timer->AutoReset = true; //指定した間隔が経過するたびに Timer で Elapsed イベントを発生させる
    			this->m_Timer->Elapsed += gcnew System::Timers::ElapsedEventHandler(this, &Form1::MyClock); //イベントハンドラ
    			this->m_Timer->Start();
    		}
    
    		void Func()
    		{
    			  UINT uType = MB_ICONINFORMATION;
    			  ::MessageBoxA(0, "AAAAAAAAAAAAA\0", "AAA\0", uType);
    		}
    
    		void MyClock(System::Object^ obj, System::Timers::ElapsedEventArgs^ args)
    		{
    			if( m_bThreadTimeout == TRUE ){
    				m_bThreadTimeout = FALSE;
    				this->Invoke(gcnew MyCallback(this, &Form1::Func)); // メインスレッドで Func を呼び出す
    			}
    		}
    
    	protected:
    		/// <summary>
    		/// 使用中のリソースをすべてクリーンアップします。
    		/// </summary>
    		~Form1()
    		{
    			if (components)
    			{
    				delete components;
    			}
    		}
    	private: System::Windows::Forms::Button^  button1;
    	protected: 
    
    	private:
    		/// <summary>
    		/// 必要なデザイナー変数です。
    		/// </summary>
    		System::ComponentModel::Container ^components;
    
    #pragma region Windows Form Designer generated code
    		/// <summary>
    		/// デザイナー サポートに必要なメソッドです。このメソッドの内容を
    		/// コード エディターで変更しないでください。
    		/// </summary>
    		void InitializeComponent(void)
    		{
    			this->button1 = (gcnew System::Windows::Forms::Button());
    			this->SuspendLayout();
    			// 
    			// button1
    			// 
    			this->button1->Location = System::Drawing::Point(0, 0);
    			this->button1->Name = L"button1";
    			this->button1->Size = System::Drawing::Size(110, 40);
    			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, 12);
    			this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
    			this->ClientSize = System::Drawing::Size(284, 261);
    			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) {
    				 m_bThreadTimeout = TRUE;
    			 }
    	};
    }

    参考サイト:

    http://blog.goo.ne.jp/ton-chan-615/e/493b58e6a341511604de1eb422a23965

    https://msdn.microsoft.com/ja-jp/library/ms171728%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
    2017年11月24日 1:37

すべての返信

  • 何かビルド設定を変えていますか?

    通常、Visual C++ のプロジェクトを作成すると、Unicode 文字セットがデフォルトになります。
    その状態では、MessageBox 関数は MessageBoxW 関数と解釈され、そこに const char* と解釈できる、文字列リテラルを渡しているのでビルドエラーになるはずです。
    (TCHAR の思想に則れば、_T("AAAA") というように、_T マクロを使う)

    さて、ヒープ破壊の検知がなされるということは、コードに何らかの不具合があるか、対象の DLL の潜在不具合があるか、常駐しているアプリケーションなどに問題があるかであり、「デバッガなしでは何も起きないから問題ない」ではありません。
    その問題を追及し、解消するのがプログラマーとして必要になります。
    (無視できません)

    なお、上記コードを Visual C++ 2010 + Windows 10 x64 (1703) で試す限りは、問題が再現しませんでした。
    2017年11月23日 21:44
    モデレータ
  • Windows 7 でご質問のプログラムを動かしたところ再現しました。メインスレッド以外から MessageBox の呼び出しをするのはもしかすると安全ではないのかもしれません。下記のようにプログラムを修正したところ正常に動作しました。

    #pragma once
    
    #pragma comment(lib, "User32.lib")
    #include <Windows.h>
    
    namespace MyFormsApp {
    
    	using namespace System;
    	using namespace System::Windows::Forms;
    
    	/// <summary>
    	/// Form1 の概要
    	/// </summary>
    	public ref class Form1 : public System::Windows::Forms::Form
    	{
    	public:
    		BOOL m_bThreadTimeout;
    		System::Timers::Timer^ m_Timer;  //タイマー
    		delegate void MyCallback();
    
    		Form1(void)
    		{
    			InitializeComponent();
    
    			this->m_Timer = gcnew System::Timers::Timer(); //タイマー
    			this->m_Timer->Interval = 10; //1000=1秒
    			this->m_Timer->AutoReset = true; //指定した間隔が経過するたびに Timer で Elapsed イベントを発生させる
    			this->m_Timer->Elapsed += gcnew System::Timers::ElapsedEventHandler(this, &Form1::MyClock); //イベントハンドラ
    			this->m_Timer->Start();
    		}
    
    		void Func()
    		{
    			  UINT uType = MB_ICONINFORMATION;
    			  ::MessageBoxA(0, "AAAAAAAAAAAAA\0", "AAA\0", uType);
    		}
    
    		void MyClock(System::Object^ obj, System::Timers::ElapsedEventArgs^ args)
    		{
    			if( m_bThreadTimeout == TRUE ){
    				m_bThreadTimeout = FALSE;
    				this->Invoke(gcnew MyCallback(this, &Form1::Func)); // メインスレッドで Func を呼び出す
    			}
    		}
    
    	protected:
    		/// <summary>
    		/// 使用中のリソースをすべてクリーンアップします。
    		/// </summary>
    		~Form1()
    		{
    			if (components)
    			{
    				delete components;
    			}
    		}
    	private: System::Windows::Forms::Button^  button1;
    	protected: 
    
    	private:
    		/// <summary>
    		/// 必要なデザイナー変数です。
    		/// </summary>
    		System::ComponentModel::Container ^components;
    
    #pragma region Windows Form Designer generated code
    		/// <summary>
    		/// デザイナー サポートに必要なメソッドです。このメソッドの内容を
    		/// コード エディターで変更しないでください。
    		/// </summary>
    		void InitializeComponent(void)
    		{
    			this->button1 = (gcnew System::Windows::Forms::Button());
    			this->SuspendLayout();
    			// 
    			// button1
    			// 
    			this->button1->Location = System::Drawing::Point(0, 0);
    			this->button1->Name = L"button1";
    			this->button1->Size = System::Drawing::Size(110, 40);
    			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, 12);
    			this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
    			this->ClientSize = System::Drawing::Size(284, 261);
    			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) {
    				 m_bThreadTimeout = TRUE;
    			 }
    	};
    }

    参考サイト:

    http://blog.goo.ne.jp/ton-chan-615/e/493b58e6a341511604de1eb422a23965

    https://msdn.microsoft.com/ja-jp/library/ms171728%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
    2017年11月24日 1:37
  • Application Verifier (AppVerif.exe) で、その "TestApp.exe" プロセスを検証すれば、問題部分を教えてくれるかも。
    ---------------------------------------------
    Windows SDK ツール:Application Verifier のご紹介
    https://blogs.msdn.microsoft.com/japan_platform_sdkwindows_sdk_support_team_blog/2011/05/29/windows-sdk-application-verifier/
    ---------------------------------------------
    2017年11月24日 3:13
  • 質問するときは最低限のマナーとして、OSのバージョンや.NETのバージョンくらい書きましょう。また、提示されたコードから類推するとWinFormsを利用しているようですが、そういった説明も一切ありません。Windowsフォームアプリケーションプロジェクトのテンプレートを使っているのか、それともWin32/MFCアプリケーションプロジェクトにおいて/clrを有効にすることでWinFormsを利用しているのか不明ですが、どういう手順でプロジェクトを作成したのか、またプロジェクトの設定を既定値から変更したのであればその旨も手順として記載するべきです。

    そもそもWin32 APIのMessageBox()関数を使っている理由は何でしょうか? 通例、Windows FormsアプリケーションであればSystem::Windows::Forms::MessageBox::Show()メソッドを使うべきです。Win32 APIのMessageBox()は、Win32 APIのみを直接使用する従来のネイティブアプリケーションコードから利用することを想定されています。いずれにせよ、(たとえデバッグ用途であっても)サブスレッドから直接メッセージボックスを表示させるのは推奨されません。

    また、タイマースレッドとの同期に単純なBOOL型(int型)プリミティブ変数が使われていますが、コンパイラ最適化や処理系依存動作に左右されないように、メモリバリアや同期オブジェクトを使うべきです。

    そのほか、Visual Studio 2012以降では、C++/CLI向けのWindowsフォームアプリケーションプロジェクトのテンプレートが削除されています。もともとC++/CLIはネイティブとマネージの境界面でのみ限定的に使用することを想定されており、C++/CLIでWinFormsアプリケーションを記述するのはC#と比べてかなり非効率なので、よほどの理由がないかぎりやめておいたほうがいいと思います。

    Visual Studio 2012、2013 で Visual C++ の Windows フォーム アプリケーション テンプレートが削除され、新規に作成できない

    なお、文字列リテラルの末尾\0は、MessageBox()の引数用途としては不要です。例えば"ABC"やL"ABC"だけで、NULL終端文字列となります。C++/CLIよりもまず、標準C/C++の基本を学習されたほうがよいのではないでしょうか。
    • 編集済み sygh 2017年11月24日 18:30
    2017年11月24日 15:45
  • 再現テストと修正コードの提示ありがとうございます。
    確かに頂いたコードですと、事象発生しませんね。

    MainThread以外ではMessageBoxの呼び出しはデリゲート対応しようと思います。

    2017年11月25日 0:22
  • 単純なMessageBoxだといえ、れっきとしたダイアログボックスであり、当該スレッドでメッセージループを回す必要があります。

    特に.NET Frameworkでは暗黙的にメッセージループが管理されているため、独自のスレッドで.NET Frameworkと共存させながらメッセージループを回すのは非常に困難です。結局syghさんが提案されているようにWindows APIのMessageBoxではなく、.NET FrameworkのMessageBox::Showを呼び出すべきです。こちらであればメッセージループも適切に管理されています。

    2017年11月29日 21:43
  • 不思議なことに私の環境でご質問のコードを動かしたところ、Windows 10 においてはメインスレッド以外で MessageBox::Show や Win32 API の MessageBox を呼び出しても特に問題は発生せずに正常に動作するのですが、Windows 7 で試したところ、MessageBox::Show と Win32 API の MessageBox の両方でご質問と同様な問題が発生しました。推測になりますが、ゆういち0722 さんがあえて Win32 API の MessageBox を呼び出されているのは、問題を回避するために試行錯誤があったのではないかと思いました。
    2017年11月30日 1:00