Search code examples
c#c++.netpinvokemarshalling

P/Invoke System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt


My code is raising an exception. I am using P/Invoke to call some C API from a .NET project.

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

It's a void function which takes as input a struct of primitives. I have several working API methods with several different structs (much more "complicated") that are working.

Setup:

  • VS 17 2022
  • C++ 20
  • .NET Framework 4.7.2

It happens both in Debug and Release mode.

C/C++ side:

typedef struct
{
    float visibility; //[m] Visibility range
    float thickness;  //[m] Thickness of the fog layer
    // int placeholder;
} Fog;

/// @brief set fog data to be forwarded to the IG
/// @param fog \ref Fuze::Interfaces::Fog with fog data
/// @param regionId the id of the region to be updated
extern "C" __declspec(dllexport) void SetFogData(const Fog* fog, int regionId);

///interface.cpp
#include "Interface.h"
#include <iostream>

void SetFogData(const Fog* fog, int regionId)
{
    std::cout << "\n\nForwarding fog data to plugins\n";
    std::cout << "Fog data: " << fog->thickness << " " << fog->visibility << std::endl;
}

C# side:

using System.Runtime.InteropServices;

namespace Fuze
{
    [StructLayout(LayoutKind.Sequential)]
    public struct Fog
    {
        public float Visibility; // [m] Visibility range
        public float Thickness;  // [m] Thickness of the fog
        // int placeholder;
    }
    static public class API
    {
        private const string dllPath = "Fuze_API.dll";

        [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
        public static extern void SetFogData(Fog fog, int regionId);

    }
}

// main.cs

using static Fuze.API;

class FakeClientDotNet
{
    static void Main(string[] args)
    {
        Fuze.Fog fog = new Fuze.Fog();
        fog.Thickness = 50.0f;
        fog.Visibility = 250.0f;
        SetFogData(fog, 0);
        
        System.Console.WriteLine("\nCompleted\n");
    }
}

This code generates the error. If I uncomment the int placeholder field (in both structs), it starts working and the data provided via P/Invoke is correct. I have the same behavior with a struct made of an enum (with the int specifier) and an int, in which I have to add a third int member to not get the exception in the title.


Solution

  • Your C function takes a Fog* (so, a pointer):

    extern "C" __declspec(dllexport) void SetFogData(const Fog* fog, int regionId);
    

    But your P/Invoke declaration takes a Fog by value:

    public static extern void SetFogData(Fog fog, int regionId);
    

    That's a struct, and it's cdecl, so it gets copied on the stack. And then the native side tries to interpret that fog object as a pointer and stuff goes wrong. Or at least that's a guess, in reality it's our good old friend undefined behaviour.

    If you want to pass a pointer, pass a reference and let the marshaller turn that into a pointer:

    public static extern void SetFogData(ref Fog fog, int regionId);
    

    And then, later:

    SetFogData(ref fog, 0);