如何为遗留API/框架实现大量复杂的包装器(C+;+;宏与C+;+;模板与代码生成器)? 我们使用VC6编译器在C++中实现了非常古老的遗留系统。现在我们正在重构代码。我们也切换到VC9编译器

如何为遗留API/框架实现大量复杂的包装器(C+;+;宏与C+;+;模板与代码生成器)? 我们使用VC6编译器在C++中实现了非常古老的遗留系统。现在我们正在重构代码。我们也切换到VC9编译器,c++,templates,macros,code-generation,legacy-code,C++,Templates,Macros,Code Generation,Legacy Code,我们使用一个外部专有框架,它也是遗留代码,不可单元测试。为了使代码单元可测试,我们为框架类引入了接口和包装器(提示:请参阅Martin Fowler的“使用遗留代码”): 现在我们依赖于接口。包装器调用框架方法,我们可以在单元测试中愉快地使用mock 现在我们来谈谈我们的问题 框架类包含许多需要包装和模拟的方法。为了实现这一目标,我们的供应商团队编写了一个API,它使用C++宏生成接口、包装器和MOCK实现。 包装头文件的示例: class PlanWrapper : public IPlan

我们使用一个外部专有框架,它也是遗留代码,不可单元测试。为了使代码单元可测试,我们为框架类引入了接口和包装器(提示:请参阅Martin Fowler的“使用遗留代码”):

现在我们依赖于接口。包装器调用框架方法,我们可以在单元测试中愉快地使用mock

现在我们来谈谈我们的问题

框架类包含许多需要包装和模拟的方法。为了实现这一目标,我们的供应商团队编写了一个API,它使用C++宏生成接口、包装器和MOCK实现。 包装头文件的示例:

class PlanWrapper : public IPlan
{
  // ... 
  WRP_DECLARE_DEFAULTS(FrameworkPlan); // macro
  WRP_DECLARE_CSTR_ATTR(FrameworkPlanLabel); // macro
  // ...
};
宏WRP_DECLARE_CSTR_ATTR的定义如下:

#define WRP_DECLARE_CSTR_ATTR(AttrName) \
    virtual bool set##AttrName (LPCTSTR Value_in); \
    virtual bool get##AttrName (CString& Value_out); \
    virtual bool unset##AttrName (); \
    virtual bool isSet##AttrName ()
#define WRP_IMPLEMENT_W_CSTR_ATTR(ClassName,AtrTypeObj,AttrName) \
    bool ClassName##Wrapper::set##AttrName (LPCTSTR Value_in) { \
            AtrTypeObj aValue = Value_in; \
        FrameworkLink<ClassName> convertedObj = NULL_LINK; \
        framework_cast(convertedObj, m_Object); \
        return convertedObj != NULL_LINK ? \
                       convertedObj->set##AttrName (aValue) : false; \
    }
    // ...
包装器cpp文件的示例:

#include "StdAfx.h"

using namespace SomeNamespace;

WRP_IMPLEMENT_MODDICOM_DEFAULTS(FrameworkPlan)
WRP_IMPLEMENT_W_CSTR_ATTR (FrameworkPlan,FrameworkType1, FrameworkPlanLabel)
// ...
宏WRP_IMPLEMENT_W_CSTR_ATTR的定义如下:

#define WRP_DECLARE_CSTR_ATTR(AttrName) \
    virtual bool set##AttrName (LPCTSTR Value_in); \
    virtual bool get##AttrName (CString& Value_out); \
    virtual bool unset##AttrName (); \
    virtual bool isSet##AttrName ()
#define WRP_IMPLEMENT_W_CSTR_ATTR(ClassName,AtrTypeObj,AttrName) \
    bool ClassName##Wrapper::set##AttrName (LPCTSTR Value_in) { \
            AtrTypeObj aValue = Value_in; \
        FrameworkLink<ClassName> convertedObj = NULL_LINK; \
        framework_cast(convertedObj, m_Object); \
        return convertedObj != NULL_LINK ? \
                       convertedObj->set##AttrName (aValue) : false; \
    }
    // ...
