Google Calendar Bug

修改C:Documents and Settings[your username]Application DataMozillaFirefoxProfileseuaxxzw1.defaultextensions{9BE36B8C-F2A0- 41d1-AA51-402328BE8496}chromegoocal.jar包里面: contentgoocal.js

57行
window.removeEventListenter("load", gc.onload, false);
=>
window.removeEventListener("load", gc.onload, false);

也是因为影响不大,google测试也没发现

System.Transactions:实现你自己的Resource Manager

System.Transactions:实现你自己的Resource Manager

By Sahil Malik

.net 2.0所带来最大的变化之一也许就是System.Transactions命名空间的引入。在我以前关于SQLCLR的文章中,也简略的提到过这个命名空间。但是谁说事务的概念只能局限于数据库?难道我们在数据库以外的领域就不需要可靠的代码了么?答案是,我们当然需要! 这正是你不能忽视System.Transactions命名空间的原因,也是她将在下一代微软平台引起深刻变革的原因。让我们首先来看看 System.Transactions是如何工作的。

System.Transactions如何工作?

System.Transactions为您提供最简洁明了的方式来实现事务方式的各种操作。你当然可以用各种各样的方式使用她,但也许最典型的一个实现就是如下,将事务操作包裹在TransactionScope(用来构建代码段事务的一个实例变量)里面:

using (TransactionScope ts = new TransactionScope())
{
// 事务代码放这里
ts.Complete();
}

在上面注释的部分里面,你可以使用持有或实现了resource manager(RM)的类。

事务工作于各种被RM管理着的资源上。RM同其他实体(典型的包括:其他进程或者被称为transaction managers(TMs)的服务)协同工作。

RM与TM在事务中协同工作的方式常被称为”两段提交过程”。

下面是典型的“两段提交过程”工作流:

1.RM加入事务;
2.RM做完准备工作后,向TM发送第一阶段完成信号。这个阶段也被称为”准备阶段”;
3.TM在所有RMs成功执行完准备工作后,向所有RMs发送绿灯信号;
4.RMs获得绿灯信号后实际开始提交工作(如果收到红灯信号,则回滚他们自己的工作)。这是第二个阶段–“提交阶段”;
5.否则,事务协调器同所有RMs进行协调以保证他们要么成功,要么一起回滚;

上面提到的整个过程都由System.Transactions框架提供,例如,使用MSDTC(Microsoft分布式事务协调器)作为TMs。为了使用System.Transactions来管理参与事务操作的资源,你必须要么使用现有的RM或者实现你自己的RM。

就目前而论, System.Transactions已可以使用,但实际能应用的RMs却很少,所以很多程序员可能会发现实现自己的RM非常有用。在这之后,可以将这些RMs同未来.net框架中或第三方提供的TM协同工作。另外,同.net 2.0一同发布的一个很常用的RM就是SqlConnection类。本文中会以此类和一个自定义的RM为例,描述它们究竟如何同TM一起工作。但是,如果你确实不得不实现自己的RM,最重要的问题恐怕就是–“RM究竟要做些什么?”

RM究竟要做些什么?

影响实现你自己的RM的一个关键问题就是:这个RM管理的资源本身的特性是持久的还是易变的?

在典型的”两段提交过程”中,持久的资源需要”失败恢复”。一个很好的例子就是事务文件拷贝。如果你不得不实现一个RM来封装拷贝文件的操作到一个事务中,文件可以在第一个阶段被实际拷贝。如果RM失败,恢复策略将保证在回滚过程中,TM有足够的信息来恢复原来的状态。具体来说,可能就是指删除文件或者用以前存在的文件来代替。这种资源的收集操作,使用System.Transactions.Transaction.EnlistDurable方法。

反之,易变资源是不需要”失败恢复”的。同样一个很好的例子,就是持有在内存中的数据。封装这类操作到事务中的RM使用System.Transactions.Transaction.EnlistVolatile方法。

另外一种收集资源的方式是PSPE(可提升的单段式收集)。这种方式由持久的RM拥有事务,当条件改变时,可以逐步升级为由TM管理。因为持久的资源收集比易变的资源收集代价更大,PSPE提供了一种很有吸引力的持久资源收集的实现方式,但是这种方式在许多地方是以牺牲性能为代价的。

在选好资源收集的方式后,接下来你需要实现IEnlistmentNotification接口,以使你从TM获得必要的回调来进行恰当的提交或回滚。 Windows平台的TMs的例子是适合易变资源收集的LTM(轻量级的事务管理器)以及适合分布式或者持久资源收集的MSDTC。

话不多说,下面的例子将向你示范如何实现这些概念。

使用易变资源收集方式实现RM

下面的例子示范了在事务中写成员变量的RM:

