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称号。

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 );

发布Web Services

笔记本上Release后一切正常的Web Services,部署到另外一台2003的服务器上,出现两个问题:

1、Critical Error:

…没有权限访问注册表

其实只是在写入EventLog的时候ASPNET的权限不够,不过奇怪的是把ASPNET和匿名帐户加入Administrator组,重启,好像权限还是不够>.<

查看其列出的跟踪堆栈,发现好像是Enterprise Library中ADO.NET连接数据库的问题,在网上查了查,有用的文章不多,最后居然就在EntLib的FAQ里,发现是没有正确部署EnterLib程序集的问题。

安装程序工具(Installutil.exe)
允许通过执行指定程序集的安装程序组件来安装和卸载服务器资源。
注意: 在“%windir%Microsoft.NETFrameworkv1.1.xxxx”中可以找到Installutil.exe,其中xxxx是您使用的 .NET Framework 的内部版本号。

Installutil Microsoft.Practices.EnterpriseLibrary.Common.dll
Installutil Microsoft.Practices.EnterpriseLibrary.Configuration.dll
Installutil Microsoft.Practices.EnterpriseLibrary.Data.dll

一切恢复正常。

不过还是不知道为什么他的程序集部署的时候必须Install,而自己的dll就不用这一步。

PS:
删除用
Installutil /u xxx.dll

2、localhost访问没有问题,但用www.xxx.com访问,出错

在 .NET Framework 1.1 远程环境中,默认情况下 HTTP GET 和 HTTP POST 同时被禁用。这是出于安全方面的考虑。
当 Web 服务升级到 .NET Framework 1.1 后,应用程序使用 HTTP GET 或 HTTP POST 调用 Web 服务时会失败。
这些应用程序会收到以下一条错误信息 System.Net.WebException指出无法识别请求格式。

由于使用POST,现在已基本不可能转成SOAP协议,只有将其打开,更改Web.Config文件:
<configuration>
<system.web>
<webServices>
<protocols>
<!--
<add name="HttpGet">
-->
<add name="HttpPost"/>
</protocols>
</webServices>
</system.web>
</configuration>

关于C#一个基础问题的初步探查

关于C#一个弱弱问题的初步探查

在书上看到这样一个例子:

已知:
interface IControl
{
void Paint();
}
class Control : IControl
{
public void Paint(){ ... }
}
class TextBox : Control
{
new public void Paint(){ ... }
}

问:
IControl it = new TextBox();
it.Paint(); //调用哪个Paint()?

书上的结论是Control::Paint(),并且吹了一通什么没有virtual关键字或者让TextBox直接继承至IControl就是静态决议,什么如果Control::Paint()加上virtual又怎么怎么,如果TextBox::Paint()去掉new又怎么怎么,感觉甚是繁琐、复杂和多变,让人如坠万丈深渊,万劫不复…(- -)

就我原来从C++带来的理解:IControl是个interface,其实隐含IControl::Paint()是abstract 以及 public,只是不能显式写出;所以成员函数的调用决议应该是运行期完成,也就是说为TextBox::Paint();

实际上机测试结果,令人汗颜,他的结果的确是正确的,但是他的后续所有解释都是错误的;不管如何加virtual、去掉new,最后的结果都是一个—Control::Paint(),程序代码如下:
using System;
namespace TestInterface
{
interface IControl
{
void Paint();
}
class Control : IControl
{
public void Paint()
{
Console.WriteLine("Control::Paint()");
}
}
///

/// TextBox 的摘要说明。
///

class TextBox : Control
{
new public void Paint()
{
Console.WriteLine("TextBox::Paint()");
}
}
///

/// TestInterface 的摘要说明。
///

class TestInterface
{
///

/// 应用程序的主入口点。
///

[STAThread]
static void Main(string[] args)
{
//
// TODO: 在此处添加代码以启动应用程序
//
IControl it = new TextBox();
it.Paint();
}
}
}

如果去掉new当然仅仅会导致警告,实际上还是覆盖。

