Migrating ATL service applications to Visual C++.NET





5.00/5 (12 votes)
Jul 29, 2002
11 min read

253572

1641
Guidelines to migrate ATL-based Windows service application to Visual C++.NET
migrate
1. To remove from one country or region to another, with a view to
residence; to change one's place of residence; to remove; as, the Moors who
migrated from Africa into Spain; to migrate to the West.
2. To pass periodically from one region or climate to another for feeding or
breeding; -- said of certain birds, fishes, and quadrupeds.
Webster’s Revised Unabridged Dictionary
Introduction
This article presents steps required to migrate Windows service applications (also called NT services) written using Visual C++ 6.0 and ATL to Visual C++.NET. Most of ATL and MFC code requires only recompilation when upgrading Visual Studio from version 6.0 to .NET. Unfortunately, this is not the case with ATL service applications. Part of ATL7 that handles Windows services has been rewritten, so most of the code that ATL Wizard generated in Visual C++ 6.0 is no longer needed – actually, the code is still in use, but it has been moved to ATL classes rather than being copied by Wizard to every service application you create. The downside of this improvement is that it breaks applications that were written using ATL3 (the version that came with Visual C++ 6.0).
So what should you do if you have a service application that no longer works after rebuilding with Visual C++ .NET? You have three main options:
- Rewrite the whole application in C# (hey, isn’t it cool?)
- Keep a copy of Visual C++ 6.0 – just to maintain you service applications.
- Make necessary changes to make your code compatible with ATL7.
First option is not as attractive as it may look at first glance. Windows service applications are often written to manage low-level resources, including hardware. Its source code is frequently dependent on third party libraries – usually "C" libraries. Such applications need to be debugged very thoroughly (comparing to UI programs), and once the code is stable, it usually lasts long.
The second option is the cheapest one in terms of team resource allocation, but it is kind of cheap in other terms too. Doesn’t it irritate you as a developer if you need to give up with a problem (probably a minor one) instead of fixing it?
So we’ll take the third approach: we'll fix it! As you are going to see, this choice will not only correct program behaviour, it will also improve and simplify the source code.
Phase 1. The meaning of life
Yes, you’ve finally found the meaning of life, and you want to tell the rest of the world about it. Perhaps, those who have read Douglas Adams’ books already know the answer (42, remember?), but at you find their number insufficient. So you build a simple Windows service application using Visual C++ 6.0 and ATL. It takes just a few minutes, and your service is up. I have included a Visual C++ 6.0 workspace that contains ATL service source code and a test command-line application. Here are the highlights of the project:
-
The service is called
MyService1
. Use commands"net start MyService1"
and"net stop MyService1"
to start and stop the service from a command line prompt, or you can use Control Panel "Services" applet. -
Before you can start the service, you should register it by issuing a command
"MyService1 /Service"
(when ATL Wizard generates service code, it does not include service registration in custom build step). -
The application also includes an interface (
IMeaningOfLife1
) and its implementation in a coclassMeaningOfLife1
. The only method (read-only property) that the interface has isMeaningOfLife
, and of course it returns 42 as it should. - Since the application does not have thread affinity, it’s implemented as free-threaded.
The project code for this sample service is included with this article (project
MyService1
).
Phase 2. Problem
Your service works very well, there are thousands of people who find the meaning
of life every day, and you have new ideas (perhaps, less philosophical, but
this time giving you a sign of income). You’re hungry for new technology, and
you love .NET from the first sight. So you install Visual Studio.NET, and
recompile all your projects. When you are about to free some disk space and
consider Visual Studio 6.0 as a best candidate for such purpose, you discover
that there’s one application that did not survive tools upgrade: your famous MyService1
that serves MeaningOfLife
to the masses. You would like to debug
the application, but can’t even start it as a service! And when you connect to
MeaningOfLife
component from a test client, it works just fine.
Something is definitely broken at service level.
Phase 3. Analysis
ATL3 (which was shipped with Visual Studio 6.0) had full support for Windows services, but service handling code really lacked encapsulation. If you ran ATL Wizard and generated service code, you could discover the following:
-
Header file
"stdafx.h"
did not only include necessary system headers, it also contained full declaration for a classCServiceModule
that was (and had to be!) inserted between references to"atlbase.h"
and"atlcom.h"
. Not the best place to declare new classes! -
The main service C++ file contained of more than 400 hundred lines of code that
implemented
CServiceModule
functions. You wouldn’t be changing most of these functions even if you needed to customize the service (why would you need to changeFindOneOf
,Lock
orLogEvent
?) What you would typically need is to be able to add custom steps to service initialization and termination. Unfortunately, you would have to modify directlyCServiceModule::Run()
. I wrote several Windows services using ATL3 and all of them had very similarCServiceModule
code, that differed only by class GUIDs and calls to custom initialization routines from insideCServiceModule::Run()
method.
The part of ATL7 that handles Window services have been completely redesigned in
Visual C++.NET. Similar to other ATL classes, service module has been turned
into a template CAtlServiceModule
with major functionality
implemented inline in ATL headers. What happens now if you leave your old code
unmodified, is that your service will contain an old version of CServiceModule
code (as generated by ATL Wizard), while ATL headers have its own – an improved
one. What becomes a real problem is that the new implementation is not
compatible with the old one. Fortunately, it’s easy to fix. Migrating your
Windows service code becomes more or less removal of hundreds of lines that ATL
Wizard used to inject into your project.
Phase 4. Solution
Now that we know what caused the problem and that it’s easy to fix (at least
according to the promise), let’s do it. I included a new version of MyService1
application (this time named MyService2
, so they can coexist). I
highlighted some of the changes that were necessary to make so the new version
could work. I also listed some simple changes that improve code quality.
-
Remove from
"stdafx.cpp"
the following code:#ifdef _ATL_STATIC_REGISTRY #include <statreg.h> #include <statreg.cpp> #endif
This step is not obligatory, it is just the code you no longer need.
-
Some of the functions in ATL registry classes have been deprecated (and
replaced with new ones that are strongly typed). You should replace calls to
SetValue
andQueryValue
withSetStringValue
andQueryStringValue
respectively.
-
Add a few new directives to
"stdafx.h"
:#define _ATL_NO_AUTOMATIC_NAMESPACE #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS #define _ATL_ALL_WARNINGS using namespace ATL;
-
Now the major step. Remove
CServiceModule
declaration from"stdafx.h"
. Let ATL7 use the most recent one!
-
Remove
CServiceModule
implementation from your service C++ file. This step requires some accuracy, because you have probably included custom service initialization and cleanup code (rememberCServiceModule::Run()
method). You should move this code to methodsCServiceModule::PreMessageLoop
andCServiceModule::PostMessageLoop
that ATL7 offers now. So your service module class declaration should be like this:class CServiceModule : public CAtlServiceModuleT<CServiceModule, IDS_SERVICENAME> { public: DECLARE_LIBID(LIBID_MYSERVICE2Lib) DECLARE_REGISTRY_APPID_RESOURCEID(IDR_MyService2, "{E3746A83-B411-4652-A929-0E9B3C0A05A3}") HRESULT PreMessageLoop(int nShowCmd); HRESULT PostMessageLoop(); };
And service initialization and cleanup routines (PreMessageLoop
andPostMessageLoop
) will contain whatever custom steps you need. -
You should start using new
OBJECT_ENTRY_AUTO
macro instead of traditional object maps (wrapped byBEGIN_OBJECT_MAP
andEND_OBJECT_MAP
). So remove the following code from your service implementation file ("MyService2.cpp"
):BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_MeaningOfLife2, CMeaningOfLife2) END_OBJECT_MAP()
And add new code to your coclass header (in our case"MeaningOfLife2.h"
):OBJECT_ENTRY_AUTO(__uuidof(MeaningOfLife2), CMeaningOfLife2)
-
Last but not least. Do you remember that your application is free-threaded?
That’s another catch: ATL7
CAtlExeModuleT::PreMessageLoop
has a bug that prevents service to create free-threaded components (when the application is launched as a service). I was not able to find a knowledge base article on this topic, but one Microsoft employee was kind enough to post a workaround in a newsgroup (search Google forPreMessageLoop
andCoResumeClassObjects
). Finally yourPreMessageLoop
/PostMessageLoop
methods should look like this:HRESULT CServiceModule::PreMessageLoop(int nShowCmd) { HRESULT hr = __super::PreMessageLoop(nShowCmd); #if _ATL_VER == 0x0700 if (SUCCEEDED(hr) && !m_bDelayShutdown) hr = CoResumeClassObjects(); #endif if (SUCCEEDED(hr)) { // Add any custom code to initialize your service } return hr; } HRESULT CServiceModule::PostMessageLoop() { // Add any custom code to free your service resources return __super::PostMessageLoop(); }
If you now rebuild your service (and register it using "MyService2 /Service"
command), it will successfully start and stop. Ironically, if you then test MeaningOfLife
component using supplied client application, it will fail to instantiate it
throwing infamous 0x80080005
result code ("Server execution
failed"). Further research led me to additional changes:
Source code for updated (VC++.NET friendly) version of the sample Windows
service is included with this article (project MyService2
).
Phase 5. The meaning of attributed life
Let’s face it now. Your new code looks more elegant now – it is much shorter, it only implements application-specific functions, and – for those who appreciate it – it is object oriented. But since you already used ATL7 new features, maybe there’s something else you could use that would make your code even more compact and manageable. Yes, your code still inherits ATL3 source file structure, i.e. it contains:
- IDL file with interface and type library declaration;
- RGS files with duplicates of coclass, type library and service GUIDs;
- C++ header and implementation files with coclass and service class declaration and implementation (although radically reduced in size comparing with ATL3).
What’s wrong with this structure? Of course, this is not just number of files. What makes it harder to manage is that different files must include duplicate information (like interface methods and GUIDs). And while changing interface without updating its implementation will result in compiler error (and will be addressed), if you forget to update GUID value in all places it is included, you will end up in Registry hell that is not much different from DLL hell (hell is hell after all!)
Welcome to attributed programming! By adding ATL attributes you will make your code even more compact and manageable. Attributes are really simple – they provide functionality similar to macros in ATL3, but they are smarter. A macro only affects things in-place (i.e. wherever it is inserted), being a C++ creature it has no control over other parts of a program, even though several places need to be updated simultaneously. An attribute is not limited to a single piece of code – a single attribute can trigger generation of a source code, resources and registry script. Therefore use of attributes helps you write less code with less risk of making it inconsistent.
If you feel motivated for attribute programming, you can now make the following changes to your application:
-
Select project properties, highlight General configuration properties,
and for entry Use of ATL choose Static link to ATL.
-
Add
_ATL_ATTRIBUTES
to preprocessor definitions.
-
Select Embedded IDL in Linker category and specify name base for Merged
IDL Base File Name entry. For example, if you choose
_MyService.idl
, it will cause_MyService
to be used as a base name when generating proxy/stub and type library files.
-
Select MIDL category and choose Yes for Generate stubless proxies.
-
Remove from your project IDL file and coclass RGS file(s) – these files are no
longer needed! (Keep service RGS file as the only RGS file in your project).
-
Now modify your service RGS file, so it looks like this:
HKCR { NoRemove AppID { '%APPID%' = s 'MyService3' 'MyService3.EXE' { val AppID = s '%APPID%' } } }
As you can see, the file no longer contains AppID GUID value – it references it.
-
Edit project RC file and remove all references to type library (TLB) file and
obsolete RGS files.
-
Rewrite service class declaration, so it will use ATL attributes. This is how
your modified service class will look like:
[module(SERVICE, uuid="{3D87B677-0D7E-40ba-B0D4-D5D7C3B28BA6}", name="MyService3", helpstring="MyService3 1.0 Type Library", resource_name="IDS_SERVICENAME")] class CServiceModule { public: HRESULT PreMessageLoop(int nShowCmd); HRESULT PostMessageLoop(); };
Note thatCServiceModule
is no longer explicitly derived fromCAtlServiceModuleT
template (although implicit relationship is still there).
-
Remove
_tWinMain
definition. You won’t even need that!
-
Now rewrite your coclass code. First you need to declare the interface (since
IDL file is no longer part of the project):
[ object, uuid("5D5DFA33-7F6D-41b6-BC07-3CFF781B7D4C"), dual, helpstring("IMeaningOfLife3 Interface"), pointer_default(unique) ] __interface IMeaningOfLife3 : IDispatch { [propget, id(1), helpstring("property MeaningOfLife")] HRESULT MeaningOfLife([out, retval] LONG* pVal); };
Then update corresponding coclass declaration:[ coclass, threading("neutral"), vi_progid("MyService3.MeaningOfLife3"), progid("MyService3.MeaningOfLife3.1"), version(1.0), uuid("F5602F4B-00D6-445b-A34F-86F950CE2917"), helpstring("MeaningOfLife3 Class") ] class ATL_NO_VTABLE CMeaningOfLife3 : public IMeaningOfLife3 { public: CMeaningOfLife3() { } DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct() { return S_OK; } void FinalRelease() { } public: STDMETHOD(get_MeaningOfLife)(/*[out, retval]*/ long *pVal); };
You should also removeOBJECT_ENTRY_AUTO
line – attributes will take care of it.
Conclusion
Our initial effort was made simply to fix problems with incompatibility between ATL3 and ATL7 Windows service applications. However, use of new ATL features also helped us to reduce both the number of project files and its code size. If we count only significant project files (leaving out auto-generated that don't require manual maintenance), we'll get the following figures:
-
MyService1
(ATL3): 10 files, 19851 bytes -
MyService2
(ATL7, no attributes): 10 files, 9485 bytes -
MyService3
(ATL7, with attributes): 8 files, 6987 bytes
Even though we had to deal with ATL incompatibility issues in our Window service application, the final result was worth the effort. ATL7 takes from you most of the job of keeping your code consistent and readable, so you can save your creativity for more important tasks.
References
- The Hitchhiker's Guide to the Galaxy, by Douglas Adams, Ballantine Books. ISBN: 0345391802.
- Developing Applications with Visual Studio .NET, by Richard Grimes. Addison Wesley Professional. ISBN: 0201708523.
- MSDN Library