public class VolatileRM : IEnlistmentNotification
{
private int memberValue = 0;
private int oldMemberValue = 0;
public int MemberValue
{
get { return memberValue; }
}
public void SetMemberValue(int newMemberValue)
{
Transaction currentTx = Transaction.Current;
if (currentTx != null)
{
Console.WriteLine("VolatileRM: SetMemberValue - EnlistVolatile");
currentTx.EnlistVolatile(this, EnlistmentOptions.None);
}
oldMemberValue = memberValue;
memberValue = newMemberValue;
}
IEnlistmentNotification Members#region IEnlistmentNotification Members
public void Commit(Enlistment enlistment)
{
Console.WriteLine("VolatileRM: Commit");
// Clear out oldMemberValue
oldMemberValue = 0;
}
public void InDoubt(Enlistment enlistment)
{
Console.WriteLine("VolatileRM: InDoubt");
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
Console.WriteLine("VolatileRM: Prepare");
preparingEnlistment.Prepared();
}
public void Rollback(Enlistment enlistment)
{
Console.WriteLine("VolatileRM: Rollback");
// Restore previous state
memberValue = oldMemberValue;
oldMemberValue = 0;
}
}

正如你所看到的那样,我们在SetMemberValue中首先检查了现在是否在事务中。如果是的话,它如下所示,将自己收集进易变事务中,并且为可能的回滚做一些必要的操作:

public void SetMemberValue(int newMemberValue)
{
Transaction currentTx = Transaction.Current;
if (currentTx != null)
{
Console.WriteLine("VolatileRM: SetMemberValue - EnlistVolatile");
currentTx.EnlistVolatile(this, EnlistmentOptions.None);
}
oldMemberValue = memberValue;
memberValue = newMemberValue;
}

示例中也实现了一系列其他的方法。提交(Commit)、回滚(RollBack),顾名思义,要么提交工作,要么回滚工作。TM调用Prepare方法来通知RM它正请求第一阶段的事务提交工作,并且测试你的RM是否为工作做好了准备。这里,你有下面三种选择:

1.调用preparingEnlistment.Prepare()方法。

这通知TM你已做好准备,并且正等待下一步提交的通知。

2.调用preparingEnlistment.ForceRollBack()方法或者指定preparingEnlistment.RecoveryInformation。

在重新收集资源的时候,这将提供新的RM实例和完成恢复工作所需要的充足的信息。但是现在你还不必为此担心。

3.调用preparingEnlistment.Done()方法。

这将RM作为旁观者。只观察事务,但并不参与其中。通过调用Done方法,TM不会给你第二阶段的通知。

既然你已经建好了RM,你现在能够依赖框架来编写简单可靠的代码。使用上面的RM是相当轻松惬意的一件事情。例如,键入下面的代码:

VolatileRM vrm = null ;
using (TransactionScope txSc = new TransactionScope())
{
vrm = new VolatileRM();
vrm.SetMemberValue(3);
txSc.Complete();
}
Console.WriteLine("Member Value:" + vrm.MemberValue);

上面代码产生下面的输出:

VolatileRM: SetMemberValue - EnlistVolatile
VolatileRM: Prepare
VolatileRM: Commit
Member Value:3

如你所见,成员值被更新了,并且RM在准备和提交阶段得到了相应的通知。

如果你想通过RM实现强制回滚,你可以如下简单的修改RM的Prepare方法:

public void Prepare(PreparingEnlistment preparingEnlistment)
{
Console.WriteLine("VolatileRM: Prepare");
preparingEnlistment.ForceRollback();
}

这将引起如下异常:

The Transaction Has Aborted.

事实上,你可以通过适当的preparingEnlistment.RollBack重载方法引起自定义的异常。

另外一种引起回滚的方法是简单的删掉TransactionScope.Complete语句或者在同一个事务中收集其他引起回滚的RM。

VolatileRM vrm = null ;
using (TransactionScope txSc = new TransactionScope())
{
vrm = new VolatileRM();
vrm.SetMemberValue(3);
// txSc.Complete();
}
Console.WriteLine("Member Value:" + vrm.MemberValue);

当你运行这段代码的时候,将产生如下输出:

VolatileRM: SetMemberValue - EnlistVolatile
VolatileRM: Rollback
Member Value: 0

你可能觉得这个范例有点不知所云。其实,你通过这个范例可以看到,你能够如此有效的创建与其他RM协同工作的事务代码。例如,你能够将这个非数据库操作的修改成员变量的操作同数据库的事务操作放在同一个事务中。你能够使用如下的T-SQL代码轻松建立数据库:

Create Database Test
Go
Create Table Demo
(
DemoValue varchar(5)
)

然后通过下面的代码将VolatileRM和两个SqlConnection实例收入同一个事务中:

