AccessViolationException when using C++ DLL from C#
Asked Answered
C

1

0

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.

Cyst answered 9/12, 2014 at 19:11 Comment(0)
C
1

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);
      //...
}
Cestar answered 9/12, 2014 at 19:19 Comment(7)
Same problem in sprintf_s(szFilename, sizeof(szFilename), "frame%d.ppm\0", iFrame);Ultrared
I used the code in your example and I'm still getting the exact same error, which is making me think this error isn't to do with the strings, it might be the FFmpeg side of things. I'll mark this as the answer though seeing as the question was mostly about the strings. Thanks!Cyst
@CallumBooth - The changes still need to be made, as your original code was not correct wrt sizeof(const char *) being used instead of the correct string length value.Cestar
@Cestar I already went through and changed all the sizeof references to strlen though, before you posted your example code and I still got the error. I'm not using sizeof at all in my current code now.Cyst
@CallumBooth - Better you just change to std::string and drop usage of char * altogether, except of course for the first couple of lines of your exported DLL function. This way, you are guaranteed that a crash cannot occur from the string handling. More than likely, you are probably not checking some return value from an API or third-party function that is returning to you that "something's wrong". Did you check if you are checking all the functions that return success/failure?Cestar
@CallumBooth - So check your return values. If indeed you are, and it is a problem in a third-party or some other function, then you just can't let the DLL crash. If it's that fragile, then I suggest you wrap your DLL function in a try/catch. Also, make sure you built the DLL with SEH handling turned on (if you're using Visual Studio) so that you catch access violations. A little draconian, but at least DLL won't crash and you can return some sort of error code back to C#.Cestar
@Cestar I enabled native code debugging on the C# side of things and it definitely isn't the strings causing it now. It's a problem with one of the APIs I'm using. I'll add error handling in when I've found exactly why the FFmpeg APIs are crashing.Cyst

© 2022 - 2024 — McMap. All rights reserved.