65.9K
CodeProject 正在变化。 阅读更多。
Home

ATL7 和属性

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (10投票s)

2002年4月29日

CPOL

5分钟阅读

viewsIcon

196682

ATL7 和属性的描述和示例用法

其他 ATL7 的更改已在此 涵盖。

VC++ 属性旨在简化编程。它们仅供编译器使用,并且实际上不会在编译阶段之后保留(IDL 属性除外,但即使是这些属性,也仅在链接时通过运行 midl 并构建 typelib 来添加)。编译器会检查属性并注入/生成相应的 C++ 代码。在使用属性时,最好使用 /Fx 编译器开关。它的作用是在处理完属性后生成合并代码的 .h/.cpp 文件。因此,您可以自由检查编译器生成的代码。您甚至可以将其复制到您的类中,然后注释掉某个特定属性,如果您决定不使用它。VC++ 通过以下几个方面来覆盖属性:

  • COM/IDL - 提供各种用于接口、COM 类、属性/方法和事件、脚本注册的属性;

  • OLEDB - 基于 OLEDB 消费者模板注入代码;

  • C++ 编译器 - 原生 C++ 事件、各种 IDL 文件相关功能;

  • ATL Server - 基于 ATL Server 类注入代码,用于 Web 服务和 Web 应用程序。

通过属性,我们可以只拥有包含所需所有内容的头文件(.h)和实现文件(.cpp),编译器和链接器会处理其余部分。VC++ 通过所谓的 属性提供程序(ATL 的在 Atlprov.dll 中)来完成属性处理。当编译器看到属性时,它会将信息传递给属性提供程序,属性提供程序随后会注入适当的代码。

下面是一个简单的 COM 服务器,如您所见,它仅在一个文件中实现!(我并不提倡单文件实现,这仅用于说明目的。我发现用快速紧凑的示例学习新东西更容易。)

顺便说一句:在使用属性时,最好使用 CTRL+F7 来仅编译单个文件(用于语法检查),而不是构建整个项目,每次都生成 .idl、运行 midl 并生成 .tlb

// build as DLL and register with regsvr32
#define _ATL_ATTRIBUTES
#define _ATL_APARTMENT_THREADED
#include <atlbase.h>
#include <atlcom.h>
using namespace ATL;
typedef LONG HRESULT;

[module(type=DLL, name="Simple", 
    uuid="67BF6350-E112-46fc-A6B6-00EFF8CBF1BB")];

[dual, uuid("A4042AEE-12C0-48d8-8CA4-80B8F957B7B3")]
__interface ISimpleObject
{
    [propget] HRESULT String([out, retval]BSTR* pVal);
};

[coclass,uuid("E16E71E3-8217-4739-8AD7-2689581A75F0")]
class ATL_NO_VTABLE SimpleObject : public ISimpleObject
{
   public:   
   HRESULT get_String(BSTR* pVal)
   {
       if(pVal == NULL)
           return E_POINTER;       
       *pVal = CComBSTR("string").Detach();
       return S_OK;
   }
   HRESULT FinalConstruct() { return S_OK; }
   void FinalRelease() { }
};

不要忘记定义 _ATL_ATTRIBUTES 符号并包含 atlbase.h。此符号会引入 atlplus.h 文件以支持属性。如果不定义它,您将收到链接器错误,找不到入口点。另外,定义 module[(type=DLL|EXE|SERVICE)] 属性。在属性化 ATL/COM 编程中的起点是 module 属性。它有许多参数,其中一些是可选的。例如,type=EXE|DLL|SERVICEname="Type Library Name"uuid="{UUID of the type library}" 等。之后,您就可以开始定义接口和 COM 类了。如您所见,无需手动编写 .idl.def 文件。module[()] 会处理它。如果您添加 /Fx 编译器开关并打开 *.mrg.cpp 文件,您可以看到编译器生成的代码。对于下一个示例,编译器自动生成了 WinMain 函数和一个派生自 CAtlExeModuleT 的类,因为它是一个 EXE 类型的应用程序。此外,它还声明了一个全局变量 _AtlModule。因此,创建具有 COM 功能的 exe/dll 所需的就只有这些了。如果您愿意,可以派生自模块类并自定义默认行为。