private static string connStr = "Data Source=(local);Initial Catalog=Test;Integrated Security=True";
static void Main(string[] args)
{
VolatileRM vrm = null ;
using (TransactionScope txSc = new TransactionScope())
{
vrm = new VolatileRM();
vrm.SetMemberValue(3);
using (SqlConnection cn = new SqlConnection(connStr))
{
SqlCommand cmd = cn.CreateCommand();
cmd.CommandText = "Insert into Demo(DemoValue) Values ('XXX')";
cn.Open();
cmd.ExecuteNonQuery();
cn.Close();
}
using (SqlConnection cn = new SqlConnection(connStr))
{
SqlCommand cmd = cn.CreateCommand();
cmd.CommandText = "Insert into Demo(DemoValue) Values ('YYY')";
cn.Open();
cmd.ExecuteNonQuery();
cn.Close();
}
Console.WriteLine( "Transaction identifier:" + Transaction.Current.TransactionInformation.DistributedIdentifier);
txSc.Complete();
}
Console.WriteLine("Member Value:" + vrm.MemberValue);
}

框架能够通过PSPE自动识别出你的SqlConnection实例已经参与到事务中。当第二个SqlConnection实例出现时,它将在第二个 cn.Open方法被调用时自动将事务提升至MSDTC。你能够通过”控制面板->管理工具->组件服务”中观察这些操作:

提示: 连接至SQL Server 2005的SqlConnection采用PSPE方式,而连接至SQL Server 2000或更低版本的SqlConnection将采用持久方式,即使在当前事务中仅有一个RM。

当你运行程序时,产生如下输出:

VolatileRM: SetMemberValue - EnlistVolatile
Transaction identifier:c40015f6-5086-4688-b565-c65db1cbc8e7
VolatileRM: Prepare
VolatileRM: Commit
Member Value:3

如你所见,你依然在收集易变资源。但是,如果需要,你的事务将自动提升至MSDTC,并且获得分布式ID。如果你是架构师,这将给予你两个最好的世界:事务集成和可能最好的性能。你获得这些仅仅需要写如下的简单的代码:

Transaction
{
Operation A ;
Operation B ;
....
....
Commit();
}

实现基于System.Transactions的事务

现在你已经具备了基于System.Transactions实现事务处理的基础知识。你学到了各种各样的将资源收集到当前事务中的方法,并且看到了自定义RM收集易变资源是多么的简单。

下载代码

下载相关代码,点击这里

关于作者

Sahil Malik熟悉从Dos至.Net等许多尖端的微软科技。他是Pro ADO.NET 2.0的作者以及Pro ADO.NET with VB.NET 1.1的合著者。Sahil目前也在基于ADO.NET 2.0的多媒体领域有所建树。他曾被授予微软MVP称号。

古灵精怪

以前一直很惊叹解析DOC的quirks模式,居然能在如此多错误的情况下容错,保持健壮。但最近看看Macromedia公司的AS2官方类库,才更为惊叹AS脚本解析器的容错能力;
代码里面一堆堆错误,硬是能够正常跑起来,结果放到Eclipse下,一大堆报错,类库编译都通不过,用FDT+Aswing随便个小东西就得改一大堆的错误才能运行,随便举几个例子:
以下摘抄都是官方公布类库:
Directory:
Documents and SettingsAdministratorLocal SettingsApplication DataMacromediaFlash 8zh_cnConfigurationClasses
例一、类型不匹配
不过ASv1本身就是弱类型的,所以不算太错,但Number-Date真不知运行的时候为啥不会错,因为确实没找到转换函数
mxservicesWSDL.as

if (src != undefined)
{
var start = new Date();
this.wsdl.log.logInfo("Received WSDL document from the remote service", Log.VERBOSE);
this.parseXML(src);
this.loaded = true;
//加入此行,真不知 Math.round(new Date().getMilliseconds())错在哪里T T
var end = new Date();
//106 var parseTime = Math.round(new Date()) - start;
var parseTime = Math.round((end.getMilliseconds()) - start.getMilliseconds());
this.wsdl.log.logInfo("Parsed WSDL XML [" + parseTime + " millis]", Log.VERBOSE);
}

例二、变量名重复定义
例三、缺少结尾分号
mxservicesWebServiceProxy.as

// Shut off the __resolve
this.service.__resolve = function(operationName) {
var callback = new PendingCall();
callback.genSingleConcurrencyFault = function()
{
clearInterval(this.timerID); // only once
//192对应例二 var fault = new SOAPFault("Client.NoSuchMethod",
var innerFault = new SOAPFault("Client.NoSuchMethod", "Couldn't find method '" + operationName + "' in service!");
this.__handleFault(innerFault);
this.onFault(innerFault);
//197对应例三
};
callback["timerID"] = setInterval(function() { callback.genSingleConcurrencyFault(); }, 50); // 5 ms
return callback;
};

例四、相等与赋值乱用
mxservicesWSDL.as

// per WSDL 1.1 spec, if message is not named default to op Name plus suffix by mode:
if (message.name == undefined)
{
if (mode == SOAPConstants.MODE_IN)
{
message.name = operationName + "Request";
}
else
{
//598message.name == operationName + "Response";
message.name = operationName + "Response";
}
}

例五、不知为何
mxservicesNameSpace.as

//18
static var _doc = new XML();
//65不知为何必须使用this来访问static成员,反正少了编不过
var node = this._doc.createTextNode(value);

梦寐以求,太强了,自己看,不爽砍我

1:拼写检查
Spell Check

2:智能探测Filter
Filter

3:色彩
Color

4:无前缀、非前缀智能补写+“结构体”自动补全
Acronym

还不满足,还有功能不爽,看看附件里面的图,再对照自己的比较一下,差距啊:)

厚道的放出链接:

http://www.wholetomato.com/index.html

破解都找到了,我太好了T T

Crack for VA_X_Setup1438.exe

Browser Helper Object

那天有个信息安全实验室的博士生叫我们做IE插件,主要用于拦截IE浏览器的各项消息,从而做出我们自己的处理。

实现方法,当然千奇百怪,全局钩子函数,进程注入,消息拦截等等都可以,不过我最终选择了全局钩子函数,感觉应该好做一点。

由于最近比较多接触C#,第一反映当然还是.net framework。
MSDN->Filter:c#->“全局钩子”一找,郁闷死人:

在 .NET 框架中不支持全局挂钩

您无法在 Microsoft .NET 框架中实现全局挂钩。若要安装全局挂钩,挂钩必须有一个本机动态链接库 (DLL) 导出以便将其本身插入到另一个需要调入一个有效而且一致的函数的进程中。这需要一个 DLL 导出,而 .NET 框架不支持这一点。托管代码没有让函数指针具有统一的值这一概念,因为这些函数是动态构建的代理。
[http://support.microsoft.com/kb/318804]

当然,其实这段文字本身有点问题,先不说网上有人说好像并不是所有的全局挂钩都不能加载,但就.net支持对非受控代码的访问这点,就可以通过在中间增加一层Managed Code->UnManaged Code->Managed Code实现调用。

但是这样的实现难度陡增,很是麻烦。不过在搜索结果中看到一个比较乖的咚咚:Browser Helper Object,看起来感觉比较象。

记得以前有篇写Office Doc Object架构的文章很有名(好像小排发过吧),不过记忆中,当时看了一点就放弃了,感觉挺麻烦的。特别是针对COM组件的调用部分,看着就头大。

话说回来,调用COM组件在.net环境下,还是C++来得方便快捷,毕竟都是非受控的代码,不用处理太多受控、非受控对象之间的交互问题。不过C++.net依然还是VC++6.0的模样,看着都恶心,于是乎放弃:P

粗粗看了看Browser Helper Object(BHO)的机理,这个咚咚主要用于下面的场景:

想实现一个浏览器,提供一些自己的功能,但是如果用IE Control来写的话,还要自己写很多很多很多诸如:前进、后退、历史记录等等IE已经提供的功能,所以更简便的方法是给扩展、修改IE的功能,直接使用IE来实现。这方面,当然不能不提很多人深恶痛绝的3721,他提供了一个个性化的浏览器,却只写了很少的代码,其余的都是IE自带的内容。

打开一个IE,打开Spy++,再结合Internet Explorer 4.0 Architecture的图看看

Internet Explorer 4.0 Architecture

举个例子,如果想要获取IE地址栏的内容:
IEExplorer->IEFrame->WorkerW->ReBarWindow32->ComboBoxEx32->ComboBox->Edit
这就是他的层次结构

IE和Explorer在启动新线程的时候(Explorer要看看选项里面是否选择了在新的线程中打开窗口),会检查注册表项:

[HKLMSOFTWAREMicrosoftWindowsCurrentVersionExplorerBrowser Helper Objects]

他下面有很多子项,每一个子项就是一个GUID
例如:
{06849E9F-C8D7-4D59-B87D-784B7D6BE0B3}

这个GUID对应一个BHO组件(其实就是一个COM组件)
这个组件在[HKLMSOFTWAREClassesCLSID]下面进行描述,包括dll路径,签名等等信息

IE或Explorer在每个线程里会依次实例化每个在上述路径注册的BHO对象,从而BHO对象实际上运行在IE或Explorer的地址空间内,并且能够访问IE的几乎所有资源,拦截几乎所有事件,挂载自己的事件处理函数,而不需要跨进程调用和传消息。

看来完全符合最初的应用要求并且是最简单的方法了。

接下来的问题,就只剩下COM组件的使用了。.NET对COM组件的调用提供了很方便的100% pure 受控的访问方式 – Interop, Marshal。查错过程比较艰辛,因为每次重新生成都要重启Explorer.exe T T并且调试的时候,要先开IE,再下断点,再将VS .NET附加到IE进程,再点新建IE窗口…

COM组件的使用:
首先,当然是用Create GUID工具生成我们自己BHO组件的GUID({B29E305D-BC4D-4a80-B522-B0ABC9EBDFFC}),然后添加对 Microsoft Internet Controls COM组件(%systemroot%ShDocVw.dll)的引用。VS.NET会自动Wrapper之为 Interop.SHDocVw.dll,值得注意的是,这里由于最后要使用BHO注册,所以要求这个COM组件必须用私钥签名,从而使用强名机制。具体步骤如下:
1、sn -k BHO.key生成Bho.key文件,也就是我们的私钥。
2、设置项目属性,ActiveX/COM对象的包装程序集->包装程序集密钥文件,指向Bho.key
3、添加COM引用(此时查看相关信息就可以看到强名选项已设为true)
4、设置AssemblyInfo.cs填写key字段,指向Bho.key

然后,就可以声明我们要继承的各项接口:

using SHDocVw;
#region 引入IObjectWithSite接口
[ComImport(), Guid("fc4801a3-2ba9-11cf-a229-00aa003d7352")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IObjectWithSite
{
void SetSite([In ,MarshalAs(UnmanagedType.IUnknown)] object site);
void GetSite(ref Guid guid, [MarshalAs(UnmanagedType.IUnknown)] out object site);
}
#endregion
#region 定义默认BHO COM接口
[GuidAttribute("181C179B-7CC9-4457-8C1D-4B45E7C8589D")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)]
public interface IObserver
{
}
#endregion

然后定义我们自己的BHO类:

/// <summary>
///定义BHO类,此类由浏览器实例化
/// </summary>
[ClassInterfaceAttribute(ClassInterfaceType.None)]
[GuidAttribute("B29E305D-BC4D-4a80-B522-B0ABC9EBDFFC")]//由CreateGUID程序生成
[ProgIdAttribute("Observer.BrowserMonitor")]
public class BrowserMonitor : IObserver, IObjectWithSite
{}

这个类里面如下引入定义:

protected IWebBrowser2 browser; //浏览器对象
protected DWebBrowserEvents2_Event browserEvents;//浏览器事件

并且实现IObjectWithSite接口:

#region IObjectWithSite 成员
/// <summary>
/// IE调用此方法,并传递指向容器Site的IUnknown指针,由此我们可以获得IWebBrowser2接口
/// 并挂载DWebBrowserEvents2事件
/// </summary>
/// <param name="site">容器Site的IUnknown指针</param>
public void SetSite(object site)
{
// TODO: 添加 BrowserMonitor.SetSite 实现
#region 取得 IWebBrowser2 引用
if (browser != null)
Release();
if (site == null)
return;
browser = site as IWebBrowser2;
#endregion
#region 检查名称,当且仅当为IEXPLORE.EXE时加载
string hostName = browser.FullName;
if (!(hostName.ToUpper().EndsWith("IEXPLORE.EXE")))
{
Release();
return;
}
#endregion
#region 挂载浏览器事件
browserEvents = browser as DWebBrowserEvents2_Event ;
if (browserEvents != null)
{
browserEvents.DocumentComplete += new DWebBrowserEvents2_DocumentCompleteEventHandler(this.OnDocumentComplete);
}
else
{
Release();
return;
}
#endregion
}
/// <summary>
/// 调用者调用此方法以获得前面浏览器发送给SetSite()方法的浏览器对象
/// </summary>
/// <param name="guid">请求Site接口对象的GUID</param>
/// <param name="site">返回的Site接口对象</param>
public void GetSite(ref System.Guid guid, out object site)
{
// TODO: 添加 BrowserMonitor.GetSite 实现
site = null;
if (browser != null)
{
IntPtr pSite = IntPtr.Zero;
IntPtr pUnk = Marshal.GetIUnknownForObject(browser); //引用计数增加
Marshal.QueryInterface(pUnk, ref guid, out pSite); //引用计数增加
Marshal.Release(pUnk); //引用计数减少
Marshal.Release(pUnk); //引用计数减少
if (!pSite.Equals(IntPtr.Zero))
{
site = pSite;
}
else
{
// 若找不到请求的接口,将返回E_NOINTERFACE
Release();
Marshal.ThrowExceptionForHR(E_NOINTERFACE);
}
}
else
{
// 若找不到请求的接口对象,将返回E_FAIL
Release();
Marshal.ThrowExceptionForHR(E_FAIL);
}
}
#endregion

这里挂载了我们自己的事件处理函数,处理DocumentComplete事件,此外还有很多事件可以使用,例如:

DownloadBegin
DownloadComplete
BeforeNavigate2
CommandStateChange
FileDownload
NavigateComplete
NewWindow
FullScreen
MenuBar
Quit
FrameBeforeNavigate
FrameNavigateComplete
FrameNewWindow
ProgressChange
PropertyChange
StatusTextChange
TitleChange
WindowActivate
WindowMove
WindowResize

using System.IO;
#region 用于挂载的自定义函数
protected void OnDocumentComplete(object display, ref object url)
{
try
{
if (Marshal.Equals(browser, display))
{
StreamWriter sw = new StreamWriter(@"C:Test.txt", true);
sw.WriteLine(url.ToString());
sw.Close();
}
}
catch
{
Release();
Marshal.ThrowExceptionForHR(E_FAIL);
}
}
#endregion

此外,关于错误处理,由于涉及包裹为受控组件的非受控组件,所以需要使用以下方式抛出异常:

#region 定义HRESULT值 : 预定义的COMException值
//在未检查的上下文中,如果表达式产生目标类型范围之外的值,则结果被截断
const int E_FAIL = unchecked((int)0x80004005);//失败
const int E_NOINTERFACE = unchecked((int)0x80004002);//QueryInterface时,接口不存在
#endregion
Marshal.ThrowExceptionForHR(E_FAIL);
Marshal.ThrowExceptionForHR(E_NOINTERFACE);

使用一下方式,释放对象:

#region Release操作
protected void Release()
{
if (browserEvents != null)
{
Marshal.ReleaseComObject(browserEvents);
browserEvents = null;
}
if (browser != null)
{
Marshal.ReleaseComObject(browser);
browser = null;
}
}
#endregion

至此已基本完成,但是要想自动注册BHO组件,还需要做几件事情:

#region 挂载注册/注销操作
/// <summary>
/// 在注册COM组件时,由运行时调用
/// </summary>
[ComRegisterFunctionAttribute]
public static void Register(Type type)
{
// 注册BHO组件
string guid = type.GUID.ToString("B");
RegistryKey rkey =
Registry.LocalMachine.CreateSubKey(@"SOFTWAREMicrosoftWindowsCurrentVersionExplorerBrowser Helper Objects");
RegistryKey rkeyBHO = rkey.CreateSubKey(guid);
}
/// <summary>
/// 在注销COM组件时,由运行时调用
/// </summary>
[ComUnregisterFunctionAttribute]
public static void Unregister(Type type)
{
// 注销BHO组件
string guid = type.GUID.ToString("B");
RegistryKey rkey =
Registry.LocalMachine.CreateSubKey(@"SOFTWAREMicrosoftWindowsCurrentVersionExplorerBrowser Helper Objects");
rkey.DeleteSubKey(guid,false);
}
#endregion

由这些可见.NET平台对于COM组件的调用支持还是相当强大的,可以在受控环境下实现100% pure的COM访问能力。

PS:
结尾怎么写成这样了,写一写的就有点偏题了- –

BHO Project

Cached FlyWeight

其实因为C#语言自身的一些特点,和FlyWeight的实现方式不太一样- –

初学,不足之处见谅:)

详细叙述见附件

======2005-12-13======

作了一些修改,使用方式不变,不过加入了对任何单参数构造函数的支持。

有人问,为什么不支持任何多参数构造函数, 因为实际上参数就是缓存时用于查找的Key,多参数生成Key的时候较麻烦而已:)

最后,说明一点,本FlyWeight在Scavenging的时候,会根据使用的情况,删除最不常使用的Key

======2006-1-19=======

VS2005发布后,更新版本

Download Fly Weight

C#中调用Windows API的要点

在.Net Framework SDK文档中,关于调用Windows API的指示比较零散,并且其中稍全面一点的是针对Visual Basic .net讲述的。本文将C#中调用API的要点汇集如下,希望给未在C#中使用过API的朋友一点帮助。另外如果安装了Visual Studio .net的话,在C:Program FilesMicrosoft Visual Studio .NETFrameworkSDKSamplesTechnologiesInteropPlatformInvokeWinAPIsCS 目录下有大量的调用API的例子。

一、调用格式
using System.Runtime.InteropServices; //引用此名称空间,简化后面的代码
...
//使用DllImportAttribute特性来引入api函数,注意声明的是空方法,即方法体为空。
[DllImport("user32.dll")]
public static extern ReturnType FunctionName(type arg1,type arg2,...);
//调用时与调用其他方法并无区别,可以使用字段进一步说明特性,用逗号隔开,如:
[ DllImport( "kernel32", EntryPoint="GetVersionEx" )]

