Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / ATL
Article

ATL7 and Attributes

Rate me:
Please Sign up or sign in to vote.
4.88/5 (11 votes)
19 Jun 2002CPOL5 min read 191.9K   76   26
ATL7 and Attributes description and sample usage

The other ATL7 changes are covered here and here

VC++ Attributes are designed to simplify programming. They are only for the compiler and don't really survive past the compile stage (except for idl attributes, but even those are added at link time by running midl and building typelib). The compiler examines the attributes and injects/generates the appropriate C++ code. When working with attributes it's always nice to use the /Fx compiler switch. What it does is generate the .h/.cpp file of merged code after processing the attributes. So you are free to examine the code that compiler generates. You can even copy it to your class and comment out the attribute if you decide not to use some specific attribute. VC++ covers the following areas with attributes:

  • COM/IDL - provide various attributes for interfaces, COM classes, properties/methods, and events, script registration;

  • OLEDB - injects code based on OLEDB Consumer Templates;

  • C++ Compiler - native C++ events, various IDL file related features;

  • ATL Server - injects code based on ATL Server classes for Web services and Web Applications

With attributes we can have just header (.h) and implementation (.cpp) file which contains all that is needed, the compiler and linker takes care of the rest. VC++ accomplishes attribute processing with what is known as an attribute provider (ATL's is in Atlprov.dll). When compiler sees attributes it passes the info along to the attribute provider which in turn injects the appropriate code.

The following is a simple COM server, which is implemented, as you see, in just one file! (I'm not advocating one file implementation, this is only for illustration purposes. I find it easier to learn something new with quick and compact examples.)

As a side note: when working with attributes it's nice to use CTRL+F7, to compile single file only (for syntax checks) instead of building whole project, generating .idl, running midl and generating .tlb each time.

// 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() { }
};

Don't forget to define the _ATL_ATTRIBUTES symbol and to include atlbase.h. This symbol brings in the atlplus.h file for the attributes support. If you don't define it, you'll get a linker error not finding the entry point. Also, define the module[(type=DLL|EXE|SERVICE)] attribute. The starting point in attributed ATL/COM programming is the module attribute. It has a number of parameters, some of which are optional. For example, type=EXE|DLL|SERVICE, name="Type Library Name", uuid="{UUID of the type library}", etc. After that you're ready to start defining your interfaces and COM classes. As you see there is no need to write .idl or .def files manually. module[()] takes care of it. You can look at the code generated by the compiler if you add the /Fx compiler switch and open the *.mrg.cpp file. For the next example, the compiler auto-generated the WinMain function and a class derived from CAtlExeModuleT since it's an EXE type of app. In addition it declared a global variable _AtlModule. So, that's all that is required to create an exe/dll with COM functionality. If you want, you're free to derive from the module class and customize the default behavior:

[^__b__^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
};

By the way, if you want a quick UUID generated right in the VC++ .NET editor just start typing [uuid(, after that a GUID should be generated and completed for you!

The following example is for exposing COM events with attributes:

// 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;
    }
};

Again you can see all the code generated with /Fx compiler switch. No mystery there.

Sinking events using attributes is as follows:

#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);
}

You can use either #import or the server's header file with all the attributes. Make sure you don't forget the embedded_idl if you're using #import, otherwise you'll get errors on __hooking the source event interface. What it does is generates the attributes in the .tlh file just as you did for the COM server, so the compiler is happy by reading them on the client side. Next thing to remember is to use the [module(name)] block or you'll also get compiler/linker errors. In some cases [emitidl(restricted)] can be used instead, but not with __hook/__unhook. It requires the module block. If you don't need idl/tlb, *_i.c/*_p.c, .h, dlldata.c generated, as for example in a client app, all you need to do is place the following attribute: [emitidl(false)].

OLEDB related attributes are also designed to simplify database access for the client. The best thing is it doesn't require ATL project. Create a simple C++ Console project. Then Project | Add Class | ATL OLEDB Consumer. For example the following is simple example of using OLEDB attributes. I'm using a freely available MS Access database for this: USDA Nutrient Database.

NOTE: Make sure the DB file is in same directory as the .exe or if running from VS.NET make sure it's in Project Directory. Otherwise just specify the full path to the .mdb file.

[    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);
    }
};

Using it is the same as before, nothing changed really:

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;
}

You can also use parameterized queries using the db_param[] attribute or construct the query yourself:

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;
         }
     }
 }

Editing/Adding/Deleting is same as before:

// 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();

Adding NT Performance counters to your code can be also simplified with attributes. We have [perfmon], [perf_object], and [perf_counter] which correspond to CPerfMon, CPerfObject, and DEFINE_COUNTER(). Create a simple Win32 DLL project. Then choose Project | Add Class | ATL Performance Monitor Object Manager, choose to use attributes and TODO comments. You can read the comments on how to add counters. The appropriate code for DllRegisterServer/DllUnregisterServer will be added for you. 

// "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{};

After you register the performance counter DLL, entries will be added under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Perf_Mon\Performance and under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\[langid]. To use it, simply include it in your main project the header file where you defined the performance counter, for example "perf.h":<

Initializing and Accessing Performance Monitor Objects (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;
}

Now when you run the app you can open System Monitor and monitor your performance object and its counters. The performance object will be visible in Add Counters when you run the app.

The ATL server related topics and attributes are not covered in this article.

You can look into some of the walkthroughs on MSDN that deal specifically with attributes and show you their usage:

Attributes Tutorial (MSDN)
Creating a COM Server Using a Text Editor (MSDN)
Walkthrough: Creating an ActiveX Control with Attributes (MSDN)
Walkthrough: Developing a COM DLL with COM Attributes (MSDN)
Attributes by Usage (MSDN)
Alphabetical List of Attributes Samples (MSDN)
Event Handling in Visual C++ (MSDN)
Simplifying Data Access with Database Attributes (MSDN)
Consumer Wizard-Generated Classes (MSDN)
Manually Defining Performance Objects (MSDN)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralVisual Studio 2008 Pin
koriandr18-Dec-07 8:10
koriandr18-Dec-07 8:10 
Generalf Pin
Martin Schetat25-Nov-05 3:27
Martin Schetat25-Nov-05 3:27 
Questionwhat would I need... Pin
dghhngd4517-Nov-04 8:10
dghhngd4517-Nov-04 8:10 
Generalevent_source &amp; event_receiver in the same class Pin
Cocojumbo6-Oct-03 0:14
Cocojumbo6-Oct-03 0:14 
GeneralRe: event_source & event_receiver in the same class Pin
Masoud Ghaemi15-Nov-04 22:15
Masoud Ghaemi15-Nov-04 22:15 
GeneralPerformance Counter Pin
ChReichenberg3-Sep-03 21:00
ChReichenberg3-Sep-03 21:00 
GeneralRe: Performance Counter Pin
Leon Finker4-Sep-03 8:55
Leon Finker4-Sep-03 8:55 
Generaldon't insert the tlb into the dll Pin
talhalfon21-Jul-03 3:19
talhalfon21-Jul-03 3:19 
GeneralCannot hook the events in a MFC app Pin
Aldamo3-Dec-02 23:40
Aldamo3-Dec-02 23:40 
GeneralRe: Cannot hook the events in a MFC app Pin
Leon Finker4-Dec-02 5:44
Leon Finker4-Dec-02 5:44 
GeneralRe: Cannot hook the events in a MFC app Pin
Aldamo4-Dec-02 8:39
Aldamo4-Dec-02 8:39 
QuestionHow to set length field befor saving Pin
Marco11125-Oct-02 6:57
Marco11125-Oct-02 6:57 
AnswerRe: How to set length field befor saving Pin
Leon Finker25-Oct-02 8:31
Leon Finker25-Oct-02 8:31 
GeneralAuto-Generating uuids Pin
Simon Steele25-Oct-02 5:00
Simon Steele25-Oct-02 5:00 
Generalyep Pin
Leon Finker25-Oct-02 8:31
Leon Finker25-Oct-02 8:31 
GeneralBrilliant! Pin
QX7-Oct-02 0:45
QX7-Oct-02 0:45 
This is by far the best and most detailed information about handling
.NET attritubtes. I searched all the .NET MSDN for this issue,
but got nothing from it.
Thank you! You helped me a lot!Smile | :)

GeneralThanx :) Pin
Leon Finker25-Oct-02 8:34
Leon Finker25-Oct-02 8:34 
Questionhow to oevrload _tWinMain Pin
ydavid1814-Aug-02 4:44
ydavid1814-Aug-02 4:44 
AnswerRe: how to oevrload _tWinMain Pin
Leon Finker18-Aug-02 10:57
Leon Finker18-Aug-02 10:57 
GeneralATL attributes and typdefs inside of LIB block Pin
cSaRebel13-Aug-02 4:50
cSaRebel13-Aug-02 4:50 
GeneralRe: ATL attributes and typdefs inside of LIB block Pin
Leon Finker18-Aug-02 11:37
Leon Finker18-Aug-02 11:37 
GeneralGood job Leon! Pin
Ernest Laurentin30-Apr-02 5:42
Ernest Laurentin30-Apr-02 5:42 
GeneralRe: Good job Leon! Pin
Leon Finker30-Apr-02 5:49
Leon Finker30-Apr-02 5:49 
GeneralMerits and demerits Pin
Paul Selormey29-Apr-02 14:59
Paul Selormey29-Apr-02 14:59 
GeneralRe: Merits and demerits Pin
Leon Finker29-Apr-02 16:02
Leon Finker29-Apr-02 16:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.