[module(type=exe, name="Simple")]
class CSomeClass
{
   int WinMain(int nShowCmd) throw()
    {
        return __super::WinMain(nShowCmd);
        // same as return CAtlExeModuleT<CSomeClass>::WinMain(nShowCmd);
    }
    
    HRESULT RegisterClassObjects(DWORD dwClsContext, DWORD dwFlags) throw()
    {
        // example of modifying default behavior when CoRegisterClassObject is called
        dwFlags &= ~REGCLS_MULTIPLEUSE;
            dwFlags |= REGCLS_SINGLEUSE;
            return __super::RegisterClassObjects(dwClsContext, dwFlags);
    }
    // etc
};

顺便说一句,如果您想在 VC++ .NET 编辑器中快速生成 UUID,只需开始键入 [uuid(,之后就会为您生成并完成一个 GUID!

以下示例展示了如何使用属性公开 COM 事件。

// DLL Server project, register with regsvr32

[module(type=DLL, name="Simple", 
    uuid="67BF6350-E112-46fc-A6B6-00EFF8CBF1BB")];

// use object attribute to inherit from IUnknown if needed
[object, oleautomation, 
    uuid("D59F99BE-5DFB-4C0C-B9A6-16853740E4AA")]
__interface ISimpleObject    
{
[id(1)] HRESULT RaiseEvent();
};
[dual, uuid("29D2345E-5E47-4F60-B3DC-46BDF29B98A9")]
__interface _ISimpleEvent
{
[id(1)] HRESULT String();
};

[coclass, uuid("6B4A3FAC-6728-4058-8A1C-7352A27BFCCC"),
    event_source(com)]
class ATL_NO_VTABLE SimpleObject : public ISimpleObject
{
public:
    __event __interface _ISimpleEvent;
    HRESULT RaiseEvent()
    {
        __raise String();
    // or InterfaceName_EventName
    // _ISimpleEvent_String();
        return S_OK;
    }
};

同样,您可以使用 /Fx 编译器开关查看所有生成的代码。没有什么神秘之处。

使用属性接收事件如下:

#define _ATL_ATTRIBUTES
#include <atlbase.h>
#include <atlcom.h>
using namespace ATL;
#import "libid:67BF6350-E112-46fc-A6B6-00EFF8CBF1BB" \
                auto_search no_implementation named_guids \
                raw_interfaces_only raw_native_types no_namespace embedded_idl

[module(name="EventReceiver")];

[emitidl(false)]; // don't need any of COM server files 
                         // generated (.idl, .h, *.c, .tlb)

[event_receiver("com")]
class CEventSink
{
public: 
    HRESULT String()
    {
        ATLTRACE("\nGot Event\n");
        return S_OK;
    }
    void Advise(IUnknown* pObj)
    {
        __hook(_ISimpleEvent::String, pObj, CEventSink::String);        
    }
    void UnAdvise(IUnknown* pObj)
    {
        __unhook(_ISimpleEvent::String, pObj, CEventSink::String);
    }
};
int main()
{
    CComPtr<ISimpleObject> spObj;
    HRESULT hr = spObj.CoCreateInstance(CLSID_SimpleObject);
    CEventSink sink;
    sink.Advise(spObj);
    spObj->RaiseEvent();
    sink.UnAdvise(spObj);
}

您可以使用 #import 或服务器的头文件(包含所有属性)。如果您使用 #import,请确保不要忘记 embedded_idl,否则在 __hook 源事件接口时会出错。它的作用是在 .tlh 文件中生成属性,就像您为 COM 服务器所做的那样,这样客户端就可以让编译器快乐地读取它们。接下来要记住的是使用 [module(name)] 块,否则您也会遇到编译器/链接器错误。在某些情况下,可以使用 [emitidl(restricted)] 代替,但不能与 __hook/__unhook 一起使用。它需要 module 块。如果您不需要生成 idl/tlb*_i.c/*_p.c.hdlldata.c,例如在客户端应用程序中,您需要做的就是放置以下属性:[emitidl(false)]

OLEDB 相关属性也旨在简化客户端的数据库访问。最好的地方是它不需要 ATL 项目。创建一个简单的 C++ 控制台项目。然后选择“项目”|“添加类”|“ATL OLEDB 消费者”。例如,以下是一个使用 OLEDB 属性的简单示例。我在这里使用了一个免费提供的 MS Access 数据库:USDA Nutrient Database

注意:确保数据库文件与 .exe 文件在同一目录中,或者从 VS.NET 运行时,确保它在项目目录中。否则,只需指定 .mdb 文件的完整路径。

[    db_source(L"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=sr13.mdb;\
                Mode=ReadWrite|Share Deny None;Jet OLEDB:Engine Type=5;\
                Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;\
                Jet OLEDB:Global Bulk Transactions=1;")
]
class CDBSource{};

[  db_command(L"SELECT FOOD_DES.DESC, FOOD_DES.FAT_FACTOR, \
                FOOD_DES.CHO_FACTOR, FOOD_DES.NDB_NO, \
                FOOD_DES.SHRT_DESC, \
                Abbrev.Water, \
                Abbrev.Energy, \
                Abbrev.Protein \
                FROM FOOD_DES INNER JOIN ABBREV \
                ON FOOD_DES.NDB_NO = Abbrev.[NDB No]")
]
class CFOOD_DES
{
public:
    _CFOOD_DESAccessor() {}  // if you need ctor/dtor remember the actual class 
    ~_CFOOD_DESAccessor(){}  // name is not CFOOD_DES! Again, /Fx switch!

    [ db_column(1, length=m_dwDESCLength) ] TCHAR m_DESC[201]; 
    [ db_column(2) ] double m_FAT_FACTOR;
    [ db_column(3) ] double m_CHO_FACTOR;
    [ db_column(4, length=m_dwNDB_NOLength) ]    TCHAR m_NDB_NO[6];
    [ db_column(5, length=m_dwSHRT_DESCLength) ] TCHAR m_SHRT_DESC[61];    
    [ db_column(6) ] float m_Water;
    [ db_column(7) ] float m_Energy;
    [ db_column(8) ] float m_Protein;

    DBLENGTH m_dwDESCLength;
    DBLENGTH m_dwNDB_NOLength;
    DBLENGTH m_dwSHRT_DESCLength;

    void GetRowsetProperties(CDBPropSet* pPropSet)
    {
    pPropSet->AddProperty(DBPROP_CANFETCHBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);
    pPropSet->AddProperty(DBPROP_CANSCROLLBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);
    pPropSet->AddProperty(DBPROP_IRowsetChange, true, DBPROPOPTIONS_OPTIONAL);
    pPropSet->AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | 
                  DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE);
    }
};

使用方式与之前相同,实际上没有任何变化。

int main()
{
    CoInitialize(NULL);
    { // don't forget to destroy COM objects before CoUninitialize is called
        CDBSource source;
        if(SUCCEEDED(source.OpenDataSource()))
        {
            CDBPropSet propset(DBPROPSET_ROWSET);
            CFOOD_DES food;
            food.GetRowsetProperties(&propset);
            if(SUCCEEDED(food.Open(source, NULL, propset)))
            {
                if(SUCCEEDED(food.MoveFirst()))
                {
                std::string sout;
                for(int i=0; i<10; ++i)
                {
                    sout="";
                    sout.append(food.m_DESC, food.m_dwDESCLength);
                    std::cout<<sout.c_str()<<std::endl;
                    std::cout<<food.m_FAT_FACTOR<<std::endl;
                    std::cout<<food.m_CHO_FACTOR<<std::endl;
                    sout ="";
                    sout.append(food.m_NDB_NO, food.m_dwNDB_NOLength);
                    std::cout<<food.m_NDB_NO<<std::endl;
                    sout ="";
                    sout.append(food.m_SHRT_DESC, food.m_dwSHRT_DESCLength);
                    std::cout<<food.m_SHRT_DESC<<std::endl;
                    std::cout<<food.m_Water<<std::endl;
                    std::cout<<food.m_Energy<<std::endl;
                    std::cout<<food.m_Protein<<std::endl;
                    std::cout<<"---------------"<<std::endl;
                    if(food.MoveNext() == DB_S_ENDOFROWSET)
                        break;
                }
            }
        }
    }
    CoUninitialize();
    return 0;
}

您还可以使用 db_param[] 属性使用参数化查询,或者自己构建查询。

food.Close();
WCHAR sQuery[] = L"SELECT FOOD_DES.DESC, FOOD_DES.FAT_FACTOR, \
          FOOD_DES.CHO_FACTOR, FOOD_DES.NDB_NO, FOOD_DES.SHRT_DESC, \
          Abbrev.Water, Abbrev.Energy, Abbrev.Protein \
          FROM FOOD_DES INNER JOIN ABBREV ON \
          FOOD_DES.NDB_NO = Abbrev.[NDB No] \
          WHERE FOOD_DES.NDB_NO = '01011'";
 if(SUCCEEDED(food.Open(source, sQuery)))
 {
     if(SUCCEEDED(food.MoveFirst()))
     {
         std::string sout;
         for(int i=0; i<10; ++i)
         {
             sout="";
             sout.append(food.m_NDB_NO, food.m_dwNDB_NOLength);
             std::cout<<food.m_NDB_NO<<std::endl;
             // etc
             std::cout<<"---------------"<<std::endl;
             if(food.MoveNext() == DB_S_ENDOFROWSET)
                 break;
         }
     }
 }

编辑/添加/删除与之前相同。

    // updating
    // change current row fields. don't forget to update length field for strings.
    food.SetData();
    food.Update();
    food.MoveFirst();

    //delete current row
    food.Delete();
    food.Update();
    food.MoveFirst();

    //add new
    // the USDA database has constraints that have to be satisfied for Insert to work.
    sometable.MoveLast();
    sometable.ClearRecordMemory();
    // set field's length if needed
    // set other fields, etc then call:
    sometable.Insert();

使用属性还可以简化将 NT 性能计数器添加到代码中。我们有 [perfmon][perf_object][perf_counter],它们分别对应 CPerfMonCPerfObjectDEFINE_COUNTER()。创建一个简单的 Win32 DLL 项目。然后选择“项目”|“添加类”|“ATL 性能监视器对象管理器”,选择使用属性和 TODO 注释。您可以阅读关于如何添加计数器的注释。对于 DllRegisterServer/DllUnregisterServer 的相应代码将为您添加。 

// "perf.h"
[ perf_object(namestring="Perf_Obj", helpstring="Sample Description", 
    detail=PERF_DETAIL_NOVICE) ]
class PerfSampleObject
{
public:
    [ perf_counter( namestring="perf1", 
                           helpstring="Some Description",
                           countertype=PERF_COUNTER_RAWCOUNT,
                           detail=PERF_DETAIL_NOVICE, default_counter = true) ]
    ULONG m_nCounter;
};

[ perfmon(name="Perf_Mon", register=true) ]
class PerfMon{};

注册性能计数器 DLL 后,将在 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Perf_Mon\PerformanceHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\[langid] 下添加条目。要使用它,只需在主项目中包含定义性能计数器的头文件,例如 "perf.h":<

初始化和访问性能监视器对象 (MSDN)

#define _ATL_ATTRIBUTES
#include <atlbase.h>
#include <atlperf.h>
#include "X:\path_to_dll\perf.h"

PerfMon perf;

int main(int argc, _TCHAR* argv[])
{
    HRESULT hr = perf.Initialize();
    PerfSampleObject* obj=NULL;
    {
        CPerfLock lock(&perf);
        if(lock.GetStatus()== S_OK)
            perf.CreateInstanceByName(L"Perf_Obj", &obj);
        if(obj == NULL)
            return 0;
    }
    while(true)
    {
        InterlockedIncrement((LONG*)&obj->m_nCounter);
        Sleep(1000);
    }
    
    {
        CPerfLock lock(&perf);
        if(lock.GetStatus()== S_OK)
            perf.ReleaseInstance(obj);
    }
    perf.UnInitialize();
    return 0;
}

现在,当您运行应用程序时,可以打开系统监视器并监视您的性能对象及其计数器。当您运行应用程序时,性能对象将在“添加计数器”中可见。

本文未涵盖 ATL 服务器相关主题和属性。

您可以查阅 MSDN 上的一些教程,这些教程专门讨论了属性并展示了它们的用法。

属性教程 (MSDN)
使用文本编辑器创建 COM 服务器 (MSDN)
演练:使用属性创建 ActiveX 控件 (MSDN)
演练:使用 COM 属性开发 COM DLL (MSDN)
按用法分类的属性 (MSDN)
属性示例的字母顺序列表 (MSDN)
Visual C++ 中的事件处理 (MSDN)
使用数据库属性简化数据访问 (MSDN)
使用者向导生成的类 (MSDN)
手动定义性能对象 (MSDN)

© . All rights reserved.