DllImportAttribute特性的公共字段如下:

1、CallingConvention 指示向非托管实现传递方法参数时所用的CallingConvention 值。
CallingConvention.Cdecl : 调用方清理堆栈。它使您能够调用具有 varargs 的函数。
CallingConvention.StdCall : 被调用方清理堆栈。它是从托管代码调用非托管函数的默认约定。

2、CharSet 控制调用函数的名称版本及指示如何向方法封送 String 参数。
此字段被设置为 CharSet 值之一。如果 CharSet 字段设置为 Unicode,则所有字符串参数在传递到非托管实现之前都转换成 Unicode 字符。这还导致向 DLL EntryPoint 的名称中追加字母“W”。如果此字段设置为 Ansi,则字符串将转换成 ANSI 字符串,同时向 DLL EntryPoint 的名称中追加字母“A”。大多数 Win32 API 使用这种追加“W”或“A”的约定。如果 CharSet 设置为 Auto,则这种转换就是与平台有关的(在 Windows NT 上为 Unicode,在 Windows 98 上为 Ansi)。CharSet 的默认值为 Ansi。CharSet 字段也用于确定将从指定的 DLL 导入哪个版本的函数。CharSet.Ansi 和 CharSet.Unicode 的名称匹配规则大不相同。
对于 Ansi 来说,如果将 EntryPoint 设置为“MyMethod”且它存在的话,则返回“MyMethod”。如果 DLL 中没有“MyMethod”,但存在“MyMethodA”,则返回“MyMethodA”。对于 Unicode 来说则正好相反。如果将 EntryPoint 设置为“MyMethod”且它存在的话,则返回“MyMethodW”。如果 DLL 中不存在“MyMethodW”,但存在“MyMethod”,则返回“MyMethod”。如果使用的是 Auto,则匹配规则与平台有关(在 Windows NT 上为 Unicode,在 Windows 98 上为 Ansi)。如果 ExactSpelling 设置为 true,则只有当 DLL 中存在“MyMethod”时才返回“MyMethod”。

