Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / ATL
Article

Migrating ATL service applications to Visual C++.NET

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
30 Jul 200211 min read 246.8K   1.6K   53   47
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:

  1. Rewrite the whole application in C# (hey, isn’t it cool?)
  2. Keep a copy of Visual C++ 6.0 – just to maintain you service applications.
  3. 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.

  1. 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.

  2. 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.

  3. 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;
  4. Now the major step. Remove CServiceModule declaration from "stdafx.h". Let ATL7 use the most recent one!

  5. 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.
  6. 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:

  7. 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)
  8. 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))
        {
            // 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();
    }

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:

  1. Select project properties, highlight General configuration properties, and for entry Use of ATL choose Static link to ATL.

  2. Add _ATL_ATTRIBUTES to preprocessor definitions.

  3. 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.

  4. Select MIDL category and choose Yes for Generate stubless proxies.

  5. 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).

  6. 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.

  7. Edit project RC file and remove all references to type library (TLB) file and obsolete RGS files.

  8. 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).

  9. Remove _tWinMain definition. You won’t even need that!

  10. 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 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

  1. The Hitchhiker's Guide to the Galaxy, by Douglas Adams, Ballantine Books. ISBN: 0345391802.
  2. Developing Applications with Visual Studio .NET, by Richard Grimes. Addison Wesley Professional. ISBN: 0201708523.
  3. MSDN Library

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


Written By
Architect Miles AS
Norway Norway
Vagif Abilov is a Russian/Norwegian software developer and architect working for Miles. He has more than twenty years of programming experience that includes various programming languages, currently using mostly C# and F#.

Vagif writes articles and speaks at user group sessions and conferences. He is a contributor and maintainer of several open source projects, including Simple.Data OData adapter, Simple.OData.Client and MongOData.

Comments and Discussions

 
QuestionSimple work around Pin
thrishulh16-Sep-16 0:13
thrishulh16-Sep-16 0:13 
GeneralVC++ 2008 - CAtlServiceModuleT E_ACCESSDENIED Pin
rflores22-Sep-09 19:29
rflores22-Sep-09 19:29 
Hi Vagif,

I'm try to migrate to .NET 3.5 (Visual Studio 2008), but my system is giving me a E_ACCESSDENIED when AtlServiceModule try to execute "RegisterAppId".

I read in msdn, internet and nothing about this error. I'm using Windows 7 now. May be WM7?. I believe that all problem are security gratts, but where???, or how??, My User is Administrator.

is the first time that happend to me and can't find information to help me, could you help me with the error?, please.

Thks
GeneralRe: VC++ 2008 - CAtlServiceModuleT E_ACCESSDENIED Pin
rflores22-Sep-09 20:21
rflores22-Sep-09 20:21 
Generalthis is what puzzle me,thanks Pin
degestation14-Jul-08 15:43
degestation14-Jul-08 15:43 
QuestionProblem when stopping my service Pin
qds_moi19-Apr-06 23:39
qds_moi19-Apr-06 23:39 
GeneralControl Window service from MFC Pin
jl2704085-Jul-05 18:16
jl2704085-Jul-05 18:16 
GeneralRe: Control Window service from MFC Pin
lavadiablo3-Jan-06 19:52
lavadiablo3-Jan-06 19:52 
Generala very good article. i just have a little question Pin
winwnx25-Mar-05 6:05
winwnx25-Mar-05 6:05 
Generalwhy ATL 7 dll don't regsvr32.exe in no VC 7 os Pin
Eureka Jim8-Oct-04 4:46
Eureka Jim8-Oct-04 4:46 
GeneralRe: why ATL 7 dll don't regsvr32.exe in no VC 7 os Pin
byjack1313-Dec-05 20:29
byjack1313-Dec-05 20:29 
Questionhow to make installation of service Pin
shashi_don11-Sep-04 22:29
shashi_don11-Sep-04 22:29 
AnswerRe: how to make installation of service Pin
Vagif Abilov12-Sep-04 19:37
professionalVagif Abilov12-Sep-04 19:37 
GeneralRe: how to make installation of service Pin
shashi_don12-Sep-04 21:28
shashi_don12-Sep-04 21:28 
GeneralSecurity: E_ACCESSDENIED Pin
christian721-Sep-04 4:06
christian721-Sep-04 4:06 
GeneralRe: Security: E_ACCESSDENIED Pin
shashi_don13-Sep-04 0:23
shashi_don13-Sep-04 0:23 
GeneralGood ....but with tiny problem Pin
voland28-Apr-04 10:12
voland28-Apr-04 10:12 
Generalexcellent work, but struggling w/ parameters Pin
eosterm18-Feb-04 8:17
eosterm18-Feb-04 8:17 
GeneralProblems when adding MFC support Pin
bonakdar11-Feb-04 11:46
bonakdar11-Feb-04 11:46 
GeneralRe: The cure for Problems when adding MFC support Pin
bonakdar10-Mar-04 11:19
bonakdar10-Mar-04 11:19 
GeneralRe: The cure for Problems when adding MFC support Pin
Timothy A. Anderson29-Nov-04 21:18
Timothy A. Anderson29-Nov-04 21:18 
GeneralRe: Problems when adding MFC support Pin
Sygnosys2-Feb-05 7:09
Sygnosys2-Feb-05 7:09 
GeneralInitializeSecurity is missing for Visual Studio .NET 2003 Pin
Patrice Monroe15-Jan-04 23:44
Patrice Monroe15-Jan-04 23:44 
GeneralRe: InitializeSecurity is missing for Visual Studio .NET 2003 Pin
Patrice Monroe20-Jan-04 0:33
Patrice Monroe20-Jan-04 0:33 
GeneralRe: InitializeSecurity is missing for Visual Studio .NET 2003 Pin
Midi_Mick24-Oct-05 20:10
professionalMidi_Mick24-Oct-05 20:10 
GeneralChangeServiceConfig2 Pin
Inka5-Nov-03 1:30
Inka5-Nov-03 1:30 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.