アプリが暗黙的な代入演算子でクラッシュする

Ueda Kunio(上田 邦男) 40 評価のポイント
2024-10-25T10:14:28.8466667+00:00

Windowsアプリ開発(C++ネイティブアプリ)で以下のようなクラスを作成し、そのクラスを使用している関数を繰り返し実行していると突然アプリがクラッシュしました。

クラッシュダンプを見ると、暗黙的に作成されたと思われる代入演算子( operator= )でクラッシュしていました。

下記のコード内では、どこにも代入するコードは入っていないのに、それがなぜ、どこで呼ばれたのか分かりません。

この辺りの動作に詳しい方、アドバイスを頂けますでしょうか?

よろしくお願いいたします。


Header file in my sub dll

#pragma once
#include "wincodec.h"
#include "wincodecsdk.h"

class AFX_EXT_CLASS TestImageEncoder
{
private:
    IWICImagingFactory* mpFactory;

public:
    TestImageEncoder();
    virtual ~TestImageEncoder();

    BOOL Initialize();

    BOOL EncodeData( WORD* pImage, int width, int height, BYTE** pDstData, int& dstSize, BOOL isLossless );
};

Cpp file in my app sub dll

TestImageEncoder::TestImageEncoder()
    : mpFactory( NULL )
{
    Initialize();
}

TestImageEncoder::~TestImageEncoder()
{
    if ( mpFactory )
    {
        mpFactory->Release();
        mpFactory = NULL;
    }

    CoUninitialize();
}

BOOL TestImageEncoder::Initialize()
{
    // Initialize COM
    HRESULT hr;
    hr = CoInitialize( NULL );
    if ( FAILED( hr ) )
    {
        _RPTN( _CRT_WARN, "Failed CoInitialize(): %d\n", hr );
        return FALSE;
    }

    // Create the COM imaging factory
    hr = CoCreateInstance( CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS( &mpFactory ) );
    if ( FAILED( hr ) )
    {
        _RPTN( _CRT_WARN, "Failed to create a WIC imaging factory: %d\n", hr );
        return FALSE;
    }

    return TRUE;
}


BOOL TestImageEncoder::EncodeData( WORD* pImage, int width, int height, BYTE** ppDstData, int& dstSize, BOOL isLossless )
{
    // Encode image data and save it as JPEG XR file
    CString tempFullPath;
    tempFullPath = _T( "TempImage.jxr" );
    
    BOOL res = SaveAsJpegXR( tempFullPath, pImage, width, height, isLossless );
    if ( !res )
    {
        // Delete the JPEG XR file
        ::DeleteFile( tempFullPath );
        return FALSE;
    }


    // Load image data from the JPEG XR file
    CFile jxrFile;
    jxrFile.Open( tempFullPath, CFile::modeRead );
    dstSize = jxrFile.GetLength();
    *ppDstData = new BYTE[dstSize];
    ZeroMemory( *ppDstData, dstSize );
    jxrFile.Read( *ppDstData, dstSize );
    jxrFile.Close();

    // Delete the JPEG XR file
    ::DeleteFile( tempFullPath );
}


