Search code examples
c#cpinvoke

pinvoke giving AccessViolationException when calling C code


There is a library a colleague of mine has made in C and I would like to call it from C#. I think it almost right but its getting an AccessViolationException when I call the 2nd method/function. I've tried a few different things:

  1. class instead of struct
  2. adding [MarshalAs(UnmanagedType.FunctionPtr)] to callbacks/delegates (this actually throws exception on first call instead of second)
  3. Removing [MarshalAs(UnmanagedType.LPStr)] for string in struct (this will then throw exception after second method on closing brace)

The code below for the C# side and the C header I'm using.

C:

#pragma once
#define MAX_FEATURE_LENGTH 30
#define HELPERDLL_API __declspec(dllimport) 

struct HelperAttributes {
  void(*SaveCallBack) ();
  void(*ExitCallBack) ();
  char* feature_name;
  int attempt_limit;
  int check_interval;   
}; 

extern "C"
{
  void HELPERDLL_API DoStartUp();
  void HELPERDLL_API ReSpecify();
  void HELPERDLL_API Initialise(HelperAttributes* attributes);
}

C#:

namespace test
{
  public partial class Form1 : Form
  {
    public delegate void SaveCallBack();
    public delegate void ExitCallBack();

    public Form1()
    {
        InitializeComponent();
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct HelperAttributes 
    {
        public SaveCallBack saveCallBack;
        public ExitCallBack exitCallBack;
        [MarshalAs(UnmanagedType.LPStr)]
        public string feature_name;
        public int attempt_limit;
        public int check_interval;
    };

    [DllImport("testing.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int DoStartUp();
    [DllImport("testing.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int ReSpecify();
    [DllImport("testing.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int Initialise(
                                        [In, MarshalAs(UnmanagedType.LPStruct)]
                                        HelperAttributes attributes
                                       );

    private void button1_Click(object sender, EventArgs e)
    {
        HelperAttributes attributes = new HelperAttributes();

        attributes.saveCallBack = saveCallBackDelegate;
        attributes.exitCallBack = exitCallBackDelegate;
        attributes.feature_name = "XXX";
        attributes.attempt_limit = 10;
        attributes.check_interval = 30;

        Initialise(attributes);
        DoStartUp();
    }

    public void saveCallBackDelegate()
    {
        this.textBox1.Text = "save callback made";
    }

    public void exitCallBackDelegate()
    {
        this.textBox1.Text = "exit callback made";
    }
  }
}

Solution

  • The functions are all void in the unmanaged side, and declared with int return type on the managed side. That mis-match must be corrected.

    Your callback function pointers use the cdecl calling convention. The delegates you pass from the managed code use stdcall calling convention.

    Deal with that by marking the delegates like so:

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    public delegate void SaveCallBack();
    
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    public delegate void ExitCallBack();
    

    The Initialise can be more simply declared as:

    public static extern void Initialise(ref HelperAttributes attributes);
    

    Beyond that, we have to guess at the code we cannot see. For instance, does Initialise copy struct it is passed, or does it remember the address of the struct.

    It it is the latter, then that is a fatal problem. The solution there is to fix the unmanaged code so that it does not remember addresses, but rather copies content. And even if it copies the struct, does it do a deep copy, or does it copy the pointer to the character array? To get to the bottom of this requires sight of code that is not present in the question.

    And even if the function does copy the struct correctly, you will need to keep the delegates alive, as explained by Hans.