Windows下Emacs使用SBCL

今天配置Emacs时,发现Emacs无法使用SBCL,其默认为lisp。
会报下面的错误:

Searching for program: no such file or directory, lisp 

这个问题可以用一个变通的方法解决,在Emacs路径下建立下面的文件:
lisp.bat

@rem start sbcl
D:\CommonLisp\sbcl-1.1.17\sbcl.exe --core "D:\CommonLisp\sbcl-1.1.17\sbcl.core"

然后就可以使用啦。

还是linux下面简单一些,符号链接直接搞定:

ln -s /usr/bin/clisp /usr/bin/lisp

排序算法C Part01

1、冒泡排序

//冒泡排序BubbleSort
//每次扫描,比较和交换相邻元素,保证一次扫描后,最大元素在最后一个
//每次扫描后,扫描队列长度减一
//时间复杂度:O(N^2)
//空间复杂度:O(N)
//稳定性:稳定
//输入:要排序的数组,数组长度
//输出:void,Q[]将处于排序状态
void BubbleSort(int Q[], int N)
{
  int i;
  int swap=1;
  int Temp;
  
  while(swap>0)
    {
      swap=0;
      for(i=1;i<N;i++)
	{
	  if(Q[i-1]>Q[i])
	    {
	      swap++;
	      Temp=Q[i-1];
	      Q[i-1]=Q[i];
	      Q[i]=Temp;
	    }
	}
    }
}

2、选择排序

//选择排序SelectionSort
//从头扫描到尾,每次将最小元素放到第一个
//每次扫描后,队列长度减一
//时间复杂度:O(N^2)
//空间复杂度:O(N)
//稳定性:稳定
//输入:要排序的数组,数组长度
//输出:void,Q[]将处于排序状态
void SelectionSort(int Q[], int N)
{
  int i,j,k;
  int Temp;
  for(i=0;i<N;i++)
    {
      k=i;
      for(j=i+1;j<N;j++)
	{
	  if(Q[j]<Q[i])k=j;
	}  

      Temp=Q[k];
      Q[k]=Q[i];
      Q[i]=Temp;
    }
}

3、插入排序

//插入排序InsertionSort
//起始队列长度为1,每次队列长度加1
//通过元素移动,将队列调整为有序状态
//时间复杂度:O(N^2)
//空间复杂度:O(N)
//稳定性:稳定
//输入:要排序的数组,数组长度
//输出:void,Q[]将处于排序状态
void InsertionSort(int Q[], int N)
{
  int i,j;
  int Temp;
  for(i=1;i<N;i++)
    {
      Temp = Q[i];
      for(j=i;j>0 && Q[j-1]>Temp;j--)
	{
	  Q[j]=Q[j-1];
	}

      Q[j]=Temp;
    }
}

4、希尔排序

//希尔排序ShellSort
//指定一个步长,将需要排序的数字,按序号%步长分组
//对每组进行插入排序,然后减小步长,再次分组,排序直到步长为1结束
//时间复杂度:O(n^2)
//空间复杂度:O(n)
//稳定性:稳定
//输入:要排序的数组,数组长度
//输出:void,Q[]将处于排序状态
void ShellSort(int Q[], int N)
{
  int i,j;
  int Temp;
  int Step;

  for(Step=N/2;Step>0;Step=Step/2)
    {
      for(i=Step;i<N;i++)
	{
	  Temp = Q[i];
	  for(j=i;j>=Step && Q[j-Step]>Temp;j=j-Step)
	    {
	      Q[j]=Q[j-Step];
	    }
	  Q[j]=Temp;
	}
      //dumpArray(Q,N);
    }
}

5、归并排序

