基于Svchost的服务介绍

windows 系统服务分为独立进程和共享进程两种:

A、独立进程服务,一般都是一个EXE文件,里面有WinMain、ServiceMain和MessageHandler,用于安装、卸载、处理服务消息、提供服务等。
比如:
Windows Search
C:\Windows\system32\SearchIndexer.exe /Embedding
Windows Installer
C:\Windows\system32\msiexec.exe /V

B、共享进程服务,一般都是一个DLL文件,里面有DllMain、ServiceMain和MessageHandler,用于处理服务消息、提供服务。共享进程服务,一般用Svchost作为宿主进程,宿主进程只是一个框架,该进程通过启动参数+注册表定位到服务DLL,通过调用DLL中的函数,来实现业务逻辑。
比如:
Background Intelligent Transfer Service的启动参数为
C:\Windows\System32\svchost.exe -k netsvcs
Windows Firewall的启动参数为
C:\Windows\system32\svchost.exe -k LocalServiceNoNetwork

独立进程都会指定是哪个EXE,但svchost是如何知道启动netsvcs或LocalServiceNoNetwork要用哪个DLL呢?答案是在注册表中:
比如Background Intelligent Transfer Service的服务名为BITS,在下面路径中
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\BITS\Parameters\ServiceDll
指定了调用的dll为%SystemRoot%\System32\qmgr.dll
Windows Firewall的服务名为MpsSvc,在下面路径中
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\MpsSvc\Parameters\ServiceDll
指定了调用的dll为%SystemRoot%\system32\mpssvc.dll

既然这些服务是使用共享进程方式由svchost启动的,为什么系统中会有多个svchost进程呢?微软把这些服务进行了分组,同组服务共享一个svchost进程,不同组服务使用多个svchost进程,组的区别是由服务的可执行程序后边的参数决定的。
因此Background Intelligent Transfer Service的分组为netsvcs,Windows Firewall的分组为LocalServiceNoNetwork,这两个服务属于两个组,在两个不同的svchost进程中。

可以在下面的位置,找到svchost的所有组和组内的所有服务:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost
在我的Win7系统中,有如下分组:
AxInstSVGroup, DcomLaunch, LocalService, LocalServiceAndNoImpersonation, LocalServiceNetworkRestricted, LocalServiceNoNetwork, LocalServicePeerNet, LocalSystemNetworkRestricted, NetworkService, NetworkServiceAndNoImpersonation, NetworkServiceNetworkRestricted, PeerDist, RPCSS, WbioSvcGroup, WerSvcGroup, apphost, bthsvcs, defragsvc, iissvcs, imgsvc, netsvcs, regsvc, sdrsvc, secsvcs, swprv, termsvcs, wcssvc
而在netsvcs分组下,有
AeLookupSvc, AppMgmt, AudioSrv, BITS, CertPropSvc, FastUserSwitchingCompatibility, Ias, Irmon, LogonHours, NWCWorkstation, Nla, Ntmssvc, Nwsapagent, PCAudit, Rasauto, Rasman, Remoteaccess, SCPolicySvc, SENS, SRService, SessionEnv, Sharedaccess, ShellHWDetection, Tapisrv, TermService, WmdmPmSp, Wmi, gpsvc, helpsvc, iphlpsvc, lanmanserver, msiscsi, schedule, uploadmgr, winmgmt, wuauserv

这样,服务启动时,ServiceManager会查看该分组的svchost的进程是否启动,如果已经启动,则不需要新开启svchost进程,只需要直接加载服务即可。

那如何增加一个新的分组,和一个新的服务呢?那就需要修改注册表啦:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost]
"NeoTest"=hex(7):53,00,68,00,54,00,73,00,74,00,00,00,00,00

重启后才会有效。
调试的时候,应该自己新建一个分类,否则有了问题,将其他系统服务搞挂就不好了。
自己新建分类,不爽的时候,可以直接杀掉改svchost进程。

PS:在XP时代的头几年,伪装Svchost进程的恶意软件还是很多的,但现在杀毒软件都会重点处理这块儿内容,也就慢慢淡出了。

svchost一般来说,运行的都是windows内部服务,不给第三方开放。
但如果我们要自己写一个这样的服务,那实现这样的服务需要什么呢?
1、Dll入口函数:DllMain,dll入口函数
2、服务入口函数:ServiceMain,Service入口函数
3、rundll32安装服务函数:RundllInstall,生成注册表信息
4、rundll32卸载服务函数:RundllUninstall,删除注册表信息
5、将自己添加为一个新的svchost服务

比如下面的例子,将自己注册为一个NeoTest的SvchostTest服务:
Win7+VS2012u4+Unicode+MDd+Use Standard Windows Libraries

svchosttest.h

#include <windows.h>

//服务主进程入口函数
void WINAPI ServiceMain(
	DWORD dwArgc, 
	LPTSTR* lpszArgv);

//服务消息处理函数
void WINAPI ServiceHandlerEx(
	DWORD dwControl,
	DWORD dwEventType,
	LPVOID lpEventData,
	LPVOID lpContext);

