c++ - 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);
}
笔记:
- 也
Device
没有GsDevice
对象被清理。它们都仍然存在,因为我继续使用从托管代码到非托管代码的函数调用来调用它们。 - 非托管设备不断尝试调用回调并且失败。没有错误。没有例外。它只是跳过呼叫。
- 我已经比较了回调的确切指针值,从第一次(当它工作时)到它失败时的所有后续时间。它完全一样。
- C++/CLI 程序集使用 .NET 5.0 和 Visual Studio 2019 构建
- 非托管 C++ 代码是在启用 C++17 功能的情况下构建的(如果重要的话)
解决方案
您似乎正在做正确的事情来防止垃圾收集器丢弃由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 成员函数。
推荐阅读
- sql - SQL日期时间查询
- python - 导入的模块导入子模块失败
- c++ - 无法从源代码构建 GCC 10
- python - 在 Visual Studio 2019 的 Windows 中使用 CMake 使用 contrib 构建 openCV 时出现问题
- python - CPython 源代码中的列表推导在哪里实现?
- iis - IIS7 - 重写公用文件夹中的文件和子目录
- stata - 如何将一个变量的内容替换为另一个变量的内容?
- node.js - 使用带有 NestJS 的 aws 加密库的“EC2 元数据角色名称请求返回错误”
- flutter - Flutter:如何创建并公开图片到公共下载文件夹?
- sql - SQL Group By 不同列上的多个值