//归并排序MergeSort
//二路归并首先将归并长度定为2,在归并集内进行排序
//然后归并长度×2,将有序集合进行归并
//时间复杂度:O(NlogN)
//空间复杂度:O(N)
//稳定性:稳定
//输入:要排序的数组,数组长度
//输出:void,Q[]将处于排序状态
void Merge(int Q[],int TempArray[], int left, int mid, int right)
{
  int i=left,j=mid,k=left;

  while(i<mid && j<=right)
    {
      if(Q[i]>Q[j])
	{
	  TempArray[k++]=Q[j++];
	}
      else
	{
	  TempArray[k++]=Q[i++];
	}
    }

  while(i<mid)
    {
	  TempArray[k++]=Q[i++];
    }

  while(j<=right)
    {
      	  TempArray[k++]=Q[j++];
    }

  for(k=left;k<=right;k++)
    {
      Q[k]=TempArray[k];
    }

}

void MSort(int Q[],int TempArray[], int left, int right)
{
  int center;

  if(left<right)
    {
      center=(left+right)/2;
      MSort(Q,TempArray,left,center);
      MSort(Q,TempArray,center+1,right);
      Merge(Q,TempArray,left,center+1,right);
    }
}

void MergeSort(int Q[], int N)
{
  int *TempArray = malloc(N*sizeof(int));

  if(TempArray!=NULL)
    {
      MSort(Q,TempArray,0,N-1);
    }
  else
    {
      //Todo: deal with not enough memory
      //RaiseError("MergeSort: not enough memory");
    }
}

CommonLisp101(01)

1.hello.lsp

;;hello world test function
(defun sayhello()
(format t “Hello Common Lisp!”))

2.加载并运行文件,打开clisp

[1]> (load "hello.lsp")
;; Loading file hello.lsp ...
;; Loaded file hello.lsp
T
[2]> (sayhello)
Hello Common Lisp!
NIL
[3]>

cpp用函数实现sizeof操作符的功能

#include "stdafx.h"
#include <stdlib.h>

using namespace std;

template<class ToT>
int getTypeSize(ToT *t,int len=1);

#define GET_TYPE_LEN(TYPE_NAME) getTypeSize(new TYPE_NAME());

struct MM{
	int m1;
	char m2;
	long m3;
	char* m4;
};

int _tmain(int argc, _TCHAR* argv[])
{
	struct MM mm;
	int nLen1 = getTypeSize(&mm);
	int nLen2 = GET_TYPE_LEN(MM); 

	return 0;
}

template<class ToT>
int getTypeSize(ToT *t, int len)
{
	return((char *)(t+1)-(char *)t)*len;
}

这种实现,有几个问题:
1、区分是类型还是变量,暂时没有找到好的办法
2、对于数组,暂时没有好的方法处理
3、为简化处理,要求输入为指针

基于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的原理

INT 21H中断指令

1、字符功能调用类(Character-Oriented Function)

01H、07H和08H —从标准输入设备输入字符

02H —字符输出

03H —辅助设备的输入

04H —辅助设备的输出

05H —打印输出

06H —控制台输入/输出

09H —显示字符串

0AH — 键盘缓冲输入

0BH —检测输入状态

0CH —清输入缓冲区的输入功能

(1)、功能01H、07H和08H

功能描述:从标准输入设备(如:键盘)读入一个字符。该中断在处理过程中将一直处于等待状态直到有字符可读为止。该输入还可被重定向,如果这样做,则无法判断文件是否已到文件尾

入口参数:AH=01H,过滤掉控制字符,并回显

 =07H,不过滤掉控制字符,不回显

 =08H,过滤掉控制字符,不回显

出口参数:AL=输入字符的ASCII码

(2)、功能02H

功能描述:向标准输出设备(如:屏幕)输出一个字符。该输出还可被重定向,如果这样做,则将无法判断磁盘是否满

入口参数:AH=02H

DL=待输出字符的ASCII码

出口参数:无

(3)、功能03H

功能描述:从辅助设备读入一个字符,该辅助设备的缺省值为COM1

入口参数:AH=03H

出口参数:AL=读入字符的ASCII码

(4)、功能04H

功能描述:向辅助设备输出一个字符,该辅助设备的缺省值为COM1

入口参数:AH=04H

DL=待输出字符的ASCII码

出口参数:无

(5)、功能05H

功能描述:向标准的输出设备输出一个字符。该缺省的输出设备为LPT1端口的打印机,除非用MODE命令来改变

入口参数:AH=05H

