博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

    这篇文章其实有两个主题:一个是充分利用宏的特性把代码最大程度简化;另一个是如何在现有的项目中添加自动化支持。要我说哪一个主题更重要,对不起我也不知道,因为两个主题是紧密关联的,后面的介绍将以宏的使用为主,自动化接口只是宏的一种应用。

    熟悉MFC和ATL/WTL的人一定都很熟悉它们的消息映射表,利用宏进行填表是一种非常简洁非常优雅的编码方式,通俗易懂维护方便。对于宏来说,使用越是简单,其内部构造就越复杂。很多人都说MFC把宏定义运用到了极致,以前我同意,现在有所保留,为什么?因为MFC的框架太古老了,它使用的也是早期的宏特性。社会在发展,技术在进步,如今的标准已经对宏的特性进行了扩展。充分利用扩展的宏特性,能制作出更加简洁的代码。

    说了很多废话,言归正传。在大多数C++软件项目中,往往没有自动化需求,程序员通常会创造出下面的代码:

class CDog : public CAnimal
{
public:
    CString Name;
    long Height;
 
    void Drink();
    bool Eat(long lType, long lNum);
};
 

每个项目可能会出现一大堆的类定义,并且程序运作良好。忽然某一天,项目主管要求对代码执行自动化改造,或者为了制作出能在浏览器里执行的OCX控件,或者能被某些脚本语言调用,或者能嵌入到IIS中的ASP页面内执行……,总之,老板想让WEB开发人员也能逗这条小狗(CDog)。

    我相信每个人针对这种需求都有这样一些感觉:我有很多改造方案,可是不管哪个方案,工作量都很大。MFC项目天然支持自动化,但它的个头太庞大了。ATL也是纯种的自动化解决方案,但必须在纯种的ATL项目中才能使用它的各种向导功能,如果新建一个ATL项目,把这些现成的类搬进来再改造也不是一件容易的事,尤其是涉及到类与类之间的继承关系调整。有些人可能会想直接利用ATL中的IDispatchImpl<>模板类,想法很好,但几乎实现不了,因为IDispatchImpl内部严重依赖类型库,或者保存在注册表中,或者保存在程序资源中,总之IDL文件是必须要有的,而手工完成这些的工作量还不如新建一个ATL项目来得划算。还有一种方法就是让CDog直接从IDispatch派生,然后实现IDispatch的所有7个方法,在它的Invoke实现中根据DISPID不同分别访问CDog的成员变量或者调用成员函数,或者单独实现一个从IDispatch派生的类专门处理CDog,但这种方式的工作量也是显而易见的,每个类都需要独立派生,或者每个类都需要创建一个对应的自动化支持类。

    超级无敌的宏现身了,这就是我的解决方案。它的最大好处就是用最小的代价完成自动化改造,你的现有项目可以是SDK/MFC/ATL,无需创建新项目,无需IDL文件,无需类型库,无需注册组件。这种解决方案也是类似于消息映射宏,填表即可完成。还是以CDog的改造为例,老板希望这个dog具备Name和Height属性,也具备Drink和Eat方法,那么使用宏解决方案,将会是下面的代码:

class CDog : public CAnimal
{
public:
    CString Name;
    long Height;
 
    void Drink();
    bool Eat(long lType, long lNum);
 
    Begin_Disp_Map(CDog)
        Disp_Property(1, Name, CString)
        Disp_PropertyGet(2, Height, long)
        Disp_Method(3, Drink, void, 0)
        Disp_Method(4, Eat, bool, 2, long, long)
    End_Disp_Map()
};

     填完上面的表格,这个CDog已经可以被WEB开发人员牵出来遛了。顺便解释一下这个映射表的用法:

1、映射表以Begin_Disp_Map开始,唯一的参数就是需要改造的类CDog
2、映射表以End_Disp_Map结束
3、每一个属性或者方法占用一行表项
4、属性的用法 Disp_Property(dispid, property_name, property_type),以 Disp_Property(1, Name, CString) 为例,属性的DISPID是1,属性的名称是Name,属性的类型是CString。这个宏表示Name属性是可读写的,如果属性只读(如Height)应该用 Disp_PropertyGet,如果属性只写应该用 Disp_PropertyPut,参数的含义是一致的
5、方法的用法 Disp_Method(dispid, method_name, return_type, param_count, param1_type, …, paramN_type),以 Disp_Method(3, Drink, void, 0) 为例,方法的DISPID是3,方法名称是Drink,方法函数返回值类型是void,方法没有参数。再看看 Disp_Method(4, Eat, bool, 2, long, long) 示例,方法的DISPID是4,方法名称是Eat,方法函数返回类型是bool,方法有2个参数,第一个参数类型是long,第二个参数类型是long

    这个自动化接口怎么用呢?通过下面的例子可以看出来,使用非常简单,因为映射宏暗中添加了 CDog::GetDispatch() 成员函数。