BOOL TestImageEncoder::SaveAsJpegXR( CString& filePath, WORD* pImage, int width, int height, BOOL isLossless )
{
    HRESULT hr;
    IWICStream* piStream = NULL;
    IWICBitmapEncoder* piEncoder = NULL;
    IWICBitmapFrameEncode* piBitmapFrame = NULL;
    IPropertyBag2* pPropertybag = NULL;
    BOOL res = FALSE;

    __try
    {
        // Create a stream //////////////////////////////////////////////
        hr = mpFactory->CreateStream( &piStream );
        if ( FAILED( hr ) )
        {
            _RPTN( _CRT_WARN, "Failed to create stream: %d\n", hr );
            res = FALSE;
            __leave;
        }
        piStream->InitializeFromFilename( filePath, GENERIC_WRITE );

        // Create an encoder //////////////////////////////////////////////
        hr = mpFactory->CreateEncoder( GUID_ContainerFormatWmp, NULL, &piEncoder );
        if ( FAILED( hr ) )
        {
            _RPTN( _CRT_WARN, "Failed to create encoder: %d\n", hr );
            res = FALSE;
            __leave;
        }

        // Initialize the encoder ///////////////////////////
        hr = piEncoder->Initialize( piStream, WICBitmapEncoderNoCache );
        if ( FAILED( hr ) )
        {
            _RPTN( _CRT_WARN, "Failed to initialize encoder: %d\n", hr );
            res = FALSE;
            __leave;
        }

        // Create a frame ///////////////////////////////////////////////////
        hr = piEncoder->CreateNewFrame( &piBitmapFrame, &pPropertybag );
        if ( FAILED( hr ) )
        {
            _RPTN( _CRT_WARN, "Failed to create a new frame: %d\n", hr );
            res = FALSE;
            __leave;
        }

        // Set lossless if it is true /////////////////////////////////////////
        if ( isLossless )
        {
            // Enable lossless /////////////////////////////
            PROPBAG2 option = { 0 };
            option.pstrName = L"Lossless";
            VARIANT varValue;
            VariantInit( &varValue );
            varValue.vt = VT_BOOL;
            varValue.bVal = VARIANT_TRUE;
            hr = pPropertybag->Write( 1, &option, &varValue );
            if ( FAILED( hr ) )
            {
                _RPTN( _CRT_WARN, "Failed to set the property for Lossless: %d\n", hr );
                res = FALSE;
                __leave;
            }
        }

        // Initialize the frame ///////////////////////////////////////////////////
        hr = piBitmapFrame->Initialize( pPropertybag );
        if ( FAILED( hr ) )
        {
            _RPTN( _CRT_WARN, "Failed to initialize the frame: %d\n", hr );
            res = FALSE;
            __leave;
        }

        // Set image size ///////////////////////////////////////////////////
        hr = piBitmapFrame->SetSize( width, height );
        if ( FAILED( hr ) )
        {
            _RPTN( _CRT_WARN, "Failed to set size (width=%d, height=%d) : %d\n", width, height, hr );
            res = FALSE;
            __leave;
        }

        // Set the pixel format ///////////////////////////////////////////////////
        WICPixelFormatGUID formatGUID = GUID_WICPixelFormat16bppGray;
        hr = piBitmapFrame->SetPixelFormat( &formatGUID );
        if ( FAILED( hr ) )
        {
            _RPTN( _CRT_WARN, "Failed to set pixel format: %d\n", hr );
            res = FALSE;
            __leave;
        }

        // Check if the pixel format is correctly set //////////////////////////
        hr = IsEqualGUID( formatGUID, GUID_WICPixelFormat16bppGray ) ? S_OK : E_FAIL;
        if ( FAILED( hr ) )
        {
            _RPTN( _CRT_WARN, "Unexpected pixel format (not 16bppGray): %d\n", hr );
            res = FALSE;
            __leave;
        }

        UINT cbStride = width * sizeof( WORD );
        UINT cbBufferSize = height * cbStride;
        hr = piBitmapFrame->WritePixels( height, cbStride, cbBufferSize, (BYTE*)pImage );
        if ( FAILED( hr ) )
        {
            _RPTN( _CRT_WARN, "Failed to write image data: %d\n", hr );
            res = FALSE;
            __leave;
        }

        hr = piBitmapFrame->Commit();
        if ( FAILED( hr ) )
        {
            _RPTN( _CRT_WARN, "Failed to commit the frame: %d\n", hr );
            res = FALSE;
            __leave;
        }

        hr = piEncoder->Commit();
        if ( FAILED( hr ) )
        {
            _RPTN( _CRT_WARN, "Failed to commit the encoder: %d\n", hr );
            res = FALSE;
            __leave;
        }

        res = TRUE;
    }
    __finally
    {

        if ( piEncoder )
        {
            piEncoder->Release();
        }

        if ( piBitmapFrame )
        {
            piBitmapFrame->Release();
        }

        if ( pPropertybag )
        {
            pPropertybag->Release();
        }

        if ( piStream )
        {
            piStream->Release();
        }

    }

    return res;
}