DL=待输出字符的ASCII码

出口参数:无

(6)、功能06H

功能描述:控制台(如:键盘、屏幕)输入/输出。如果输入/输出操作被重定向,那么,将无法判断文件是否已到文件尾,或磁盘已满

入口参数:AH=06H,DL=输入/输出功能选择

出口参数:若DL=00H-FEH,则此功能为输出,DL为待输出字符的ASCII码;

若DL=0FFH,则此功能为输入,此时:若ZF=1,则无字符可读,否则,AL=读入字符的ASCII码

(7)、功能09H

功能描述:输出一个字符串到标准输出设备上。如果输出操作被重定向,那么,将无法判断磁盘已满

入口参数:AH=09H

DS:DX=待输出字符的地址

说明:待显示的字符串以’$’作为其结束标志

出口参数:无

(8)、功能0AH

功能描述:从标准输入设备上读入一个字节字符串,遇到“回车键”结束输入(输入的字符在标准的输出设备上有回显)。如果该输入操作被重定向,那么,将无法判断文件是否已到文件尾

入口参数:AH=0AH

DS:DX=存放输入字符的起始地址

接受输入字符串缓冲区的定义说明:

  1、第一个字节为缓冲区的最大容量,可认为是入口参数;

  2、第二个字节为实际输入的字符数(不包括回车键),可看作出口参数;

  3、从第三个字节开始存放实际输入的字符串;

  4、字符串以回车键结束,回车符是接受的最后一个字符;

  5、若输入的字符数超过缓冲区的最大容量,则多出的部分被丢弃,系统并发出响铃,直到输入“回车”键才结束输入。

例如:

  BUFF 80, ?, 80 DUP(?)   ;最多接受80个字符

出口参数:无

(9)、功能0BH

功能描述:检查标准输入设备上是否有字符可读。该输入操作可被重定向

入口参数:AH=0BH

出口参数:AL=00H——无字符可读;FFH——有字符可读

(10)、功能0CH

功能描述:清空当前的标准输入缓冲区,再读入字符。其输入操作可被重定向

入口参数:AH=0CH

AL=01H、06H、07H、08H或0AH

出口参数:若入口参数AL为0AH,则DS:DX=存放输入字符的起始地址,否则,出口参数AL=输入字符的ASCII码

2、目录控制功能(Directory-Control Function)

39H —创建目录

3AH —删除目录

3BH —设置当前目录

47H —读取当前目录

(1)、功能39H

功能描述:用指定的驱动器和路径创建一个新目录

入口参数:AH=39H

DS:DX=指定路径的字符串地址(以0为字符串的结束标志)

出口参数:CF=0——创建成功,否则,AX=错误号(03H或05H),其含义见错误代码表

(2)、功能3AH

功能描述:删除指定的驱动器和路径的目录

入口参数:AH=3AH

DS:DX=指定路径的字符串地址(以0为字符串的结束标志)

出口参数:CF=0——删除成功,否则,AX=错误号(03H或05H),其含义见错误代码表

(3)、功能3BH

功能描述:用指定的驱动器和路径设置为当前目录

入口参数:AH=3BH

DS:DX=指定路径的字符串地址(以0为字符串的结束标志)

出口参数:CF=0——设置成功,否则,AX=错误号(03H),其含义见错误代码表

(4)、功能47H

功能描述:取当前目录的完全路径字符串

入口参数:AH=47H

DL=驱动器号(0=缺省,1=A,…)

DS:SI=存放当前目录字符串的地址

出口参数:CF=0——读取成功,否则,AX=错误号(0FH),其含义见错误代码表

3、磁盘管理功能(Disk-Management Function)

0DH —磁盘复位

0EH —选择磁盘

19H —读取当前驱动器

1BH, 1CH —读取驱动器数据

2EH —设置校验标志

36H —读取驱动器分配信息

54H —读取校验标志

(1)、功能0DH

功能描述:清空当前的文件缓冲区,但在MS-DOS内,暂时写入缓冲区的数据将写入磁盘

入口参数:AH=0DH

出口参数:无

