Click here to Skip to main content
Click here to Skip to main content

ATL7 and Attributes

By , 19 Jun 2002
 

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Leon Finker
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralVisual Studio 2008memberkoriandr18 Dec '07 - 8:10 
As you know MS moved server part of ATL to an open source project CodePlex.
Performance counter object is under CodePlex as well.
However even after installing ATL from CodePlex the attributed performance counter does not compile under Visual Studio 2008 as the compiler cannot resolve [perf_object] attribute.
GeneralfmemberMartin Schetat25 Nov '05 - 3:27 
Hello Leon,
 
I built an application using the ATL performance counter objects. It works fine on Windows 2000 and XP but the counters don't show up in the Performance Monitor on Windows Server 2003.
 
Do you have any idea what could cause this problem? The Service Pack 1 maybe?
 
By the way, the MSDN sample PerformanceScribble doesn't work either...
 
Thanks,
 
Martin Schetat
Questionwhat would I need...memberdghhngd4517 Nov '04 - 8:10 
Hi,
first of all, thanks for your very nice article.
I also have a question:
I want to make a performance-monitoring application which should run on different windows NT platforms (without the .NET framework or the SDK's installed...) what do you think I should use to have an easily-distributable (no need for extra stuff (.net,....)) and easy-runnable (just one exe, if possible) application???
 
Thanks.
 
plinius.

Generalevent_source &amp; event_receiver in the same classsussCocoJumbo6 Oct '03 - 0:14 
Dear Leon,
 
How it is possible to use attributes event_source("com") and event_receiver("com") in the same class?
 
Thank you in advance,
 
Rostislav,
Kiev

GeneralRe: event_source & event_receiver in the same classmemberMasoud Ghaemi15 Nov '04 - 22:15 
Sleepy | :zzz: You must use event_receiver("com", true) inseted of event_receiver("com")
Dont forget:
1. use coclass when use from event_receiver("com", true)
2. The event receiver handler names must match the names specified in the relevant event source interface
 
Note: In this way you must hook entire event interface
 
__hook(Interfcae, Source)
 
Interface: The interface name being hooked to receiver
Source: A pointer to an instance of the event source.
 

Ghaemi,
Masoud
GeneralPerformance CountermemberChReichenberg3 Sep '03 - 21:00 
Hallo Leon,
 
I tried to build a Performance Couter like you described in your article, but allways when I try to register the dll with regsrv32 I get the errorcode 0xC0000005.
 
The registry entry in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services looks good, but there aren't any entries in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib in the Counter or Help Keys
 
Do you know how I can solve this problem?
 
Thanks
Chris
 

GeneralRe: Performance CountermemberLeon Finker4 Sep '03 - 8:55 
Hi Chris,
 
I don't know what could be causing that.
I just tried with VS2003. Registered the
dll on win2k and clean winxp system, no problem.
When viewing with regedt32.exe, Counter/Help
entries are updated for the new counter under
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\[009]
 
I can suggest setting regsvr32.exe as your
"Debug Command" in Project Properties | Debugging
 
and for
 
"Command Arguments" specifying "$(TargetPath)" in quotes.
 
Then set breakpoint in STDAPI DllRegisterServer(void)
and start debugging session.
You can step through the registration code and maybe get closer to the actual error.

Generaldon't insert the tlb into the dllmembertalhalfon21 Jul '03 - 3:19 
how can i remove the tlb from the dll ?
 
i don't want that the dll will be depend in the tlb.
 
help please
GeneralCannot hook the events in a MFC appmemberAldamo3 Dec '02 - 23:40 
Dear Leon,
 
thanks for a very informative article.
 
There is a problem I could not solve, perhaps you can help me.
I'm completely lost, and I haven't found any answer in the MSDN.
 

I have written a local COM server using ATL and attributes.
 
Here is the event source:
// _IRasEvents
[
dispinterface,
uuid("1B6AE108-5C42-4F5D-B02E-470DD71EAC3E"),
helpstring("_IRasEvents Interface")
]
__interface _IRasEvents
{
[id(1), helpstring("method TestEvents")] HRESULT TestEvents([in] BSTR msg);
};
 

The event interface is declared in the coclass as follows:
[
coclass,
threading("apartment"),
support_error_info("IRas"),
event_source("com"),
vi_progid("RasCom.Ras"),
progid("RasCom.Ras.1"),
version(1.0),
uuid("0C8982FA-F9AF-43BD-A6BD-99E361A60C7F"),
helpstring("Ras Class")
]
class ATL_NO_VTABLE CRas :
public IRas,
public _IRasEvents
{
public:
...
 
__event __interface _IRasEvents;
 
...
// _IRasEvents Methods
public:
 
};
 
In fact, all of this was generated by the wizard.
 
Now, I have a dialog based MFC application not using ATL,
which is a client of this COM server. I just included 2 header files
of the server to be able to use it:
 
#include "_RasCom.h"
#include "_RasCom_i.c"
 
Everything works fine, but I cannot hook the event.
 
What should be the first parameter of the __hook function?
Theoretically it should look like this:
 
__hook(_IRasEvents::TestEvents, pSrc, CMyClass::Handler);
 
But I get the error message: Improper syntax...
 
The server must send events to the application, and I cannot handle them.
I wrote a test app in Visual Basic, and it works and handles the event.
 
What am I doing wrong in the MFC app?
 
Can you perhaps give me a clue how I can hook the events in an MFC application.
If you need some more information, just let me know.
Thank you in advance,
 


 
Alex,
Moscow
alex.damocev@mtu-net.ru

GeneralRe: Cannot hook the events in a MFC appmemberLeon Finker4 Dec '02 - 5:44 
Hello,
 
Couple of problems i can think of:
First, I don't know if it's a good idea to mix ATL COM related attributes with MFC.
Better stick with ATL's IDispEventSimpleImpl/BEGIN_SINK_MAP/END_SINK_MAP in mfc.
 
1) COM events with attributes require some ATL headers
2) __hook() for com events needs attributes and module[] attribute is required
3) #include "_RasCom.h" and #include "_RasCom_i.c" don't have the needed attributes
 
why don't you want to use #import with embedded_idl option?
 
for example:
#import "libid:your libs uuid here" \
                        auto_search no_implementation named_guids \
                        raw_interfaces_only raw_native_types no_namespace embedded_idl
 
So if you still choose to use attributes with mfc...
here is an example in stdafx.h put:
#define _ATL_ATTRIBUTES 1
#include <atlbase.h>
#include <atlcom.h>
 
in your .cpp file
#import "libid:your libs uuid here" \
                        auto_search no_implementation named_guids \
                        raw_interfaces_only raw_native_types no_namespace embedded_idl
 
[module(name="EventReceiver")];
[emitidl(false)];
 
[event_receiver("com")]
class CEventReceiver()
{
///
};
now your code...
But beware this will export the DLL related registration functions:
DllRegisterServer/etc
 
Hope this helps

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 20 Jun 2002
Article Copyright 2002 by Leon Finker
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid