2024年1月21日发(作者:)
1. 新建项目。
1-1. 启动 Visual Studio 2008。选择 File -> New -> 。
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. 修改服务的描述:
打开 文件,在 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 = ::OpenSCManagerW(NULL, NULL, SERVICE_CHANGE_CONFIG);
SC_HANDLE hService = NULL;
if (hSCM == NULL)
hr = AtlHresultFromLastError();
else
{
hService = ::OpenService(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(" 示例服务,by yangwei@"));
ription = szDescription;
::ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION,
&Description);
::CloseServiceHandle(hService);
}
else
hr = AtlHresultFromLastError();
::CloseServiceHandle(hSCM);
}
}
}
return hr;
}
3-3. 测试修改后的服务。
重新注册服务,在服务管理器中可以看到服务的新名称和描述,并且启动状态已经修改为 Automatic。
测试成功后,注销服务。
4. 修改服务的进程安全设置。
打开 ,在 CShowjiSvcModule 的声明中,找到 InitializeSecurity 函数,修改如下:
HRESULT InitializeSecurity() throw()
{
return CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_NONE,
RPC_C_IMP_LEVEL_IDENTIFY, NULL, EOAC_NONE, NULL );
}
要根据自己的情况,做相关的安全性设置。如果没有什么特殊的安全性要求,就按照上面的格式写就行了。
如果没有设置进程安全性的代码,那么 VC 客户端创建对象的时候,会返回 E_ACCESSDENIED 的错误。
VB 客户端的错误提示如下:
实时错误 '70': 拒绝的权限(Run-time error '70': Permission denied)。
5. 增加服务的初始化和释放操作。
打开 ,在 CShowjiSvcModule 的声明中,增加以下声明:
HRESULT PreMessageLoop(int nShowCmd);
HRESULT PostMessageLoop();
增加以上声明的相关定义:
HRESULT CShowjiSvcModule::PreMessageLoop(int nShowCmd) throw()
{
HRESULT hr = __super::PreMessageLoop(nShowCmd);
if (SUCCEEDED(hr))
{
// Add any custom code to initialize your service
}
return hr;
}
HRESULT CShowjiSvcModule::PostMessageLoop() throw()
{
HRESULT hr = __super::PostMessageLoop();
if (SUCCEEDED(hr))
{
// Add any custom code to uninitialize your service
}
return hr;
}
在相关的描述位置增加自己的代码即可。
6. 为服务增加一个叫做 MyMath 的示例组件。
6-1. 选中 ShowjiSvc 项目,点菜单 Project -> ,选择 ATL 中的 ATL Simple Object,点 Add。
6-2. 在打开的 ATL Simple Object Wizard - ShowjiSvc 对话框的 Short name 中写入组件名称:MyMath。其它名称会自动填好(当然您也可以修改)。
6-3. 在 Option 中可以看到许多选项。如果您搞不懂这些,就保留默认值吧(建议看一下 COM 的相关书籍)。
6-4. 打开 ,为组件增加服务程序的 ID(代码中的红色粗体部分):
HKCR
{
.1 = s 'MyMath Class'
{
CLSID = s '{37F47E87-7D33-43CD-B591-DA01023F90BC}'
}
= s 'MyMath Class'
{
CLSID = s '{37F47E87-7D33-43CD-B591-DA01023F90BC}'
CurVer = s '.1'
}
NoRemove CLSID
{
ForceRemove {37F47E87-7D33-43CD-B591-DA01023F90BC} = s 'MyMath Class'
{
ProgID = s '.1'
VersionIndependentProgID = s ''
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 -> 。
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. 打开 文件,找到 Sum 方法,修改如下:
STDMETHODIMP CMyMath::Sum(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
Set math = New
MsgBox (10, 19)
Set math = Nothing
End Sub
执行后可以看到,VB 正常初始化了系统服务中的 COM 对象,并调用了其中的方法。
10. 部署。
要将项目部署到其它电脑,需要安装 Microsoft Visual C++ 2008 Redistributable Package,否则会提示错误:系统无法执行指定的程序(The system cannot execute the specified program.)。
安装“-service”的,卸载用“-UnRegServer”
下面介绍使用VC++开发Windows服务程序。
首先运行VC++6,选择新建工程,在出现的下面窗口中选择”ATL COM AppWizard“,并选择工程放置位置和相应的工程名,然后选择确定按钮。
此时出现如下图窗口界面,在此界面中选择”服务(EXE)“,然后选择完成按钮。
在接下来的窗口中选择确定按钮。
则VC完成向导并生成相应代码(效果如下)。
程序的进入点是全局函数_tWinMain, 仔细看一下这个函数,我们会发现当我们运行程序时,可以加上参数,例如: winsvr /RegServer 或者 winsvr -RegServer,这个是用来本地服务器注册(Register as Local S
Register as Service erver)。
其中winsvr / Service 或者 winsvr -Service,这个是服务的注册(Register as Service);winsvr
/UnRegServer 或者 winsvr -UnRegServer ,这个是服务的删除。
所以,当我们写好了服务程序,只要运行的时候加上参数 Service ,这个时候在SCM中就会看到我们的服务了。
每次编码后测试都要在命令行中加参数运行服务才可以在SCM中列出来这样很麻烦,因此可以采用如下方式来处理:选择VC IDE的菜单工程 -> 设置, 再选择自定义组建面板(如下图所示)
在"$(TargetPath)" /RegServer的下面加上:"$(TargetPath)" /Service,这样当我们每次编码后编译程序,就不用再在命令行中去加参数执行我们的服务程序完成服务的注册了。
同时通过界面我们也看到,向导为我们建立了一个类:CServiceModule,全局变量_Module就是这个类的实例。
Init():这个函数用于完成一些初始化工作;
Run():这个函数就是服务开始运行后的内容,我们接下来要修改的内容也就是从这里入手。
Install():有如下一段代码
SC_HANDLE hService = ::CreateService(
hSCM, m_szServiceName, m_szServiceName,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
szFilePath, NULL, NULL, _T("RPCSS0"), NULL, NULL);
注意:如果服务中启动的程序具有窗口(即具有交互功能则要求使用如下代码)
SC_HANDLE hService = ::CreateService(
hSCM, m_szServiceName, m_szServiceName,
SERVICE_ALL_ACCESS, SERVICE_WIN32_SHARE_PROCESS |
SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
szFilePath, NULL, NULL, _T("RPCSS0"), NULL, NULL);
这个CreateService函数原先如下:
SC_HANDLE CreateService(
SC_HANDLE hSCManager, // handle to SCM database
LPCTSTR lpServiceName, // name of service to start
LPCTSTR lpDisplayName, // display name
DWORD dwDesiredAccess, // type of access to service
DWORD dwServiceType, // type of service
DWORD dwStartType, // when to start service
DWORD dwErrorControl, // severity of service failure
LPCTSTR lpBinaryPathName, // name of binary file
LPCTSTR lpLoadOrderGroup, // name of load ordering group
LPDWORD lpdwTagId, // tag identifier
LPCTSTR lpDependencies, // array of dependency names
LPCTSTR lpServiceStartName, // account name
LPCTSTR lpPassword // account password
);
第六个参数是服务的启动类型。
SERVICE_DEMAND_START是手动启动,SERVICE_AUTO_START是自动启动。
第十一个参数是服务的依存关系,比如说服务的启动想要依存SQL Server的启动,那我们可以把这个参数写成:
_T("MSSQLSERVER0");
如果我们写的服务不依存于其他的任何服务,那我们就将此参数设置为NULL就可以了。
接下来我们实现我们需要实现的业务。
首先,我们在类CServiceModule中找到Run函数,并在Run函数中找到以下代码:
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
DispatchMessage(&msg);
并在此代码前加入自己的代码,我这里加入的代码:CustomFunc1()。
这里对应不同应用有不同写法:
1、如果要启动一个窗口进行交互,则代码如下:
定义两个成员函数CustomFunc1和CustomFunc2
void CServiceModule::CustomFunc1()
{
TCHAR szFilePath[MAX_PATH + 1];
GetModuleFileName(NULL, szFilePath, MAX_PATH);
(_tcsrchr(szFilePath, _T('')))[1] = 0; //删除文件名,只获得路径
CString str_url = szFilePath;
str_url=str_url+"";
LogEvent("运行程序:"+str_url);
UINT rtn=WinExec(str_url,SW_SHOW );
//UINT rtn=WinExec("c:",SW_SHOWNORMAL);
if(rtn>31){
LogEvent("调用成功");
}
else{
if(rtn==0){
LogEvent("内存不足");
}
else{
if(rtn==ERROR_BAD_FORMAT){ //ERROR_BAD_FORMAT = 11
LogEvent("EXE 文件无效");
}
else{
if(rtn==ERROR_FILE_NOT_FOUND){ //ERROR_FILE_NOT_FOUND = 2
LogEvent("文件名错误");
}
else{
if(rtn==ERROR_PATH_NOT_FOUND){ //ERROR_PATH_NOT_FOUND = 3
LogEvent("路径名错误");
}
else{
LogEvent("发生其它错误");
}
}
}
}
}
}
BOOL CServiceModule::CustomFunc2()
{
HDESK hdeskCurrent;
HDESK hdesk;
HWINSTA hwinstaCurrent;
HWINSTA hwinsta;
hwinstaCurrent = GetProcessWindowStation();
if (hwinstaCurrent == NULL){
LogEvent(_T("get window station err"));
return FALSE;
}
hdeskCurrent = GetThreadDesktop(GetCurrentThreadId());
if (hdeskCurrent == NULL){
LogEvent(_T("get window desktop err"));
return FALSE;
}
//打开winsta0
hwinsta = OpenWindowStation("winsta0", FALSE,
WINSTA_ACCESSCLIPBOARD |
WINSTA_ACCESSGLOBALATOMS |
WINSTA_CREATEDESKTOP |
WINSTA_ENUMDESKTOPS |
WINSTA_ENUMERATE |
WINSTA_EXITWINDOWS |
WINSTA_READATTRIBUTES |
WINSTA_READSCREEN |
WINSTA_WRITEATTRIBUTES);
if (hwinsta == NULL){
LogEvent(_T("open window station err"));
return FALSE;
}
if (!SetProcessWindowStation(hwinsta)){
LogEvent(_T("Set window station err"));
return FALSE;
}
//打开desktop
hdesk = OpenDesktop("default", 0, FALSE,
DESKTOP_CREATEMENU |
DESKTOP_CREATEWINDOW |
DESKTOP_ENUMERATE |
DESKTOP_HOOKCONTROL |
DESKTOP_JOURNALPLAYBACK |
DESKTOP_JOURNALRECORD |
DESKTOP_READOBJECTS |
DESKTOP_SWITCHDESKTOP |
DESKTOP_WRITEOBJECTS);
if (hdesk == NULL){
LogEvent(_T("Open desktop err"));
return FALSE;
}
SetThreadDesktop(hdesk);
CustomFunc1();
if (!SetProcessWindowStation(hwinstaCurrent))
return FALSE;
if (!SetThreadDesktop(hdeskCurrent))
return FALSE;
if (!CloseWindowStation(hwinsta))
return FALSE;
if (!CloseDesktop(hdesk))
return FALSE;
return TRUE;
}
2、无交互窗口,则代码如下:
void CServiceModule::CustomFunc1(){
LogEvent(_T("Custorm Function Invoked")); //这里可以改成任何你需要的代码,当然不能有显示窗口之内的代码,要显示窗口之内代码请采用上面方法1。
}
现在可以编译并执行程序了。
此时会编译时会报告一个错误:'CString' : undeclared identifier。
这时需要查看工程的一些设置:
菜单工程->设置 ,常规面板,默认的设置是:使用MFC作为静态连接库。如果是这个设置则做如下工作:
然后我们打开StdAfx.h文件,并找到#include
如果需要修改出现在scm中的服务名,可以在工程中找到资源文件中的IDS_SERVICENAME项的内容就可以了。
注意:编译好了的程序需要在控制台中输入如下指令
1、注册服务
winsvr /regserver
winsvr /service //此条指令十分重要,如果不执行则在scm中是看不到此服务的
2、注销服务
winsvr /unregserver


发布评论