(2)、功能0EH

功能描述:指定当前驱动器

入口参数:AH=0EH

DL=驱动器号(0=A,1=B,…)

出口参数:AL=系统中当前的驱动器号

(3)、功能19H

功能描述:取当前缺省驱动器号

入口参数:AH=19H

出口参数:AL=驱动器号(0=A,1=B,…)

(4)、功能1BH和1CH

功能描述:获得驱动器的分配信息

入口参数:AH=1BH——为缺省驱动器

AH=1CH——为任意驱动器,DL=驱动器号(0=缺省,1=A,…)

出口参数:AL=0FFH——失败,否则,

  AL=每簇的扇区数

  DS:BX=ID字节的地址

  CX=物理扇区的大小(字节数)

  DX=驱动器的簇数

(5)、功能2EH

功能描述:设置/清除操作系统自动读取检验标志

入口参数:AH=2EH

DL=00H

AL=00H——清除该标志,01H——设置该标志

出口参数:无

(6)、功能36H

功能描述:取选定驱动器的信息

入口参数:AH=36H

DL=驱动器号(0-缺省,1=A,2=B,…)

出口参数:若功能调用失败,AX=0FFFFH,否则,

  AX=每簇的扇区数

  BX=可用的簇数

  CX=物理扇区的大小(字节数)

  DX=驱动器中的簇数

(7)、功能54H

功能描述:读取校验标志

入口参数:AH=54H

出口参数:AL=当前检验标志值:00H—关检验,01H—开检验

4、文件操作功能(File Operation Function)

3CH —创建文件

3DH —打开文件

3EH —关闭文件

41H —删除文件

43H —读取/设置文件属性

45H —复制文件句柄

46H —重定义文件句柄

4EH —查找到第一个文件

4FH —查找下一个文件

56H — 文件换名

57H —读取/设置文件的日期和时间

5AH —创建临时文件

5BH —创建新文件

67H —设置文件句柄数(最多文件数)

6CH —扩展的打开文件功能(打开、创建或替换文件)

(1)、功能3CH

功能描述:用指定的文件名创建一个新文件。如果指定的文件已存在,则设置其长度为0。创建后,该文件是打开的,并返回其句柄

入口参数:AH=3CH

DS:DX=指定文件名字符串的地址(以0为字符串的结束标志)

CX=文件属性(这些标志位可以组合) 位0=1——只读

位2=1——系统

位5=1——归档位1=1——隐含

位3=1——卷标号

其它位保留不用,并置为0

出口参数:CF=0——创建成功,AX=文件句柄,否则,AX=错误号(03H、04H或05H),其含义见错误代码表

(2)、功能3DH

功能描述:打开指定的驱动器、路径和文件名,并返回其文件句柄

入口参数:AH=3DH

DS:DX=表明文件的字符串(以0为字符串的结束标志)

AL为打开方式: 位0~2000—只读方式  001—写方式  010—读/写方式

位3保留,其值为0

位4~6共享模式 000—兼容模式001—不共享010—拒绝写

011—拒绝读100—不拒绝任何操作

位7继承标志——0/1:子进程继承或不继承句柄

出口参数:CF=0——打开成功,AX=文件句柄,否则,AX=错误号(02H、03H、04H、05H或0CH),其含义见错误代码表

(3)、功能3EH

功能描述:关闭指定句柄的文件

入口参数:AH=3EH

BX=文件句柄

出口参数:CF=0——关闭成功,否则,AX=错误号(06H),其含义见错误代码表

(4)、功能41H

功能描述:删除指定的文件

入口参数:AH=41H

DS:DX=文件名字符串的地址

出口参数:CF=0——删除成功,否则,AX=错误号(02H、03H或05H),其含义见错误代码表

(5)、功能43H

功能描述:读取或设置指定文件的属性

入口参数:AH=43H

BX=文件句柄

DS:DX=文件名字符串的地址

AL=00H/01H——读取/设置文件属性

CX=文件属性: 位0=1——只读位1=1——隐含

位2=1——系统位3=1——卷标号

位5=1——归档其它位保留不用,并置为0

出口参数:CF=0——关闭成功,CX=文件属性,否则,AX=错误号(01H、02H、03H或05H),其含义见错误代码表

(6)、功能45H

功能描述:复制当前打开设备或文件的句柄,该句柄对应同样设备或文件的相同位置

入口参数:AH=45H

BX=待复制的文件句柄

出口参数:CF=0——复制成功,AX=新句柄,否则,AX=错误号(04H或06H),其含义见错误代码表

(7)、功能46H

功能描述:指定二个句柄,把第二句柄指向第一个句柄,即第二个句柄被重定向

入口参数:AH=46H

BX=文件或设备的句柄

CX=待重定向的文件句柄

出口参数:CF=0——重定向成功,否则,AX=错误号(04H或06H),其含义见错误代码表

(8)、功能4EH

功能描述:获取第一个与给定的文件名相匹配的文件

入口参数:AH=4EH

DS:DX=给定文件名的字符串

CX=搜索时使用的文件属性: 位0=1——只读  位1=1——隐含

位2=1——系统  位3=1——卷标号

位4=1——目录  位5=1——归档

其它位保留不用,并置为0

出口参数:CF=1——操作失败,AX=错误号(02H、03H或12H),其含义见错误代码表,否则,操作成功,DTA(Disk Transfer

Area)按下列方式填入数据: 字节00~14H保留

字节15H匹配的文件属性

字节16~17H压缩的文件名

字节18~19H压缩的文件日期

字节1A~1DH文件大小

字节1E~2AH文件名字符串

(9)、功能4FH

功能描述:在中断21H的功能4EH成功使用之后,再搜索下一个文件名

入口参数:AH=4FH

AL=返回的代码

出口参数:CF=1——操作失败,AX=错误号(12H),其含义见错误代码表,否则,操作成功,DTA中的数据如前面功能4EH所示

(10)、功能56H

功能描述:文件换名

入口参数:AH=56H

DS:DX=当前文件名字符串地址

ES:DI=新文件名字符串地址

出口参数:CF=0——操作成功,否则,AX=错误号(02H、03H、05H、11H),其含义见错误代码表

(11)、功能57H

功能描述:读取/设置文件的日期和时间

入口参数:AH=57H

BX=文件句柄 读取日期和时间AL=00H

设置日期和时间AL=01H

CX=时间(0F~0BH:小时,0AH~05H:分钟,04H~00H:2秒的个数)

DX=日期(0F~09H:年(相对1980年),08H~05H:月,04H~00H:日)

出口参数:CF=1——操作失败,AX=错误号(01H、06H),其含义见错误代码表,否则,若是读文件信息,则,CX=时间,DX=日期

(12)、功能5AH

功能描述:创建临时文件

入口参数:AH=5AH

DS:DX=路径名的地址

CX=文件属性(位可组合),其定义如下: 位0=1 只读位3-4=0  保留

位1=1 隐含位5=1   归档

位2=1 系统位6-15=0 保留

出口参数:CF=0——操作成功,AX=文件句柄,DS:DX=完整的路径文件地址,否则,AX=错误号(03H、04H或05H),其含义见错误代码表

(13)、功能5BH

功能描述:创建新文件

入口参数:AH=5BH

DS:DX=路径名的地址

CX=文件属性(位可组合),其定义如下: 位0=1只读位4=0保留

位1=1隐含位5=1归档

位2=1系统位6-15=0保留

位3=1卷标号

出口参数:CF=0——操作成功,AX=文件句柄,否则,AX=错误号(03H、04H、05H或50H),其含义见错误代码表

(14)、功能67H

功能描述:设置文件句柄数(最多文件数)

入口参数:AH=67H

BX=句柄的数量

出口参数:CF=0——操作成功,否则,AX=错误号,其含义见错误代码表

(15)、功能6CH

功能描述:扩展的打开文件功能(打开、创建或替换文件)

入口参数:AH=6CH

AL=00H

DS:SI=路径名的地址

BX=打开方式 位2~0000—只读  001—只写  010—可读、写

位3保留(0)

位6~4000—兼容   001—拒绝读写  010——拒绝写

011—拒绝读  100——不拒绝任何操作

位70—子进程继承句柄,1—子进程不继承句柄

位12~8保留(0)

位13致命错误处理程序,0—执行INT 24H,否则,返回错误代码给进程

位14写入方式:0—写入缓冲区,1—直接写入文件

位15保留(0)

CX=文件属性

位0=1 只读位4=0   保留

位1=1 隐含位5=1   归档

位2=1 系统位6-15=0  保留

位3=1 卷标签

DX=打开标志 位3~0  0—打开失败,1—打开文件,2—替换文件

位7~4  0—打开失败,1—创建文件

位15~8  0—保留

出口参数:CF=1——操作失败,AX=错误号,其含义见错误代码表,否则,

AX=文件句柄

CX=1——文件存在,打开之

 =2——文件不存在,创建之

5、文件操作功能(FCB)(File Operation Function)

0FH —打开文件

10H —关闭文件

11H、12H —查找第一个或下一个文件

13H —删除文件

16H —创建文件

17H —文件换名

23H —读取文件的大小

29H —分析文件名

(1)、功能0FH

功能描述:打开文件,并使之为顺序读/写作好准备

入口参数:AH=0FH

DS:DX=文件控制块的地址

出口参数:AL=00H——打开成功,否则,AL=FFH(如文件找不到)

在MS-DOS操作系统中,文件控制块的字段如下表所示。 字段名偏移量字段含义

驱动器字00H1 for drive A, 2 for drive B,…

当前块字段0CH00H

记录大小字段0EH0080H

文件长度字段10H文件字节数

日期字段14H日期

时间字段16H时间

(2)、功能10H

功能描述:关闭文件

入口参数:AH=10H

DS:DX=文件控制块的地址

出口参数:AL=00H——关闭成功,否则,AL=FFH

(3)、功能11H和12H

功能描述:查找第一个或下一个相匹配的文件

入口参数:AH=11H——第一个相匹配的文件

  =12H——下一个相匹配的文件

DS:DX=文件控制块的地址

出口参数:AL=00H——查找到,否则,AL=FFH

(4)、功能13H

功能描述:在指定(或缺省)的驱动器中,删除所有相匹配的文件

入口参数:AH=13H

DS:DX=文件控制块的地址

出口参数:AL=00H——删除成功,否则,AL=FFH

(5)、功能16H

功能描述:在当前目录中创建一个文件,其文件长度为0,并打开该文件,为随后的读/写操作作好必要的准备

入口参数:AH=16H

DS:DX=未打开的文件控制块的地址

出口参数:AL=00H——创建成功,否则,AL=FFH(如:磁盘满)

(6)、功能17H

功能描述:在指定的驱动器的当前目录中,把所有相匹配的文件换名

入口参数:AH=17H

DS:DX=指定文件控制块的地址

出口参数:AL=00H——换名成功,否则,AL=FFH

(7)、功能23H

功能描述:在当前目录中查找一个相匹配的文件。如果发现,则用其记录数来更新其文件大小

入口参数:AH=23H

DS:DX=未打开的文件控制块的地址

出口参数:AL=00H——匹配成功,FCB中偏移量为21H的字段被设置为其记录数,否则,AL=0FFH

(8)、功能29H

功能描述:分析一个字符串(文件名)置入FCB表中的不同字段

入口参数:AH=29H

CX=要写入的记录数

DS:SI=字符串段的地址

ES:DI=FCB的地址

AL=分析的控制标志位 位3=1——若字符串中有文件后缀,则FCB中的文件后缀将改变

=0——若后缀忽略修改,或若分析后无后缀,则FCB中后缀字段被置为“空”

位2=1——若字符串中有文件名,则FCB中的文件名将改变

=0——若文件名忽略修改,或若分析后无文件名,则FCB中文件名字段被置为“空”

位1=1——若字符串中指定了驱动器号,则FCB中的ID字节被修改

=0——若ID字节忽略修改,或若分析后没有指定驱动器号,则FCB中驱动器字段被置为0(缺省值)

