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 coclass MeaningOfLife1
. The only method
(read-only property) that the interface has is MeaningOfLife
, 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 class CServiceModule
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 change FindOneOf
, Lock
or LogEvent
?)
What you would typically need is to be able to add custom steps to service
initialization and termination. Unfortunately, you would have to modify
directly CServiceModule::Run()
. I wrote several Windows services
using ATL3 and all of them had very similar CServiceModule
code,
that differed only by class GUIDs and calls to custom initialization routines
from inside CServiceModule::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
and QueryValue
with SetStringValue
and QueryStringValue
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 (remember CServiceModule::Run()
method). You should move this code to methods CServiceModule::PreMessageLoop
and CServiceModule::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
and
PostMessageLoop
) will contain whatever custom steps you need.
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:
-
You should start using new
OBJECT_ENTRY_AUTO
macro instead of
traditional object maps (wrapped by BEGIN_OBJECT_MAP
and END_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 for PreMessageLoop
and CoResumeClassObjects
). Finally your PreMessageLoop
/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))
{
}
return hr;
}
HRESULT CServiceModule::PostMessageLoop()
{
return __super::PostMessageLoop();
}
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 that CServiceModule
is no longer explicitly derived from CAtlServiceModuleT
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)( long *pVal);
};
You should also remove OBJECT_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