Tech Off Thread

17 posts

.NET Gurus: Pinning a delegate for P/Invoke

Back to Forum: Tech Off
  • User profile image
    PhrostByte

    I'm attempting to P/Invoke a C library.  It requires me to pass a delegate which the library will store for later use.

    Because I do not use this delegate later in managed code it gets collected.  Easy enough to stop, but pinning it in one memory location is another story.  Is this possible?

  • User profile image
    Rossj

    You could use GCHandle but possibly different info from Chris Brumme here

    Chris Brumme wrote:

    Finally, a word on pinning.  I often see applications that aggressively pin managed objects or managed delegates that have been passed to unmanaged code.  In many cases, the explicit pin is unnecessary.  It arises because the developer has confused the requirement of tracking an object instance via a handle with the requirement of keeping the bytes of that object at a fixed location in memory.

     

    ...The PInvoke layer is hooked into the CLR’s stack crawling mechanism for GC reporting.  So it can defer all overhead related to pinning unless a GC actually occurs while the PInvoke call is in progress.  Applications that explicitly pin buffers around PInvoke calls are often doing so unnecessarily.


  • User profile image
    PhrostByte

    That is good advice if you are passing a delegate through one function.  In my case the library is storing it.

    I am doing:

    setdelegate(d); // p/invoked, stores the function pointer

    somefunc() // p/invoked, can call function pointer.

    the delegate can be moved before or during somefunc()'s execution.




    Rossj wrote:
    You could use GCHandle but possibly different info from Chris Brumme here

    Chris Brumme wrote:

    Finally, a word on pinning.  I often see applications that aggressively pin managed objects or managed delegates that have been passed to unmanaged code.  In many cases, the explicit pin is unnecessary.  It arises because the developer has confused the requirement of tracking an object instance via a handle with the requirement of keeping the bytes of that object at a fixed location in memory.

     

    ...The PInvoke layer is hooked into the CLR’s stack crawling mechanism for GC reporting.  So it can defer all overhead related to pinning unless a GC actually occurs while the PInvoke call is in progress.  Applications that explicitly pin buffers around PInvoke calls are often doing so unnecessarily.


  • User profile image
    PhrostByte

    Rossj wrote:
    PhrostByte wrote: That is good advice if you are passing a delegate through one function.  In my case the library is storing it.
    I am doing:
    setdelegate(d); // p/invoked, stores the function pointer
    somefunc() // p/invoked, can call function pointer.
    the delegate can be moved before or during somefunc()'s execution.


    Do you mean moved (in memory) or changed (to point to a different method) ?


    in memory.

  • User profile image
    Rossj

    PhrostByte wrote:
    That is good advice if you are passing a delegate through one function.  In my case the library is storing it.
    I am doing:
    setdelegate(d); // p/invoked, stores the function pointer
    somefunc() // p/invoked, can call function pointer.
    the delegate can be moved before or during somefunc()'s execution.


    Do you mean moved (in memory) or changed (to point to a different method) ?

    If the former you did notice GCHandle.Alloc( myDelegate, GCHandleType.Pinned ) ? In the latter case I don't know - it seems a little unsafe.

  • User profile image
    Rossj

    PhrostByte wrote:

    in memory.


    GCHandle.Alloc( myDelegate, GCHandleType.Pinned ) ?

    As in

    GCHandle handle = GCHandle.Allow( d, GCHandleType.Pinned );
    setdelegate(d); // p/invoked, stores the function pointer
    somefunc() // p/invoked, can call function pointer.
    handle.Free();

    Edit: Free is not static, and should only be called once.

  • User profile image
    PhrostByte

    Tried that, GCHandle.Alloc() throws an exception about non-blittable memory.

    Rossj wrote:
    PhrostByte wrote:
    in memory.


    GCHandle.Alloc( myDelegate, GCHandleType.Pinned ) ?

    As in

    GCHandle handle = GCHandle.Allow( d, GCHandleType.Pinned );
    setdelegate(d); // p/invoked, stores the function pointer
    somefunc() // p/invoked, can call function pointer.
    handle.Free();

    Edit: Free is not static, and should only be called once.

  • User profile image
    Rossj

    PhrostByte wrote:
    Tried that, GCHandle.Alloc() throws an exception about non-blittable memory.



    One of your delegate parameters probably does not have a common representation in the unmanaged language.  Just a long shot, do you have a bool parameter? Try byte instead.

  • User profile image
    Rossj

    Before I forget make sure you don't declare your delegate as a local variable .. as in DON'T do ..

    public void myMethod(){    
    CDelegate d = new CDelegate( someMethod ); SetCallback( d );   
    DoSomethingThatWillTriggerTheCallback();
    }

  • User profile image
    PhrostByte

    Am I not able to use marshaling in a delegate?  It compiles fine.

    delegate int ReportFilter(IntPtr doc, int lvl, uint line, uint col, [MarshalAs(UnmanagedType.LPStr)] string msg);

    edit: still doesn't work when declaring msg as an IntPtr.

  • User profile image
    Rossj

    PhrostByte wrote:
    Am I not able to use marshaling in a delegate?  It compiles fine.

    delegate int ReportFilter(IntPtr doc, int lvl, uint line, uint col, [MarshalAs(UnmanagedType.LPStr)] string msg);

    edit: still doesn't work when declaring msg as an IntPtr.



    I am not sure that the MarshalAs is necessary. I'm just on my way home, if no-one else answers before then I'll look it up later.

  • User profile image
    PhrostByte

    Probably not, but the exception is still being thrown Sad  Anyone have a solution?

    Rossj wrote:
    PhrostByte wrote: Am I not able to use marshaling in a delegate?  It compiles fine.

    delegate int ReportFilter(IntPtr doc, int lvl, uint line, uint col, [MarshalAs(UnmanagedType.LPStr)] string msg);

    edit: still doesn't work when declaring msg as an IntPtr.



    I am not sure that the MarshalAs is necessary. I'm just on my way home, if no-one else answers before then I'll look it up later.

  • User profile image
    PhrostByte

    Declare your function pointer as typedef void (*FuncPtr)(int i);


    Rossj wrote:
    PhrostByte wrote: Probably not, but the exception is still being thrown Sad  Anyone have a solution?


    I wrote a simple bit of code to replicate this and it works, or rather I can replicate either of the two problems you mentioned.  Without the GCHandle a NPE is thrown in the dllimport'ed function that calls the delegate from C (after it has actually done the callback), and with the handle I get ArgumentException thrown about non-blittable types.

    Please forgive the ugly code ..

    C code wrote:
    #include <windows.h>

    typedef void FuncPtr( int i );

    FuncPtr *cb;

    __declspec( dllexport )
    void SetCallback( FuncPtr ptr )
    {
        cb = &ptr;
    }

    __declspec( dllexport )
    void SomeFunc()
    {
        if ( *cb ) {
            *cb( 10 );        
        }
    }


    C# wrote:
        class Class1
        {
            public delegate void MyDelegate( int i );

            MyDelegate deleg;
    //        GCHandle handle;

            public Class1()
            {
                deleg = new MyDelegate( SomeCallback );

    //            handle = GCHandle.Alloc( deleg, GCHandleType.Pinned );
                SetCallback( deleg );
            }

            // Calls into native code to fire the callback
            public void WorkerMethod()
            {
                SomeFunc();
            }

            public void Close()
            {
    //            handle.Free();
            }

            [DllImport("nativecode.dll")]
            public static extern void SetCallback( MyDelegate d );

            [DllImport("nativecode.dll")]
            public static extern void SomeFunc();


            public static void SomeCallback( int i )
            {
                Console.WriteLine( "Callback " + i.ToString() );
            }
           

            [STAThread]
            static void Main(string[] args)
            {
                Class1 c = new Class1();
                c.WorkerMethod();
                c.Close();
            }
        }

  • User profile image
    Rossj

    undefined

  • User profile image
    Rossj

    PhrostByte wrote:
    Probably not, but the exception is still being thrown Sad  Anyone have a solution?


    I wrote a simple bit of code to replicate this and it works, or rather I can replicate either of the two problems you mentioned.  Without the GCHandle a NPE is thrown in the dllimport'ed function that calls the delegate from C (after it has actually done the callback), and with the handle I get ArgumentException thrown about non-blittable types.

    Please forgive the ugly code ..

    C code (updated) wrote:

    #include <windows.h>

    typedef void (*FuncPtr)( int i );

    FuncPtr cb;

    __declspec( dllexport )
    void SetCallback( FuncPtr ptr )
    {
        cb = ptr;
    }


    __declspec( dllexport )
    void SomeFunc()
    {
        if ( cb ) {
            cb( 10 );        
        }
        else {
            printf("Failed\n");
        }
    }


    C# wrote:

        class Class1
        {
            public delegate void MyDelegate( int i );

            MyDelegate deleg;
    //        GCHandle handle;

            public Class1()
            {
                deleg = new MyDelegate( SomeCallback );

    //            handle = GCHandle.Alloc( deleg, GCHandleType.Pinned );
                SetCallback( deleg );
            }

            // Calls into native code to fire the callback
            public void WorkerMethod()
            {
                SomeFunc();
            }

            public void Close()
            {
    //            handle.Free();
            }

            [DllImport("nativecode.dll")]
            public static extern void SetCallback( MyDelegate d );

            [DllImport("nativecode.dll")]
            public static extern void SomeFunc();


            public static void SomeCallback( int i )
            {
                Console.WriteLine( "Callback " + i.ToString() );
            }
           

            [STAThread]
            static void Main(string[] args)
            {
                Class1 c = new Class1();
                c.WorkerMethod();
                c.Close();
            }
        }

  • User profile image
    Rossj

    Doh! Not only am I forgetting how to write C I can't even post here without messing it up now Smiley

    Still no joy though Sad

    Sorry I can't help.

  • User profile image
    Rossj


    Mmm. Fixed it.
    Needed to compile DLL with /Gz so that it used stdcall ... now it's all funky.

Comments closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums, or Contact Us and let us know.