CDog dog;
CComPtr<IDispatch> disp = dog.GetDispatch();
disp.Invoke0(OLESTR("Drink")); // 这句自动化调用将导致 CDog::Drink() 函数被执行

     事实上,我实现的自动化支持的宏定义曾经有两个版本,用法相似,但是实现方法完全不同。第一个版本采用的是自动创建类型库,然后通过 CreateStdDispatch() 函数创建 IDispatch 接口,这种方式有一些限制和缺陷,由于调用了API,我也不清楚内部有多少限制,因此导致第二个版本的诞生,这个版本完全在我自己的“掌控”中,可随时调整和改造。此版本是一个独立的头文件macro.h,里面几乎全部是宏定义,还有几个作为辅助工具的模板类定义和数据结构。任何人都可以随意使用该文件,也可以随意修改拷贝,不用交版税^_^。

    还有必要提醒一下这套解决方案的使用条件:

1、Windows平台,VC版本至少是2005,VC6/2003等早期版本不能使用。
2、用到了少量的ATL头文件,如果实际使用,可能需要自己添加对这些头文件的包含。
3、不依赖任何其它库,任何项目类型都可以使用。
4、变量类型请尽量使用VARIANT中支持的基础类型。

    如果用上面的示例代码进行实际的编译测试,你会发现编译通不过,原因在于 Name 的类型是 CString,这是一种其它库封装的高级类型,VARIANT不能识别。有两种解决方法:一种是懒人用的,把CString改成 CComBSTR,映射表中也需要对应修改;另一种是高级方法,这需要使用者完全理解了我实现的宏定义,通过自己创建特化模板类来扩充对 CString 的支持,例如 template<> class CVarTypeInfo< CString > { … };

    最后,我得申明一下,这套宏代码我自己一直在使用,而且一直在根据自己的实际需求改进和扩充,但本人没有义务一定要把最新改进版本贡献出来。

    最后的最后,提供整个宏定义的 macro.h 头文件,自动化支持的部分可以直接被使用,宏的使用供有心人研究吧。

#ifndef __MACRO_H__
#define __MACRO_H__
#pragma once
//////////////////////////////////////////////////////////////////////////
// 基础工具宏定义
#define __for_each_number(v, ...) /
    v(0, __VA_ARGS__) /
    v(1, __VA_ARGS__) /
    v(2, __VA_ARGS__) /
    v(3, __VA_ARGS__) /
    v(4, __VA_ARGS__) /
    v(5, __VA_ARGS__) /
    v(6, __VA_ARGS__) /
    v(7, __VA_ARGS__) /
    v(8, __VA_ARGS__) /
    v(9, __VA_ARGS__) /
    v(10, __VA_ARGS__) /
    v(11, __VA_ARGS__) /
    v(12, __VA_ARGS__) /
    v(13, __VA_ARGS__) /
    v(14, __VA_ARGS__) /
    v(15, __VA_ARGS__)
 
// 数值减 1 的常数
#define __cntdec_0 0
#define __cntdec_1 0
#define __cntdec_2 1
#define __cntdec_3 2
#define __cntdec_4 3
#define __cntdec_5 4
#define __cntdec_6 5
#define __cntdec_7 6
#define __cntdec_8 7
#define __cntdec_9 8
#define __cntdec_10 9
#define __cntdec_11 10
#define __cntdec_12 11
#define __cntdec_13 12
#define __cntdec_14 13
#define __cntdec_15 14
 
#define __cntdec(n) __cntdec_##n
// 连接两个符号
#define __connect2(x, y) x##y
#define __connect(x, y) __connect2(x, y)
 