//更新服务状态
int UpdateSvcStatus(
	DWORD dwState, 
	DWORD dwExitCode, 
	DWORD dwProgress );

//实际业务数据处理
int StartProcessing(
	TCHAR *cmd, 
	int bInteract);

//安装服务
int InstallService(TCHAR *name);

//卸载服务
int UninstallService(TCHAR *name);

//Rundll接口
void CALLBACK RundllInstall(
	HWND hwnd, 
	HINSTANCE hinst, 
	TCHAR *param, 
	int nCmdShow);

//Rundll接口
void CALLBACK RundllUninstall(
	HWND hwnd, 
	HINSTANCE hinst, 
	TCHAR *param, 
	int nCmdShow);

//输出调试内容
void OutputString(TCHAR *lpFmt, ... );

svchosttest.cpp

#include <stdio.h>
#include <time.h>
#include <assert.h>
#include <tchar.h>
#include "svchosttest.h"

#define DEFAULT_SERVICE_NAME TEXT("SvchostTest")
#define DEFAULT_SERVICE_GROUP TEXT("NeoTest")
#define SERVICE_START_CMD TEXT("%SystemRoot%\\System32\\svchost.exe -k NeoTest")
#define SERVICE_DLL TEXT("ServiceDll")
#define MY_EXECUTE_NAME TEXT("Notepad.exe")
#define WAIT_INTERVAL (100)

//dll模块句柄
HANDLE hDll = NULL;
//描述服务状态的服务句柄和状态
SERVICE_STATUS_HANDLE hSrv;
DWORD dwCurrState;
//关键区用于同步服务状态
CRITICAL_SECTION criticalStatus;

//dll入口函数
BOOL APIENTRY DllMain( HANDLE hModule,
					  DWORD  ul_reason_for_call,
					  LPVOID lpReserved
					  )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		hDll = hModule;
#ifdef _DEBUG
		AllocConsole();
		OutputString(TEXT("SvcHostDLL: DllMain called DLL_PROCESS_ATTACH"));
		break;

	case DLL_THREAD_ATTACH:
		OutputString(TEXT("SvcHostDLL: DllMain called DLL_THREAD_ATTACH"));
		break;

	case DLL_THREAD_DETACH:
		OutputString(TEXT("SvcHostDLL: DllMain called DLL_THREAD_DETACH"));
		break;

	case DLL_PROCESS_DETACH:
		UpdateSvcStatus( SERVICE_STOP_PENDING, 0, 0 );
		Sleep(WAIT_INTERVAL);
		UpdateSvcStatus( SERVICE_STOPPED, 0, 0 );
		OutputString(TEXT("SvcHostDLL: DllMain called DLL_PROCESS_DETACH"));
#endif
		break;
	}

	return TRUE;
}

//服务入口函数
void WINAPI ServiceMain(
	DWORD dwArgc, 
	LPTSTR *lpszArgv)
{
	//For Debug
	//直接DebugBreak会导致服务退出
	//DebugBreak();
	//下面也不好用
	//DWORD pId = GetCurrentProcessId();
	//DebugActiveProcess(pId);
	//手动附加到进程还可以的
#ifdef _DEBUG
	while(!IsDebuggerPresent())
	{
		Sleep(WAIT_INTERVAL);
	}
	DebugBreak();
#endif

	//使用ANSI编译时,字符集与Schost.exe传递过来的不同,
	//从而导致lpszArgv里面只有服务名称的第一个字母,而不是服务全名,需要自行处理
	//使用Unicode编译时,就不会有这个问题
	//TCHAR svcname[256];
	//_tcsncpy(svcname, (TCHAR*)lpszArgv[0], _countof(svcname));
	//OutputString(TEXT("SvcHostDLL: ServiceMain(%d, %s) called"), dwArgc, svcname);
	TCHAR svcname[256];
	_tcsncpy(svcname, DEFAULT_SERVICE_NAME, _countof(svcname));

	hSrv = RegisterServiceCtrlHandlerEx(svcname, (LPHANDLER_FUNCTION_EX)ServiceHandlerEx, NULL );
	if( hSrv == NULL )
	{
		OutputString(TEXT("SvcHostDLL: RegisterServiceCtrlHandler %S failed"), lpszArgv[0]);
#ifdef _DEBUG
		FreeConsole();
#endif
		return;
	}

	InitializeCriticalSection(&criticalStatus);
	UpdateSvcStatus( SERVICE_START_PENDING, 0, 0 );
	OutputString(TEXT("SvcHostDLL: ServiceMain() Start Pending"));
	Sleep(WAIT_INTERVAL);
	UpdateSvcStatus( SERVICE_RUNNING, 0, 0 );
	OutputString(TEXT("SvcHostDLL: ServiceMain() Running"));

	//开启服务
	TCHAR svccmd[256];
	if(dwArgc > 1)
	{
		_tcsncpy(svccmd, (TCHAR*)lpszArgv[1], _countof(svccmd));
	}
	else
	{
		_tcsncpy(svccmd, MY_EXECUTE_NAME, _countof(svccmd));
	}
	int bInteract = dwArgc > 2 ? 1 : 0;
	StartProcessing(svccmd, bInteract);

	do{
		//这里也可以用其它同步方式处理
		Sleep(WAIT_INTERVAL);
	}while(dwCurrState != SERVICE_STOP_PENDING && dwCurrState != SERVICE_STOPPED);

	OutputString(TEXT("SvcHostDLL: ServiceMain done"));

#ifdef _DEBUG
	FreeConsole();
#endif

	return;
}

