none
使用Visual Studio .net 2008创建ATL Service的问题 RRS feed

  • 问题

  • 各位好!

    我原先使用VS2005,

    先建立一个ATL Service(ATL Project -> Service(exe))

    然后追加一个ATL Single Object(加入ConnectionPoint,别的都默认)

    最后制作一个MFC的普通exe去调用ATL Service里面的ATL Single Object(#import,CComPtr<>::CoCreateInstance等等)

    很顺利。

    ATL Service不启动的时候,启动exe,Service会自动启动。

     

    可是这样的过程在VS2008时就遇到了不可思议的问题:

    如果只是当作ActiveX exe注册的话(-regserver),普通exe(MFC)也很正常。

    一旦ATL Service作为Service注册(-service)后,普通exe(MFC)就无法使用Service了。

    不管ATL Service是否已经启动,打开普通exe后,CComPtr<>::CoCreateInstance必然失败。(AfxOleInit我没有忘)

    更为奇怪的是,如果我将原来的ATL Service的源码升级到VS2008,那一切正常。(即,使用VS2005生成的代码很正常)

    补充:普通exe不管是使用VS2005,还是使用VS2008创建,结果一样。。。

    因此,我怀疑是ATL Service存在着什么问题。

    可是,将VS2005生成代码跟VS2008生成的比较后发现,几乎没有可能引起类似问题的差别存在。(包括atlbase.h)

     

    不知道有没有人遇到过类似的问题呢?

    还是说RTM版天生有缺陷……

    2007年12月30日 9:04

答案

  • 粘贴过来后,格式有点乱了,原文看这里:

    http://hi.baidu.com/yangw80/blog/item/b6b99fd7b56d7dd9a144dfb1.html

    http://yangwei.spaces.live.com/blog/cns!9C2BF7C11B42AE84!708.entry

     

    用 VC++ 2008 编写 Windows Service(系统服务)
    2008-03-30 08:08
     

    代码下载
    by 杨伟<yangwei@ligsoft.com>

    现在许多 Windows Service 应用都可以用 c# 很好的完成,不过毕竟是托管代码,性能上不及非托管的 VC。网上能找到的 VC 写系统服务的例子,多数都用的 VC6(ATL3.0)。ATL3.0 写系统服务也很不错,只是封装的不很完善,还需要用户写不少代码。ATL7.0 及后续带的系统服务的模板有了更新,用起来简单多了。本文以 ATL 9.0 为例。

    文章只是流水般的写了一下步骤。要再具体一些,恐怕就不是一篇文章能做到的了。市面上讲 COM 的书很多,不妨买来看看。

    下面就是用 Visual Studio 2008 中的 VC++ 2008 写系统服务的详细步骤(我用的 VC 是英文版的):

    1. 新建项目。

    1-1. 启动 Visual Studio 2008。选择 File -> New -> Project...。

    1-2. 在 New Project 对话框中,选择 Visual C++ 中的 ATL Project 模板,写入项目名称:ShowjiSvc,点 OK。

    1-3. 在打开的 ATL Project Wizard - ShowjiSvc 中,点 Application Settings,选择 Server type 为 Service(EXE),点 Finish。

    2. 安装、卸载服务。

    2-1. 编译项目。

    2-2. 打开命令提示符,切换到编译后的目录,执行以下命令安装服务:
    ShowjiSvc /Service

    2-3. 打开服务管理,能找到一个名为 ShowjiSvc 的服务,启动类型是 Manual。可以启动服务、停止服务。

    2-4. 停止服务,然后用以下命令卸载服务:
    ShowjiSvc /UnRegServer

    3. 修改服务的配置。

    3-1. 修改服务名称:
    在 Resource View 中打开 String Table,修改 IDS_SERVICENAME 的 Caption 为服务的名称:Showji Mobile Service。

    3-2. 修改服务的描述:
    打开 ShowjiSvc.cpp 文件,在 CShowjiSvcModule 的声明中增加以下函数声明:

      HRESULT RegisterAppId(bool bService);

    再增加此函数的定义:

    HRESULT CShowjiSvcModule::RegisterAppId(bool bService = false) throw()
    {
      HRESULT hr = S_OK;
      BOOL res = __super::RegisterAppId(bService);
      if (bService)
      {
        if (IsInstalled())
        {
          SC_HANDLE hSCM = :SurprisepenSCManagerW(NULL, NULL, SERVICE_CHANGE_CONFIG);
          SC_HANDLE hService = NULL;
          if (hSCM == NULL)
            hr = AtlHresultFromLastError();
          
    else
          {
            hService = :SurprisepenService(hSCM, m_szServiceName, SERVICE_CHANGE_CONFIG);
            if (hService != NULL)
            {
              ::ChangeServiceConfig(hService, SERVICE_NO_CHANGE,
                      SERVICE_AUTO_START,  //
    修改服务为自动启动
                      NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                      m_szServiceName); //
    通过修改资源IDS_SERVICENAME 修改服务的显示名字

              SERVICE_DESCRIPTION Description;
              TCHAR  szDescription[1024];
              ZeroMemory(szDescription, 1024);
              ZeroMemory(&Description, sizeof(SERVICE_DESCRIPTION));
              lstrcpy(szDescription, _T("www.showji.com
    示例服务,by yangwei@ligsoft.com"
    ));
              Description.lpDescription = szDescription;
              ::ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &Description);

              ::CloseServiceHandle(hService);
            }
            
    else
              hr = AtlHresultFromLastError();

            ::CloseServiceHandle(hSCM);
          }
        }
      }
      return hr;
    }

    3-3. 测试修改后的服务。
    重新注册服务,在服务管理器中可以看到服务的新名称和描述,并且启动状态已经修改为 Automatic。
    测试成功后,注销服务。

    4. 修改服务的进程安全设置。

    打开 ShowjiSvc.cpp,在 CShowjiSvcModule 的声明中,找到 InitializeSecurity 函数,修改如下:

      HRESULT InitializeSecurity() throw()
      {
        CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_NONE,
            RPC_C_IMP_LEVEL_IDENTIFY, NULL, EOAC_NONE, NULL );
        return S_OK;
      }

    要根据自己的情况,做相关的安全性设置。如果没有什么特殊的安全性要求,就按照上面的格式写就行了。

    如果没有设置进程安全性的代码,那么 VC 客户端创建对象的时候,会返回 E_ACCESSDENIED 的错误。
    VB 客户端的错误提示如下:
    实时错误 '70': 拒绝的权限(Run-time error '70': Permission denied)。

    5. 增加服务的初始化和释放操作。

    打开 ShowjiSvc.cpp,在 CShowjiSvcModule 的声明中,增加以下声明:

      HRESULT PreMessageLoop(int nShowCmd);
      HRESULT PostMessageLoop();

    增加以上声明的相关定义:

    HRESULT CShowjiSvcModule:Stick out tonguereMessageLoop(int nShowCmd) throw()
    {
      HRESULT hr = __super:Stick out tonguereMessageLoop(nShowCmd);

      if (SUCCEEDED(hr))
      {
       
    // Add any custom code to initialize your service
      }

       return hr;
    }



    HRESULT CShowjiSvcModule:Stick out tongueostMessageLoop() throw()
    {
       HRESULT hr = __super:Stick out tongueostMessageLoop();

       if (SUCCEEDED(hr))
       {
        
    // Add any custom code to uninitialize your service

       }

       return hr;
    }

    在相关的描述位置增加自己的代码即可。

    6. 为服务增加一个叫做 MyMath 的示例组件。

    6-1. 选中 ShowjiSvc 项目,点菜单 Project -> Add Class...,选择 ATL 中的 ATL Simple Object,点 Add。

    6-2. 在打开的 ATL Simple Object Wizard - ShowjiSvc 对话框的 Short name 中写入组件名称:MyMath。其它名称会自动填好(当然您也可以修改)。

    6-3. 在 Option 中可以看到许多选项。如果您搞不懂这些,就保留默认值吧(建议看一下 COM 的相关书籍)。

    6-4. 打开 MyMath.rgs,为组件增加服务程序的 ID(代码中的红色粗体部分):

    HKCR
    {
       ShowjiSvc.MyMath.1 = s 'MyMath Class'
       {
         CLSID = s '{37F47E87-7D33-43CD-B591-DA01023F90BC}'
       }
       ShowjiSvc.MyMath = s 'MyMath Class'
       {
         CLSID = s '{37F47E87-7D33-43CD-B591-DA01023F90BC}'
         CurVer = s 'ShowjiSvc.MyMath.1'
       }
       NoRemove CLSID
       {
         ForceRemove {37F47E87-7D33-43CD-B591-DA01023F90BC} = s 'MyMath Class'
         {
           ProgID = s 'ShowjiSvc.MyMath.1'
           VersionIndependentProgID = s 'ShowjiSvc.MyMath'
           ForceRemove 'Programmable'
           LocalServer32 = s '%MODULE%'
          
    val AppID = s '%APPID%'
           'TypeLib' = s '{55E58774-E86F-4482-A521-38AE8C85FD1D}'
         }
       }
    }

    AppID 必须设置,否则客户端创建对象的时候会超时并报错误:80080005 server execution failed。
    并且,系统日志中会出现一个错误:The server {uuid} did not register with DCOM within the required timeout.
    VB 客户端的错误提示如下:
    实时错误 '429': ActiveX 部件不能创建对象(Run-time error '429': ActiveX component can't create object)。

    7. 为 MyMath 组件增加一个方法。

    7-1. 在 Class View 中,右击 IMyMath,选择 Add -> Add Method...。

    7-2. 在打开的 Add Method Wizard - ShowjiSvc 对话框中,输入 Method name: Sum;
    Parameter type 选择 LONG,Parameter name 写 a,Parameter attributes 选择 in,点一下 Add;
    Parameter type 选择 LONG,Parameter name 写 b,Parameter attributes 选择 in,点一下 Add;
    Parameter type 选择 LONG*,Parameter name 写 s,Parameter attributes 选择 retval,点一下 Add;
    点 Finish 添加这个方法。

    7-3. 打开 MyMath.cpp 文件,找到 Sum 方法,修改如下:

    STDMETHODIMP CMyMath:Tongue Tiedum(LONG a, LONG b, LONG* s)
    {
       *s = a + b;
       return S_OK;
    }

    8. 编译、注册、启动服务。

    8-1. 编译代码。

    8-2. 注册服务,执行以下命令行代码:
    ShowjiSvc /Service

    8-3. 启动服务,执行以下命令行代码:
    net start "Showji Mobile Service"
    当然,也可以在服务管理器中启动服务。
    相对应的停止服务的命令是:
    net stop "Showji Mobile Service"

    9. 客户端访问测试(用 VB6 举例)。

    9-1. 打开 Visual Basic 6.0。

    9-2. 新建“标准 EXE”工程。

    9-3. 点菜单:工程 -> 引用...,找到 ShowjiSvc 1.0 Type Library 并勾选,点确定。

    9-4. 双击 Form1 窗体,进入代码模式,输入以下代码:

    Private Sub Form_Load()
      Dim math As ShowjiSvcLib.MyMath
      Set math = New ShowjiSvcLib.MyMath
      MsgBox math.Sum(10, 19)
      Set math = Nothing
    End Sub

    执行后可以看到,VB 正常初始化了系统服务中的 COM 对象,并调用了其中的方法。

    10. 部署。

    要将项目部署到其它电脑,需要安装 Microsoft Visual C++ 2008 Redistributable Package,否则会提示错误:系统无法执行指定的程序(The system cannot execute the specified program.)。
    Microsoft Visual C++ 2008 Redistributable Package 下载地址:
    x86 版:
    http://www.microsoft.com/downloads/details.aspx?FamilyID=9b2da534-3e03-4391-8a4d-074b9f2bc1bf
    x64 版:http://www.microsoft.com/downloads/details.aspx?FamilyID=bd2a6171-e2d6-4230-b809-9a8d7548c1b6

    11. 相关代码下载:
    http://www.ligsoft.com/members/yangwei/archive/ShowjiSvc.zip (22k)
    如果以上地址无效了,请给我发邮件:
    yangwei@ligsoft.com

    2008年4月6日 1:21

全部回复

  • 粘贴过来后,格式有点乱了,原文看这里:

    http://hi.baidu.com/yangw80/blog/item/b6b99fd7b56d7dd9a144dfb1.html

    http://yangwei.spaces.live.com/blog/cns!9C2BF7C11B42AE84!708.entry

     

    用 VC++ 2008 编写 Windows Service(系统服务)
    2008-03-30 08:08
     

    代码下载
    by 杨伟<yangwei@ligsoft.com>

    现在许多 Windows Service 应用都可以用 c# 很好的完成,不过毕竟是托管代码,性能上不及非托管的 VC。网上能找到的 VC 写系统服务的例子,多数都用的 VC6(ATL3.0)。ATL3.0 写系统服务也很不错,只是封装的不很完善,还需要用户写不少代码。ATL7.0 及后续带的系统服务的模板有了更新,用起来简单多了。本文以 ATL 9.0 为例。

    文章只是流水般的写了一下步骤。要再具体一些,恐怕就不是一篇文章能做到的了。市面上讲 COM 的书很多,不妨买来看看。

    下面就是用 Visual Studio 2008 中的 VC++ 2008 写系统服务的详细步骤(我用的 VC 是英文版的):

    1. 新建项目。

    1-1. 启动 Visual Studio 2008。选择 File -> New -> Project...。

    1-2. 在 New Project 对话框中,选择 Visual C++ 中的 ATL Project 模板,写入项目名称:ShowjiSvc,点 OK。

    1-3. 在打开的 ATL Project Wizard - ShowjiSvc 中,点 Application Settings,选择 Server type 为 Service(EXE),点 Finish。

    2. 安装、卸载服务。

    2-1. 编译项目。

    2-2. 打开命令提示符,切换到编译后的目录,执行以下命令安装服务:
    ShowjiSvc /Service

    2-3. 打开服务管理,能找到一个名为 ShowjiSvc 的服务,启动类型是 Manual。可以启动服务、停止服务。

    2-4. 停止服务,然后用以下命令卸载服务:
    ShowjiSvc /UnRegServer

    3. 修改服务的配置。

    3-1. 修改服务名称:
    在 Resource View 中打开 String Table,修改 IDS_SERVICENAME 的 Caption 为服务的名称:Showji Mobile Service。

    3-2. 修改服务的描述:
    打开 ShowjiSvc.cpp 文件,在 CShowjiSvcModule 的声明中增加以下函数声明:

      HRESULT RegisterAppId(bool bService);

    再增加此函数的定义:

    HRESULT CShowjiSvcModule::RegisterAppId(bool bService = false) throw()
    {
      HRESULT hr = S_OK;
      BOOL res = __super::RegisterAppId(bService);
      if (bService)
      {
        if (IsInstalled())
        {
          SC_HANDLE hSCM = :SurprisepenSCManagerW(NULL, NULL, SERVICE_CHANGE_CONFIG);
          SC_HANDLE hService = NULL;
          if (hSCM == NULL)
            hr = AtlHresultFromLastError();
          
    else
          {
            hService = :SurprisepenService(hSCM, m_szServiceName, SERVICE_CHANGE_CONFIG);
            if (hService != NULL)
            {
              ::ChangeServiceConfig(hService, SERVICE_NO_CHANGE,
                      SERVICE_AUTO_START,  //
    修改服务为自动启动
                      NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                      m_szServiceName); //
    通过修改资源IDS_SERVICENAME 修改服务的显示名字

              SERVICE_DESCRIPTION Description;
              TCHAR  szDescription[1024];
              ZeroMemory(szDescription, 1024);
              ZeroMemory(&Description, sizeof(SERVICE_DESCRIPTION));
              lstrcpy(szDescription, _T("www.showji.com
    示例服务,by yangwei@ligsoft.com"
    ));
              Description.lpDescription = szDescription;
              ::ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &Description);

              ::CloseServiceHandle(hService);
            }
            
    else
              hr = AtlHresultFromLastError();

            ::CloseServiceHandle(hSCM);
          }
        }
      }
      return hr;
    }

    3-3. 测试修改后的服务。
    重新注册服务,在服务管理器中可以看到服务的新名称和描述,并且启动状态已经修改为 Automatic。
    测试成功后,注销服务。

    4. 修改服务的进程安全设置。

    打开 ShowjiSvc.cpp,在 CShowjiSvcModule 的声明中,找到 InitializeSecurity 函数,修改如下:

      HRESULT InitializeSecurity() throw()
      {
        CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_NONE,
            RPC_C_IMP_LEVEL_IDENTIFY, NULL, EOAC_NONE, NULL );
        return S_OK;
      }

    要根据自己的情况,做相关的安全性设置。如果没有什么特殊的安全性要求,就按照上面的格式写就行了。

    如果没有设置进程安全性的代码,那么 VC 客户端创建对象的时候,会返回 E_ACCESSDENIED 的错误。
    VB 客户端的错误提示如下:
    实时错误 '70': 拒绝的权限(Run-time error '70': Permission denied)。

    5. 增加服务的初始化和释放操作。

    打开 ShowjiSvc.cpp,在 CShowjiSvcModule 的声明中,增加以下声明:

      HRESULT PreMessageLoop(int nShowCmd);
      HRESULT PostMessageLoop();

    增加以上声明的相关定义:

    HRESULT CShowjiSvcModule:Stick out tonguereMessageLoop(int nShowCmd) throw()
    {
      HRESULT hr = __super:Stick out tonguereMessageLoop(nShowCmd);

      if (SUCCEEDED(hr))
      {
       
    // Add any custom code to initialize your service
      }

       return hr;
    }



    HRESULT CShowjiSvcModule:Stick out tongueostMessageLoop() throw()
    {
       HRESULT hr = __super:Stick out tongueostMessageLoop();

       if (SUCCEEDED(hr))
       {
        
    // Add any custom code to uninitialize your service

       }

       return hr;
    }

    在相关的描述位置增加自己的代码即可。

    6. 为服务增加一个叫做 MyMath 的示例组件。

    6-1. 选中 ShowjiSvc 项目,点菜单 Project -> Add Class...,选择 ATL 中的 ATL Simple Object,点 Add。

    6-2. 在打开的 ATL Simple Object Wizard - ShowjiSvc 对话框的 Short name 中写入组件名称:MyMath。其它名称会自动填好(当然您也可以修改)。

    6-3. 在 Option 中可以看到许多选项。如果您搞不懂这些,就保留默认值吧(建议看一下 COM 的相关书籍)。

    6-4. 打开 MyMath.rgs,为组件增加服务程序的 ID(代码中的红色粗体部分):

    HKCR
    {
       ShowjiSvc.MyMath.1 = s 'MyMath Class'
       {
         CLSID = s '{37F47E87-7D33-43CD-B591-DA01023F90BC}'
       }
       ShowjiSvc.MyMath = s 'MyMath Class'
       {
         CLSID = s '{37F47E87-7D33-43CD-B591-DA01023F90BC}'
         CurVer = s 'ShowjiSvc.MyMath.1'
       }
       NoRemove CLSID
       {
         ForceRemove {37F47E87-7D33-43CD-B591-DA01023F90BC} = s 'MyMath Class'
         {
           ProgID = s 'ShowjiSvc.MyMath.1'
           VersionIndependentProgID = s 'ShowjiSvc.MyMath'
           ForceRemove 'Programmable'
           LocalServer32 = s '%MODULE%'
          
    val AppID = s '%APPID%'
           'TypeLib' = s '{55E58774-E86F-4482-A521-38AE8C85FD1D}'
         }
       }
    }

    AppID 必须设置,否则客户端创建对象的时候会超时并报错误:80080005 server execution failed。
    并且,系统日志中会出现一个错误:The server {uuid} did not register with DCOM within the required timeout.
    VB 客户端的错误提示如下:
    实时错误 '429': ActiveX 部件不能创建对象(Run-time error '429': ActiveX component can't create object)。

    7. 为 MyMath 组件增加一个方法。

    7-1. 在 Class View 中,右击 IMyMath,选择 Add -> Add Method...。

    7-2. 在打开的 Add Method Wizard - ShowjiSvc 对话框中,输入 Method name: Sum;
    Parameter type 选择 LONG,Parameter name 写 a,Parameter attributes 选择 in,点一下 Add;
    Parameter type 选择 LONG,Parameter name 写 b,Parameter attributes 选择 in,点一下 Add;
    Parameter type 选择 LONG*,Parameter name 写 s,Parameter attributes 选择 retval,点一下 Add;
    点 Finish 添加这个方法。

    7-3. 打开 MyMath.cpp 文件,找到 Sum 方法,修改如下:

    STDMETHODIMP CMyMath:Tongue Tiedum(LONG a, LONG b, LONG* s)
    {
       *s = a + b;
       return S_OK;
    }

    8. 编译、注册、启动服务。

    8-1. 编译代码。

    8-2. 注册服务,执行以下命令行代码:
    ShowjiSvc /Service

    8-3. 启动服务,执行以下命令行代码:
    net start "Showji Mobile Service"
    当然,也可以在服务管理器中启动服务。
    相对应的停止服务的命令是:
    net stop "Showji Mobile Service"

    9. 客户端访问测试(用 VB6 举例)。

    9-1. 打开 Visual Basic 6.0。

    9-2. 新建“标准 EXE”工程。

    9-3. 点菜单:工程 -> 引用...,找到 ShowjiSvc 1.0 Type Library 并勾选,点确定。

    9-4. 双击 Form1 窗体,进入代码模式,输入以下代码:

    Private Sub Form_Load()
      Dim math As ShowjiSvcLib.MyMath
      Set math = New ShowjiSvcLib.MyMath
      MsgBox math.Sum(10, 19)
      Set math = Nothing
    End Sub

    执行后可以看到,VB 正常初始化了系统服务中的 COM 对象,并调用了其中的方法。

    10. 部署。

    要将项目部署到其它电脑,需要安装 Microsoft Visual C++ 2008 Redistributable Package,否则会提示错误:系统无法执行指定的程序(The system cannot execute the specified program.)。
    Microsoft Visual C++ 2008 Redistributable Package 下载地址:
    x86 版:
    http://www.microsoft.com/downloads/details.aspx?FamilyID=9b2da534-3e03-4391-8a4d-074b9f2bc1bf
    x64 版:http://www.microsoft.com/downloads/details.aspx?FamilyID=bd2a6171-e2d6-4230-b809-9a8d7548c1b6

    11. 相关代码下载:
    http://www.ligsoft.com/members/yangwei/archive/ShowjiSvc.zip (22k)
    如果以上地址无效了,请给我发邮件:
    yangwei@ligsoft.com

    2008年4月6日 1:21
  • 我是个菜鸟,慢慢看吧!
    2008年4月7日 14:42
  • Hi Yang Wei,

     

    I am learning your ATL service sample, thanks.

    Do you have any C++ client program to call MyMath?

    My client code does not seem to trigger any service code, however no error returned.

     

    Thanks

     

    -Gary

    2011年8月12日 8:14