Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

callback from native linux library (.so) of .NET core managed function - problem #19696

Closed
rottor12 opened this issue Dec 15, 2016 · 6 comments
Closed
Assignees
Milestone

Comments

@rottor12
Copy link

I want to callback a method in C# code (.NET Core) from C++ code in my .so library on Linux

when I execute a callback from this function, all works fine:

extern "C" void TestCallback()
{
 fnSimpleCallback(); // fnSimpleCallback is saved callback function pointer
}

however, when I try to execute a callback from a thread (std::thread), it either crashes or does nothing

here is the code:

extern "C" void StartTestThread()
{
	std::thread thread(call_from_thread);
	thread.detach();
}
void call_from_thread() {
	std::cout << "sleeping for 5 sec ... " << std::flush ;
	usleep(5000000);
	std::cout << " ... after sleep trying to callback from new thread .. " << (void *)fnCallback << std::endl;

	if (nullptr != fnSimpleCallback)
		fnSimpleCallback(); // does nothing
}

======================

here is the C# code:

       static IntPtr p_fnSimpleCallback;
       static System.Runtime.InteropServices.GCHandle gchSimpleCallback;

       public static void Main(string[] args)
       {   
           p_fnSimpleCallback = Marshal.GetFunctionPointerForDelegate<WrapperAPILib.DlgSimpleCallback>(fnSimpleCallback);
           gchSimpleCallback = GCHandle.Alloc(p_fnSimpleCallback, GCHandleType.Pinned);

           WrapperAPILib.SetSimpleCallback( p_fnSimpleCallback  );
           
           WrapperAPILib.StartTestThread();
           
           //Console.WriteLine("Sleeping in Main ...");
           //System.Threading.Thread.Sleep( 6000);
           //WrapperAPILib.StartTestThread();

       Console.ReadKey();
}

as you can notice, I've already tried using GCHandle to keep reference to my delegate alive

when I uncomment subsequent Sleep(6000). the first StartTestThread() correctly executes my callback, but after the Sleep(6000) passes, the second StartTestThread() does nothing

@janvorli
Copy link
Member

It should work. Let me look into that.

@janvorli janvorli self-assigned this Dec 15, 2016
@janvorli
Copy link
Member

@rottor12 I have looked into it and found you have a bug there. You need to create a handle for the delegate itself, not the function pointer. So you need to do

WrapperAPILib.DlgSimpleCallback d = fnSimpleCallback;
gchSimpleCallback = GCHandle.Alloc(d); // it cannot be pinned and it would also not make sense to pin the delegate object. 
p_fnSimpleCallback = Marshal.GetFunctionPointerForDelegate<WrapperAPILib.DlgSimpleCallback>(d);
WrapperAPILib.SetSimpleCallback( p_fnSimpleCallback  );

Then it works as expected.

@rottor12
Copy link
Author

I've made the changes you described, but .. the behavior is still the same

this behavior is repeatable, and the Main() function doesn't even finish, so the pointer to a function nor delegate should'n't be garbage collected just after the specific line is executed

@janvorli
Copy link
Member

It doesn't matter that the main function doesn't finish. When the variable is not used in the code flow anymore and it is not stored in a global or referred to by another heap object, the GC can collect it any time. You would need to use GC.KeepAlive at the end of Main to ensure that. That is another way to solve the issue with your example, but creating GC handle works as well.

I have tried to create and run your example on my local machine without my fix, but with the env var COMPlus_GCStress=3. The delegate was getting collected. When I have fixed the problem like I have said, the issue went away, the delegate was not collected anymore and the test successfully called the callback twice, waited for the key press and then exited.

Let me paste here the complete testing code I was using:

using System;
using System.Runtime.InteropServices;

namespace reversepinvokefromthread
{
    class WrapperAPILib
    {

        public delegate void DlgSimpleCallback();

        [DllImport("reversepinvokefromthread.so")]
        static public extern void SetSimpleCallback(IntPtr cb);
        [DllImport("reversepinvokefromthread.so")]
        static public extern void StartTestThread();
    }

    class Program
    {
        static IntPtr p_fnSimpleCallback;
        static GCHandle gchSimpleCallback;

        static void fnSimpleCallback()
        {
            Console.WriteLine("Got here");
        }

        static void Main(string[] args)
        {
            WrapperAPILib.DlgSimpleCallback del = new WrapperAPILib.DlgSimpleCallback(fnSimpleCallback);
            p_fnSimpleCallback = Marshal.GetFunctionPointerForDelegate<WrapperAPILib.DlgSimpleCallback>(del);
            gchSimpleCallback = GCHandle.Alloc(del);

            WrapperAPILib.SetSimpleCallback(p_fnSimpleCallback);

            WrapperAPILib.StartTestThread();

            Console.WriteLine("Sleeping in Main ...");
            System.Threading.Thread.Sleep( 6000);
            WrapperAPILib.StartTestThread();

            Console.ReadKey();
        }
    }
}

C++ code built with clang++-3.5 -g -shared -fPIC -std=c++11 ./reversepinvokefromthread.cpp -o reversepinvokefromthread.so

#include <iostream>
#include <thread>
#include <unistd.h>

void (*fnSimpleCallback)();

void call_from_thread()
{
    std::cout << "sleeping for 5 sec ... " << std::flush ;
    usleep(5000000);
    std::cout << " ... after sleep trying to callback from new thread .. " << (void *)fnSimpleCallback << std::endl;

    if (nullptr != fnSimpleCallback)
        fnSimpleCallback(); // does nothing
}

extern "C" void StartTestThread()
{
    std::thread thread(call_from_thread);
    thread.detach();
}

extern "C" void SetSimpleCallback(void* cb)
{
    fnSimpleCallback = (void (*)())cb;
}

Output:

sleeping for 5 sec ...  ... after sleep trying to callback from new thread .. 0x7fa910669104
Sleeping in Main ...
Got here
sleeping for 5 sec ...  ... after sleep trying to callback from new thread .. 0x7fa910669104
Got here

@rottor12
Copy link
Author

rottor12 commented Dec 16, 2016

I'll take a look at this once again ...

the only difference is that I'm using g++:
Thread model: posix
gcc version 4.8.5 (SUSE Linux)

thanks for your time to help me with this

@tarekgh
Copy link
Member

tarekgh commented Dec 19, 2016

closing the issue here but @rottor12 please feel free to reopen it if you still have issue with the proposed changes. thanks.

@tarekgh tarekgh closed this as completed Dec 19, 2016
@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 2.0.0 milestone Jan 31, 2020
@dotnet dotnet locked as resolved and limited conversation to collaborators Dec 26, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants