2007年5月22日星期二

IEnumerable 结合 IEnumerator 的设计思想

(任何引用请注明:转载于美人山下http://beautyhill.blogspot.com)
最近一直在考虑“面向接口编程”的思想。接口的作用大体是为了更好的规范、扩展能力,尤其重要的,它使得一个大的部件在功能上进行有机划分;这种分割使得功能职责简洁明了,确保开发人员不易出错。然则,我一直不甚明白“*able”与“*ator”的关系;或者说,为什么不用一个单单的“*ator”,设计成配对的目的是什么?
以下摘自http://codebetter.com/blogs/david.hayden/archive/2005/03/08/59419.aspx
--------------------------------------------------------------------------------
Implementing IEnumerable and IEnumerator on Your Custom Objects
Often you want to be able to enumerate through a collection of objects using the foreach statement in C#:

Using foreach in C#
foreach (Student student in myClass)
Console.WriteLine(student);


To be able to pull that off, myClass must implement IEnumerable:



IEnumerable Defined
Exposes the enumerator, which supports a simple iteration over a collection.

public IEnumerator GetEnumerator();


As is says above, IEnumerable just exposes another interface, called IEnumerator:



IEnumerator Defined
Supports a simple iteration over a collection. Enumerators only allow reading the data in the collection. Enumerators cannot be used to modify the underlying collection.

public object Current;
public void Reset();
public bool MoveNext();


To see this in action, let's create an example by which we can iterate through a list of students in a class using the foreach statement in C#.

First, we need to create a Student Class as shown below. At the minimum we want to override ToString() (Overriding System.Object.ToString() and IFormattable) so it says something meaningful and for kicks I decided to override Equals() and GetHashCode() (Object Identity vs. Object Equality - Overriding System.Object.Equals(Object obj)) only as an example.



Student Class
public class Student
{
#region Private Members

private readonly string _id;
private string _firstname;
private string _lastname;

#endregion

#region Properties

public string ID { get { return _id; } }

public string Firstname
{
get { return _firstname; }
set { _firstname = value; }
}

public string Lastname
{
get { return _lastname; }
set { _lastname = value; }
}

#endregion

#region Constructors

public Student(string id, string firstname, string lastname)
{
_id = id;
_firstname = firstname;
_lastname = lastname;
}

#endregion

#region Overriding System.Object

public override string ToString()
{
return String.Format("{0} {1}, ID: {2}", _firstname, _lastname, _id);
}

public override bool Equals(object obj)
{
if (obj == null) return false;
if (Object.ReferenceEquals(this,obj)) return true;
if (this.GetType() != obj.GetType()) return false;

Student objStudent = (Student)obj;
if (_id.Equals(objStudent._id)) return true;

return false;
}

public override int GetHashCode()
{
return _id.GetHashCode();
}

#endregion
}


We now need a custom class, called ClassList, that essentially holds the students. Forgetting IEnumerable for a moment, here is my barebones class. The class contains an ArrayList, called _students, which is populated with 3 students in its constructor. The list of students is private to ClassList and is currently not exposed outside of the class.



ClassList Class with no IEnumerable
public class ClassList
{
#region Private Members

private readonly string _id;

private ArrayList _students;

#endregion

#region Properties

public string ID { get { return _id; } }

#endregion

#region Constructors

public ClassList(string id)
{
_id = id;

_students = new ArrayList();

_students.Add(new Student("12345", "John", "Smith"));
_students.Add(new Student("09876", "Jane", "Doe"));
_students.Add(new Student("76403", "Bob", "Johnson"));
}

#endregion

}


To support iteration using foreach on the class, I need to implement IEnumerable on ClassList. Since my students are in an ArrayList, and I know ArrayList implements IEnumerable, I am going to "cheat" and pass back the IEnumerator for _students:



Using The ArrayList's IEnumerator
public IEnumerator GetEnumerator()
{
return (_students as IEnumerable).GetEnumerator();
}


This will allow me to use foreach on ClassList and here is sample code you can run to see how it works:



Example 1: Testing The ArrayList's IEnumerator
using System;
using System.Collections;

public class Student
{
#region Private Members

private readonly string _id;
private string _firstname;
private string _lastname;

#endregion

#region Properties

public string ID { get { return _id; } }

public string Firstname
{
get { return _firstname; }
set { _firstname = value; }
}

public string Lastname
{
get { return _lastname; }
set { _lastname = value; }
}

#endregion

#region Constructors

public Student(string id, string firstname, string lastname)
{
_id = id;
_firstname = firstname;
_lastname = lastname;
}

#endregion

#region Overriding System.Object

public override string ToString()
{
return String.Format("{0} {1}, ID: {2}", _firstname, _lastname, _id);
}

public override bool Equals(object obj)
{
if (obj == null) return false;
if (Object.ReferenceEquals(this,obj)) return true;
if (this.GetType() != obj.GetType()) return false;

Student objStudent = (Student)obj;
if (_id.Equals(objStudent._id)) return true;

return false;
}

public override int GetHashCode()
{
return _id.GetHashCode();
}

#endregion
}

public class ClassList : IEnumerable
{
#region Private Members

private readonly string _id;

private ArrayList _students;

#endregion

#region Properties

public string ID { get { return _id; } }

#endregion

#region Constructors

public ClassList(string id)
{
_id = id;

_students = new ArrayList();

_students.Add(new Student("12345", "John", "Smith"));
_students.Add(new Student("09876", "Jane", "Doe"));
_students.Add(new Student("76403", "Bob", "Johnson"));
}

#endregion

#region IEnumerable Members

public IEnumerator GetEnumerator()
{
return (_students as IEnumerable).GetEnumerator();
}

#endregion

}

public class MyClass
{
public static void Main()
{
ClassList myClass = new ClassList("History 204");

foreach (Student student in myClass)
Console.WriteLine(student);

Console.ReadLine();
}
}


If that is all the functionality you need, then passing the ArrayList's IEnumerator is the ticket. No sense creating your own IEnumerator if you don't have to.

However, we can create our own class, called ClassEnumerator, that implements IEnumerator and accomplishes the same thing as above. The class essentially just iterates through the _students arraylist using an index. Reset() sets the index back to -1. MoveNext() jumps ahead and returns a boolean as to whether we have hit the end. And, the Current property gets the current student.



ClassEnumerator
private class ClassEnumerator : IEnumerator
{
private ClassList _classList;
private int _index;

public ClassEnumerator(ClassList classList)
{
_classList = classList;
_index = -1;
}

#region IEnumerator Members

public void Reset()
{
_index = -1;
}

public object Current
{
get
{
return _classList._students[_index];
}
}

public bool MoveNext()
{
_index++;
if (_index >= _classList._students.Count)
return false;
else
return true;
}

#endregion
}


We will stick ClassEnumerator within ClassList and pass it back via the IEnumerable interface. Here is the entire code listing using our custom IEnumerator:



Testing ClassEnumerator
using System;
using System.Collections;

public class Student
{
#region Private Members

private readonly string _id;
private string _firstname;
private string _lastname;

#endregion

#region Properties

public string ID { get { return _id; } }

public string Firstname
{
get { return _firstname; }
set { _firstname = value; }
}

public string Lastname
{
get { return _lastname; }
set { _lastname = value; }
}

#endregion

#region Constructors

public Student(string id, string firstname, string lastname)
{
_id = id;
_firstname = firstname;
_lastname = lastname;
}

#endregion

#region Overriding System.Object

public override string ToString()
{
return String.Format("{0} {1}, ID: {2}", _firstname, _lastname, _id);
}

public override bool Equals(object obj)
{
if (obj == null) return false;
if (Object.ReferenceEquals(this,obj)) return true;
if (this.GetType() != obj.GetType()) return false;

Student objStudent = (Student)obj;
if (_id.Equals(objStudent._id)) return true;

return false;
}

public override int GetHashCode()
{
return _id.GetHashCode();
}

#endregion
}

public class ClassList : IEnumerable
{
#region Private Members

private readonly string _id;

private ArrayList _students;

#endregion

#region Properties

public string ID { get { return _id; } }

public int TotalStudents { get { return _students.Count; } }

#endregion

#region Constructors

public ClassList(string id)
{
_id = id;

_students = new ArrayList();

_students.Add(new Student("12345", "John", "Smith"));
_students.Add(new Student("09876", "Jane", "Doe"));
_students.Add(new Student("76403", "Bob", "Johnson"));
}

#endregion

#region IEnumerable Members

public IEnumerator GetEnumerator()
{
return (IEnumerator)new ClassEnumerator(this);
}

#endregion

#region ClassEnumerator

private class ClassEnumerator : IEnumerator
{
private ClassList _classList;
private int _index;

public ClassEnumerator(ClassList classList)
{
_classList = classList;
_index = -1;
}

#region IEnumerator Members

public void Reset()
{
_index = -1;
}

public object Current
{
get
{
return _classList._students[_index];
}
}

public bool MoveNext()
{
_index++;
if (_index >= _classList._students.Count)
return false;
else
return true;
}

#endregion
}

#endregion
}

public class MyClass
{
public static void Main()
{
ClassList myClass = new ClassList("History 204");

Console.WriteLine("Class: {0}, Total Students: {1}\n",
myClass.ID, myClass.TotalStudents.ToString());

foreach (Student student in myClass)
Console.WriteLine(student);

Console.ReadLine();
}
}
--------------------------------------------------------------------------------
注意到,ClassList在实现IEnumerable的GetEnumerator()方法时,有两个选择:调用(cheat)字段ArrayList的GetEnumerator(),或者自行创建ClassEnumerator,再操作。这可能就是“*ator”与“*able”成对出现的原因:灵活性!
这种灵活性体现在,对于外界有一个统一的“*able”接口提供调用,对于内部实现,可以调用(cheat)其他现成的“*ator”,或者自己实现;但“*able”作为规范是必须实现的!
这其实是一个二次规范:“*able”作为必备实现接口,一级规范;“*ator”作为可选实现接口,二级规范。将接口的功能进行了“二次切割”,确实不同凡响。

2007年5月18日星期五

挽黛玉

(任何引用请注明:转载于美人山下http://beautyhill.blogspot.com)
今生无缘来生见,
携子之手爱缠绵。
茉莉牡丹常相守,
只恨岁月已百年。

c# 配置文件操作(转载)

(任何引用请注明:转载于美人山下http://beautyhill.blogspot.com)
namespace System.Configuration;

1. 创建配置节类

必须创建继承自ConfigurationSection的对象才能进行配置数据读写操作,ConfigurationSection提供了索引器用来获取和设置配置数据,需要注意的是拥有ConfigurationProperty特性的属性才会被存储,并且名称要保持大小写完全一致,如下面的代码中,所有的"id"必须保持一样。

class ConfigSectionData : ConfigurationSection
{
[ConfigurationProperty("id")]
public int Id
{
get { return (int)this["id"]; }
set { this["id"] = value; }
}

[ConfigurationProperty("time")]
public DateTime Time
{
get { return (DateTime)this["time"]; }
set { this["time"] = value; }
}
}

2. 创建配置文件操作对象

Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

ConfigSectionData data = new ConfigSectionData();
data.Id = 1000;
data.Time = DateTime.Now;

config.Sections.Add("add", data);
config.Save(ConfigurationSaveMode.Minimal);

上面的例子是操作 app.config,在根节点(configuration)下写入名称为"add"的配置数据。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="add" type="ConsoleApplication1.ConfigSectionData, ... />
</configSections>
<add id="1000" time="02/18/2006 21:51:06" />
</configuration>

需要注意的 VS2005 在IDE模式下会将信息写入 *.vshost.exe.config,并且在程序关闭时覆写该文件,因此您可能看不到您写入的配置数据,只要在资源管理其中执行 *.exe 文件,您就可以在 *.exe.config 文件中看到结果了。

如果我们需要操作非缺省配置文件,可以使用ExeConfigurationFileMap对象。

ExeConfigurationFileMap file = new ExeConfigurationFileMap();
file.ExeConfigFilename = "test.config";
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(file, ConfigurationUserLevel.None);

ConfigSectionData data = new ConfigSectionData();
data.Id = 1000;
data.Time = DateTime.Now;

config.Sections.Add("add", data);
config.Save(ConfigurationSaveMode.Minimal);

如果我们不希望在根节点下写入配置数据,可以使用ConfigurationSectionGroup对象。

ExeConfigurationFileMap file = new ExeConfigurationFileMap();
file.ExeConfigFilename = "test.config";
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(file, ConfigurationUserLevel.None);

ConfigSectionData data = new ConfigSectionData();
data.Id = 1000;
data.Time = DateTime.Now;

config.SectionGroups.Add("group1", new ConfigurationSectionGroup());
config.SectionGroups["group1"].Sections.Add("add", data);

config.Save(ConfigurationSaveMode.Minimal);

下面就是生成的配置文件。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="group1" type="System.Configuration.ConfigurationSectionGroup, ... >
<section name="add" type="ConsoleApplication1.ConfigSectionData, ... />
</sectionGroup>
</configSections>
<group1>
<add id="1000" time="02/18/2006 22:01:02" />
</group1>
</configuration>

3. 读取配置文件

ExeConfigurationFileMap file = new ExeConfigurationFileMap();
file.ExeConfigFilename = "test.config";
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(file, ConfigurationUserLevel.None);

ConfigSectionData data = config.SectionGroups["group1"].Sections["add"] as ConfigSectionData;
//ConfigSectionData data = config.Sections["add"] as ConfigSectionData; // 从根节读取

if (data != null)
{
Console.WriteLine(data.Id);
Console.WriteLine(data.Time);
}

4. 写配置文件

在写入 ConfigurationSectionGroup 和 ConfigurationSection 前要判断同名配置是否已经存在,否则会写入失败。
另外如果配置文件被其他Configuration对象修改,则保存会失败,并抛出异常。建议采用Singleton模式。

ExeConfigurationFileMap file = new ExeConfigurationFileMap();
file.ExeConfigFilename = "test.config";
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(file, ConfigurationUserLevel.None);

ConfigSectionData data = new ConfigSectionData();
data.Id = 2000;
data.Time = DateTime.Now;

ConfigurationSectionGroup group1 = config.SectionGroups["group1"];
if (group1 == null)
config.SectionGroups.Add("group1", new ConfigurationSectionGroup());

ConfigurationSection data = group1.Sections["add"] as config;
if (add == null)
config.SectionGroups["group1"].Sections.Add("add", data);
else
{
group1.Sections.Remove("add");
group1.Sections.Add("add", data);

// 或者直接修改原配置对象,前提是类型转换要成功。
//ConfigSectionData configData = add as ConfigSectionData;
//configData.Id = data.Id;
//configData.Time = data.Time;
}

config.Save(ConfigurationSaveMode.Minimal);

5. 删除配置节

删除ConfigurationSectionGroup
config.SectionGroups.Remove("group1");
//config.SectionGroups.Clear();

config.Save(ConfigurationSaveMode.Minimal);

删除ConfigurationSection
config.Sections.Remove("add1");
//config.Sections.Clear();

if (config.SectionGroups["group1"] != null)
{
config.SectionGroups["group1"].Sections.Remove("add2");
//config.SectionGroups["group1"].Sections.Clear();
}

config.Save(ConfigurationSaveMode.Minimal);

6. 其他

可以使用 ConfigurationManager.OpenMachineConfiguration() 来操作 Machine.config 文件。
或者使用 System.Web.Configuration 名字空间中的 WebConfigurationManager 类来操作 ASP.net 配置文件。
ConfigurationManager还提供了AppSettings、ConnectionStrings、GetSection()等便捷操作。

7. 使用自定义类

2006-4-17 补充,回复 ggy 网友的疑问。
引用至 ggy
比如ConfigSectionData里面
除了简单类型之外,可不可以有自定义的类
可以使用自定义类,不过需要定义一个转换器。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.ComponentModel;

// 要写入配置文件的自定义类
class CustomData
{
public CustomData(string s)
{
this.s = s;
}

private string s;
public string S
{
get { return s; }
set { s = value; }
}
}

// 自定义的转换器(演示代码省略了类型判断)
class CustomConvert : ConfigurationConverterBase
{
public override bool CanConvertFrom(ITypeDescriptorContext ctx, Type type)
{
return (type == typeof(string));
}

public override object ConvertTo(ITypeDescriptorContext ctx, CultureInfo ci, object value, Type type)
{
return (value as CustomData).S;
}

public override object ConvertFrom(ITypeDescriptorContext ctx, CultureInfo ci, object data)
{
return new CustomData((string)data);;
}
}

class ConfigSectionData : ConfigurationSection
{
[ConfigurationProperty("id")]
public int Id
{
get { return (int)this["id"]; }
set { this["id"] = value; }
}

[ConfigurationProperty("time")]
public DateTime Time
{
get { return (DateTime)this["time"]; }
set { this["time"] = value; }
}

[ConfigurationProperty("custom")]
[TypeConverter(typeof(CustomConvert))] // 指定转换器
public CustomData Custom
{
get { return (CustomData)this["custom"]; }
set { this["custom"] = value; }
}
}

public class Program
{
static void Main(string[] args)
{
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

ConfigSectionData data = new ConfigSectionData();
data.Id = 1000;
data.Time = DateTime.Now;
data.Custom = new CustomData("abcdefg...");

config.Sections.Add("add", data);
config.Save(ConfigurationSaveMode.Minimal);

// 读取测试
ConfigSectionData configData = (ConfigSectionData)config.Sections["add"];
Console.WriteLine(configData.Custom.S);
}
}

保存后的配置文件
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="add" type="..." />
</configSections>
<add id="1000" time="04/17/2006 22:06:58" custom="abcdefg..." />
</configuration>

更详细的信息可以看 MSDN 中关于 System.Configuration.ConfigurationConverterBase 的说明。

2007年5月11日星期五

c#写系统日志(http://www.113317.com/blog/article.asp?id=508)

(任何引用请注明:转载于美人山下http://beautyhill.blogspot.com)
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace Log
{
class LogWirter
{
///


/// 事件源名称
///

private string eventSourceName;
EventLogEntryType eventLogType;
public LogWirter()
{
eventSourceName = "test";
eventLogType = EventLogEntryType.Error;
}

///
/// 消息事件源名称
///

public string EventSourceName
{
set { eventSourceName = value; }
}

///
/// 消息事件类型
///

public EventLogEntryType EventLogType
{
set { eventLogType = value; }
}

///
/// 写入系统日志
///

/// 事件内容
public void LogEvent(string message)
{
if (!EventLog.SourceExists(eventSourceName))
{
EventLog.CreateEventSource(eventSourceName, "Application");
}
EventLog.WriteEntry(eventSourceName, message, EventLogEntryType.Error);
}
}
}

用C#创建Windows Service (http://www.cnblogs.com/guanjinke/archive/2006/12/01/578399.html)

(任何引用请注明:转载于美人山下http://beautyhill.blogspot.com)
      现在的.NET框架已经为Windows service的开发提供足够强大的支持,你只需要关注service所要实现的逻辑,而完全不用关心service底层是如何实现的,相比以前用MFC来说,真是质的飞跃啊。闲话少谈,看看如何实现一个最基本的windows service,以及如何来调试windows service
      打开VS 2005,点击File->New->Project….,在创建工程向导中选择Windows service模板,如下图所示:
      

在工程名输入框中,输入TestService然后点击OKVS为我们生成了最基本的代码,我们将Service的名称改为我们期望的名称,在solution explorer中右键点击vs自动生成的service文件“Service1.cs,在弹出的菜单中选择Rename,输入“MyFirstService”,然后切换到设计视图,也就是在solution explorer中双击MyfirstService.cs,在Property Explorer中将ServiceName属性改为MyFirstService,这时一个可运行的windows service已经创建成功。我们添加一些代码来测试一下这个service,最简单的测试方式就是向windows event log里来添加一些消息以显示我们的Service的状态。 
   我们需要一个EventLog组件向windows log中添加信息,在solution explorer中双击MyfirstSerice文件切换到设计视图,将工具箱中的EventLog组件拖到设计视图中,我们保留EventLog组件的默认名称eventLog1。选中eventLog1,在property explorer中将Log属性选为Application,在Source属性中输入“MyFirstService”。接下来,我们在设计视图的空白处,右键点击,并选择View Code切换到代码视图,在OnStartOnStop方法里输入代码实现日志写入,代码如下:
   
   


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;

namespace TestService
{
    
public partial class MyFirstService : ServiceBase
    
{
        
public MyFirstService()
        
{
            InitializeComponent();
        }


        
protected override void OnStart(string[] args)
        
{
            
// TODO: Add code here to start your service.
            eventLog1.WriteEntry("Service start");
        }


        
protected override void OnStop()
        
{
            
// TODO: Add code here to perform any tear-down necessary to stop your service.
            eventLog1.WriteEntry("Service stop");
        }

    }

}



   要运行这个service我们还要做下边的几个步骤:
   1.为我们的Service添加Installer,右键点击设计视图,选择Add InstallerVS将会为我们添加ProjectInstaller.cs,并在ProjectInstaller中添加组件serviceInstaller1serviceProcessInstaller1,现在我们来修改他们的属性来控制Service的安装和启动选项。在ProjectInstaller得设计视图中选中serviceProcessInstaller1,将它得Account属性选为LocalSystem,这样以这个帐号服务启动。如果你希望系统启动时自动启动服务得话,将serviceInstaller1StartType的属性选为Automatic,如果手动启动的话,选为manaul
   2.安装service,我们要用到IntallUtil.exe这个程序,这个程序位于C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727。点击开始菜单,选择“运行”,在运行对话框中输入cmd,进入到命令行窗口,输入cd :\WINDOWS\Microsoft.NET\Framework\v2.0.50727,进入到这个目录,然后输入installutil F:\Programs\C#\TestService\TestService\bin\Debug\testserveice.exe, installutil后边的内容就是我们的工程生成的可执行程序的路径,情根据需要修改。
   如果你给ServiceInstaller1StartType设为Automatic的话,安装完服务,服务已经运行起来了,如果StartTypeManual的话,你需要手动启动。现在我们进入“服务”,要打开服务,请单击开始,指向设置,然后单击控制面板。依次单击性能和维护管理工具,然后双击服务。在里边你应该能够看到我们制作的Service MyFirstService。在这里边,我们可以启动,关闭服务,还可以设置服务的启动类型。然后,我们看看服务有没有正确的写入日志,我们需要进入到事件查看器,要打开事件查看器,请单击开始,指向设置,然后单击控制面板。单击性能和维护,单击管理工具,然后双击事件查看器。如下图所示,我们的消息已经成功的写到了系统日志里了。
   
   如果你不需要这个Service了,仍然使用InstallUtil这个程序来卸载,不过在InstallUtil后跟参数 –u,比如installutil –u F:\Programs\C#\TestService\TestService\bin\Debug\testserveice.exe
   Service的调试方法与普通的程序调试方法是不一样的。我来介绍一下。
   1.          Build你的项目
   2.          设置断点,因为我们的Service非常的简单,没有什么执行逻辑,所以设置断点没有任何意义,大家可以自己写一些代码来实践。一般来说,我们服务里需要用到一个另外的线程来执行任务,你需要在线程的执行代码中来设置断点。
 
3.          安装service,我们前边有介绍如何安装。
  4.          如果你的Service启动类型是手动(Manual),你需要到“服务”里启动你的Service。一般来说,如果你的service在开发阶段,我推荐你将Service的启动类型设置为Manual,这样便于调试,因为如果service在运行过程中,你将无法build工程。   
  5.         
VS中,从菜单中选择Debug->Attach Process….,将会出现下图:

里边列出了正在运行的进程,如果你找不到自己的
service,请选中Show processes from all users。在Available processes列表中选中我们的service所在的进程TestService,然后点击Attach按钮,如果你设置的断点合理的话,那么,程序就会停在断点处,接下来你就可以进行调试了。


 


2007年5月10日星期四

.Net Remoting配置文件的用法

(任何引用请注明:转载于美人山下http://beautyhill.blogspot.com)
How to use .Net Remoting Configuration files

.NET Remoting configuration files allow you to specify parameters for most aspects of the remoting framework. These files can define tasks as simple as registering a channel and specifying a Type as a server-activated object, or can be as complex as defining a whole chain of IMessageSinks with custom properties.

通过.Net Remoting配置文件可以为Remote Objects设定许多参数,如Channel、SAO服务端激活对象类型(Singleton/SingleCall)等等,方便以后在不用修改代码或重新编译的情况下,改变Remote Objects的行为。

1,如下是Server端典型的Remoting配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.runtime.remoting>
    <application>
      <channels>
        <channel ref="http"/>
      </channels>
      <service>
        <wellknown mode="Singleton"
                   type="ComponentHost.CustomerManager, ComponentHost"
                   objectUri="CustomerManager.soap" />
      </service>
     </application>
   </system.runtime.remoting>
</configuration> 

(1)当Remote Objects部署在Console/Windows Form、Windows Services下时(上面的配置文件channel需要设置port属性),相应Server端声明Remote Objects的代码可以简化为:
string filename = "server.exe.config";
RemotingConfiguration.Configure(filename); 

(2)如果Remote Objects部署在IIS时,根本就不需要任何代码声明。但是需要将上述配置文件命名为:web.config,并且将Remote Objects的DLL文件安置在web application的BIN文件夹。 
一般在实际应用中,基本上将Remote Objects部署在IIS环境中,好处是(I)不需要编写额外的代码;(II)只要启动机器,远程对象就启动了。不需要你半夜三更跑到公司去登录,然后启动发生故障的远程服务;(III)容易与IIS认证服务进行集成;(IV)可能还有更多优点,我现在没有想到。 

(3)如果需要声明多个远程对象,只需要在<service>与</service>之间添加相应的Remote Objects配置信息即可。

(4)另外需要注意type属性为:<namespace>.<class>, <assembly> 

2,如下是Client端典型的配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.runtime.remoting>
    <application>
      <client>
        <wellknown type="ComponentHost.CustomerManager, RemotingTest" 
                   url="http://localhost/ComponentHost/CustomerManager.soap" />      
      </client>
    </application>
  </system.runtime.remoting>
</configuration> 

要注意type属性的设定:<namespace>.<class>, <assembly>

如果Client通过SoapSuds产生Remote Objects的元数据assembly,或者是Shared Assembly(如Interface或Abstract Class),这里<assembly>则为上述assembly的名称。

如果是通过SoapSuds产生Source code,则<assembly>为Client应用程序名(无exe后缀)。 

同时,Client端application调用Remote Objects时,可以省掉:注册通道、Activator.GetObject()/RemotingConfiguration.RegisterActivatedServiceType()等代码,取而代之的代码为:
string filename = “clientApplication.exe.config”;
RemotingConfiguration.Configure(filename);

下面通过new来创建Remote Object实例。 

3,标准的.Net Remoting Configuration配置文件

MSDN中有.Net Remoting Configuration file中全部元素/属性的完整的详细说明,需要的时候再查阅了。一般情况下,知道下面这些属性就够用了。
<configuration>
   <system.runtime.remoting>
      <application>
        <lifetime /> ―― 配置Remote Objects生存期的信息
        <channels /> ―― 配置与远程对象进行通信的信道
        <service /> 
        <client />
      </application>
   </system.runtime.remoting>
</configuration>

简单说明:
(1)<service> ―― 仅在Server端配置
      <service>
          <wellknown /> ―― 配置要发布的SAO(已知)对象的信息
          <activated /> ―― 配置要发布的CAO客户端激活对象的信息
      </service> 

(2)<client> ―― 仅在Client端配置,与Server端<service>对应
         <client>
            <wellknown />
            <activated />
        </client>

When using CAOs, the <client> property has to specify the URI to the server for all underlying <activated> entries.

Note:When using CAOs from more than one server, you have to create several <client> properties in your configuration file.

当调用CAO远程对象时,必须设定<client>的url属性。如果CAO来自不同的Server,则需要在配置文件中定义多个<client>。如下所示:
     <client url="http://localhost/MyServer>
        <activated type="Server.MyRemote, Client" />
      </client>

4,定制Client/Server Channel元素

(1)Client Side
<channel ref="http">
    <clientProviders>
          <formatter ref="binary" />  
    </clientProviders>
</channel>

其中,formatter ref=”binary” or “soap”。formatter ref指要在通道上发送的消息格式,在此示例中为二进制,以增强性能。 

(2)Server Side
<channel ref="http">
    <serverProviders>
          <provider ref="wsdl" />
          <formatter ref="binary" typeFileterLevel="Full" />
          <formatter ref="soap" typeFileterLevel="Full" />
    </serverProviders>
</channels>   

typeFilterLevel表示当前自动反序列化级别,支持的值包括 Low(默认值)和 Full。

Reference:
1. Ingo Rammer, Advanced .Net Remoting.
2. MSDN

2007年5月9日星期三

使用C#控制远程计算机的服务(http://www.cnblogs.com/onekey/archive/2004/08/25/36513.aspx)

(任何引用请注明:转载于美人山下http://beautyhill.blogspot.com)
    在.net中提供了一些类来显示和控制Windows系统上的服务,并可以实现对远程计算机服务服务的访问,如System.ServiceProcess命名空间下面的ServiceController 类,System.Management下面的一些WMI操作的类。虽然用ServiceController可以很方便的实现对服务的控制,而且很直观、简洁和容易理解。但是我认为他的功能同通过WMI来操作服务相比,那可能就有些单一了,并且对多个服务的操作可能就比较麻烦,也无法列出系统中的所有服务的具体数据。这里要讲的就是如何使用System.Management组件来操作远程和本地计算机上的服务。
    WMI作为Windows 2000操作系统的一部分提供了可伸缩的,可扩展的管理架构.公共信息模型(CIM)是由分布式管理任务标准协会(DMTF)设计的一种可扩展的、面向对象的架构,用于管理系统、网络、应用程序、数据库和设备。Windows管理规范也称作CIM for Windows,提供了统一的访问管理信息的方式。如果需要获取详细的WMI信息请读者查阅MSDN。System.Management组件提供对大量管理信息和管理事件集合的访问,这些信息和事件是与根据 Windows 管理规范 (WMI) 结构对系统、设备和应用程序设置检测点有关的。
    但是上面并不是我们最关心的,下面才是我们需要谈的话题。
    毫无疑问,我们要引用System.Management.Dll程序集,并要使用System.Management命名空间下的类,如ManagementClass,ManagementObject等。下面用一个名为Win32ServiceManager的类把服务的一些相关操作包装了一下,代码如下:
using System;
using System.Management;
namespace ZZ.Wmi{
public class Win32ServiceManager{
private string strPath;
private ManagementClass managementClass;
public Win32ServiceManager():this(".",null,null){
}
public Win32ServiceManager(string host,string userName,string password){
this.strPath = "\\\\"+host+"\\root\\cimv2:Win32_Service";
this.managementClass = new ManagementClass(strPath);
if(userName!=null&&userName.Length>0){
ConnectionOptions connectionOptions = new ConnectionOptions();
connectionOptions.Username = userName;
connectionOptions.Password = password;
ManagementScope managementScope = new ManagementScope( "\\\\" +host+ "\\root\\cimv2",connectionOptions) ;
this.managementClass.Scope = managementScope;
}
}
// 验证是否能连接到远程计算机
public static bool RemoteConnectValidate(string host,string userName,string password){
ConnectionOptions connectionOptions = new ConnectionOptions();
connectionOptions.Username = userName;
connectionOptions.Password = password;
ManagementScope managementScope = new ManagementScope( "\\\\" +host+ "\\root\\cimv2",connectionOptions) ;
try{
managementScope.Connect();
}
catch{
}
return managementScope.IsConnected;
}
// 获取指定服务属性的值
public object GetServiceValue(string serviceName,string propertyName){
ManagementObject mo = this.managementClass.CreateInstance();
mo.Path = new ManagementPath(this.strPath+".Name=\""+serviceName+"\"");
return mo[propertyName];
}
// 获取所连接的计算机的所有服务数据
public string [,] GetServiceList(){
string [,] services = new string [this.managementClass.GetInstances().Count,4];
int i = 0;
foreach(ManagementObject mo in this.managementClass.GetInstances())
{
services[i,0] = (string)mo["Name"];
services[i,1] = (string)mo["DisplayName"];
services[i,2] = (string)mo["State"];
services[i,3] = (string)mo["StartMode"];
i++;
}
return services;
}
// 获取所连接的计算机的指定服务数据
public string [,] GetServiceList(string serverName){
return GetServiceList(new string []{serverName});
}
// 获取所连接的计算机的的指定服务数据
public string [,] GetServiceList(string [] serverNames){
string [,] services = new string [serverNames.Length,4];
ManagementObject mo = this.managementClass.CreateInstance();
for(int i = 0;i {
mo.Path = new ManagementPath(this.strPath+".Name=\""+serverNames[i]+"\"");
services[i,0] = (string)mo["Name"];
services[i,1] = (string)mo["DisplayName"];
services[i,2] = (string)mo["State"];
services[i,3] = (string)mo["StartMode"];
}
return services;
}
// 停止指定的服务
public string StartService(string serviceName){
string strRst = null;
ManagementObject mo = this.managementClass.CreateInstance();
mo.Path = new ManagementPath(this.strPath+".Name=\""+serviceName+"\"");
try
{
if((string)mo["State"]=="Stopped")//!(bool)mo["AcceptStop"]
mo.InvokeMethod("StartService",null);
}
catch(ManagementException e)
{
strRst =e.Message;
}
return strRst;
}
// 暂停指定的服务
public string PauseService(string serviceName){
string strRst = null;
ManagementObject mo = this.managementClass.CreateInstance();
mo.Path = new ManagementPath(this.strPath+".Name=\""+serviceName+"\"");
try
{
//判断是否可以暂停
if((bool)mo["acceptPause"]&&(string)mo["State"]=="Running")
mo.InvokeMethod("PauseService",null);
}
catch(ManagementException e)
{
strRst =e.Message;
}
return strRst;
}
// 恢复指定的服务
public string ResumeService(string serviceName){
string strRst = null;
ManagementObject mo = this.managementClass.CreateInstance();
mo.Path = new ManagementPath(this.strPath+".Name=\""+serviceName+"\"");
try
{
//判断是否可以恢复
if((bool)mo["acceptPause"]&&(string)mo["State"]=="Paused")
mo.InvokeMethod("ResumeService",null);
}
catch(ManagementException e)
{
strRst =e.Message;
}
return strRst;
}
// 停止指定的服务
public string StopService(string serviceName){
string strRst = null;
ManagementObject mo = this.managementClass.CreateInstance();
mo.Path = new ManagementPath(this.strPath+".Name=\""+serviceName+"\"");
try
{
//判断是否可以停止
if((bool)mo["AcceptStop"])//(string)mo["State"]=="Running"
mo.InvokeMethod("StopService",null);
}
catch(ManagementException e)
{
strRst =e.Message;
}
return strRst;
}
}
}
    在Win32ServiceManager中通过RemoteConnectValidate静态方法来测试连接成功与否;另外提供了GetServiceValue方法和GetServiceList方法以及它的重载来获取服务信息;后面的四个方法就是对服务的状态控制了。
    下面建立一个简单的窗口来使用它。
    大致的界面如下:


通过vs.net 2003可以很快做出上面的窗体,下面列出了一些增加的代码:
using ZZ.Wmi;
namespace ZZForm{
public class Form1 : System.Windows.Forms.Form{
//……
private Win32ServiceManager serviceManager;
public Form1(){
InitializeComponent();
this.serviceManager = null;
}
//……
[STAThread]
static void Main() {
Application.Run(new Form1());
}
//修改服务状态
private void buttonChangeState_Click(object sender, System.EventArgs e){
switch(((Button)sender).Text){
case "启动":
string startRst = this.serviceManager.StartService(this.listViewService.SelectedItems[0].SubItems[0].Text);
if(startRst==null)
MessageBox.Show("操作成功,请点击获取刷新按钮刷新结果!");
else
MessageBox.Show(startRst);
break;
case "暂停":
string startPause = this.serviceManager.PauseService(this.listViewService.SelectedItems[0].SubItems[0].Text);
if(startPause==null)
MessageBox.Show("操作成功,请点击获取刷新按钮刷新结果!");
else
MessageBox.Show(startPause);
break;
case "继续":
string startResume = this.serviceManager.ResumeService(this.listViewService.SelectedItems[0].SubItems[0].Text);
if(startResume==null)
MessageBox.Show("操作成功,请点击获取刷新按钮刷新结果!");
else
MessageBox.Show(startResume);
break;
case "停止":
string startStop = this.serviceManager.StopService(this.listViewService.SelectedItems[0].SubItems[0].Text);
if(startStop==null)
MessageBox.Show("操作成功,请点击获取刷新按钮刷新结果!");
else
MessageBox.Show(startStop);
break;
}
}

//获取和刷新数据
private void buttonLoadRefresh_Click(object sender, System.EventArgs e){
if(this.textBoxHost.Text.Trim().Length>0){
if(this.textBoxHost.Text.Trim()=="."){
this.serviceManager = new Win32ServiceManager();
}
else{
if(Win32ServiceManager.RemoteConnectValidate(this.textBoxHost.Text.Trim(),this.textBoxName.Text.Trim(),this.textBoxPassword.Text.Trim())){
this.serviceManager = new Win32ServiceManager(this.textBoxHost.Text.Trim(),this.textBoxName.Text.Trim(),this.textBoxPassword.Text.Trim());
}
else{
MessageBox.Show("连接到远程计算机验证错误.");
return;
}
}
string [,] services = serviceManager.GetServiceList();
this.listViewService.BeginUpdate();
this.listViewService.Items.Clear();
for(int i=0;i<services.GetLength(0);i++){
ListViewItem item = new ListViewItem(new string[]{services[i,0],services[i,1],services[i,2],services[i,3]});
this.listViewService.Items.Add(item);
}
this.listViewService.EndUpdate();
}
else
MessageBox.Show("请输入计算机名或IP地址");
}
}
}
    说明,其实一个服务的属性和方法除了上面这几个还有很多,我们可以通过实例化ManagementClass类,使用它的Properties属性和Methods属性列出所有的属性和方法。上面的Win32ServiceManager中生成的每个服务实例都是ManagementObejct类型的,其实还有一种强类型的类,可以通过编程和工具来生成。
    总结,通过引用System.Management命名空间,上面简单的实现了通过访问\root\cimv2:Win32_Service名称空间对服务进行显示和操作。此外,我们还可以通过访问其他名称空间来访问计算机的一些硬件信息,软件信息以及网络等,有兴趣的读者可以研究一下。

控制系统服务[Windows Services](http://www.rainsts.net/article.asp?id=76)

(任何引用请注明:转载于美人山下http://beautyhill.blogspot.com)
可以使用System.ServiceProcess.ServiceController类控制Windows Services。
CUI项目需要添加System.ServiceProcess引用。

注意服务名称和显示名称是不同的,我们调用时使用的是服务名称。在服务名称单击右键,其属性窗口中可看到详细信息。
如自动更新服务,“Automatic Updates”是显示名称,其服务名称是:wuauserv。

1. 获取全部服务及其状态
ServiceController[] scs = ServiceController.GetServices();
foreach (ServiceController sc in scs)
{
Console.WriteLine("{0}({1}); Status:{2}", sc.DisplayName, sc.ServiceName, sc.Status);
}

2. 检查服务是否已经安装
bool exists = false;
ServiceController[] scs = ServiceController.GetServices();
foreach (ServiceController sc in scs)
{
if (string.Compare(sc.ServiceName, "MSSQLSERVER", true) == 0)
{
exists = true;
break;
}
}

3. 检查服务状态
ServiceController sc = new ServiceController("MSSQLSERVER");
if (sc.Status == ServiceControllerStatus.Stopped)
Console.WriteLine("Stopped");

4. 启动或停止服务 (可能还要用到CanStop等属性)
ServiceController sc = new ServiceController("MSSQLSERVER");
if (sc.Status == ServiceControllerStatus.Stopped)
{
sc.Start();
sc.WaitForStatus(ServiceControllerStatus.Running);
}
else
{
sc.Stop();
sc.WaitForStatus(ServiceControllerStatus.Stopped);
}

2007年5月8日星期二

调用API注册和注销Windows Service (http://blog.csdn.net/Mittermeyer/archive/2004/01/04/11653.aspx)

(任何引用请注明:转载于美人山下http://beautyhill.blogspot.com)
0、写在前面
DotNET平台下的类库封装的相当完善,普通的应用完全可以利用类库完成所有的工作。对于Windows Service的支持也是一样,只需要继承DotNET下提供的ServiceBase就可以创建Windows的Service,调用ServiceControl类的方法就可以控制Service的启动和关闭,非常容易。
然而生成了一个Service类型的应用程序之后,必须在SCM(Service Control Manager)中注册,才能够被当作一个Service被系统调用。但是对于Service的注册和注销却比较麻烦,DotNET的确也提供了对应的类ServiceInstaller和ServiceProcessInstaller,但是只能够和Installer的安装过程集成使用,不能够单独在普通应用程序中调用,也就是说为了安装一个Service用作测试,我不得不创建一个安装包。
怎样利用API向SCM注册以及注销一个Service呢,下面就简单的介绍一下。

1、实现概述
打开Control Panel中Service的管理工具,可以看到本机所有已注册的Service,选择一个Service的属性会发现一个Service有很多设置项:名称、描述、类型、运行帐号等。所有这些都可以通过调用API函数来设定。
在Service的API中,每一个服务的实例通过句柄来表示,而需要服务的句柄之前必须要先要的到SCM的句柄(Handle),所以所有的调用都是通过OpenSCManager开始,成功的调用OpenSCManager后将获得一个SCM的句柄。如果是注册Service,那么利用SCM句柄调用CreateService来创建一个新的服务;如果是注销Service,则调用OpenService函数,获得一个已经存在的Service的句柄,然后利用这个服务句柄调用DeleteService注销服务。最后通过CloseServiceHandle关闭Service的句柄和SCM的句柄。
过程很简单,在CSharp下比较麻烦的地方就是API的声明,下面会一一提到。

2、注册Service
前面已经提到注册Service一共用到了三个API,OpenSCManager、CreateService和CloseServiceHandle。使用之前先介绍一下这些API的声明。
[DllImport("advapi32.dll")]
public static extern System.IntPtr OpenSCManager(
System.String lpMachineName,
System.String lpDatabaseName,
System.UInt32 dwDesiredAccess
);
[DllImport("advapi32.dll",EntryPoint = "CreateServiceA")]
public static extern System.IntPtr CreateService(
System.IntPtr hSCManager,
System.String lpServiceName,
System.String lpDisplayName,
System.UInt32 dwDesiredAccess,
System.UInt32 dwServiceType,
System.UInt32 dwStartType,
System.UInt32 dwErrorControl,
System.String lpBinaryPathName,
System.String lpLoadOrderGroup,
System.IntPtr lpdwTagId,
System.String lpDependencies,
System.String lpServiceStartName,
System.String lpPassword
);
[DllImport("advapi32.dll")]
public static extern System.Boolean CloseServiceHandle(
System.IntPtr hSCObject
);
这个了关于Service的API都是在Advapi32.dll中实现的,函数的原型可以自行查找头文件,在Winsvc和Winbase中。
熟悉Windows编程的话一定会了解,句柄类型就是一个32位的整型,在DotNET下用IntPtr来声明,DWORD对应UINT32,LPCSTR对应String类型,唯一需要强调的是CreateService这个函数的lpdwTagId,是一个DWORD*,这里声明也IntPtr,因为在调用中绝大多数情况下传递NULL值,如果用out UINT32,无法传递NULL值。
仔细看CreateService中声明可以发现,这个函数真的可以做很多事情,其中包括Service的名称(lpServiceName,服务的标识,调用OpenSerive等函数时用到)、显示名(lpDisplayName,就是我们在Service的管理工具中看到的名称)、服务类型(dwServiceType,指定服务的运行方式:独立进程、共享进程、驱动程序还是交互式登录模式等等)、启动类型(dwStartType,自动、手动还是禁止等等)、服务失败的严重性(dwErrorControl)、实现服务代码的二进制文件的路径(lpBinaryPathName)、加载顺序组的名称(lpLoadOrderGroup)、接受Tag标志码(lpdwTagId)、依赖服务的名称组(lpDependencies)、启动服务的帐号(lpServiceStartName,如果为NULL,表示使用LocalSystem)、启动服务帐号的口令(lpPassword)。如果调用成功,那么将返回一个非0的句柄,表示服务注册成功。
看了上面的一系列属性说明,大家也许发现还少了一样,就是在Service的管理工具中最长最醒目的一栏Description,Description的设置需要调用另一个API,如下:
public static extern System.Boolean ChangeServiceConfig2(
System.IntPtr hService,
System.UInt32 dwInfoLevel,
ref System.String lpInfo
);
其中lpInfo的声明原型是一个LPVOID,如果设置Description属性的话指向的是一个结构:
typedef struct _SERVICE_DESCRIPTION {
LPTSTR lpDescription;
} SERVICE_DESCRIPTION, *LPSERVICE_DESCRIPTION;
这个结构里面包含了一个字符指针,也就是说需要在函数调用时传递一个指向字符指针的指针,偷懒起见,ref System.String就足够了,无需再定义结构了,呵呵!
完整的例子如下,常量定义见本文最后:
private static System.Boolean RegistService()
{
System.Boolean fRet = false;
System.IntPtr hServiceManager = IntPtr.Zero,hService = IntPtr.Zero;
System.String sServicePath = null,sDesc = null;

sServicePath = Application.StartupPath + @"\sampleservice.exe";
hServiceManager = OpenSCManager(Environment.MachineName,null,SC_MANAGER_ALL_ACCESS);
if (hServiceManager != IntPtr.Zero)
{
hService = CreateService(hServiceManager,"sampleservice","Sample Service",SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,
sServicePath,null,IntPtr.Zero,null,null,null);
if (hService != IntPtr.Zero)
{
sDesc = "This is a sample service.";
fRet = ChangeServiceConfig2(hService,SERVICE_CONFIG_DESCRIPTION,ref sDesc);
CloseServiceHandle(hService);
hService = IntPtr.Zero;
}
CloseServiceHandle(hServiceManager);
hServiceManager = IntPtr.Zero;
}
return fRet;
}

3、注销Service
相对于注册,注销就简单很多了,用到了OpenSCManager、OpenService、DeleteService和CloseServiceHandle四个API,另两个API的声明如下:
[DllImport("advapi32.dll")]
public static extern System.IntPtr OpenService(
System.IntPtr hSCManager,
System.String lpServiceName,
System.UInt32 dwDesiredAccess
);
[DllImport("advapi32.dll")]
public static extern System.Boolean DeleteService(
System.IntPtr hService
);
比较简单没什么可多说的看一个完整的例子:
private static System.Boolean UnRegistService()
{
System.Boolean fRet = false;
System.IntPtr hServiceManager = IntPtr.Zero,hService = IntPtr.Zero;

hServiceManager = OpenSCManager(Environment.MachineName,null,SC_MANAGER_ALL_ACCESS);
if (hServiceManager != IntPtr.Zero)
{
hService = OpenService(hServiceManager,"sampleservice",SERVICE_ALL_ACCESS);
if (hService != IntPtr.Zero)
{
fRet = DeleteService(hService);
CloseServiceHandle(hService);
hService = IntPtr.Zero;
}
CloseServiceHandle(hServiceManager);
hServiceManager = IntPtr.Zero;
}
return fRet;
}

4、最后
可以在实现Service的工程中增加一下额外的处理,Main函数中判断一下调用参数,如果是“-I”,则表示注册Service,如果是“-U”表示注销Service,反之则作为Service正常运行。

5、API和常量的声明
本文所用到的所有API和常量如下:
// declare APIs for service
[DllImport("advapi32.dll")]
public static extern System.IntPtr OpenSCManager(
System.String lpMachineName,
System.String lpDatabaseName,
System.UInt32 dwDesiredAccess
);
[DllImport("advapi32.dll",EntryPoint = "CreateServiceA")]
public static extern System.IntPtr CreateService(
System.IntPtr hSCManager,
System.String lpServiceName,
System.String lpDisplayName,
System.UInt32 dwDesiredAccess,
System.UInt32 dwServiceType,
System.UInt32 dwStartType,
System.UInt32 dwErrorControl,
System.String lpBinaryPathName,
System.String lpLoadOrderGroup,
System.IntPtr lpdwTagId,
System.String lpDependencies,
System.String lpServiceStartName,
System.String lpPassword
);
[DllImport("advapi32.dll")]
public static extern System.IntPtr OpenService(
System.IntPtr hSCManager,
System.String lpServiceName,
System.UInt32 dwDesiredAccess
);
[DllImport("advapi32.dll")]
public static extern System.Boolean DeleteService(
System.IntPtr hService
);
[DllImport("advapi32.dll")]
public static extern System.Boolean CloseServiceHandle(
System.IntPtr hSCObject
);
[DllImport("advapi32.dll")]
public static extern System.Boolean ChangeServiceConfig2(
System.IntPtr hService,
System.UInt32 dwInfoLevel,
ref System.String lpInfo
);

public const System.UInt32 STANDARD_RIGHTS_REQUIRED = 0xF0000;
// Service Control Manager object specific access types
public const System.UInt32 SC_MANAGER_CONNECT = 0x0001;
public const System.UInt32 SC_MANAGER_CREATE_SERVICE = 0x0002;
public const System.UInt32 SC_MANAGER_ENUMERATE_SERVICE = 0x0004;
public const System.UInt32 SC_MANAGER_LOCK = 0x0008;
public const System.UInt32 SC_MANAGER_QUERY_LOCK_STATUS = 0x0010;
public const System.UInt32 SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020;
public const System.UInt32 SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED |
SC_MANAGER_CONNECT |
SC_MANAGER_CREATE_SERVICE |
SC_MANAGER_ENUMERATE_SERVICE |
SC_MANAGER_LOCK |
SC_MANAGER_QUERY_LOCK_STATUS |
SC_MANAGER_MODIFY_BOOT_CONFIG;
// Service object specific access type
public const System.UInt32 SERVICE_QUERY_CONFIG = 0x0001;
public const System.UInt32 SERVICE_CHANGE_CONFIG = 0x0002;
public const System.UInt32 SERVICE_QUERY_STATUS = 0x0004;
public const System.UInt32 SERVICE_ENUMERATE_DEPENDENTS = 0x0008;
public const System.UInt32 SERVICE_START = 0x0010;
public const System.UInt32 SERVICE_STOP = 0x0020;
public const System.UInt32 SERVICE_PAUSE_CONTINUE = 0x0040;
public const System.UInt32 SERVICE_INTERROGATE = 0x0080;
public const System.UInt32 SERVICE_USER_DEFINED_CONTROL = 0x0100;
public const System.UInt32 SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED |
SERVICE_QUERY_CONFIG |
SERVICE_CHANGE_CONFIG |
SERVICE_QUERY_STATUS |
SERVICE_ENUMERATE_DEPENDENTS |
SERVICE_START |
SERVICE_STOP |
SERVICE_PAUSE_CONTINUE |
SERVICE_INTERROGATE |
SERVICE_USER_DEFINED_CONTROL;
// service type
public const System.UInt32 SERVICE_KERNEL_DRIVER = 0x00000001;
public const System.UInt32 SERVICE_FILE_SYSTEM_DRIVER = 0x00000002;
public const System.UInt32 SERVICE_ADAPTER = 0x00000004;
public const System.UInt32 SERVICE_RECOGNIZER_DRIVER = 0x00000008;
public const System.UInt32 SERVICE_DRIVER = SERVICE_KERNEL_DRIVER |
SERVICE_FILE_SYSTEM_DRIVER |
SERVICE_RECOGNIZER_DRIVER;
public const System.UInt32 SERVICE_WIN32_OWN_PROCESS = 0x00000010;
public const System.UInt32 SERVICE_WIN32_SHARE_PROCESS = 0x00000020;
public const System.UInt32 SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS |
SERVICE_WIN32_SHARE_PROCESS;
public const System.UInt32 SERVICE_INTERACTIVE_PROCESS = 0x00000100;
public const System.UInt32 SERVICE_TYPE_ALL = SERVICE_WIN32 |
SERVICE_ADAPTER |
SERVICE_DRIVER |
SERVICE_INTERACTIVE_PROCESS;
// Start Type
public const System.UInt32 SERVICE_BOOT_START = 0x00000000;
public const System.UInt32 SERVICE_SYSTEM_START = 0x00000001;
public const System.UInt32 SERVICE_AUTO_START = 0x00000002;
public const System.UInt32 SERVICE_DEMAND_START = 0x00000003;
public const System.UInt32 SERVICE_DISABLED = 0x00000004;
// Error control type
public const System.UInt32 SERVICE_ERROR_IGNORE = 0x00000000;
public const System.UInt32 SERVICE_ERROR_NORMAL = 0x00000001;
public const System.UInt32 SERVICE_ERROR_SEVERE = 0x00000002;
public const System.UInt32 SERVICE_ERROR_CRITICAL = 0x00000003;
// Info levels for ChangeServiceConfig2 and QueryServiceConfig2
public const System.UInt32 SERVICE_CONFIG_DESCRIPTION = 1;
public const System.UInt32 SERVICE_CONFIG_FAILURE_ACTIONS = 2;

C# windows service承載遠程對象(http://www.cnblogs.com/snowlove67/archive/2006/03/22/356089.html)

(任何引用请注明:转载于美人山下http://beautyhill.blogspot.com)
參考文章:https://www.microsoft.com/china/msdn/library/architecture/architecture/architecturetopic/BuildSucApp/BSAAsecmodsecmod29.mspx?mfr=true

1.首先寫接口定義遠程對象需要操作的方法,這個接口定義在單獨的類庫,供windowsservice引用和遠程客戶端引用.
public interface IRemoteObjectMethod
{
void DoSth();
}

2.然後建立windows service 工程,添加實現遠程對象接口的類.
public class RemoteObject : MarshalByRefObject, IRemoteObjectMethod
{
public RemoteObject(){}
// 添加方法的具體實現
}

3.添加註冊遠程對象配置節到app.config.
如下:
<system.runtime.remoting>
<application name="ServerToClientService">
<service>
<wellknow type="DownLoaderService.MarshalDownSyncObject, DownLoaderService" objectUri="DownSyncObject" mode="singlecall"/>
</service>
<channels>
<channel ref="tcp" port="6767">
<serverProviders>
<formatter ref="binary"/>
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>

4.在windows service的開始方法裏面添加註冊遠程對象的代碼.
protected override void OnStart(string[] args)
{
RemotingConfiguration.Configure (AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
}

5.安裝windows服務.這個就不必詳細寫了.

至此,windows service的部分已經完成,接下來的都是在遠程客戶機上的操作.

6.添加獲取遠程對象的類和配置文件.
public class RemoteObjectFactory
{
// 承載遠程對象的服務名稱
private static string svc_Name = ConfigurationSettings.AppSettings["ServiceName"];
// 遠程服務所在的ip地址
private static string svc_ipAddress = ConfigurationSettings.AppSettings["ServiceIP"];
// 遠程對象註冊時的端口號
private static int svc_port = int.Parse(ConfigurationSettings.AppSettings["ServicePort"]);
// 遠程對象的標識
private static string svc_objectUri = ConfigurationSettings.AppSettings["ObjectUri"];
// 遠程連接通道的類型
private static string channelType = ConfigurationSettings.AppSettings["ChannelType"];


public static IRemoteObjectMethod GetServerSyncObject()
{
string url = channelType + "://" + svc_ipAddress + ":" + svc_port.ToString() + "/" + svc_Name + "/" + svc_objectUri;
IRemoteObjectMethod obj = (IRemoteObjectMethod)Activator.GetObject (typeof (IRemoteObjectMethod), url);
if (obj == null)
{
throw new Exception("Service not exists or Remoteobject not exists!");
}
return obj;
}
}
創建遠程對象獲取的方法,並在app.config添加相應的配置項.

7.遠程客戶端調用
IRemoteObjectMethod remoteObject = RemoteObjectFactory.GetServerSyncObject();
remoteObject.DoSth();

這樣似乎可以比較輕鬆的實現遠程方法的調用:)

用Visual C#创建Windows服务程序(http://book.chinaz.com/net/asp.net1/dot42.htm)

(任何引用请注明:转载于美人山下http://beautyhill.blogspot.com)
一.Windows服务介绍:
Windows服务以前被称作NT服务,是一些运行在Windows NT、Windows 2000和Windows XP等操作系统下用户环境以外的程序。在以前,编写Windows服务程序需要程序员很强的C或C++功底。然而现在在Visual
Studio.Net下,你可以运用C++或Visual C#或Visual Basic.Net很轻松的创建一个Windows服务程序。同样,你还可以运用其他任何与CLR相容的语言来创建Windows服务程序。本文就向大家介绍如何运用Visual
C#来一步一步创建一个文件监视的Windows服务程序,然后介绍如何安装、测试和调试该Windows服务程序。

在介绍如何创建Windows服务程序以前,我先向大家介绍一些有关Windows服务的背景知识。一个Windows服务程序是在Windows操作系统下能完成特定功能的可执行的应用程序。Windows服务程序虽然是可执行的,但是它不像一般的可执行文件通过双击就能开始运行了,它必须有特定的启动方式。这些启动方式包括了自动启动和手动启动两种。对于自动启动的Windows服务程序,它们在Windows启动或是重启之后用户登录之前就开始执行了。只要你将相应的Windows服务程序注册到服务控制管理器(Service Control Manager)中,并将其启动类别设为自动启动就行了。而对于手动启动的Windows服务程序,你可以通过命令行工具的NET START 命令来启动它,或是通过控制面板中管理工具下的服务一项来启动相应的Windows服务程序(见图1)。

同样,一个Windows服务程序也不能像一般的应用程序那样被终止。因为Windows服务程序一般是没有用户界面的,所以你也要通过命令行工具或是下面图中的工具来停止它,或是在系统关闭时使得Windows服务程序自动停止。因为Windows服务程序没有用户界面,所以基于用户界面的API函数对其是没有多大的意义。为了能使一个Windows服务程序能够正常并有效的在系统环境下工作,程序员必须实现一系列的方法来完成其服务功能。Windows服务程序的应用范围很广,典型的Windows服务程序包含了硬件控制、应用程序监视、系统级应用、诊断、报告、Web和文件系统服务等功能。


二.创建Windows服务程序:
在介绍如何创建Windows服务程序以前,我先向大家介绍一下.Net框架下与Windows服务相关的命名空间和其中的类库。.Net框架大大地简化了Windows服务程序的创建和控制过程,这要归功于其命名空间中的功能强大的类库。和Windows服务程序相关的命名空间涉及到以下两个:System.ServiceProcess和System.Diagnostics。

要创建一个最基本的Windows服务程序,我们只需要运用.Net框架下的System.ServiceProcess命名空间以及其中的四个类:ServiceBase、ServiceInstaller、ServiceProcessInstaller以及ServiceController,其体系结构可见图2。


其中ServiceBase类定义了一些可被其子类重载的函数,通过这些重载的函数,服务控制管理器就可以控制该Windows服务程序了。这些函数包括:OnStart()、OnStop()、OnPause()以及OnContinue()等四个。而且ServiceBase类的子类还可以重载OnCustomCommand()函数来完成一些特定的操作。通过重载以上的一些函数,我们就完成了一个Windows服务程序的基本框架,这些函数的重载方法如下:
protected override void OnStart(string[] args){
}
protected override void OnStop(){
}
protected override void OnPause(){
}
protected override void OnContinue(){
}

ServiceBase类还为我们提供了一些属性,而这些属性是任何Widnows服务程序所必须的。其中的ServiceName属性指定了Windows服务的名称,通过该名称系统就可以调用Windows服务了,同时其它应用程序也可以通过该名称来调用它的服务。而CanPauseAndContinue和CanStop属性顾名思义就是允许暂停并恢复和允许停止的意思。

要使得一个Windows服务程序能够正常运行,我们需要像创建一般应用程序那样为它创建一个程序的入口点。在Windows服务程序中,我们也是在Main()函数中完成这个操作的。首先我们在Main()函数中创建一个Windows服务的实例,该实例应该是ServiceBase类的某个子类的对象,然后我们调用由基类ServiceBase类定义的一个Run()方法。然而Run()方法并不就开始了Windows服务程序,我们必须通过前面提到的服务控制管理器调用特定的控制功能来完成Windows服务程序的启动,也就是要等到该对象的OnStart()方法被调用时服务才真正开始运行。如果你想在一个Windows服务程序中同时启动多个服务,那么只要在Main()函数中定义多个ServiceBae类的子类的实例对象就可以了,方法就是创建一个ServiceBase类的数组对象,使得其中的每个对象对应于某个我们已预先定义好的服务。
static void Main(string[] args){
System.ServiceProcess.ServiceBase[] MyServices;
MyServices = new System.ServiceProcess.ServiceBase[] { new Service1(), new Service2() };
System.ServiceProcess.ServiceBase.Run(MyServices);
}

三.添加文件监视服务
了解了Windows服务的基本体系结构和创建方法后,我们就可以试着往服务中添加一些实际的功能了。下面我将向大家介绍一个能监视本地文件系统的文件监视服务-FileMonitorService。该服务能根据预先设定的本地目录路径监视其中的文件包括子文件夹中的任何变化:文件创建、文件删除、文件改名、文件修改。同时,该服务还为每种变化创建了一个相对应的计数器,计数器的作用就是反映该种变化的频度。
首先,我们打开Visual Studio.Net,新建一个Visual C#的Windows服务的项目,如图3所示:

在重载Windows服务的OnStart()函数之前,我们先给其类添加一些计数器对象,这些计数器分别对应了文件的创建、删除、改名以及修改等变化。一旦指定目录中的文件发生以上的某种变化,与其相对应的计数器就会自动加1。所有的这些计数器都是定义为PerformanceCounter类型的变量的,该类是包含在System.Diagnostics命名空间中的。
private System.Diagnostics.PerformanceCounter fileCreateCounter;
private System.Diagnostics.PerformanceCounter fileDeleteCounter;
private System.Diagnostics.PerformanceCounter fileRenameCounter;
private System.Diagnostics.PerformanceCounter fileChangeCounter;

InitializeComponent()方法中创建以上定义的各个计数器对象并确定其相关属性。同时我们将该Windows服务的名称设置为“FileMonitorService”,设定其即是允许暂停并恢复的又是允许停止的。
private void InitializeComponent(){
this.components = new System.ComponentModel.Container(); this.fileChangeCounter = new System.Diagnostics.PerformanceCounter();
this.fileDeleteCounter = new System.Diagnostics.PerformanceCounter();
this.fileRenameCounter = new System.Diagnostics.PerformanceCounter();
this.fileCreateCounter = new System.Diagnostics.PerformanceCounter();
fileChangeCounter.CategoryName = "File Monitor Service";
fileDeleteCounter.CategoryName = "File Monitor Service";
fileRenameCounter.CategoryName = "File Monitor Service";
fileCreateCounter.CategoryName = "File Monitor Service";
fileChangeCounter.CounterName = "Files Changed";
fileDeleteCounter.CounterName = "Files Deleted";
fileRenameCounter.CounterName = "Files Renamed";
fileCreateCounter.CounterName = "Files Created";
this.ServiceName = "FileMonitorService";
this.CanPauseAndContinue = true;
this.CanStop = true;
servicePaused = false;
}

接着就是重载OnStart()函数和OnStop()函数,OnStart()函数完成了一些必要的初始化工作。在.Net框架下,文件的监视功能可以由FileSystemWatcher类来完成,该类是包含在System.IO命名空间下的。该Windows服务所要完成的功能包括了监视文件的创建、删除、改名和修改等变化,而FileSystemWatcher类包含所有了对应于这些变化的处理函数。
protected override void OnStart(string[] args){
FileSystemWatcher curWatcher = new FileSystemWatcher();
curWatcher.BeginInit();
curWatcher.IncludeSubdirectories = true;
curWatcher.Path = System.Configuration.ConfigurationSettings.AppSettings["FileMonitorDirectory"];
curWatcher.Changed += new FileSystemEventHandler(OnFileChanged);
curWatcher.Created += new FileSystemEventHandler(OnFileCreated);
curWatcher.Deleted += new FileSystemEventHandler(OnFileDeleted);
curWatcher.Renamed += new RenamedEventHandler(OnFileRenamed);
curWatcher.EnableRaisingEvents = true;
curWatcher.EndInit();
}

注意其中被监视的目录是存放在一个应用程序配置文件中的,该文件是一个XML类型的文件。这种做法的好处就是我们不必重新编译并发布该Windows服务而只要直接修改其配置文件就可以达到更改所要监视的目录的功能了。

当该Windows服务启动后,一旦被监视的目录中的文件发生某种变化,与其相对应的计数器的值便会相应的增加,方法很简单,只要调用计数器对象的IncrementBy()即可。
private void OnFileChanged(Object source, FileSystemEventArgs e){
if( servicePaused == false ){
fileChangeCounter.IncrementBy(1);
}
}
private void OnFileRenamed(Object source, RenamedEventArgs e){
if( servicePaused == false ){
fileRenameCounter.IncrementBy(1);
}
}
private void OnFileCreated(Object source, FileSystemEventArgs e){
if( servicePaused == false ){
fileCreateCounter.IncrementBy(1);
}
}
private void OnFileDeleted(Object source, FileSystemEventArgs e){
if( servicePaused == false ){
fileDeleteCounter.IncrementBy(1);
}
}

OnStop()函数即是停止Windows服务的,在该Windows服务中,服务一旦停止,所有的计数器的值都应归零,但是计数器并不提供一个Reset()方法,所以我们只好将计数器中的值减去当前值来达到这个目的。
protected override void OnStop(){
if( fileChangeCounter.RawValue != 0 ){
fileChangeCounter.IncrementBy(-fileChangeCounter.RawValue);
}
if( fileDeleteCounter.RawValue != 0 ){
fileDeleteCounter.IncrementBy(-fileDeleteCounter.RawValue);
}
if( fileRenameCounter.RawValue != 0 ){
fileRenameCounter.IncrementBy(-fileRenameCounter.RawValue);
}
if( fileCreateCounter.RawValue != 0 ){
fileCreateCounter.IncrementBy(-fileCreateCounter.RawValue);
}
}

同时,因为我们的Windows服务是允许暂停并恢复的,所以我们还得重载OnPause()函数和OnContinue()函数,方法很简单,只要设定前面定义的布尔值servicePaused即可。
protected override void OnPause(){
servicePaused = true;
}
protected override void OnContinue(){
servicePaused = false;
}

这样,该Windows服务的主体部分已经完成了,不过它并不有用,我们还必须为其添加安装文件。安装文件为Windows服务的正确安装做好了工作,它包括了一个Windows服务的安装类,该类是重System.Configuration.Install.Installer继承过来的。安装类中包括了Windows服务运行所需的帐号信息,用户名、密码信息以及Windows服务的名称,启动方式等信息。
[RunInstaller(true)]
public class Installer1 : System.Configuration.Install.Installer{
///


/// 必需的设计器变量。
///

private System.ComponentModel.Container components = null;
private System.ServiceProcess.ServiceProcessInstaller spInstaller;
private System.ServiceProcess.ServiceInstaller sInstaller;
public Installer1(){
// 该调用是设计器所必需的。
InitializeComponent();
// TODO: 在 InitComponent 调用后添加任何初始化
}
#region Component Designer generated code
///
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
///

private void InitializeComponent(){
components = new System.ComponentModel.Container();
// 创建ServiceProcessInstaller对象和ServiceInstaller对象
this.spInstaller = new System.ServiceProcess.ServiceProcessInstaller();
this.sInstaller = new System.ServiceProcess.ServiceInstaller();
// 设定ServiceProcessInstaller对象的帐号、用户名和密码等信息
this.spInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
this.spInstaller.Username = null;
this.spInstaller.Password = null;
// 设定服务名称
this.sInstaller.ServiceName = "FileMonitorService";
// 设定服务的启动方式
this.sInstaller.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
this.Installers.AddRange(new System.Configuration.Install.Installer[]{this.spInstaller, this.sInstaller });
}
#endregion
}

同样,因为该Windows服务中运用到了计数器对象,我们也要为其添加相应的安装文件,安装文件的内容和作用与前面的类似。限于篇幅,这里就不给出相应的代码了,有兴趣的读者可以参考文后附带的源代码文件。
到此为止,整个Windows服务已经构建完毕,不过Windows服务程序和一般的应用程序不同,它不能直接调试运行。如果你直接在IDE下试图调试运行之,就会报出如图4所示提示。


根据其中提示,我们知道安装Windows服务需要用到一个名为InstallUtil.exe的命令行工具。而运用该工具安装Windows服务的方法是非常简单的,安装该Windows服务的命令如下:
installutil FileMonitorService.exe

而要卸载该Windows服务,你只要输入如下的命令即可:
installutil /u FileMonitorService.exe

Windows服务安装成功后,它便会出现在服务控制管理器中,如图5所示。


这样,该文件监视的Windows服务就完成了,一旦我们对被监视的目录中的文件进行操作,相应的计数器就会运作,起到监视文件变化的作用。不过这个功能对于一般的用户而言没多大意义,然而你可以在此基础上添加新的功能,比如构建一个后台的文件处理系统,一旦被监视的目录中的文件发生某种变化,Windows服务便对其进行特定的操作,而最终用户就不必去关心后台处理程序是如何实现的了。

四.总结
本文向大家介绍了Windows服务的一些基本概念和构建一般的Windows服务所需的方法,同时还向大家展示了一个具有文件监视功能的Windows服务程序。通过本文,读者应该能体会到构建Windows服务并不是想象中的那么复杂,这主要还得归功于.Net框架为我们所作的大量努力。同时,希望大家能在本文给出的实例的基础上构建更加完善和更加强大的Windows服务程序。最后希望本文对大家能有不少帮助。