首页 > 解决方案 > GetFunctionPointerForDelegate 回调只工作一次

问题描述

我在 C++/CLI 中遇到了非托管到托管回调的问题。它只工作一次,然后默默地失败

我的托管Device类拥有GsDevice来自单独 DLL 的非托管对象 ()。它为非托管对象提供了一个通过互操作设置的回调函数。 GsDevice当事情发生变化时需要异步通知设备。

回调每次都默默地失败。调试器正踩在它上面(即使是在反汇编中。然后我试图在GsDevice它收到它的那一刻调用它。然后它工作。只是一次。

我假设某些东西正在从我下面被移动或垃圾收集,但我无法确定是什么或为什么。谁能发现我做错了什么?

下面是非托管 GsDevice类。它存在于一个单独的、非托管的 DLL 中。除了托管客户端使用的订阅机制和此类回调托管客户端的通知机制外,我已经省略了大部分内容。

.H 文件

struct GsDeviceMembers;
class GsDevice : public std::enable_shared_from_this<GsDevice>
{
    GsDeviceMembers* m_p;   // PIMPL idiom

public:
    typedef void (__stdcall *PROPCHANGECALLBACK)(GsDevice*, const std::string&);
    void GsDeviceDllExport setPropertyChangedCallback(PROPCHANGECALLBACK cb);
protected:
    void raisePropertyChanged(const std::string& prop);
}

和 .CPP 文件

void GsDevice::setPropertyChangedCallback(PROPCHANGECALLBACK cb)
{
    std::unique_lock<std::mutex> lk (m_d->mtxClients);
    cb(this, std::string("joe"));   // TEST:  Call callback right away to ensure it's good.
    m_p->cb = cb;                   // Save off the one client's callback
}

void GsDevice::raisePropertyChanged(const std::string& prop)
{
    std::unique_lock<std::mutex> lk (m_d->mtxClients);
    m_p->cb(this, prop);  // Invoke callback:  THIS NEVER WORKS*****
}

下面是托管客户端Device类,它尝试从非托管接收回调GsDevice

.H 文件

struct DeviceUnmanagedMembers;  for PIMPL idiom storing unmanaged members.

public ref class Device : public INotifyPropertyChanged
{
public:
    virtual event PropertyChangedEventHandler^ PropertyChanged; 

private:
    DeviceUnmanagedMembers* m_p;    // PIMPL idiom
    GCHandle m_callbackHandle;      // Keeps callback locked into one memory address

protected:
    Device(std::shared_ptr<GsDevice> dev);
    delegate void PropertyChangedDelegate(GsDevice* sender, const std::string& propName);
    virtual  void OnPropertyChanged(Gs:Device* pSender, const std::string& propName);

private:
    // I am saving off these as member variables in case it helps .NET keep things
    // alive.  So far, that's not working.

    PropertyChangedDelegate^ m_dg;   
    System::IntPtr^          m_ptr;
};

.CPP 文件

struct DeviceUnmanagedMembers
{
    std::shared_ptr<GsDevice>            spDevice;  
    DeviceUnmanagedMembers(std::shared_ptr<GsDevice> dev) : spDevice(dev) { }
};

Device::Device(std::shared_ptr<GsDevice> dev) : m_p(new DeviceUnmanagedMembers(dev))
{  
    // Get a delegate for our callback function and lock it into memory

    auto pcDelegate  = gcnew PropertyChangedDelegate(this, &Device::OnPropertyChanged);
    m_callbackHandle = GCHandle::Alloc(pcDelegate);
    auto callback    = Marshal::GetFunctionPointerForDelegate(pcDelegate);
    auto ptrInt      = callback.ToPointer();
    auto ptrFun      = static_cast<GsDevice::PROPCHANGECALLBACK>(ptrInt); 

    // Now hook up to the unamanged client for a callback.  This actually immediately
    // calls me back (to OnPropertyChanged) but all subsequent attempts by the
    // unmanaged object to call me back do nothing.

    dev->setPropertyChangedCallback(ptrFun);

    // Save off these items just in case it helps (It doesn't)

    m_dg             = pcDelegate;
    m_ptr            = callback;
}

void Device::OnPropertyChanged(GsDevice* sender, const std::string& propName)
{    
    // This is the function the unmanaged client is trying to call back.  It works
    // the very first time the client does it but never after.

    RaisePropertyChanged(propName);  
}

笔记:

标签: c++c++-cliinterop.net-5

解决方案


您似乎正在做正确的事情来防止垃圾收集器丢弃由GetFunctionPointerForDelegate. (事实上​​,其中一个GCHandle.Alloc或保持一个指向委托实例的成员变量就足够了,你不会从两者中获得任何好处)。这是一个常见的陷阱,GetFunctionPointerForDelegate但它不是你的问题。

我注意到的另一件事是您没有使您的委托适合与GetFunctionPointerForDelegate. GetFunctionPointerForDelegate确实,如果您在没有应用的委托类型上使用.NET 应该抛出异常UnmanagedFunctionPointerAttribute

将该属性添加到delegate void PropertyChangedDelegate(GsDevice* sender, const std::string& propName);,设置正确的调用约定(可能还有其他内容),然后重试。

除此之外,您的函数类型实际上根本不适合 .NET。 GetFunctionPointerForDelegate不希望使用像const std::string&.

非托管 API 是否不提供一些用户上下文指针,您可以在其中将 GCHandle 存储到托管对象?如果是这样,则创建一个普通的非成员函数作为回调传递(您现在可以只使用&name_of_function-- 没有委托,没有 GetFunctionPointerForDelegate 来创建它)并在该函数中检索指针,重新水合 GCHandle,找到您的托管对象,将另一个参数转换为 aSystem::String^并调用 .NET 成员函数。


推荐阅读