3、EntryPoint 指示要调用的 DLL 入口点的名称或序号。
如果你的方法名不想与api函数同名的话,一定要指定此参数,例如:
[DllImport("user32.dll",CharSet="CharSet.Auto",EntryPoint="MessageBox")]
public static extern int MsgBox(IntPtr hWnd,string txt,string caption, int type);

4、ExactSpelling 指示是否应修改非托管 DLL 中的入口点的名称,以与 CharSet 字段中指定的 CharSet 值相对应。
如果为 true,则当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Ansi 值时,向方法名称中追加字母 A,当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Unicode 值时,向方法的名称中追加字母 W。此字段的默认值是 false。

5、PreserveSig 指示托管方法签名不应转换成返回 HRESULT、并且可能有一个对应于返回值的附加 [out, retval] 参数的非托管签名。

6、SetLastError 指示被调用方在从属性化方法返回之前将调用 Win32 API SetLastError。
true 指示调用方将调用 SetLastError,默认为 false。运行时封送拆收器将调用 GetLastError 并缓存返回的值,以防其被其他 API 调用重写。用户可通过调用 GetLastWin32Error 来检索错误代码。

二、参数类型:
1、数值型直接用对应的就可。(DWORD -> int , WORD -> Int16)

2、API中字符串指针类型 -> .net中string

3、API中句柄 (dWord) -> .net中IntPtr

4、API中结构 -> .net中结构或者类。
注意这种情况下,要先用StructLayout特性限定声明结构或类
公共语言运行库利用StructLayoutAttribute控制类或结构的数据字段在托管内存中的物理布局,即类或结构需要按某种方式排列。如果要将类传递给需要指定布局的非托管代码,则显式控制类布局是重要的。它的构造函数中用LayoutKind值初始化 StructLayoutAttribute 类的新实例。 LayoutKind.Sequential 用于强制将成员按其出现的顺序进行顺序布局。
LayoutKind.Explicit 用于控制每个数据成员的精确位置。利用 Explicit,每个成员必须使用 FieldOffsetAttribute 指示此字段在类型中的位置。如:
[StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)]
public class MySystemTime
{
[FieldOffset(0)]public ushort wYear;
[FieldOffset(2)]public ushort wMonth;
[FieldOffset(4)]public ushort wDayOfWeek;
[FieldOffset(6)]public ushort wDay;
[FieldOffset(8)]public ushort wHour;
[FieldOffset(10)]public ushort wMinute;
[FieldOffset(12)]public ushort wSecond;
[FieldOffset(14)]public ushort wMilliseconds;
}

下面是针对API中OSVERSIONINFO结构,在.net中定义对应类或结构的例子:
/**********************************************
* API中定义原结构声明
* OSVERSIONINFOA STRUCT
* dwOSVersionInfoSize DWORD ?
* dwMajorVersion DWORD ?
* dwMinorVersion DWORD ?
* dwBuildNumber DWORD ?
* dwPlatformId DWORD ?
* szCSDVersion BYTE 128 dup (?)
* OSVERSIONINFOA ENDS
*
* OSVERSIONINFO equ
*********************************************/
//.net中声明为类
[ StructLayout( LayoutKind.Sequential )]
public class OSVersionInfo
{
public int OSVersionInfoSize;
public int majorVersion;
public int minorVersion;
public int buildNumber;
public int platformId;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )]
public String versionString;
}
//或者
//.net中声明为结构
[ StructLayout( LayoutKind.Sequential )]
public struct OSVersionInfo2
{
public int OSVersionInfoSize;
public int majorVersion;
public int minorVersion;
public int buildNumber;
public int platformId;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )]
public String versionString;
}

此例中用到MashalAs特性,它用于描述字段、方法或参数的封送处理格式。用它作为参数前缀并指定目标需要的数据类型。例如,以下代码将两个参数作为数据类型长指针封送给 Windows API 函数的字符串 (LPStr):
[MarshalAs(UnmanagedType.LPStr)]
String existingfile;
[MarshalAs(UnmanagedType.LPStr)]
String newfile;