//调用SetServiceStatus设置服务状态
int UpdateSvcStatus( DWORD dwState, DWORD dwExitCode, DWORD dwProgress )
{
	EnterCriticalSection(&criticalStatus);
	SERVICE_STATUS srvStatus;
	srvStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	srvStatus.dwCurrentState = dwCurrState = dwState;
	srvStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
	srvStatus.dwWin32ExitCode = dwExitCode;
	srvStatus.dwServiceSpecificExitCode = NO_ERROR;
	srvStatus.dwCheckPoint = dwProgress;
	srvStatus.dwWaitHint = 3000;
	BOOL bRet = SetServiceStatus( hSrv, &srvStatus );
	LeaveCriticalSection(&criticalStatus);
	return bRet; 
}

//服务消息处理函数
void WINAPI ServiceHandlerEx(   
	DWORD dwControl,
	DWORD dwEventType,
	LPVOID lpEventData,
	LPVOID lpContext)
{
	switch( dwControl )
	{
	case SERVICE_CONTROL_STOP:
		UpdateSvcStatus( SERVICE_STOP_PENDING, 0, 1 );
		OutputString(TEXT("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_STOP"));
		Sleep(WAIT_INTERVAL);
		UpdateSvcStatus( SERVICE_STOPPED, 0, 0 );
		break;
	case SERVICE_CONTROL_PAUSE:
		UpdateSvcStatus( SERVICE_PAUSE_PENDING, 0, 1 );
		OutputString(TEXT("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_PAUSE"));
		UpdateSvcStatus( SERVICE_PAUSED, 0, 0 );
		break;
	case SERVICE_CONTROL_CONTINUE:
		UpdateSvcStatus( SERVICE_CONTINUE_PENDING, 0, 1 );
		OutputString(TEXT("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_CONTINUE"));
		UpdateSvcStatus( SERVICE_RUNNING, 0, 0 );
		break;
	case SERVICE_CONTROL_INTERROGATE:
		OutputString(TEXT("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_INTERROGATE"));
		UpdateSvcStatus( dwCurrState, 0, 0 );
		break;
	case SERVICE_CONTROL_SHUTDOWN:
		OutputString(TEXT("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_SHUTDOWN"));
		UpdateSvcStatus( SERVICE_STOPPED, 0, 0 );
		break;
	}
}

//服务主进程
//这里只是启动了一个进程,就退出了
//如果要在这里实现业务逻辑的话,需要自己做好同步
int StartProcessing(TCHAR *cmd, int bInteract)
{
	OutputString(TEXT("SvcHostDLL: RealService called '%s' %s"), cmd, bInteract ? "Interact" : "");
	STARTUPINFO si = {0};
	si.cb = sizeof(si);
	//winlogon初始桌面:
	//应用程序桌面(\Windows\WinSta0\Default)
	//Winlogon桌面(\Windows\WinSta0\Winlogon)
	//屏幕保护桌面(\Windows\WinSta0\ScrenSaver)
	//相关API:
	//OpenWindowStation,SetProcessWindowStation
	//OpenDesktop,SetThreadDesktop,ConnectToDesktop,SwitchDesktop
	if(bInteract) si.lpDesktop = TEXT("WinSta0\\Default");
	PROCESS_INFORMATION pi;
	if(!CreateProcess(NULL, cmd, NULL, NULL, false, 0, NULL, NULL, &si, &pi))
	{
		OutputString(TEXT("SvcHostDLL: CreateProcess(%s) error:%d"), cmd, GetLastError());
	}
	else
	{
		OutputString(TEXT("SvcHostDLL: CreateProcess(%s) to %d"), cmd, pi.dwProcessId);
	}

	return 0;
}