// 生成不同个数的顺序符号
#define __repeat_0(m, ...)
#define __repeat_1(m, ...)    __repeat_0(m, __VA_ARGS__)  m(1, __VA_ARGS__)
#define __repeat_2(m, ...)    __repeat_1(m, __VA_ARGS__)  m(2, __VA_ARGS__)
#define __repeat_3(m, ...)    __repeat_2(m, __VA_ARGS__)  m(3, __VA_ARGS__)
#define __repeat_4(m, ...)    __repeat_3(m, __VA_ARGS__)  m(4, __VA_ARGS__)
#define __repeat_5(m, ...)    __repeat_4(m, __VA_ARGS__)  m(5, __VA_ARGS__)
#define __repeat_6(m, ...)    __repeat_5(m, __VA_ARGS__)  m(6, __VA_ARGS__)
#define __repeat_7(m, ...)    __repeat_6(m, __VA_ARGS__)  m(7, __VA_ARGS__)
#define __repeat_8(m, ...)    __repeat_7(m, __VA_ARGS__)  m(8, __VA_ARGS__)
#define __repeat_9(m, ...)    __repeat_8(m, __VA_ARGS__)  m(9, __VA_ARGS__)
#define __repeat_10(m, ...) __repeat_9(m, __VA_ARGS__)  m(10, __VA_ARGS__)
#define __repeat_11(m, ...) __repeat_10(m, __VA_ARGS__)  m(11, __VA_ARGS__)
#define __repeat_12(m, ...) __repeat_11(m, __VA_ARGS__)  m(12, __VA_ARGS__)
#define __repeat_13(m, ...) __repeat_12(m, __VA_ARGS__)  m(13, __VA_ARGS__)
#define __repeat_14(m, ...) __repeat_13(m, __VA_ARGS__)  m(14, __VA_ARGS__)
#define __repeat_15(m, ...) __repeat_14(m, __VA_ARGS__)  m(15, __VA_ARGS__)
 
#define __last_repeat_0(m, ...)
#define __last_repeat_1(m, ...)    m(1, __VA_ARGS__)
#define __last_repeat_2(m, ...)    m(2, __VA_ARGS__)
#define __last_repeat_3(m, ...)    m(3, __VA_ARGS__)
#define __last_repeat_4(m, ...)    m(4, __VA_ARGS__)
#define __last_repeat_5(m, ...)    m(5, __VA_ARGS__)
#define __last_repeat_6(m, ...)    m(6, __VA_ARGS__)
#define __last_repeat_7(m, ...)    m(7, __VA_ARGS__)
#define __last_repeat_8(m, ...)    m(8, __VA_ARGS__)
#define __last_repeat_9(m, ...)    m(9, __VA_ARGS__)
#define __last_repeat_10(m, ...) m(10, __VA_ARGS__)
#define __last_repeat_11(m, ...)  m(11, __VA_ARGS__)
#define __last_repeat_12(m, ...)  m(12, __VA_ARGS__)
#define __last_repeat_13(m, ...)  m(13, __VA_ARGS__)
#define __last_repeat_14(m, ...)  m(14, __VA_ARGS__)
#define __last_repeat_15(m, ...)  m(15, __VA_ARGS__)
 
#define __repeat(n, m_begin, m_end, ...) __connect(__repeat_, __cntdec(n))(m_begin, __VA_ARGS__) __connect(__last_repeat_, n)(m_end, __VA_ARGS__)
 
// 基础工具宏结束
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Add IDispatch to class
//////////////////////////////////////////////////////////////////////////
// 扩充 CVarTypeInfo 模板类的定义
//template<>
//class CVarTypeInfo< void >
//{
//public:
//    static const VARTYPE VT = VT_EMPTY;
//    //static char VARIANT::* const pmField;
//};
template<>
class CVarTypeInfo< bool >
{
public:
    static const VARTYPE VT = VT_BOOL;
    static VARIANT_BOOL VARIANT::* const pmField;
};
 
__declspec( selectany ) VARIANT_BOOL VARIANT::* const CVarTypeInfo< bool >::pmField = &VARIANT::boolVal;
 
// 扩充 CComBSTR 类型,用这种类型代替BSTR,能防止内存泄露或者内存释放错误
template<>
class CVarTypeInfo< CComBSTR >
{
public:
    static const VARTYPE VT = VT_BSTR;
    static BSTR VARIANT::* const pmField;
};
 
__declspec( selectany ) BSTR VARIANT::* const CVarTypeInfo< CComBSTR >::pmField = &VARIANT::bstrVal;
 