注意结构作为参数时候,一般前面要加上ref修饰符,否则会出现错误:对象的引用没有指定对象的实例。
[DllImport("kernel32", EntryPoint="GetVersionEx")]
public static extern bool GetVersionEx2(ref OSVersionInfo2 osvi);

三、如何保证使用托管对象的平台调用成功?
如果在调用平台 invoke 后的任何位置都未引用托管对象,则垃圾回收器可能将完成该托管对象。这将释放资源并使句柄无效,从而导致平台invoke 调用失败。用 HandleRef 包装句柄可保证在平台 invoke 调用完成前,不对托管对象进行垃圾回收。
例如下面:
FileStream fs = new FileStream( "a.txt", FileMode.Open );
StringBuilder buffer = new StringBuilder( 5 );
int read = 0;
ReadFile(fs.Handle, buffer, 5, out read, 0 ); //调用Win API中的ReadFile函数

由于fs是托管对象,所以有可能在平台调用还未完成时候被垃圾回收站回收。将文件流的句柄用HandleRef包装后,就能避免被垃圾站回收:
[DllImport( "Kernel32.dll" )]
public static extern bool ReadFile(
HandleRef hndRef,
StringBuilder buffer,
int numberOfBytesToRead,
out int numberOfBytesRead,
ref Overlapped flag );
......
......
FileStream fs = new FileStream( "HandleRef.txt", FileMode.Open );
HandleRef hr = new HandleRef( fs, fs.Handle );
StringBuilder buffer = new StringBuilder( 5 );
int read = 0;
// platform invoke will hold reference to HandleRef until call ends
ReadFile( hr, buffer, 5, out read, 0 );

关于Single Sign On的一些设想

单点登陆,始终有其吸引力。然而就实现而言,结合栋力无限现有情况,比较困难。

主要原因在于栋力无限门户下属各个子站点,不仅有自己开发的,也有直接使用的他人开发的开源项目的。特别对于后者,如果花大力气,对其进行修改,使其使用我们自身的passport,一旦其有所升级、打patch等变动,就会导致大量的重复劳动。

在现有条件下,是否存在一种花费最小代价的Single Sign On实现方式?换句话说,使得插接到passport的各个子站点,所作的修改尽量的少?

最近有一个设想,就是如果想要尽可能少的修改各个子站点,实现passport的通用性,唯有通过模拟各个子站点自身的登陆行为。

法1、填写username password,并post到其form的action链接里面;
法2、直接在用户本机写入cookie文件;

两种方式其实都可以。具体一点说:

第一阶段试验目标:

1、登陆passport.dormforce.net后,passport修改用户登陆状态信息为已登录(并以Web Services或其他方式对外公开)。

2、passport调用放在各个子站点自身程序里的一个模板页面,那个页面实现的唯一功能就是采用法1或法2模拟登陆本子站点,若有必要,在这里,还可以增加对Session,Application等变量的支持;(由于开源,可以跟踪入内,找到其登陆代码,copy过来修改一下即可)(调用的时候,最好使用AJAX等技术后台加载,防止页面频繁刷新,提高用户体验)

3、passport调用放在各个子站点自身程序里的一个模板页面,那个页面实现的唯一功能就是采用法1或法2登出本子站点,若有必要,在这里,还可以增加对Session,Application等变量的支持;(由于开源,可以跟踪入内,找到其登出代码,copy过来修改一下即可)(调用的时候,最好使用AJAX等技术后台加载,防止页面频繁刷新,提高用户体验)

至此,应该能够实现在passport登陆后,畅通访问各个子站点;在passport登出后,退出所有子站点;

第二阶段试验目标:

1、通过用户登陆状态信息等其他验证安全的手段,确保放在各个自站点的模拟登陆页面安全。

2、修改各个子站点的登陆和登出链接,指向passport

至此,应该能够实现我们最初的目标。

我再稍微总结一下:

passport实现的功能就是登陆时,自动登陆各个站点;登出时,自动登出所有站点。这里和通常意义的passport不一样,因为通常意义的passport一般就只有一个cookie。

一个新插入的子站点(不管自身项目还是开源项目),若想使用passport认证,则进行如下操作:

1、找到登陆的代码,套入passport提供的模板页面中;
2、找到登陆和登出的链接,指向passport;

当然,对于第二步来说,仍然比较困难,但是较之完全替换其帐户系统,理论上讲应该轻松许多,这也是我目前能想到的代价最小的实现Single Sign On的方式了。

由于这只是一些设想,跟曾毅和卢浩森聊了一下,据称问题不大,而且曾毅貌似已在音乐站上测试成功第一阶段的目标,所以特贴出来,看看各位达人认为:

1、有可行性么?
2、有没有其他更好更贴近栋力无限自身特点的方式?