#定义WRP#u实现(W#u CSTR#u ATTR(ClassName,AtrTypeObj,AttrName)\
bool ClassName###Wrapper::set##AttrName(LPCTSTR Value_in){\
AtrTypeObj aValue=值_in\
FrameworkLink convertedObj=空链接\
framework_cast(convertedObj,m_对象)\
返回convertedObj!=空链接\
convertedObj->set##AttrName(aValue):false\
}
// ...
我们有一大堆更复杂的东西,但我想你明白了

API的问题在于它极其复杂、不可读、不可调试和不可测试

我们希望提出一个更好的机制来实现同样的目标。我们的想法是使用新编译器附带的一些高级功能,如高级模板、类型列表、特征等

有了模板,我们几乎可以实现我们的目标,但方法名却让我们束手无策。我们可以对类型进行泛化,但如何处理属性名呢

我们还考虑创建一个自动生成包装器+接口+模拟代码的工具。然而,我们的外部框架的API非常复杂,编写这样一个工具的成本非常高


你认为解决这个问题的最好办法是什么?也许你已经处理过类似的事情,并且可以提供很好的提示?我们期待着看到你的答案也许考虑制作一个特定的GCC插件>,或者是一个<强>扩展< <强> >。(MELT是一种高级领域特定语言,用于扩展GCC)。然而,这种GCC定制需要大量的工作(数周,而不是数小时)

使用抽象工厂代替宏来解决此问题

class IApiFactory{
 virtual ISomeApi1* getApi1() =0;
 virtual ISomeApi2* getApi2() =0;
 .....
};
为普通api和moc api实现此接口后,将工厂实例传递给系统,如:

MySystem system( new NormalApiFactory );

您的系统必须声明为:

class MySystem{
public:
  MySystem( IApiFactory* factory );
};
在工厂中,您将返回正常的api实现或moc对象。
当然,您可以从根工厂返回“将返回其他工厂或对象”的工厂。

我喜欢代码生成器。您可以创建一个包含所有要包装的内容的字典,这需要时间,然后使用一个简单的工具生成代码。除了代码之外,您还可以清楚地看到遗留系统的参考资料,以及以后替换它的指南。

我想我应该使用代码生成工具。我可能会制作一些简单的实用程序:一个用于生成与遗留框架类对应的接口,一个用于生成包装器,另一个用于生成模拟对象(或至少是骨架)

这意味着要有某种方法来解析遗留框架的代码。我会看一看,或者干脆在源文件上运行并处理生成的标记。

我们的程序转换工具可以读取源代码并对其执行任意转换

DMS具有VC6和更现代的Visual Studio功能,它可能用于读取要模拟的组件的头文件,并生成模拟

已被用于对C++代码进行大规模的转换,尤其是涉及到界面的改变和各种方式的交互。看我的一篇技术论文,
.

有一种方法可以以完全受控的方式对代码生成进行结构化,并且仍然可以自由地生成您需要的任何困难的API内容

如果您使用Visual Studio(至少2005年,但最好是2008年或2010年),那么您可以使用T4团队提到的结构化代码生成方法,该方法仅基于T4和XML:

我是ADM方法论的创新者和首席架构师,也是该博客的作者


不幸的是,我没有与您的案例相匹配的具体案例链接,但我很高兴为您提供解决方案(如果您想让社区受益,请发布一个案例)。

我希望我们可以使用GCC。不幸的是,我们完全坚持使用Visual Studio编译器。+1用于为遗留代码或不可用的源代码使用包装器。@BasileStrynkevitch“API的问题在于它极其复杂,不可读,不可调试,也不可测试。”“我们想提出一种更好的机制来实现同样的目标。我们的想法是使用新编译器附带的一些高级功能,如高级模板、类型列表、特征等。“我只是建议使用一种标准机制来解决这个问题。@Alexteo抱歉,可能描述有点混乱。我说的“API”是指M