位0=1——

=0——忽略前导分割符

不忽略前导分割符

出口参数:AL=00H—没有通配字符 01H—有通配字符 FFH—驱动器号非法

DS:SI=分析后文件名第一个字符的地址

ES:DI=格式化后的、未打开的FCB地址

6、记录操作功能(Record Function)

1AH —设置数据传输区地址

2FH —读取数据传输区地址

3FH —读文件或设备

40H —写文件或设备

42H —设置文件指针

5CH —文件区域加锁或解锁

68H —提交文件缓冲区数据

(2)、功能2FH

功能描述:为FCB读/写操作而获取DTA的当前地址

入口参数:AH=2FH

出口参数:ES:BX=DTA的段地址和偏移量

(3)、功能3FH

功能描述:从先前打开的文件中读出指定数目的字节,并移动文件指针

入口参数:AH=3FH

BX=文件句柄

CX=将要读出的字节数

DS:DX=存放字符的缓冲区地址

出口参数:CF=0——读取成功,AX=读取的字符数,否则,AX=错误号(05H或06H),其含义见错误代码表

(4)、功能40H

功能描述:向先前打开的文件写入指定数量的字节,并相应修改文件指针

入口参数:AH=40H

BX=文件句柄

CX=写入的字节数

DS:DX=存放数据的缓冲区地址

出口参数:CF=0——关闭成功,AX=写入的字节数,否则,AX=错误号(05H或06H),其含义见错误代码表

(5)、功能42H

功能描述:设置文件指针的相对位置(相对与文件头、文件尾和当前位置)

入口参数:AH=42H

BX=文件句柄

CX=偏移量的高位

DX=偏移量的低位

AL=00H——从文件头开始的绝对偏移量

 =01H——从当前文件指针开始的偏移量(可带符号)

 =02H——从文件尾开始的偏移量(可带符号)

出口参数:CF=0——设置成功,DX是指针的高位,AX是其低位,否则,AX=错误号(01H和06H),其含义见错误代码表。

(6)、功能5CH

功能描述:文件区域加锁或解锁

入口参数:AH=5CH

AL=00H——区域加锁  01H——区域解锁

BX=文件句柄

CX:DX=区域偏移量

SI:DI=区域长度

DS:DX=路径名的地址

出口参数:CF=0——操作成功,否则,AX=错误号(01H、06H、21H或24H),其含义见错误代码表

(7)、功能68H

功能描述:提交文件缓冲区数据

入口参数:AH=68H

BX=文件句柄

出口参数:CF=0——操作成功,否则,AX=错误号,其含义见错误代码表

PS:最近翻出来的资料,很老了,原作者不详。

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

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

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

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

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

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

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

参考:
DebugBreak not breaking

VS2012格式化全部代码

只需要在VS2012中运行下面代码即可:
Tools->Library Package Manager->Package Manager Console

function FormatItems($projectItems) {
    $projectItems |
    % {
        # Write-Host "    Examining item: $($_.Name)";

        if ($_.Name -and $_.Name.ToLower().EndsWith(".cs") `
            -and (-not $_.Name.ToLower().Contains(".designer."))) {

            $win = $_.Open('{7651A701-06E5-11D1-8EBD-00A0C90F26EA}');
            $win.Activate();

            $dte.ExecuteCommand('Edit.FormatDocument');

            if (!$_.Saved) {
                Write-Host "    Saving modified file: $($_.Name)";
                $dte.ExecuteCommand('File.SaveSelectedItems');
            }

            $dte.ExecuteCommand('Window.CloseDocumentWindow');
        }

        if ($_.ProjectItems -and ($_.ProjectItems.Count -gt 0)) {
            # Write-Host "    Opening sub-items of $($_.Name)";

            FormatItems($_.ProjectItems);
        }
    };
}

$dte.Solution.Projects | % {
    Write-Host "-- Project: $($_.Name)";

    FormatItems($_.ProjectItems)
}
;

注意:上面代码会遍历文件,打开,格式化,保存再关闭。运行前请保存并备份!

参考:How to format all files in Visual Studio 2012?