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:
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.
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);