//写注册表安装服务
//HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Svchost\\netsvcs
//HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\服务名称\\Parameters\\ServiceDll
//这里默认设置为自动启动,不与桌面交互,调整要修改注册表
int InstallService(TCHAR *newName)
{
	// 注册表中查找svchost配置信息
	// 调阅API注册服务
	// 配置服务所需的启动参数
	int rc = 0;
	HKEY hkRoot = HKEY_LOCAL_MACHINE, hkParam = 0;
	SC_HANDLE hscm = NULL, schService = NULL;

	try{
		TCHAR buff[500];
		TCHAR *svcname = DEFAULT_SERVICE_NAME;
		if(newName && newName[0]) svcname = newName;

		//新增服务
		hscm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
		if (hscm == NULL)
		{
			throw "OpenSCManager()";
		}

		TCHAR *svhostStartCmd = SERVICE_START_CMD;

		schService = CreateService(
			hscm,						// SCManager database
			svcname,					// name of service
			NULL,						// service name to display
			SERVICE_ALL_ACCESS,         // desired access
			SERVICE_WIN32_SHARE_PROCESS,// service type
			SERVICE_AUTO_START,			// start type
			SERVICE_ERROR_NORMAL,		// error control type
			svhostStartCmd,				// service's binary
			NULL,						// no load ordering group
			NULL,						// no tag identifier
			NULL,						// no dependencies
			NULL,						// LocalSystem account
			NULL);						// no password

		if (schService == NULL)
		{
			OutputString(TEXT("CreateService(%s) error %d"), svcname, rc = GetLastError());
			throw "";
		}
		OutputString(TEXT("CreateService(%s) SUCCESS. Config it"), svcname);

		CloseServiceHandle(schService);
		CloseServiceHandle(hscm);

		//新增服务配置,设置服务dll路径
		hkRoot = HKEY_LOCAL_MACHINE;
		_tcsncpy(buff, TEXT("SYSTEM\\CurrentControlSet\\Services\\"), _countof(buff));
		_tcsnccat(buff, svcname, 100);
		rc = RegOpenKeyEx(hkRoot, buff, 0, KEY_ALL_ACCESS, &hkRoot);
		if(ERROR_SUCCESS != rc)
		{
			OutputString(TEXT("RegOpenKeyEx(%s) KEY_SET_VALUE error %d."), svcname, rc);
			throw "";
		}

		rc = RegCreateKey(hkRoot, TEXT("Parameters"), &hkParam);
		SetLastError(rc);
		if(ERROR_SUCCESS != rc)
		{
			throw "RegCreateKey(Parameters)";
		}

		if(!GetModuleFileName(HMODULE(hDll), buff, _countof(buff)))
		{
			throw "GetModuleFileName() get dll path";
		}

		rc = RegSetValueEx(hkParam, TEXT("ServiceDll"), 0, REG_EXPAND_SZ, (BYTE*)buff, _tcsclen(buff)+1);
		SetLastError(rc);
		if(ERROR_SUCCESS != rc)
		{
			throw "RegSetValueEx(ServiceDll)";
		}

		OutputString(TEXT("Config service %s ok."), svcname);
	}
	catch(TCHAR *ex)
	{
		if(ex && ex[0])
		{
			rc = GetLastError();
			OutputString(TEXT("%s error %d"), ex, rc);
		}
	}

	if(hkRoot)
	{
		RegCloseKey(hkRoot);
	}

	if(hkParam)
	{
		RegCloseKey(hkParam);
	}
	
	if(schService)
	{
		CloseServiceHandle(schService);
	}

	if(hscm)
	{
		CloseServiceHandle(hscm);
	}

	return rc;
}

//用于rundll32.exe安装服务
//rundll32.exe SvchostTest.dll,RundllInstall
void CALLBACK RundllInstall(
	HWND hwnd,        // handle to owner window
	HINSTANCE hinst,  // instance handle for the DLL
	TCHAR *param,     // string the DLL will parse
	int nCmdShow      // show state
	)
{
	InstallService(param);
}

//写注册表卸载服务
int UninstallService(TCHAR *newName)
{
	int rc = 0;
	SC_HANDLE schService;
	SC_HANDLE hscm;

	try
	{
		hscm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
		if (hscm == NULL)
		{
			OutputString(TEXT("OpenSCManager() error %d"), rc = GetLastError() );
			return rc;
		}

		TCHAR *svcname = DEFAULT_SERVICE_NAME;
		if(newName && newName[0]) svcname = newName;

		schService = OpenService(hscm, svcname, DELETE);
		if (schService == NULL)
		{
			OutputString(TEXT("OpenService(%s) error %d"), svcname, rc = GetLastError() );
			return rc;
		}

		if (!DeleteService(schService) )
		{
			OutputString(TEXT("OpenService(%s) error %d"), svcname, rc = GetLastError() );
			return rc;
		}

		OutputString(TEXT("DeleteService(%s) SUCCESS."), svcname);
	}
	catch(TCHAR* ex)
	{
		rc = GetLastError();
		OutputString(TEXT("Exception Catched 0x%X"), rc);
	}

	CloseServiceHandle(schService);
	CloseServiceHandle(hscm);
	return rc;
}

//用于rundll32.exe卸载服务
//rundll32.exe SvchostTest.dll,RundllUninstall
void CALLBACK RundllUninstall(
	HWND hwnd,        // handle to owner window
	HINSTANCE hinst,  // instance handle for the DLL
	TCHAR *param,     // string the DLL will parse
	int nCmdShow      // show state
	)
{
	UninstallService(param);
}

//输出日志到日志文件和DbgPrint
void OutputString( TCHAR *lpFmt, ... )
{
	TCHAR buff[1024];
	va_list    arglist;
	va_start( arglist, lpFmt );
	_vsntprintf( buff, _countof(buff), lpFmt, arglist );
	va_end( arglist );

	DWORD len;
	HANDLE herr = GetStdHandle(STD_OUTPUT_HANDLE);
	if(herr != INVALID_HANDLE_VALUE)
	{
		WriteFile(herr, buff, _tcslen(buff), &len, NULL);
		WriteFile(herr, "\r\n", 2, &len, NULL);
	}
	else
	{
		FILE *fp = fopen("SvcHost.DLL.log", "a");
		if(fp)
		{
			TCHAR date[20], time[20];
			fprintf(fp, "%s %s - %s\n", _tstrdate(date), _tstrtime(time), buff);
			if(!stderr) fclose(fp);
		}
	}

	OutputDebugString(buff);

	return;
}