书中还提到“我们的误解来自一个假设:Control::Paint()被自动视为vitrual”,根据实际上机测试,这个假设的确是成立的,而不是什么误解,至于加上virtual和去掉virtual所带来的区别仅仅在于是否加入关键字final。如果没有加上了virtual,那么Control::Paint()会自动视为final(IL中),有例为证:

Contorl::Paint()
.method public hidebysig newslot virtual final //如果加上vitrual则这里去掉final
instance void Paint() cil managed
{
// 代码大小 11 (0xb)
.maxstack 1
IL_0000: ldstr "Control::Paint()"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method Control::Paint
TextBox::Paint()
.method public hidebysig instance void Paint() cil managed
{
// 代码大小 11 (0xb)
.maxstack 1
IL_0000: ldstr "TextBox::Paint()"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method TextBox::Paint

而在Main()中仅是简单调用IControl::Paint()

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// 代码大小 13 (0xd)
.maxstack 1
.locals init ([0] class TestInterface.IControl it)
IL_0000: newobj instance void TestInterface.TextBox::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: callvirt instance void TestInterface.IControl::Paint()
IL_000c: ret
} // end of method TestInterface::Main

===== 华丽的分割线 =====
呵呵,以上就是我初步的分析,特别混乱,不要见笑,下面我做个总结(个人意见、仅供参考)

原来的C++到C#,在函数重载这一块有一个很明显的区别,那就是override关键字。如果重载必须显式的指明override关键字,如果没有,将隐藏基类成员。这点是很自然的,但是本例的特别之处在于interface的引入。

—– interface —–
interface默认为abstract类,但是和普通abstract类不同的是,他并没有派生自System.Object;其内接口也默认为abstract virtual如下:

.class interface private abstract auto ansi IControl
{
} // end of class IControl
.method public hidebysig newslot abstract virtual
instance void Paint() cil managed
{
} // end of method IControl::Paint

之所以interface被默认为public(指其内接口,而interface本身可以不为public),abstract就不用解释了,但是明示出来反而会出错;
—– interface end —–

—– interface implement —–
.class private auto ansi beforefieldinit Control
extends [mscorlib]System.Object
implements TestInterface.IControl
{
} // end of class Control
.method public hidebysig newslot virtual final //如果加上vitrual则这里去掉final
instance void Paint() cil managed
{
// 代码大小 11 (0xb)
.maxstack 1
IL_0000: ldstr "Control::Paint()"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method Control::Paint

这里太有趣了(- -,过分了…还是说稍微有点意思)interface接口的实现会在IL中自动加上virtual关键字,而不像刚才书上说的,什么误解,这是为什么呢?因为如果像本例中一样,用IControl调用Paint(),而IControl本身是不会实现Paint()的,所以他的实现必须在IL中以virtual关键字标明,让动态决议的时候,能够考虑到这个实现,不然IControl::Paint()的调用就会出问题。

至于这里提到的final,也是interface implement比较独特的一点。override会使得本身重载基类成员,并且本身成为virtual,所以自己的子类也可以重载自己。在程序中,若想重载基类成员,但又不想让子类继续重载,就会使用override sealed关键字,这里IL中的final关键字实际上就是指的sealed。

由此,我们一般写一个函数:

public void foo();

子类都无法override,除非函数改为:

virtual void foo();

但是这里由于interface implement本身需要申明为override(IL中仍为virtual),所以为了延续惯例,用sealed(IL中为final)封闭之,除非你显示的指示virtual,这样就和我们通常的观念保持一致鸟~~~~
—– interface implement end —–

—– extended classes —–
.class private auto ansi beforefieldinit TextBox
extends TestInterface.Control
{
} // end of class TextBox
extends TestInterface.Control
.method public hidebysig instance void Paint() cil managed
{
// 代码大小 11 (0xb)
.maxstack 1
IL_0000: ldstr "TextBox::Paint()"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method TextBox::Paint

这里不管你是否采用new关键字,实际上都起到了覆盖的作用,只是如果没有new关键字会引发警告,提醒你是否确实是想覆盖,如果你想关闭警告,就必须显示的告诉编译器我想覆盖—就是使用new关键字。

值得一提的有两点:
1、如果程序中直接写virtual而不用override,其实还是覆盖。可以从IL中看出:
程序<->IL
virtual<->newslot virtual
override<->virtual
2、如果这个类直接多重继承IControl,像这样:
class TextBox : Control, IControl
{
...
}

实际上这个类就不再属于extended classes了,而是interface implement。由于对于interface implement的选择是virtual的,所以很自然会调用TextBox::Paint();
—– extended classes end —–

如果以上的讨论正确,本例就很好解释鸟~~~

IControl::Paint()由于是virtual,他会先行寻找可行函数,再进行最佳决议;

由于new关键字的覆盖函数并未override,所以他连可行函数都算不上。自然无论如何都不可能被调用。相反,可能函数只有Control::Paint();

如果让TextBox::Paint()用override修饰(自然Control::Paint()需要用virtual修饰),那么可能函数有Control::Paint(),TextBox.Paint(),其中根据动态决议,最佳为TextBox.Paint();

ZTMD宁愿重写,也不排错

奋战了一个星期的简易登陆系统排错,最后的结果让人汗颜:
将Web.Config里面的
<authentication mode="Windows">
</authentication>

改为
<authentication mode="Forms">
<forms name="TheSky" path="/" loginUrl="/TheSky/WebModules/Users/Login.aspx" protection="All" timeout="30">
</forms>
</authentication>

我一直没有查Web.Config。唉,已经欲哭无泪了,呜呜…

ps:ztmd,ztmD,ztMd,zTmd,Ztmd,ztMD,zTmD,ZtmD,zTMd,ZtMd,ZTmd,zTMD,ZtMD,ZTmD,ZTMd,ZTMD

假期意识流

意识流

—假期生活乱纪

这个假期不堪回首,单调得可怕,除了4本书之外,记忆中仅存的就全是某大学的研究生会的网站了。网站说来更不堪回首,完全算得上“赶鸭子上架”的典型(我“可爱”的姐在网站“八字还没有一撇”的时候,居然就敢将“网站建设费”挥霍一空,后来备受挫折后,无力还债,于是栽赃到了我的头上- -!)。下面将网站建设过程中的点点滴滴乱纪下来,想到哪里写到哪里,逻辑未免混乱,各位大虾莫要见笑。目的也很简单,让各位看看一个新手眼中的网站建设过程。

1、 研究生会:

因为这个网站,我就已经堂而皇之成为某大学研究生会编制中的一员,这种“海纳百川”的“气势”令我无不拜倒- -!

2、 网站需求:

刚开始的网站需求很“明确”—“美观、大方、展现当代研究生风采”。让我感到在这种“派头”的指导老师手下干起事情来一定比较“自由”,但是后来事实证明我错了,这种指导老师也有开始变“聪明”的时候。

需求增加了一条—“采用最新的Asp.Net技术,并且务必使用SQL Server最新版本”,我在深入理解这条需求之后,对于各个软件、框架的版本甚是犹豫,不知beta版是否也计算在内- -!

3、 美工:

这个我没有发言权,反正阿梅和阿楠看过(因为当时我不会用PS分页- -!)

4、 VS .NET环境:

由于以前习惯于Borland的BCB,于是“疏”在于没RAD就不活了,“密”在于任何东西都希望有根有据,简单明了。对于VC++6.0从未涉猎,因为当时第一印象就觉得不像是拿来编程的,加之MFC的封装没有一定的实力,定会感到极其莫名其妙,那消息映射的编程方式更是使得局势错综复杂,不像VCL里面虽然依然有很多不懂,但至少容易把握过程。

介于上述原因,所以当我观之Visual Studio .NET的时候还是大吃一惊,惊叹微软也终于RAD了一把。于是看了本《C# Primer》上手就干。

但是现实总是残酷的,虽然很多东西感觉用起很顺手,但还是有很多极其不习惯:

首当其冲的就是一个解决方案里面的类、函数等等,不用.h,不用#include好像就能够在各个文件中可见,原因可能是采用namespace的组织方式,但是老是觉得没有#include <*.h>就是不爽(我理想中的世界应该是把文件include过来后,再在namespace里面避免污染,而不要在一个解决方案里面的文件到处都可见似的)。

其次就是所谓的大名鼎鼎的“Code Behind”,这个倒是在很大程度上解决了网页程序设计中的版权问题,并且使代码和外观分离,便于和传统窗口编程靠拢。但是对于新手而言,在不具备驾驭的能力的情况下,无非是场灾难,代码直接生成DLL,不用export,不用声明,只需assembly一下,就可以使用,随便咋看都极其莫名其妙。

介于以上,我终于放弃了使用VS .NET,本来想选择同出Borland之手的C# Builder,但是令人郁闷的是没有找到注册码- -!所以最后我还是选择了以前写ASP程序时候采用的Dreamweaver+EditPlus+MSDN,本来记事本足已,但是少了调试器,要是连行号都看不到,那我还是直接跳楼算了。

5、 风格:

回头看我写的代码,感觉很多地方看起来还是像用ASP写的:P,不过因为毕竟是ASP.NET的东西,还是初步感受了一下ASP.NET的便利,诸如:DataGrid、DataList、Page_Load、还有在连接SQL数据库时候的便利等等等等。

6、 语言:

这个是我最无语的一点,由于C# 是刚学的,所以开始的时候还是在尝试VB,在花了很长时间都没有发现下面代码为什么通不过之后,我还是痛下决心用C# 吧- -!

#region code
<script language=”VB” runat=server>
sub foo(object sender,eventargs e)
{
//----------
}
</script>
#endregion

接踵而来的转化工作,幸好还不算很困难,但确实很烦,下面将我在转化时的主要工作总结一下:

a) 大小写:VB里面的标识符不区分大小写,如:page_load、datagrid也可以通过,但是到了C# 里面就必须区分大小写了(这里是改得最郁闷的了,对照到MSDN一个一个挨到改,想当初写VB Script的时候不偷懒就好了)

b) 索引(Item):VB里面使用object(index)就可以索引了,但是到了C#里面索引可必须使用object[index]符号,不过如果你使用Item调用就一致了,不过新手一般都不会那样做。

其他可能还有很多转化工作,不过我倒是幸运的没有碰到。

7、 基本代码组织方式:

由于那个网站大量的都是带图像文本处理,所以基本上前台采用的是把分页后形成的静态页面里面的动态部分挖空,以iframe填充之,iframe的src里面要不就指向list.aspx文件(显示前多少项,以及more按钮),并传入类别、css、行数、列数作为参数控制外观;要不就指向multilist.aspx文件(显示所有内容,以DataGrid进行分页控制),传入的外观控制参数一致。并且二者都接受hot=1参数,若设置了,则排序的方式由order by subdate desc变为order by 点击次数 desc

8、 前台:

前台如前述方式架构,我觉得最有用的就是DataGrid与DataList的基本使用及外观控制,如果实在觉得不方便,新手还是用<ItemTemplate>来得快:),另外在进行数据绑定的时候,我基本上就只用到了下面一条语句:

<%# ((DataRowView)Container.DataItem)["foo"] %>

9、 后台管理:

a)FreeTextBox:

说起后台管理自然想到了熟悉的DormForce Blog的后台,那里的那个输入控件,令人感觉甚爽,在IE里面察看源,发现名字叫做FreeTextBox- -!这个名字实在是…

于是Google后下载了一个,感觉封装得很好,使用也很方便,直接register就可以用了,令我倍感ASP.NET还是很爽的,也对Web Control产生了一丝眷恋。但是问题也出来了,他的“插入图片”按钮令人极其郁闷,只能插入图片URL,而没有提供上传,本来想在FTB控件旁边单独提供一个UploadImg按钮,提供上传然后返回URL,供使用FTB插入之需。

但是在看了“宝玉”的文章后突然发现FTB对于Button提供了接口ToolBarButton,于是可以把上传的工作集成到按钮里面。用宝玉提供的方法,在VS .NET里面“依样画葫芦”很快便搞定了,其实就是直接从ToolBarButton直接继承构造新的Button就可以了。

但是问题是我没有用Code Behind的方式,而且找了半天也没有发现,如果不用Code Behind,把类的定义写在哪里?所以只有自己想办法,看了半天英文版的FTB帮助,终于找到了方法:

<FTB:TOOLBARBUTTON title="插入图片"
runat="server" ScriptBlock="myOpenFunc();" ButtonImage="insertimage" />

