2

I'm writing a wrapper around some C code, and I can't quite figure out how to write this bit without being able to use the ref keyword in a lambda.

The unmanaged function wrappers look like this:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int SDL_EventFilter(ref object userData, ref SDL_Event @event);

[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_AddEventWatch")]
internal static extern void SDL_AddEventWatch(SDL_EventFilter filter, ref object userData);

But I don't want to use SDL_Event directly (it's a complicated struct), so I've wrapped it in my own class, Event. However, C is expecting a pointer to an SDL_Event not an Event so I have to write some more code to wrap it all up:

public delegate void EventFilter(object userData, Event @event);

public static void AddEventWatch(EventFilter filter, object userData)
{
    SDL_AddEventWatch((ref data, ref e) =>        // <-- can't do this
    {
        filter(data, new Event(ref e));
        return 0;
    }, ref userData);
}

This basically takes the SDL_Event that I'm given and converts it into my Event class. However, I can't use the ref keyword inside a lambda, but I'm not sure how to get around it.

I could define a regular helper method instead of using a lambda, but I need to use the local filter variable inside of it, and I don't know how to get that into the function without changing the signature (then it wouldn't match SDL_EventFilter).

In JavaScript, PHP, or Python I could construct a callable object, make filter a member variable, and then use the object as the callback. I'm not sure if there's a similar concept in C#, is there?

Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236

3 Answers3

3

When using the ref keyword, the type must also be given. Like this:

(ref object data, ref SDL_Event e) => { ... }

The parameter list of a lambda is like the parameter list of an ordinary named method. The types can be left out in a lambda, however, but only when no parameters have modifyers such as ref, out, params.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • Not sure why this wasn't mentioned in the bug I linked to, but syntactically this compiles. It, however, causes an exception but that has something to do with passing it off to the DLL. I will have to investigate that further, but I reckon this answers the question as posed. Thanks! – mpen Aug 07 '13 at 06:33
  • 1
    @Mark The question you linked was also about "capturing" `ref` parameters from an ordinary "named" method inside a lambda. As in this example `public object NamedMethod(ref int i) { Func lambda = () => i; return lambda; }`. Here the lambda introduces no `ref` itself, but it tries to capture a `ref` parameter from the containing method. There's no chance this could compile since the compiler won't know what to capture, really. But that is different from the situation in my answer. – Jeppe Stig Nielsen Aug 07 '13 at 08:31
2

Yes you can't do it since Lambda actually captures the variable in CompilerGenerated class and reuses it.

We can make use of ref keyword while passing to a method, but still if you capture the parameter to someother variable It wont behave as you expect.

So easy workaround will be create a method and use instead of lambda.

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • I addressed that, 2nd to last paragraph. How can I write a method? The method has to match the signature of `SDL_EventFilter`, but it can't if I want to pass in `filter`. – mpen Aug 05 '13 at 22:21
  • @Mark You need signature to be same as 2 parameter? if not you can create one more parameter right? – Sriram Sakthivel Aug 05 '13 at 22:26
  • 1
    You need to write a class to capture the `filter`. In general, this is how lambdas are implemented by the compiler. http://stackoverflow.com/questions/8993426/what-are-c-sharp-lambdas-compiled-into-a-stackframe-an-instance-of-an-anonymo – lukegravitt Aug 05 '13 at 22:26
  • @lukegravitt: That would be my last paragraph. Is there an MSDN article on doing that? I didn't know this was possible in C#. Edit: I think you edited in a link. Thanks! – mpen Aug 05 '13 at 22:27
  • Or you can use `Tuple` instead of custom class – Sriram Sakthivel Aug 05 '13 at 22:31
  • @SriramSakthivel: What do you mean a `Tuple`? Tuples aren't callable...?? – mpen Aug 05 '13 at 22:37
  • I mean you can wrap any type inside Tuple and pass to method right? like this `Tuple tuple = Tuple.Create(new Class1(), new object());` To avoid creating custom class for this. – Sriram Sakthivel Aug 05 '13 at 22:43
  • @SriramSakthivel: No...the method has a very specific signature. It has to match what's defined in C. – mpen Aug 05 '13 at 22:56
1

Something like this (untested):

class Capture
{
    private readonly EventFilter filter;

    public Capture(EventFilter filter)
    {
        this.filter = filter;
    }

    public int Method(ref object userData, ref SDL_Event @event)
    {
        this.filter(userData, new Event(ref event));
        return 0;
    }
}

public static void AddEventWatch(EventFilter filter, object userData)
{
    var capture = new Capture(filter);
    SDL_AddEventWatch(capture.Method, ref userData);
}
lukegravitt
  • 892
  • 4
  • 12
  • That looks like what I need :-) Will try this out in a bit. Thanks! – mpen Aug 05 '13 at 22:57
  • Didn't quite work out like I hoped. I'm getting an exception: "An invalid VARIANT was detected during a conversion from an unmanaged VARIANT to a managed object. Passing invalid VARIANTs to the CLR can cause unexpected exceptions, corruption or data loss." – mpen Aug 07 '13 at 06:27
  • Sounds like it doesn't like using refs in unmanaged code either. Unfortunately, I don't really know how to get around that. – lukegravitt Aug 07 '13 at 16:56
  • It's not the refs, they behave like pointers AFAIK. I think the problem is passing a managed class to an unmanaged DLL. I asked a question about that [here](http://stackoverflow.com/q/18096415/65387). – mpen Aug 07 '13 at 18:30