このクラスを使用して以下のような実装をしました。

下記関数内で16回繰り返し、さらにTestProcess関数自体も20回ほど繰り返し実行されるようになっています。

Method in my app exe

void TestProcess(WORD* srcData, int width, int height)
{
    CFile fImage;
    fImage.Open( "TestImageData.DAT", CFile::modeWrite | CFile::modeCreate );

    int srcSize = wBytes_per_Pixel * dwWidth * dwHeight;
    BYTE* pDst = NULL;
    int dstSize = 0;

    TestImageEncoder encoder;
    for ( int i = 0; i < 16; i++ )
    {
        encoder.EncodeData( srcData, width, height, &pDst, dstSize );

        fImage.Write( &dstSize, sizeof( int ) );
        fImage.Write( pDst, dstSize );

        delete[] pDst;
        pDst = NULL;
    }
}

クラッシュダンプのスタックトレース(一部)は以下の用になっていました。

mfc140u!CWnd::OnWndMsg+0xe75
mfc140u!CWnd::WindowProc+0x3f
mfc140u!AfxCallWndProc+0x11e
mfc140u!AfxWndProc+0x54
mfc140u!AfxWndProcBase+0x49
user32!UserCallWinProcCheckWow+0x2d1
user32!DispatchMessageWorker+0x1f1
mfc140u!AfxInternalPumpMessage+0x52
mfc140u!CWinThread::Run+0x81
mfc140u!AfxWinMain+0xc0
MyApp!TestImageEncoder::operator=+0x5af62  <- なぜかここで暗黙的代入演算子が呼ばれています。
kernel32!BaseThreadInitThunk+0x1d
ntdll!RtlUserThreadStart+0x28
C++
C++
C プログラミング言語の拡張機能として作成された高レベルの汎用プログラミング言語。低レベルのメモリ操作機能に加えて、オブジェクト指向、汎用、関数型の機能を備えています。
20 件の質問
{count} 件の投票

1 件の回答

並べ替え方法: 最も役に立つ
  1. gekka 9,586 評価のポイント MVP
    2024-10-27T06:30:25.9933333+00:00

    関数の引数はスタック領域に用意されていて、かつその型がポインタ型なので、そのポインタの扱いをミスってスタック破壊しているんじゃなかな。

    そのスタック破壊の結果、スタック領域に記録されている呼び出し関数のアドレスを、たまたま暗黙的代入演算子の関数アドレスで上書きしてしまったとかで。

    BOOL EncodeData(WORD* pSrcData, int srcSize, BYTE** pDstData, int& dstSize)
    {
    	*pDstData = new BYTE[512];
    
    	memcpy(&pDstData+1024, &pSrcData+1024, 512); //ポインタの扱いを間違えてスタック破壊
    
    	return TRUE;
    }
    

    上記のコードを実行すると、メニュー->デバッグ->ウィンドウ->呼び出し履歴 で表示される履歴が、破壊されるのがわかると思う。(512Byteの範囲に関数のアドレスが書き込まれていれば)

    なので、とりあえずはTestProcess関数内やEncodeData関数内でのメモリポインタの扱いにミスがないかを確認してみては?  

    # EncodeDataの戻り値のTRUE/FALSE判定してないので、失敗していてもDo Somethingのところを実行してるのが原因の可能性もあるけど。

    17:44 memcpyのアンド記号が消えてたのを修正


お客様の回答

回答は、質問作成者が [承諾された回答] としてマークできます。これは、ユーザーが回答が作成者の問題を解決したことを知るのに役立ちます。