// END of CVarTypeInfo. 使用者可以自行扩充新的类型,例如用 CString 来保存字符串
//////////////////////////////////////////////////////////////////////////
// 定义多参数的模板类
//////////////////////////////////////////////////////////////////////////
// 方法工具模板类和工具宏
#define __tparam(n, ...) typename T##n,
#define __tparam_end(n, ...) typename T##n
#define __param_type(n, ...) if (FAILED(v[n-1].ChangeType(CVarTypeInfo<T##n>::VT, &dp->rgvarg[dp->cArgs-n]))) return E_INVALIDARG;
#define __funcparam(n, ...) v[n-1].*CVarTypeInfo<T##n>::pmField,
#define __funcparam_end(n, ...) v[n-1].*CVarTypeInfo<T##n>::pmField
#define __funcparam_type(n, ...) T##n,
#define __funcparam_type_end(n, ...) T##n
#define __method_helper_t(n, ...) /
    template<class TT, typename rettype, __repeat(n, __tparam, __tparam) rettype (TT::* func)(__repeat(n, __funcparam_type, __funcparam_type_end)) > /
    class _MethodHelper_##n /
    { /
    public: /
    static HRESULT CallMethod (LPVOID pT, DISPPARAMS* dp, VARIANT* pvarResult) /
    { /
        if (pT==NULL) return E_FAIL; /
        if (dp->cArgs < n) return DISP_E_BADPARAMCOUNT; /
        CComVariant v[n+1]; /*加1是为了避免 n==0 时的编译错误*/ /
        __repeat(n, __param_type, __param_type) /
        CComVariant vRet = (reinterpret_cast<TT*>(pT)->*func)( __repeat(n, __funcparam, __funcparam_end) ); /
        if (pvarResult && vRet.vt!=VT_EMPTY) vRet.Detach(pvarResult); /
        return S_OK; /
    } /
    }; /
    /* 返回 VOID 的特化模板类*/ /
    template<class TT, __repeat(n, __tparam, __tparam) void (TT::* func)(__repeat(n, __funcparam_type, __funcparam_type_end)) > /
    class _MethodHelper_##n<TT, void, __repeat(n, __funcparam_type, __funcparam_type) func> /
    { /
    public: /
    static HRESULT CallMethod (LPVOID pT, DISPPARAMS* dp, VARIANT* pvarResult) /
    { /
        if (pT==NULL) return E_FAIL; /
        if (dp->cArgs < n) return DISP_E_BADPARAMCOUNT; /
        CComVariant v[n+1]; /
        __repeat(n, __param_type, __param_type) /
        (reinterpret_cast<TT*>(pT)->*func)( __repeat(n, __funcparam, __funcparam_end) ); /
        return S_OK; /
    } /
    };
 
// 预定义个 16 方法调用工具模板类
__for_each_number(__method_helper_t)
 
#define _method_helper(T, name, type, paramcnt, ...) _MethodHelper_##paramcnt<T,type,__VA_ARGS__,&T::name>::CallMethod
//////////////////////////////////////////////////////////////////////////
// 属性GET工具模板类和工具宏
template<class T, typename rettype, rettype T::* member>
class _GetHelper
{
public:
    static HRESULT CallGet(LPVOID pT, DISPPARAMS* dp, VARIANT* pvarResult)
    {
        if (pT==NULL) return E_FAIL;
        CComVariant vRet = reinterpret_cast<T*>(pT)->*member;
        if (pvarResult) vRet.Detach(pvarResult);
        return S_OK;
    }
};
 
#define _get_helper(T, name, type) _GetHelper<T,type,&T::name>::CallGet
//////////////////////////////////////////////////////////////////////////
// 属性PUT工具模板类和工具宏
template<class T, typename rettype, rettype T::* member>
class _PutHelper
{
public:
    static HRESULT CallPut(LPVOID pT, DISPPARAMS* dp, VARIANT* pvarResult)
    {
        if (pT==NULL) return E_FAIL;
        if (dp->cArgs != 1) return DISP_E_BADPARAMCOUNT;
        CComVariant v;
        if (FAILED(v.ChangeType(CVarTypeInfo<rettype>::VT, dp->rgvarg))) return DISP_E_BADVARTYPE;
#pragma warning(push)
#pragma warning(disable:4800)
        reinterpret_cast<T*>(pT)->*member = v.*CVarTypeInfo<rettype>::pmField;
#pragma warning(pop)
        return S_OK;
    }
};
 
#define _put_helper(T, name, type) _PutHelper<T,type,&T::name>::CallPut
//////////////////////////////////////////////////////////////////////////
// 映射表工具模板类和映射宏
typedef HRESULT (* fnDispMethod)(LPVOID pT, DISPPARAMS* dp, VARIANT* pVarResult);
struct DispMethodData 
{
    LPCOLESTR name;        // property or method name
    DISPID dispid;            // dispid
    fnDispMethod pfnGet;
    fnDispMethod pfnPut;
    fnDispMethod pfnMethod;
};
 