svchosttest.def

EXPORTS
ServiceMain
RundllUninstall
RundllInstall

参考(部分资料未能找到原文出处):
Writing a service that runs under svcho
Svchost.exe的原理

Win7调试服务程序,Debugbreak函数不响应,直接退出

上周调试Win7下的一个服务程序,以前都是用Debugbreak()直接可以进入调试的,但这次直接退出了。

查了一下,这样设置一下就可以:
控制面板->操作中心->维护->检查问题报告的解决方案->设置
每次发生问题时,在检查解决方案之前先询问我

保存设置后,就可以进入断点了,再次鄙视微软。

后来,又发现,即使设置后,仍然无法响应断点,没办法,只好用比较挫的代码搞定了:

	//在第一个DebugBreak()前面,添加下面的语句
	while(!IsDebuggerPresent())
	{
		Sleep(100);
	}
	DebugBreak();

这样,服务启动后,会一直等待调试器。启动服务后,手动通过VS、任务管理器或Process Explorer附加到进程,就可以对启动的服务进行调试了。

参考:
DebugBreak not breaking

诡异的dll(2)

还是昨天,还是同一个哥们。他要实现一个语音叫号的功能,要先普通话叫号,然后再粤语叫号。

诡异的事情发生了,在调试状态下,怎么运行都是正常的,
但直接双击运行,只能叫第一种声音(粤语或普通话),另一个声音(粤语或普通话)怎么都发不出来。
悲剧的是,他语音叫号也是调用了一个dll。

只好慢慢调试了,开始以为是线程退出的太快,或者变量作用域问题,或变量中繁体中文乱码问题,或者函数退出太快,或者是release时优化掉了…

弄了一个多小时,没有任何进展。无奈之下,我翻出了Process Explorer,去查看环境变量。
结果调试的时候,第一个环境变量是

__COMPAT_LAYER=WinXpsp3

天啊,这不是兼容模式吗。
于是将exe设成兼容模式,双击运行,就好了。

原来,他用BCB6做开发,在Win7 sp1上无法正常运行,就设成了兼容模式,调试时运行的exe当然也继承了兼容模式的环境变量咯。
吐血啊……

回家,搜了一下__COMPAT_LAYER环境变量,可以设置为以下数值:

兼容性:

兼容模式
WIN95 Windows 95
WIN98 Windows 98 / Windows Me
NT4SP5 Windows NT 4.0 (Service Pack 5)
WIN2000 Windows 2000
WINXP Windows XP
WINXPSP2 Windows XP (Service Pack 2)
WINXPSP3 Windows XP (Service Pack 3)
WINSRV03SP1 Windows Server 2003 (Service Pack 1)
WINSRV08SP1 Windows Server 2008 (Service Pack 1)
VISTARTM Windows Vista
VISTASP1 Windows Vista (Service Pack 1)
VISTASP2 Windows Vista (Service Pack 2)
WIN7RTM Windows 7

权限:

权限
RUNASADMIN 以管理员权限运行
RUNASINVOKER 以调用者权限运行

显示模式:

显示模式
DISABLETHEMES 禁用视觉主题
640X480 用640×480屏幕分辨率运行
HIGHDPIAWARE 高DPI设置时禁用显示缩放
256COLOR 用256色运行
DISABLEDWM 禁用桌面元素

另外,也可以通过注册表,设置程序的兼容模式:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers]
"D:\\TEST\\Hello.exe"="WINXPSP3"
    HKEY hKey;
    LPCTSTR strSubKey = "Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers";
    long lRet = ::RegOpenKeyEx( HKEY_CURRENT_USER, strSubKey, 0, KEY_WRITE, &hKey );
    if ( lRet == ERROR_SUCCESS )
    {
        TCHAR achValue[] = { _T("WINXPSP3") };
        CString strExePath = _T("D:\\Test\\Hello.exe");
        lRet  = ::RegSetValueEx( hKey, strExePath, NULL, REG_SZ, (LPBYTE)&achValue, sizeof(achValue) );
        RegCloseKey( hKey );
    } 

诡异的dll(1)

昨天,和哥们一起调试程序。由于上线压力较大,他最近一直在狂改程序,但遇到了一个问题:

从PACS Server取影像时,怎么都取不回来。

首先排查了PACS Server设置,发现稍微有些问题,修改配置后,我以前写的一个测试程序,
就可以正常取回影像了。

但诡异的事情发生了,我写的程序,可以查,可以取,但他写的就是无法取回影像,只能查询。

吐血啊。

经过了一个小时的奋战,实在发现不了问题,他决定把无线网卡禁用后再测一下,
结果就通了~~

