AccessViolationException when using C++ DLL from C

2019-08-31 04:48发布

I have a C++ DLL for use from C#. I have a function which takes a string passed to it, and I have those set on the C++ function parameters as const char * like so:

int __stdcall extract_all_frames(const char* szDestination, float scaleFactor)

The main body of this function is copied directly from a working FFmpeg example function so I'm almost certain the problem isn't there. I feel like the problem is in this modification I made to it:

//Open file
char szFilename[32];
sprintf_s(szFilename, sizeof(szFilename), "frame%d.ppm\0", iFrame);

// JOIN szFILENAME AND szDESTINATION
std::string buffer(szDestination, sizeof(szDestination));
buffer.append("\\");
buffer.append(szDestination);

Which is supposed to be a concatenated path and directory. I then pass buffer.c_str() into fopen_s(), which takes const char * not std::string. Whenever calling this function from C#, I get the following exception:

A first chance exception of type 'System.AccessViolationException' occurred in XRF FFmpeg Invoke Test.exe

Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

This is the complete code:

#include "stdafx.h"
#pragma comment (lib, "avcodec.lib")
#pragma comment (lib, "avformat.lib")
#pragma comment (lib, "avutil.lib")
#pragma comment (lib, "swscale.lib")

extern "C"
{
    #include <libavcodec\avcodec.h>
    #include <libavformat\avformat.h>
    #include <libavutil\avutil.h>
    #include <libswscale\swscale.h>
}

#include <string>
#include "Xrf.FFmpeg.hpp"

