11

I have a third-party C library that provides this header:

//CLibrary.h
#include <Windows.h>
#include <process.h>
typedef void (WINAPI *CLibEventCallback)(int event, void *data);
__declspec(dllexport) bool CLibStart (CLibEventCallback callback, void *data);

// CLibrary.c -- sample implementation
static CLibEventCallback cb;

void _cdecl DoWork (void *ptr)
{
    for (int i = 0; i < 10; ++i)
    {
        cb (i*i, ptr);
        Sleep (500);
    }
}

__declspec(dllexport) bool CLibStart (CLibEventCallback callback, void *data)
{
    cb = callback; // save address for DoWork thread...
    _beginthread (DoWork, 0, data);
    return true;
}

I need to create a C++/CLI class that can call CLibStart and provide a class method as the function pointer. As suggested below, this needs to be done with GetFunctionPointerForDelegate. Because the delete constructor includes 'this' and doesn't require a static method, I don't need to pass 'this' into CLibStart.

using namespace System;
using namespace System::Runtime::InteropServices;

namespace Sample {
    public ref class ManagedClass
    {   
        delegate void CLibraryDelegate (int event, void *data);

    private:
        CLibraryDelegate^ managedDelegate;
        IntPtr unmanagedDelegatePtr;
        int someInstanceData;

    public:
        ManagedClass() 
        { 
            this->managedDelegate = gcnew CLibraryDelegate(this, &ManagedClass::ManagedCallback);
            this->unmanagedDelegatePtr = Marshal::GetFunctionPointerForDelegate(this->managedDelegate);
            this->someInstanceData = 42;
        }

        void Start ()
        {
            // since the delegate includes an implicit 'this' (as static function is not needed)
            // I no longer need to pass 'this' in the second parameter!
            CLibStart ((CLibEventCallback) (void *) unmanagedDelegatePtr, nullptr); 
        }

    private:
        void Log (String^ msg)
        {
            Console::WriteLine (String::Format ("someInstanceData: {0}, message: {1}", this->someInstanceData, msg));  
        }

        void ManagedCallback (int eventType, void *data)
        {
            // no longer need "data" to contain 'this'
            this->Log (String::Format ("Received Event {0}", eventType));
        }
    };
}

All of this compiles and runs fine using this C# tester:

using System;
using Sample;

namespace Tester
{
    class Program
    {
        static void Main(string[] args)
        {
            var mc = new ManagedClass();
            mc.Start();
            Console.ReadKey();
        }
    }
}

Sample output:

Received Event 0
Received Event 1
Received Event 4
Received Event 9
Received Event 16
Received Event 25
Received Event 36
Received Event 49
Received Event 64
Received Event 81

Outstanding questions:

  1. I have this feeling that I need to use gcroot and/or pin_ptr? If so, how? where?

Thanks.

Robᵩ
  • 163,533
  • 20
  • 239
  • 308
Tony
  • 1,986
  • 2
  • 25
  • 36
  • 3
    Use Marshal::GetFunctionPointerForDelegate(). It doesn't have to be a static method. You must keep the delegate object alive by storing it. – Hans Passant Nov 01 '12 at 20:01
  • You can use the `gcroot` template to keep the object alive, if you use @HansPassant's suggestion. – Alexandre C. Nov 01 '12 at 21:05
  • Thanks. I have updated the 'question' with a solution that appears to be working (compiles and runs in VS2010). But I'm not sure if/where/how I need to use gcroot and/or pin_ptr. Any ideas? – Tony Nov 02 '12 at 12:34
  • How can the C++/CLI class' 'log' method access the implicit 'this' when it is invoked by the callback? – Tony Nov 02 '12 at 14:54
  • Found solution to my above question about log. 'this' isn't needed as the delegate includes an implicit 'this' due to the fact that static method is no required. – Tony Nov 02 '12 at 17:44
  • 1
    There isn't anything in this code that prevents the "mc" object from getting garbage collected. Which will also collect the delegate. It works right now because the debugger extends the lifetime of the variable to the end of the method. But in Real Life it will make a loud kaboom when that happens. Add the objects to a static List<> and remove them again when the native code is guaranteed to stop making callbacks. – Hans Passant Nov 02 '12 at 17:58
  • Thanks for the comment, I thought there was something I needed to worry about. What objects do I need to place in a List<>? – Tony Nov 02 '12 at 19:26
  • "It works right now because the debugger extends the lifetime of the variable to the end of the method" -- nonsense. `mc` is alive as long as `Main` is alive. The problem is that in a real application, it wouldn't be `Main`, it would be some method that returns while the app continues, leaving `cb` pointing to garbage-collectible memory. – Jim Balter Oct 09 '17 at 08:03
  • Perhaps it's not the problem here. But if someone searching like me ends up here: Make sure the calling convention of your managed delegate matches the unmanaged callback. C# uses stdcall, C++ usually uses cdecl. You can add `UnmanagedFunctionPointerAttribute` to the delegate definition to specify the calling convention. – Jiří Skála Jul 02 '20 at 14:11
  • @JimBalter: Wrong. Tracking handles don't obey C++ scoping rules. That's the whole reason for `GC::KeepAlive()` – Ben Voigt Jun 30 '21 at 18:38
  • Um, `mc` and `Main` are in C#; they have nothing to do with C++ or its scoping rules--nothing in my comment or Hans Passant's has anything to do with that. (Not that I care about this 9 year old topic.) – Jim Balter Jun 30 '21 at 22:48

1 Answers1

-1

gcroot should be in place where ref class stores delegate, like:

gcroot<CLibraryDelegate^> managedDelegate;
StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
  • It not only shouldn't, it can't. `gcroot` is an unmanaged type and cannot be a member of a `ref class`. – Ben Voigt Jun 30 '21 at 18:36