7

I am trying to create a wrapper to a C dll and I am trying to call a function that has takes a callback function, receives an object as a pointer that is passed back.

The .h file delares

extern int SetErrorHandler(void (*handler) (int, const char*, void*),
                              void *data_ptr);

The handler is a callback function that is called when an error occurs and the data_ptr is any object (state) that is passed back to you, in the case of my app that will just be this (current object).

I am able to call functions in a dll that uses marshalled constant types like simple types strings, ints etc. But I cant figure out how to just marshall a pointer to a C# object that is the state.

In order to pass the object reference to the C function from what I have find by searching here and otherwise it seems that I need a structure type to be able to marshall to the function so I created a struct to hold my object:

[StructLayout(LayoutKind.Sequential)]
struct MyObjectState
{
   public object state;
}

EDIT: I tried to put an attribute: [MarshalAs(UnmanagedType.Struct, SizeConst = 4)] on the public object state property, but this produces the same error, so I removed it, doesnt seem it would work anyway.

The struct contains a single object property to hold any object for the callback function.

I declared the delegate in C# as follows:

delegate void ErrorHandler(int errorCode, IntPtr name, IntPtr data);

Then I declared the import function in C# as follows:

[DllImport("somedll.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int SetErrorHandler handler, IntPtr data);

Then I created a callback function in my C# code:

void MyErrorHandler(int errorCode, IntPtr name, IntPtr data)
{
    var strName = Marshal.PtrToStringAnsi(name);
    var state = new MyObjectState();
    Marshal.PtrToStructure(data, state);
    Console.WriteLine(strName);
}

I am able to call the library function as follows:

var state = new MyObjectState()
{
    state = this
};
IntPtr pStruct = Marshal.AllocHGlobal(Marshal.SizeOf(state));
Marshal.StructureToPtr(state, pStruct, true);
int ret = SetErrorHandler(MyErrorHandler, pStruct);

The call works and the callback function is called but I am unable to access the data in the callback function and when i try Marshal.PtrToStructure I get an error:

The structure must not be a value class.

I did a lot of searching here and found various things on Marshall and void* but nothing has helped me to get this to work

Thanks.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Andre
  • 3,717
  • 4
  • 25
  • 29

2 Answers2

3

You are making this more complicated than it needs to be. Your C# client does not need to use the data_ptr parameter because a C# delegate already has a built in mechanism for maintaining the this pointer.

So you can simply pass IntPtr.Zero to the delegate. Inside your error handler delegate you just ignore the value of data_ptr since this will be available.

In case you don't follow this description, here's a short program to illustrate what I mean. Note how MyErrorHandler is an instance method that acts as the error handler, and can modify instance data.

class Wrapper
{
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void ErrorHandler(int errorCode, string name, IntPtr data);

    [DllImport("somedll.dll", CallingConvention = CallingConvention.Cdecl)]
    static extern int SetErrorHandler(ErrorHandler handler, IntPtr data);

    void MyErrorHandler(int errorCode, string name, IntPtr data)
    {
        lastError = errorCode;
        lastErrorName = name;
    }

    public Wrapper()
    {
        SetErrorHandler(MyErrorHandler, IntPtr.Zero);
    }            

    public int lastError { get; set; }
    public string lastErrorName { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Wrapper wrapper = new Wrapper();
    }
}
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • In this case it is true, Indeed I did not really need to pass this to the data as it will be available in my object callback, but i wanted to provide the correct call to the function for when the user wants to provide a context. I guess the management of the data can be handled by the C# object to maintain state/context. So I guess this solves my problem, but I guess the original question of how to or is it possible to pass the object remains unanswered – Andre Apr 12 '12 at 07:45
  • You really don't want to pass a reference to a C# object in my view. Remember that managed objects can be moved by GC. Are you going to force such objects to be pinned? And what can the native code do with such a thing? It's completely opaque. You could use `ObjectIDGenerator` to get a unique ID for each object, but personally I think your question is best dealt with by side-stepping it! – David Heffernan Apr 12 '12 at 07:52
  • 1
    What you say makes sense, and I guess the answer is don't do it/avoid it, it might not be impossible but you should'nt, Thanks – Andre Apr 12 '12 at 08:00
2

There may very well be a way to do this, but I gave up a long time ago. The solution I've come up with is slightly hackish, but it's very effective and works with everything I've thrown at it:

C# -> Managed C++ -> Native calls

Doing it this way you end up writing a small wrapper in managed C++, which is kind of a pain, but I found to be more capable and less painful than all of that marshaling code.

Honestly though I'm kind of hoping that someone gives a non-evasive answer, I've struggled with this for quite a while myself.

Chris Eberle
  • 47,994
  • 12
  • 82
  • 119
  • Thanks Chris, this is useful, but I don't have the tools or knowledge at this point to write the Managed C++ library, perhaps i can figure it out sometime in the future. I don't suppose you can provide a link to a generic one with documentation how to use it? Finally if this all doesn't work for me the solution is to make sure that any data i need in the callback must be added to the struct and marshalled as simple values? Thanks – Andre Apr 12 '12 at 05:54
  • PS: im also having an additional problem with my callback, it works fine but even if i remove all the code in the callback function body, at the end of the function I get an access violation (Attempted to read or write protected memory) – Andre Apr 12 '12 at 06:19
  • Actually I just figured it out: I was missing the attribute [UnmanagedFunctionPointer(CallingConvention.Cdecl)] on my delegate: http://stackoverflow.com/questions/4906931/nullreferenceexception-during-c-callback-to-c-sharp-function – Andre Apr 12 '12 at 06:21