template<class T>
class DispProvider : public IDispatch
{
private:
    T* _owner;
public:
    DispProvider() : _owner(NULL) {}
    void SetOwner(T* owner) { _owner = owner; }
 
    /* IDispatch Methods*/
    STDMETHOD_(ULONG, AddRef)() { return 2; }
    STDMETHOD_(ULONG, Release)() { return 1; }
    STDMETHOD(QueryInterface)(REFIID iid, LPVOID* ppvObj)
    {
        if (!_owner) return E_UNEXPECTED;
        if (!ppvObj) return E_POINTER;
        *ppvObj = NULL;
        if (IsEqualIID(iid, __uuidof(IUnknown)) ||
            IsEqualIID(iid, __uuidof(IDispatch)))
            *ppvObj = this;
        if (*ppvObj)
        {
            ((LPUNKNOWN)(*ppvObj))->AddRef();
            return S_OK;
        }
        return E_NOINTERFACE;
    }
    STDMETHOD(GetTypeInfoCount)(UINT *pctinfo) { *pctinfo=0; return E_NOTIMPL; }
    STDMETHOD(GetTypeInfo)(UINT /*iTInfo*/, LCID /*lcid*/, ITypeInfo **ppTInfo) { *ppTInfo = NULL; return E_NOTIMPL; }
    STDMETHOD(GetIDsOfNames)(REFIID riid, OLECHAR ** rgszNames, UINT cNames, LCID lcid, DISPID * rgDispId)
    {
        ATLASSERT(cNames == 1);
        if (cNames != 1) return E_NOTIMPL;
        if (!_owner) return E_UNEXPECTED;
 
        *rgDispId = DISPID_UNKNOWN;
        const DispMethodData* pMap = T::__GetDispMapEntry(*rgszNames);
        if (pMap)
            return *rgDispId = pMap->dispid, S_OK;
        return DISP_E_MEMBERNOTFOUND;
    }
    STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pdispparams, VARIANT *pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr)
    {
        if (!_owner) return E_UNEXPECTED;
 
        const DispMethodData* pMap = T::__GetDispMapEntry(NULL, &dispIdMember);
        if (pMap)
        {
            fnDispMethod pfn = (wFlags&DISPATCH_METHOD) ? pMap->pfnMethod : (wFlags==DISPATCH_PROPERTYGET) ? pMap->pfnGet : pMap->pfnPut;
            if (pfn)
                return pfn(_owner, pdispparams, pVarResult);
        }
        return DISP_E_MEMBERNOTFOUND;
    }
};
 
#define Begin_Disp_Map(classname) /
public: /
    DispProvider<classname> __disp; /
    IDispatch* GetDispatch() { return __disp.SetOwner(this), (IDispatch*)&__disp; } /
    static const DispMethodData* __GetDispMapEntry(LPCOLESTR pszByName=NULL/*find by name*/, DISPID* pByDispid=NULL/*find by dispid*/) /
    { /
        typedef classname owner_class; /
        static const DispMethodData __map_entry[] = {
 
#define Disp_PropertyGet(dispid, name, type) /
            {OLESTR(#name), dispid, _get_helper(owner_class,name,type), NULL, NULL},
 
#define Disp_PropertyPut(dispid, name, type) /
            {OLESTR(#name), dispid, NULL, _put_helper(owner_class,name,type), NULL},
 
#define Disp_Property(dispid, name, type) /
            {OLESTR(#name), dispid, _get_helper(owner_class,name,type), _put_helper(owner_class,name,type), NULL},
 
#define Disp_Method(dispid, name, type, paramcnt, ...) /
            {OLESTR(#name), dispid, NULL, NULL, _method_helper(owner_class,name,type,paramcnt,__VA_ARGS__)},
 
#define End_Disp_Map() /
            {NULL, DISPID_UNKNOWN, NULL, NULL, NULL} /
        }; /
        if (pszByName==NULL && pByDispid==NULL) return __map_entry; /
        for (int i=0; i<sizeof(__map_entry)/sizeof(__map_entry[0]) - 1; i++) /
        { /
            if (pByDispid) /
            { /
                if (__map_entry[i].dispid == *pByDispid) return &__map_entry[i]; /
            } /
            else /*if (pszByName)*/ /
            { /
                if (lstrcmpiW(__map_entry[i].name, pszByName) == 0) return &__map_entry[i]; /
            } /
        } /
        return NULL; /
    }
 
 
 
#endif // __MACRO_H__