原来他取影像用的是一个dll,估计这个dll中,启用监听的时候,会自动选用某一块网卡,
但刚好选了无线网卡,悲剧啊。

能不能不要自作主张选网卡啊~~
能不能调试的时候,先把不需要的网卡禁用了啊~~

汉字繁简体互换

1、chs2cht.h

#pragma once
char* UnicodeToBIG5(const wchar_t* szUnicodeString);
char* UnicodeToGB2312(const wchar_t* szUnicodeString);
wchar_t* GB2312ToUnicode(const char* szGBString);
char* GB2312ToBIG5(const char* szGBString);

2、chs2cht.cpp

#include "chs2cht.h"
#include <atlstr.h>
//GB2312 转 Unicode:
wchar_t* GB2312ToUnicode(const char* szGBString)  
{  
	UINT nCodePage = 936; //GB2312  
	int nLength=MultiByteToWideChar(nCodePage,0,szGBString,-1,NULL,0);  
	wchar_t* pBuffer = new wchar_t[nLength+1];  
	MultiByteToWideChar(nCodePage,0,szGBString,-1,pBuffer,nLength);  
	pBuffer[nLength]=0;  
	return pBuffer;  
}

//BIG5 转 Unicode:  
wchar_t* BIG5ToUnicode(const char* szBIG5String)  
{  
	UINT nCodePage = 950; //BIG5  
	int nLength=MultiByteToWideChar(nCodePage,0,szBIG5String,-1,NULL,0);  
	wchar_t* pBuffer = new wchar_t[nLength+1];  
	MultiByteToWideChar(nCodePage,0,szBIG5String,-1,pBuffer,nLength);  
	pBuffer[nLength]=0;  
	return pBuffer;  
}

//Unicode 转 GB2312:  
char* UnicodeToGB2312(const wchar_t* szUnicodeString)  
{  
	UINT nCodePage = 936; //GB2312  
	int nLength=WideCharToMultiByte(nCodePage,0,szUnicodeString,-1,NULL,0,NULL,NULL);  
	char* pBuffer=new char[nLength+1];  
	WideCharToMultiByte(nCodePage,0,szUnicodeString,-1,pBuffer,nLength,NULL,NULL);  
	pBuffer[nLength]=0;  
	return pBuffer;  
}

//Unicode 转 BIG5:  
char* UnicodeToBIG5(const wchar_t* szUnicodeString)  
{  
	UINT nCodePage = 950; //BIG5  
	int nLength=WideCharToMultiByte(nCodePage,0,szUnicodeString,-1,NULL,0,NULL,NULL);  
	char* pBuffer=new char[nLength+1];  
	WideCharToMultiByte(nCodePage,0,szUnicodeString,-1,pBuffer,nLength,NULL,NULL);  
	pBuffer[nLength]=0;  
	return pBuffer;  
}

//繁体中文BIG5 转 简体中文GB2312  
char* BIG5ToGB2312(const char* szBIG5String)  
{  
	LCID lcid = MAKELCID(MAKELANGID(LANG_CHINESE,SUBLANG_CHINESE_SIMPLIFIED),SORT_CHINESE_PRC);  
	wchar_t* szUnicodeBuff = BIG5ToUnicode(szBIG5String);  
	char* szGB2312Buff = UnicodeToGB2312(szUnicodeBuff);  
	int nLength = LCMapStringA(lcid,LCMAP_SIMPLIFIED_CHINESE, (LPCSTR)szGB2312Buff,-1,NULL,0);  
	char* pBuffer = new char[nLength + 1];  
	LCMapStringA(0x0804,LCMAP_SIMPLIFIED_CHINESE,(LPCSTR)szGB2312Buff,-1,pBuffer,nLength);  
	pBuffer[nLength] = 0;  

	delete[] szUnicodeBuff;  
	delete[] szGB2312Buff;  
	return pBuffer;  
}

//简体中文GB2312 转 繁体中文BIG5  
char* GB2312ToBIG5(const char* szGBString)  
{  
	LCID lcid = MAKELCID(MAKELANGID(LANG_CHINESE,SUBLANG_CHINESE_SIMPLIFIED),SORT_CHINESE_PRC);  
	int nLength = LCMapStringA(lcid,LCMAP_TRADITIONAL_CHINESE,szGBString,-1,NULL,0);  
	char* pBuffer=new char[nLength+1];  
	LCMapStringA(lcid,LCMAP_TRADITIONAL_CHINESE,szGBString,-1,pBuffer,nLength);  
	pBuffer[nLength]=0;  
	wchar_t* pUnicodeBuff = GB2312ToUnicode(pBuffer);  
	char* pBIG5Buff = UnicodeToBIG5(pUnicodeBuff);  
	delete[] pBuffer;  
	delete[] pUnicodeBuff;  
	return pBIG5Buff;  
}

3、test.cpp

#include "chs2cht.h"
#include <iostream> 
#include <locale.h>
#include <atlstr.h>  

using namespace std;