其中的ButtonImage指定名称后会自动从aspnet_client目录调用图片资源,若要更换可以直接替换文件即可,不过注意保持长宽比(不然会很难看的)。另外我感觉aspnet_client这个名字很可疑,可能有特殊的用途,诸如用于客户端的啥子东西,没有敢改名字:(

其中的ScriptBlock直接在FTB生成的Html页面里面加入内容,如上例就生成如下咚咚:

… = new FTBButton(… function(){myOpenFunc();} …)…

myOpenFunc()为调用此FTB控件页面上<script>内定义的函数。藉由此实现runat=server代码对于本机函数的调用,即通常所谓的Response.Write法。

另外,我还需要从本机函数调取runat=server中控件的属性。想了半天,找到了如下的不是办法的办法:

function myOpenFunc(){
var dt=eval('myForm.myDataTable');
var tempurl='../inc/uploadimg.aspx?tab=';
tempurl = tempurl+dt.value;
window.open(tempurl,'_uploadimg','width=300 height=50');
}

其中myForm、myDataTable分别为runat=server里面Form和DropDownList的Id名称,使得本机获得的html页面里的form和select的id为这二者,从而为本机函数的实现提供可能性。

在uploadimg.aspx中只需完成上传文件,调用opener.top.InsertText(…)方法传入图片地址即可,而在myOpenFunc()所在页面中定义InsertText方法完成插入。

b) 编辑区域:

编辑区域的实质,宝玉已经说得比较清楚了,其实就是一个document.designMode设为on的Iframe。因此可以方便的用本机函数进行各种编辑工作以及调用focus()获取焦点。

c) DropDownList的数据绑定:

由于需要,选定一个DropDownList后要对另外一个重新进行数据绑定,实现方式如下:

指定DropDownList的属性OnSelectedIndexChanged=”SelectionChangeFunc” AutoPostBack=”true”,SelectionChangeFunc会传送给一个delegate型别从而起到类似函数指针的作用。

void SelectionChangeFunc(Object sender, System.EventArgs e)
{
myDataClass.DataSource = CreateDataSource(...);
myDataClass.DataTextField = "TextField";
myDataClass.DataValueField = "ValueField";
myDataClass.DataBind();
myDataClass.SelectedIndex = 0;
}
ICollection CreateDataSource(…)
{
System.Data.DataTable myDt = new System.Data.DataTable();
myDt.Columns.Add(new System.Data.DataColumn("TextField", typeof(String)));
myDt.Columns.Add(new System.Data.DataColumn("ValueField", typeof(String)));
...
myDt.Rows.Add(CreateRow("…", "…", myDt));
...
System.Data.DataView myDv = new System.Data.DataView(dt);
return myDv;
}
System.Data.DataRow CreateRow(String Text, String Value, System.Data.DataTable dt)
{
System.Data.DataRow myDr = dt.NewRow();
dr[0] = Text;
dr[1] = Value;
return myDr;
}

d) DataGrid的CommandButton:

column字段:<asp:ButtonColumn HeaderText="Del Button"
Text="Del" CommandName="DelBtn" />

指定DataGrid属性OnItemCommand=”Grid_DelCommand”

通过Grid_DelCommand函数处理所有的CommandButton信息,而Grid_DelCommand函数只需根据CommandName指定的值选择操作即可。

void Grid_DelCommand(Object sender, DataGridCommandEventArgs e)
{
if(((LinkButton)e.CommandSource).CommandName == "DelBtn")
{
...
}
}

这里有两个问题:

其一,好像DataGrid被提交的时候也会调用此OnItemCommand函数,因为当时由于只有一个command,没有用if判断CommandName值的时候,一提交就要出错,而且是在这个函数里面出错的。所以建议就算只有一个command也用if后者select判断一下。

