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.
#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);
}
HRESULT RegisterClassObjects(DWORD dwClsContext, DWORD dwFlags) throw()
{
dwFlags &= ~REGCLS_MULTIPLEUSE;
dwFlags |= REGCLS_SINGLEUSE;
return __super::RegisterClassObjects(dwClsContext, dwFlags);
}
};
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:
[module(type=DLL, name="Simple",
uuid="67BF6350-E112-46fc-A6B6-00EFF8CBF1BB")];
[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();
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)];
[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 __hook
ing 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() {}
~_CFOOD_DESAccessor(){}
[ 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);
{
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;
std::cout<<"---------------"<<std::endl;
if(food.MoveNext() == DB_S_ENDOFROWSET)
break;
}
}
}
Editing/Adding/Deleting is same as before:
food.SetData();
food.Update();
food.MoveFirst();
food.Delete();
food.Update();
food.MoveFirst();
sometable.MoveLast();
sometable.ClearRecordMemory();
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_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)