Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ATL7 and Attributes

0.00/5 (No votes)
19 Jun 2002 1  
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:

[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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here