void save_frame(AVFrame* pFrame, int iFrame, const char* szDestination)
{
    //Open file
    char szFilename[32];
    sprintf_s(szFilename, sizeof(szFilename), "frame%d.ppm\0", iFrame);

    // JOIN szFILENAME AND szDESTINATION
    std::string buffer(szDestination, sizeof(szDestination));
    buffer.append("\\");
    buffer.append(szDestination);

    FILE* pFile;
    errno_t openError = fopen_s(&pFile, buffer.c_str(), "wb");

    if (pFile == NULL)
    {
        return;
    }

    //Write header
    int width = pFrame->width;
    int height = pFrame->height;

    fprintf(pFile, "P6\n%d %d\n255\n", width, height);

    //Write pixel data
    for (int y = 0; y < height; y++)
    {
        fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
    }

    // Close file
    fclose(pFile);
}

    int __stdcall extract_all_frames(const char* szPath, const char* szDestination, float scaleFactor)
    {
        // Check if scaleFactor is valid
        if ((scaleFactor != 0.f) && 
            (scaleFactor > 3.f))
        {
            fprintf(stderr, "Xrf: Scale factor '%f' out of bounds!\nMust be greater than 0, and less then or equal to 3.0.\n", scaleFactor);
            return -1;
        }

        // Register all formats and codecs
        av_register_all();

        AVFormatContext* pFormatCtx;
        if (avformat_open_input(&pFormatCtx, szPath, nullptr, nullptr) != 0)
        {
            fprintf(stderr, "libavformat: Couldn't open file '%s'!\n", szPath);
            return -1;
        }

        // Retrieve stream information
        if (avformat_find_stream_info(pFormatCtx, nullptr) < 0)
        {
            fprintf(stderr, "libavformat: Unable to find stream information!\n");
            return -1;
        }

        // Dump information about file onto standard error
        av_dump_format(pFormatCtx, 0, szPath, 0);

        // Find the first video stream
        size_t i;
        int videoStream = -1;
        for (i = 0; i < pFormatCtx->nb_streams; i++)
        {
            if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
            {
                videoStream = i;
                break;
            }
        }
        if (videoStream == -1)
        {
            fprintf(stderr, "libavformat: No video stream found!\n");
            return -1;
        }

        // Get a pointer to the codec context for the video stream
        AVCodecContext* pCodecCtx = pFormatCtx->streams[videoStream]->codec;

        // Scale the frame
        int scaleHeight = static_cast<int>(floor(pCodecCtx->height * scaleFactor));
        int scaleWidth = static_cast<int>(floor(pCodecCtx->width * scaleFactor));

        //Check if frame sizes are valid (not 0, because that's dumb)
        if (scaleHeight == 0 || scaleWidth == 0)
        {
            fprintf(stderr, "Xrf: Scale factor caused a zero value in either width or height!\n");
            return -1;
        }

        // Find the decoder for the video stream
        AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
        if (pCodec == NULL)
        {
            fprintf(stderr, "libavcodec: Unsupported codec!\n");
            return -1; // Codec not found
        }

        // Open codec
        AVDictionary* optionsDict = nullptr;
        if (avcodec_open2(pCodecCtx, pCodec, &optionsDict) < 0)
        {
            fprintf(stderr, "libavcodec: Couldn't open codec '%s'!\n", pCodec->long_name);
            return -1;
        }

        // Allocate video frame
        AVFrame* pFrame = av_frame_alloc();
        // Allocate an AVFrame structure
        AVFrame* pFrameRGB = av_frame_alloc();

        if (pFrameRGB == NULL)
        {
            fprintf(stderr, "libavformat: Unable to allocate a YUV->RGB resampling AVFrame!\n");
            return -1;
        }

        // Determine required buffer size and allocate buffer
        int numBytes = avpicture_get_size(PIX_FMT_RGB24, scaleWidth, scaleHeight);
        uint8_t* buffer = static_cast <uint8_t *> (av_malloc(numBytes * sizeof(uint8_t)));

        struct SwsContext* sws_ctx = sws_getContext(pCodecCtx->width,
            pCodecCtx->height,
            pCodecCtx->pix_fmt,
            scaleWidth,
            scaleHeight,
            PIX_FMT_RGB24,
            SWS_BILINEAR,
            nullptr, nullptr, nullptr);

        // Assign appropriate parts of buffer to image planes in pFrameRGB
        // Note that pFrameRGB is an AVFrame, but AVFrame is a superset
        // of AVPicture
        avpicture_fill(reinterpret_cast <AVPicture *> (pFrameRGB),
            buffer,
            PIX_FMT_RGB24,
            scaleWidth,
            scaleHeight);

        // Read frames and save first five frames to disk
        AVPacket packet;
        int frameFinished;
        while (av_read_frame(pFormatCtx, &packet) >= 0)
        {
            // Is this a packet from the video stream?
            if (packet.stream_index == videoStream)
            {
                // Decode video frame
                avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

                // Did we get a video frame?
                if (frameFinished)
                {
                    // Convert the image from its native format to RGB
                    sws_scale(sws_ctx,
                        static_cast <uint8_t const * const *> (pFrame->data),
                        pFrame->linesize,
                        0,
                        pCodecCtx->height,
                        pFrameRGB->data,
                        pFrameRGB->linesize);

                    // Save the frame to disk
                    if (++i <= 5)
                    {
                        save_frame(pFrameRGB, i, szDestination);
                    }
                }
            }

            // Free the packet that was allocated by av_read_frame
            av_free_packet(&packet);
        }

        av_free(buffer); // Free the RGB image
        av_free(pFrameRGB);
        av_free(pFrame); // Free the YUV frame
        avcodec_close(pCodecCtx); // Close the codec
        avformat_close_input(&pFormatCtx); // Close the video file

        return 0;
    }

I don't know if the error is in my modification (most likely, I'm extremely new to C++), or the other code, as the exception only throws on the invocation line in C#, not the actual C++ line causing the problem.

1条回答
混吃等死
2楼-- · 2019-08-31 05:50

This is wrong:

std::string buffer(szDestination, sizeof(szDestination));

szDestination is a pointer, thus sizeof(szDestination) will return the pointer size, in bytes, not the number of characters.

If szDestination is a null terminated string, use strlen or similar function to determine the number of characters. If it isn't null terminated, then you need to pass the number of bytes to copy as a parameter.

The better thing to do is when your DLL function is called:

int __stdcall extract_all_frames(const char* szPath, const char* szDestination, float scaleFactor)

take those pointers and immediately assign them to std::string. Then drop all usage of char* or const char* from there. There is no need for your helper functions to deal with "dumb" character pointers.

Example:

int __stdcall extract_all_frames(const char* szPath, const char* szDestination, float scaleFactor)
{
   std::string sPath = szPath;
   std::string sDestination = sDestination;
   // From here, use sPath and sDestination
  //...
}

// redefinition of save_frame
//...
void save_frame(AVFrame* pFrame, int iFrame, const std::string& szDestination)
{
   //Open file
   std::string buffer = "frame" + to_string(iFrame) + ".ppm\0";
   buffer.append("\\");
   buffer.append(szDestination);
      //...
}
查看更多
登录 后发表回答