其二,我本来是把<asp:ButtonColumn>定义为“PushButton”的,但是在进行强制类型转换的时候没有找到相应的类型,不管是Button、PushButton都不对,所以只好换用默认的LinkButton才调试通过。

e) DataGrid的分页:

设置DataGrid的

AllowPaging=True
PagerStyle-Mode=NumericPages
PagerStyle-HorizontalAlign=Right
OnPageIndexChanged=”Change_Page_Index”属性

定义下列函数:

void Change_Page_Index(Object sender, DataGridPageChangedEventArgs e)
{
myDataGrid.CurrentPageIndex=e.NewPageIndex;
...
myDataGrid.DataBind();
}
void PagerButtonClick(Object sender, EventArgs e)
{
String arg = ((LinkButton)sender).CommandArgument;
switch(arg)
{
case ("next"):
if (myDataGrid.CurrentPageIndex < (myDataGrid.PageCount - 1))
myDataGrid.CurrentPageIndex ++;
break;
case ("prev"):
if (myDataGrid.CurrentPageIndex > 0)
myDataGrid.CurrentPageIndex --;
break;
case ("last"):
myDataGrid.CurrentPageIndex = (myDataGrid.PageCount - 1);
break;
default:
myDataGrid.CurrentPageIndex = Convert.ToInt32(arg);
break;
}
myDataGrid.DataBind();
}

这里注意的是换了页要重新DataBind()一下。

f) SQL:

不多说了,下面是我觉得常用的函数:

System.Data.DataSet MyQuery(String querystring)
{
String connectionString = @"...";
System.Data.IDbConnection dbConnection = new
System.Data.SqlClient.SqlConnection(connectionString);
System.Data.IDbCommand dbCommand = new
System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = querystring;
dbCommand.Connection = dbConnection;
System.Data.IDbDataAdapter dataAdapter = new
System.Data.SqlClient.SqlDataAdapter();
dataAdapter.SelectCommand = dbCommand;
System.Data.DataSet dataSet = new System.Data.DataSet();
dataAdapter.Fill(dataSet);
return dataSet;
}
int MyDelete(String delString)
{
String connectionString = @"...";
System.Data.IDbConnection dbConnection = new
System.Data.SqlClient.SqlConnection(connectionString);
System.Data.IDbCommand dbCommand = new
System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = delString;
dbCommand.Connection = dbConnection;
int rowsAffected = 0;
dbConnection.Open();
try
{
rowsAffected = dbCommand.ExecuteNonQuery();
}
finally
{
dbConnection.Close();
}
return rowsAffected;
}
int MyInsert(...)
{
String connString = "...";
System.Data.IDbConnection dbConnection = new
System.Data.SqlClient.SqlConnection(connString);
String queryString = "INSERT INTO [...]([...], ...) VALUES (";
queryString +="@..., ...)";
System.Data.IDbCommand dbCommand = new
System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = queryString;
dbCommand.Connection = dbConnection;
System.Data.IDataParameter dbParam_... = new
System.Data.SqlClient.SqlParameter();
dbParam_...ParameterName = "@...";
dbParam_...Value = ...;
dbParam_...DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_...);
...
int rowsAffected = 0;
dbConnection.Open();
try {
rowsAffected = dbCommand.ExecuteNonQuery();
}
finally {
dbConnection.Close();
}
return rowsAffected;
}

10、 其他点点滴滴:

a) 去掉图片加链接后外包框:

<img border=0>

b) 去掉IE图片悬停工具栏:

<img galleryimg=”no”>或者<meta http-equiv="imagetoolbar" content="no">

c) 去掉链接点击后的虚线:

<a href="…" onFocus="this.blur()"></a>

d) 使iframe自适应长宽:

<iframe src="..."
onload="this.height =
this.document.body.offsetHeight;this.width=this.document.body.offsetWidth;">
</iframe>

另外千万注意不要漏了</iframe>,网页被撑开了,我查了半天才发现是这个原因。

e) 连续滚动:

一般的marquee会两次滚动间会有一段时间的空白,采用此处的代码可以使得其连续滚动。

上面这些就是这个网站的点点滴滴,没有什么技术含量,只是写篇乱纪,以标明这个颓废的寒假。