【问题标题】:Factory pattern across Dll boundaries using unique_ptr [closed]使用 unique_ptr 跨 Dll 边界的工厂模式 [关闭]
【发布时间】:2013-09-11 01:40:45
【问题描述】:

我正在寻找对 IoC 框架(如 Castle(.NET)或 Java)有一定经验的开发人员的意见。

但是,我正在寻找使用 C++ 实现的类似功能。这不包括 COM,原因我稍后会解释。有没有办法在 C++ 中实现诸如工厂、抽象工厂或服务提供者之类的模式,主要但不限于 Windows 平台?

具体来说,IoC 应允许客户端应用在运行时发现和加载未知数量的接口实现。

作为我希望的答案:

  1. 指向特定商业或开源 C++ IoC 实现的链接,或
  2. 尝试实施它的个人经验,以及一些主要问题的列表(如果有)。

由于意外的否决,我必须添加一些额外的规范(OP 中可能没有明确说明):

  1. 如果实现需要一个封闭的系统是可以的,其中所有组件都需要链接到相同版本的 C++ 运行时库。这不是很好,但可以接受。作为客户端应用程序软件安装的一部分,我一直在研究安装所需版本的 C++ 运行时的软件。 IOW,这是一个广泛兼容性的问题,但不是我在这里提出的问题的答案。

  2. 我提到 unique_ptr 是因为它是 C++ 标准。自定义智能指针也可以。我希望现在可以更好地探索 unique_ptr。

  3. 我并不是要一份关于如何将自定义删除器与 unique_ptr 一起使用的代码草案。对于甚至无法编译的代码来说,要少得多。这是得到所有支持的答案。可悲的是,在我评论说“这就像一个试图引导聋子的盲人”之后,惩罚我作为所谓的罪犯并支持所谓的受害者的愤怒被夸大了,导致了不合理的反对票和赞成票。我真的很难过,在像 SO 这样的网站上,这样的事情会破坏纯粹的技术问题,并将其变成关于礼貌的个人咆哮。避免在明确要求实际实施经验的问题上投掷毫无头绪的代码草稿会更有礼貌。 (一)

  4. 在没有得到任何明智的答案或良好的链接后,我花了相当长的时间尝试自己创建一个示例实现。我在 MSVC 2010 下的 Release 和 Debug 版本中都尝试了它,它按我的预期工作。这是我没有投票接受的答案,因为这是我对自己问题的回答。我仍然希望那些在这些问题上有实际经验的人能提供一个好的 cmets。这个答案有 2 票反对,即使代码按照它所说的那样做,如果你真的尝试编译和运行它。

(a) 根据麦格劳-希尔美国成语和短语动词词典和剑桥成语词典,链接到令人反感、不礼貌和粗鲁的短语的含义:

Blind leading the Blind

【问题讨论】:

  • 问题不是针对shared_ptr,而是针对unique_ptr。这不是关于使用它是否安全,而是如何 使用它来实现工厂模式。跨 dll 边界的安全性不会自动告诉您使用动态加载而不是隐式加载的 dll 可能会遇到哪些问题。我研究了 3 个 C++ 开源工厂(实际上是 IoC)实现,但没有一个能够处理 Dll 中的组件。如果你理解了这个问题,也许你可以告诉我,在哪里有现成的 C++ IoC 框架,它们通过 unique_ptr 或其他方式在 DLL 中动态加载组件?

标签: windows dll c++11 inversion-of-control unique-ptr


【解决方案1】:

如果我们看一下unique_ptr 类,我们可以看到默认情况下它使用default_delete 类的一个实例。 default_delete 类具有以下方法: void operator()(T *ptr) const。要实现自定义删除器,您需要创建一个类似于以下内容的类(代码改编自 here):

class MyDeleter
{
public:
    MyDeleter(FactoryReference *f)
    {
        m_factoryRef = f;
    }
    void operator()(IFace *ptr) const
    {
        delete ptr;
        m_factoryRef->unloadDLL();
    }
private:
    FactoryReference *m_factoryRef;
};

std::unique_ptr<IFace, MyDeleter> GetIFace()
{
    FactoryReferece *myFactory = /* a factory reference */;
    return new std::unique_ptr<IFace, MyDeleter>(myFactory->getIFaceSubclass(), MyDeleter(myFactory));
}

【讨论】:

  • 哎哟。我不会说我是瞎子、聋子和哑巴。可能是聋子,但不是全部三个。侮辱那些试图帮助你的人有点粗鲁。此外,如果您对我的代码有疑问,请具体说明。告诉我为什么它不起作用/不起作用,而不仅仅是它不起作用。
  • 好的,新的 IFace 将如何工作? IFace 是一个抽象类。你不能实例化抽象类。
  • 有效点和我的错误。固定。
  • @Tony 如果你心脏骤停,你就不会在这里寻求志愿者的免费帮助。在这里你必须有礼貌。期间。
  • 我认为你们的反应都非常夸张。我说“像盲人等”。这是一个隐喻的意思:嘿,你比我有更多的问题。我对那些不能编译并且至少有三个主要缺陷的代码这么说。我相信这是一个代码很重要的网站,而不是个人看法。在 SO 网站上,以对什么是不礼貌的个人判断的名义,对正确的代码投反对票,对不正确的代码投反对票,这完全是错误的 IMO。
【解决方案2】:

这是可以做到的。这是我测试的示例实现的框架。它是用 Visual C++/VS 2010 构建的。

接口在Iface.h头文件中声明:

#pragma once

#include <memory>
#include <string>

#ifdef IFACE_EXPORTS
#define IFACE_API __declspec(dllexport)
#else
#define IFACE_API __declspec(dllimport)
#endif

