0

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";
    }
  }
}
2
  • have edited code in line with comments, now a working example except for 64 bit, anything else wrong with this? Commented Jun 13, 2014 at 14:40
  • I rolled the question back. Please don't remove the original question when you make an edit. If you have another question, that should be another question. If you have problems with your 64 bit code, please supply enough information for it to be diagnosed. We've simply no idea what is inside the unmanaged DLL. Commented Jun 13, 2014 at 14:56

3 Answers 3

3
    HelperAttributes attributes = new HelperAttributes();

That's very, very troublesome. You have clear memory management problems in this code. The struct you allocate here has a very limited lifetime. It is only valid for the duration of the Click event handler method, nanoseconds at best. This blows up in more than one way:

  • The C code must not store the passed pointer, it must copy the struct. It probably does not do that. You now have a "dangling pointer", a pretty infamous C bug. Dereferencing the pointer, later, produces arbitrary garbage.

  • The delegate objects that your code creates and assigns to the saveCallback and exitCallback members of the struct cannot survive. The garbage collector cannot find out that the C code requires them to stay alive. So the next garbage collection destroys the objects, Big Kaboom when the C code makes the callback. Also note that you must declare them with [UnmanagedFunctionPointer] to make them Cdecl if they actually take arguments.

There's more than one way to solve these problems. By far the simplest way is to move the variable outside of the method and declare it static. That keeps it valid for the lifetime of the program. The garbage collector can always see a reference to the delegate objects that way. You however cannot bypass the need to fix the C code and have it make a copy, this struct is not blittable and the C code gets a pointer to a temporary copy that the pinvoke marshaller created. It becomes junk as soon as Initialise() returns.

Sign up to request clarification or add additional context in comments.

4 Comments

Very good. As well as copying the struct, it has to copy the string too.
thanks for the tips! I'll check the C code to make sure it is copying the struct.
I think "You have clear memory management problems in this code" is a bit strong. There's no evidence that the C function is improperly holding onto the pointer vs. copying the values.
Of course there is, it crashes :) The delegate object collection is a sure-fire bug.
2

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.

5 Comments

excellent! worked great, the other thing I had to change was making the struct a class, otherwise it was giving me a MarshalDirectiveException. thanks!
Make it a class and pass by value. Or make it a struct and pass by ref. Comes to the same in the end.
would there be a difference for 32/64 dll's. I have it working for 32 bit but when I changed the path to a 64 bit version of testing.dll it gets another AccessViolationException?
Nothing in the question appears to be 32/64 bit sensitive
i didn't think there should be any difference. im wondering if there is something happening in the c code as it looks at the registry. I'll investigate further with that. Running it normally (opening exe) works ok, its just inside my debugger.
0

In your C dll you need to use dllexport instead of dllimport

#define HELPERDLL_API __declspec(dllexport)

In C# code import functions like that

[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 

8 Comments

ah missed that! could I another #define and define the functions as an export as well?
There are many ways to export functions from dll. check this link msdn.microsoft.com/en-us/library/z4zxe9k8.aspx
You mean to say you want to import and export the same function? This is not possible.
if the DoStartUp function hasn't been exported how can it be called? the exception doesn't happen till after this?
It is a red herring, the C code cannot compile with dllimport. The posted snippet is from the .h file, it doesn't get used here.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.