int main(int argc, char** argv)
{
    //“区域设置”为简体中文 
    locale loc( "chs" ); 
    char str[100];  
    cin>>str;  
    
    char * rlt=GB2312ToBIG5(str);  
    CString cStr1;   
    cStr1.Format( TEXT("%s"),rlt); 

    //“区域设置”为繁体中文 
    setlocale(LC_ALL, ".950");  
    cout<<rlt<<endl; 

    return 0;
}

JNI简单例子2

最近又用jni写了一个编码工具,从中抽出了最简单的代码,如下:

1、Compress4j.java

package com.neohope.test.compress.jni;

/**
 *
 * @author Hansen
 */
public class Compress4j {
    private native int encodeR(String inFilePath,String outFilePaht);
    private native int decodeR(String inFilePath,String outFilePaht);
    
    static{
        try{
            System.loadLibrary("compress4j");
        }catch(UnsatisfiedLinkError e){
            //nothing to do
            System.out.println("Error: Couldn't load compress4j");
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }
    
    public String encode(String inFilePath,String outFilePath)
    {
        encodeR(inFilePath, outFilePath);
        return "xxxxx";
    }
    
    public String decode(String inFilePath,String outFilePath)
    {
        decodeR(inFilePath, outFilePath);
        return "xxxxx";
    }
}

2、找到生成的classes文件夹,运行命令行:

javah -classpath . -jni com.neohope.test.compress.jni.Compress4j 

3、生成的.h文件如下

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_neohoe_test_compress_jni_Compress4j */

#ifndef _Included_com_neohoe_test_compress_jni_Compress4j
#define _Included_com_neohoe_test_compress_jni_Compress4j
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_neohoe_test_compress_jni_Compress4j
 * Method:    encodeR
 * Signature: (Ljava/lang/String;Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_neohoe_test_compress_jni_Compress4j_encodeR
  (JNIEnv *, jobject, jstring, jstring);

/*
 * Class:     com_neohoe_test_compress_jni_Compress4j
 * Method:    decodeR
 * Signature: (Ljava/lang/String;Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_neohoe_test_compress_jni_Compress4j_decodeR
  (JNIEnv *, jobject, jstring, jstring);

#ifdef __cplusplus
}
#endif
#endif

3、编写成的.cc文件如下

#include "com_neohoe_test_compress_jni_Compress4j.h"

JNIEXPORT jint JNICALL Java_com_neohoe_test_compress_jni_Compress4j_encodeR
  (JNIEnv *env, jobject, jstring inpathjs, jstring outpathjs)
{
    //first convert jstring to const char*
    const char* inpath = env->GetStringUTFChars( inpathjs, false);
    const char* outpath = env->GetStringUTFChars( outpathjs, false);

    //...

    return 0;
}

JNIEXPORT jint JNICALL Java_com_neohoe_test_compress_jni_Compress4j_decodeR
  (JNIEnv *env, jobject, jstring inpathjs, jstring outpathjs)
{
    //first convert jstring to const char*
    const char* inpath = env->GetStringUTFChars( inpathjs, false);
    const char* outpath = env->GetStringUTFChars( outpathjs, false);

    //...

    return 0;
}

4、在VS工程中,新增头文件目录
%JAVA_HOME%/include
%JAVA_HOME%/include/win32

5、编译为dll即可使用啦

6、关于mt和md的问题
编译为MT和MTd的话,用JNI是没有任何问题的
编译为MD的话,需要安装VC2010sp1可再发行包
编译为MDd的话,就比较麻烦了,手动复制依赖的dll后,我用c++调用dll可以成功,但用jni就一直提示:缺少依赖的dll

VC的链接顺序

CRT库在链接new, delete, DllMain这些符号的时候,使用的是弱外链接
MFC库同样也包含了new, delete, DllMain这几个符号,而MFC库必须在CRT库之前被链接才会成功。

如果link时出现了符号冲突,做下面的设置即可
project>properties>configuration properties>linker>input
在”Additional dependency”中依次增加 Nafxcwd.lib Libcmtd.lib
在add to “ignore specific library”中依次增加Nafxcwd.lib;Libcmtd.lib

VC服务程序调用远程资源

当VC服务程序调用远程资源时,经常返回路径不存在等问题
这是因为Windows中,服务程序以System用户登录,而不是桌面用户登录
这样就导致,虽然桌面程序已经映射网络资源,但System用户仍无法访问的问题
为了可以访问远程资源,可以调用API:WNetAddConnection2

BOOL AcessNetworkDrtive(CString szDevice,CString szDeviceName,CString szUsername,CString szPassword)
{
	DWORD dwRetVal;
	NETRESOURCE nr;

	memset(&nr, 0, sizeof (NETRESOURCE));
	nr.dwType = RESOURCETYPE_ANY;
	nr.lpLocalName = strDevice.GetBuffer(szDevice.GetLength());
	nr.lpRemoteName = szDeviceName.GetBuffer(szDeviceName.GetLength());
	nr.lpProvider = NULL;

	dwRetVal = WNetAddConnection2(&nr, szUsername, szUsername, CONNECT_UPDATE_PROFILE);

	if (dwRetVal != NO_ERROR)
	{
		CString cError;
		cError.Format(TEXT("[ERROR]WNetAddConnection2 Failed: %u\n"), dwRetVal);
		LogEvent(cError,TEXT("With remote name "),nr.lpRemoteName);
		return dwRetVal;
	}

	return 	dwRetVal;
}

CPP类型转换

1、static_cast

这是应该首先选用的转换方式,一般用于在相关类型间进行转换或在继承链中进行转换。
其运作方式类似于隐式转换 (比如int转为float,或指针转换为 void*), 而且它可以调用显示转换(当然包括隐式转换)。

在很多情况下, 显示声明static_cast是不必要的,
但有一点需要指出的是应当尽量避免type(XXX) 与(type)XXX 语法(详情见下文)。
然而type(XXX,YYY) 是安全的,而且一定会去调用构造函数。

static_cast也可以在父类及子类之间进行。 当子类向父类转换时是可以顺利进行的。但父类向子类转换时,只要子类没有继承virtual父类,就可以一直进行。但是它从父类转为子类时,不会做任何检查,即使类型不完全匹配。

2、const_cast

用来向一个变量增加或删除const属性。 其他任何C++ casts都无法完成此任务。
需要指出的是,当变量为const,该操作符是未定义的。

用它来声明一个非const类型的const引用是安全的。
当重载const类型的类成员变量时,该转换十分有用。
它也可以用来把一个对象增加const属性,比如调用重载的方法。

const_cast也适用于volatile,但很少见。

3、dynamic_cast

一般用来解决多态问题。你可以把指针或参考转换为任何继承链上的类型。
(一个多态类型至少用一个虚函数,无论是声明还是继承的)。
当从子类转换为父类时,dynamic_cast一定会成功。但从父类转换为子类,dynamic_cast会确保对象为子类的一个完全实现。

dynamic_cast将返回其是否可能。如果无法转换,他将返回NULL(对指针),或抛出std::bad_cast异常(对参考)。

dynamic_cast有一些限制:
如果在继承链中,同一类型有多个对象(dreaded diamond)同时你没有使用虚接口,转换不会进行。
他也只能应用于public继承,当protect或private继承时,会失败。

注意:
dynamic_cast需要编译器支持RTTI(运行时类型识别)。

4、reinterpret_cast

这是最危险的转换方式,慎用。 它直接把一种类型转换为另一种
(比如把值从一种指针转换为另一种指针,或把指针储存在int中, 或其他让人讨厌的事情)

使用reinterpret_cast,唯一能保证的是,当你从结构转回为原类型时,你能得到相同的结果。
当然,reinterpret_cast也有一些转换无法进行。

他通常用于比较奇怪的转换,或bit操作,比图把raw数据流转换为实际数据,或把数据储存在对齐指针的低字节。
当其他转换无法使用时,可以尝试reinterpret_cast

5、C-style cast
C-style cast 是使用(type)object 或 type(object)的转换方式。
C-style cast 被定义为下列转换中第一个成功返回的:
const_cast
static_cast
static_cast, then const_cast
reinterpret_cast
reinterpret_cast, then const_cast

在一些时候,他可以被用来替代其他casts,但用于他最后可能会调用reinterpret_cast(应显示调用),因此C-style cast是十分危险的。
除非你能保证,不会调用reinterpret_cast,或者reinterpret_cast一定会失败,否则不要轻易使用。即使如此,也应该考虑显示调用上面的转换。

当使用static_cast时,C-style casts 也可以忽略权限控制,这意味着他可以进行其他cast无法进行的操作。
这是避免C-style casts的一个不错的理由。

(翻译自Stack Overflow的一个帖子,水平有限,哈哈)

参考资料:
http://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-and-reinterpret-cast-be-used
http://www.cplusplus.com/doc/tutorial/typecasting/
http://en.wikibooks.org/wiki/C%2B%2B_Programming/Programming_Languages/C%2B%2B/Code/Statements/Variables/Type_Casting

函数调用约定

1.STDCALL
微软制定的c语言调用约定,也就是传说中的WINAPI
参数从右向左进行压栈(微软官方文档说从左向右压栈),返回值存在eax中,
被调用函数清理栈,不允许变长参数列表
函数名前增加_,函数名后增加@和参数个数(32位机器上一定被4整除)
ret指令中,有可选参数指定有多少字节需要弹栈

2.CDECL
c语言的默认调用约定
参数从右向左进行压栈,返回值存在eax中,
调用函数清理栈,可用变长参数列表
函数名前会增加_

3.FASTCALL
非跨编译器的c语言调用约定,
前2-3个4字节内的参数被存放到寄存器(edx,eax,ecx)中,其他参数,或大于4字节的参数,按从右到左的顺序被存放在栈中
多数情况下,调用函数负责清理栈
仅适用于参数少,而且实时性要求很高的情况下
函数名前增加@,函数名后增加@和参数个数

4.THISCALL
cpp的调用约定
参数从右向左进行压栈,返回值存在eax中,this指针存在ecx中
每种编译器对函数名称有不同的修饰方法,但基于重载的需要,一般都会增加参数类型和类名
当使用extern “C”的时候,cpp编译器会使用c语言的修饰方法来修饰函数名,这样就可以方便的供别人调用了。