namespace Generic
{

class Iface
{
public:
   virtual void DoSomething(std::string const&) = 0; 
   virtual ~Iface() {};
};

IFACE_API std::unique_ptr<Iface> GetIface();

typedef std::unique_ptr<Iface> (*GetIfaceType)();

}

几个 Dll 实现了这个接口,而第三个 Dll 称为 Factory,动态加载其中一个。反过来,Factory 导出一个函数供应用程序使用,该函数将隐式链接到它(即通过导出库。)

这是 FactoryIface.h,将包含在客户端应用程序中:

#pragma once

#include <memory>
#include "Iface.h"

#ifdef FACTORY_EXPORTS
#define FACTORY_API __declspec(dllexport)
#else
#define FACTORY_API __declspec(dllimport)
#endif

class FACTORY_API IFaceCustomDeleter
{
   void* hMod_;
public:
   IFaceCustomDeleter(void* hMod=nullptr);
   void operator()(Generic::Iface* ptr);
};

FACTORY_API std::unique_ptr<Generic::Iface, IFaceCustomDeleter> FactoryGetIface();

请注意,必须导出自定义删除器类。那是我忘记做的第一件事。构建客户端应用程序很快就指向了它。下面是关键部分,FactoryDll 中 FactoryGetIface() 的实现:

// A trick to get decorated name of exported function
std::string DecoratedName;

namespace Generic
{
   std::unique_ptr<Iface> GetIface()
   {
      DecoratedName = __FUNCDNAME__;
      return std::unique_ptr<Iface>(nullptr);
   }
}

using namespace Generic;

// for keeping Iface from dynamically loaded DLL
std::unique_ptr<Iface> ExternalIface;

// Custome deleter implementation

IFaceCustomDeleter::IFaceCustomDeleter(void* hMod) 
   : hMod_(hMod) 
{
}

void IFaceCustomDeleter::operator()(Iface* ptr)
{
   ExternalIface.reset(nullptr);
   if(hMod_)
      ::FreeLibrary((HMODULE)hMod_);
}


FACTORY_API std::unique_ptr<Generic::Iface, IFaceCustomDeleter> FactoryGetIface()
{
   // determine path of DLL to load for Iface implementation
   auto path = L".\\IfaceImplB.dll";

   auto hmod = ::LoadLibrary(path);
   if(hmod)
   {
      GetIface(); // get decorated name

      GetIfaceType ptrGetIface;
      ptrGetIface = (GetIfaceType)::GetProcAddress(hmod, DecoratedName.c_str());
      // Alternatively, use full decorated name like below:
      //ptrGetIface = (GetIfaceType)::GetProcAddress(hmod,
      //"?GetIface@Generic@@YA?AV?$unique_ptr@VIface@Generic@@U?$default_delete@VIface@Generic@@@std@@@std@@XZ");

      if(ptrGetIface)
      {
         ExternalIface = ptrGetIface();
         ExternalIface->DoSomething("Hello from Factory");
         IFaceCustomDeleter del(hmod);
         return std::unique_ptr<Generic::Iface, IFaceCustomDeleter>(ExternalIface.get(), del);
      }
   }

   IFaceCustomDeleter del;
   return std::unique_ptr<Generic::Iface, IFaceCustomDeleter>(nullptr, del);
}

请注意,Dll 路径是硬编码的:这只是一个示例,但它可以编译和工作。在实际应用中,可以有,例如。解析一些指定要加载的 Dll 的 xml。

通过 Factory 使用 Iface 的非常基本的控制台应用程序如下所示:

#include "FactoryIface.h"

int _tmain(int argc, _TCHAR* argv[])
{
   {
      std::unique_ptr<Generic::Iface, IFaceCustomDeleter> iface = FactoryGetIface();
      iface->DoSomething("Hello from Factory user");
   }

    return 0;
}

当我运行它时,我会得到如下输出:

In IfaceImplB constructor
Doing something: Hello from Factory
Doing something: Hello from Factory user
In IfaceImplB destructor

【讨论】:

  • stl 容器由于实现更改无法跨 DLLS 共享,共享一个会导致内存损坏
  • 没有问题,比如我在界面中使用std::string。这是一个工作代码,您可以直接使用它来构建所需的组件并说服自己它可以工作。正如我在 OP 中所说的那样“我也知道如果 C++ 运行时 Dll 会出现问题,上述 Dll 将链接到,来自不同的供应商或来自同一供应商但不同的版本”。这意味着,就问题而言,可以假设我完全控制将使用的 C++ 运行时版本。
  • 我强烈建议您阅读Microsoft documentation 这并不像简单地使用同一版本那么简单。每个 dll 都有自己的堆,因此您不能 delete 一个 dll 中的一个项目在另一个 dll 中使用 new 分配。这就是CoTaskMemAlloc 存在的原因。因此,除非您在全局范围内重载 operator newoperator delete,否则这是行不通的。
  • 我强烈建议您阅读其他一些关于跨 Dll 边界共享 C++ 对象的 SO 帖子,而不仅仅是您引用的重复内容。你会读到上面的代码没有问题,只要所有组件都链接到 C++ 运行时 Dll,而不是静态库链接。运行时 DLL 有 一个 堆,在进程中的所有组件之间共享。
  • 这是一篇关于它的 SO 帖子:SO question link
猜你喜欢
  • 2013-12-12
  • 2015-02-01
  • 1970-01-01
  • 2016-11-15
  • 1970-01-01
  • 1970-01-01
  • 2011-03-31
  • 1970-01-01
相关资源
最近更新 更多