[转载]网站转接支付宝解决方案

mikel阅读(1050)

[转载]网站转接支付宝解决方案 – 幽云十六州 – 博客园

大型网上购物系统除了能让会员选择货到付款结账方式外,还应该提供一些更方便快捷的网上支付方式。如果网上商店没有足够的实力提供会员直接在网站中 建立现金账户的功能,就可以将订单信息转接到支付宝,让会员从支付宝付款。当然就算会员可以在网站上建立自己的现金账户,提供支付宝支付功能也不失为另一 种方便快捷的支付方式,这可以给客户提供更多可选的支付方式。

首先,网上购物系统必须与支付宝公司签订合作协议,以确保从本购物网站上传到

支付宝网站上的订单信息能被正确接收。

当会员于购物网站上买下一系列商品并选择支付宝付款方式后,购物系统即将会员购物的订单信息转发到支付宝,网站页面也会转到支付宝的付款页面。此 时,支付宝页面会发送一个验证信息到本网站以确认支付宝正确收到订单信息。

会员于支付宝网站付款完成后,网站页面会重新跳回本购物网站,同时支付宝会将已付款的订单信息发回本网站以便对本购物网站的数据库进行必要的修改操 作。另外本网站还需要向支付宝网站发送一个返回信息,告知支付宝本系统已正确收到付款完毕的订单信息并且已经完成对数据的处理操作。

向支付宝网站传送订单信息时主要参数的含义:

gateway :支付接口

service:识别是何接口实现何功能的表示

seller_email:商家签约时的支付宝账号,即收款的支付宝账号

key:安全校验码,与partner是一组

partner:商户ID,合作伙伴ID

sign_type:加密类型

_input_charset:编码类型

show_url:展示地址,即在支付宝页面时商品名称旁边的“详情”的链接地址

out_trade_no:会员订单编号,订单编号必须在本系统中保持唯一

subject:商品名称,也可称为订单名称,该接口并不是单一的只能买一样东西,可把一次支付当作一次下订单

body:商品描述,即备注

total_fee:商品价格,也可称为订单的总金额

源码分析(C#):

首先必须建立一个通知页面(Notify.aspx)和一个返回页面(Return.aspx)以接受并验证从支付宝返回的信息并对数据库中相应的 订单信息做修改处理操作。

Notify.aspx.cs

代码

using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Text; using System.Collections.Specialized; using System.IO; using Gateway; /// <summary> /// 创建该页面文件时,请留心该页面文件中无任何HTML代码及空格。 /// 该页面称作“通知页”,是异步被支付宝服务器所调用。 /// 当支付宝的订单状态改变时,支付宝服务器则会自动调用此页面,因此请做好自身网站订单信息与支付宝上的订单的同步工作 /// </summary> public partial class Alipay_Notify : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { string alipayNotifyURL = "https://www.alipay.com/cooperate/gateway.do?service=notify_verify"; //string alipayNotifyURL = "http://notify.alipay.com/trade/notify_query.do?";//此路径是在上面链接地址无法起作用时替换使用。 string partner = ""; //partner合作伙伴id(必须填写) string key = ""; //partner 的对应交易安全校验码(必须填写) string _input_charset = "utf-8";//编码类型,完全根据客户自身的项目的编码格式而定,千万不要填错。否则极其容易造成MD5加密错误。 alipayNotifyURL = alipayNotifyURL + "&partner=" + partner + "&notify_id=" + Request.Form["notify_id"]; //获取支付宝ATN返回结果,true是正确的订单信息,false 是无效的 string responseTxt = AliPay.Get_Http(alipayNotifyURL, 120000); //*******加密签名程序开始******* int i; NameValueCollection coll; //Load Form variables into NameValueCollection variable. coll = Request.Form; // Get names of all forms into a string array. String[] requestarr = coll.AllKeys; //进行排序; string[] Sortedstr = AliPay.BubbleSort(requestarr); //构造待md5摘要字符串 ; StringBuilder prestr = new StringBuilder(); for (i = 0; i < Sortedstr.Length; i++) { if (Request.Form[Sortedstr[i]] != "" && Sortedstr[i] != "sign" && Sortedstr[i] != "sign_type") { if (i == Sortedstr.Length - 1) { prestr.Append(Sortedstr[i] + "=" + Request.Form[Sortedstr[i]]); } else { prestr.Append(Sortedstr[i] + "=" + Request.Form[Sortedstr[i]] + "&"); } } } prestr.Append(key); string mysign = AliPay.GetMD5(prestr.ToString(), _input_charset); //*******加密签名程序结束******* string sign = Request.Form["sign"]; if (mysign == sign && responseTxt == "true") //验证支付发过来的消息,签名是否正确,只要成功进如这个判断里,则表示该页面已被支付宝服务器成功调用 //但判断内出现自身编写的程序相关错误导致通知给支付宝并不是发送success的消息或没有更新客户自身的数据库的情况,请自身程序编写好应对措施,否则查明原因时困难之极 { if (Request.Form["trade_status"] == "WAIT_BUYER_PAY")// 判断支付状态_等待买家付款(文档中有枚举表可以参考) { //更新自己数据库的订单语句,请自己填写一下 string strOrderNO = Request.Form["out_trade_no"];//订单号 string strPrice = Request.Form["total_fee"];//金额 如果你申请了商家购物卷功能,在返回信息里面请不要做金额的判断,否则会校验通过不了。 } else if (Request.Form["trade_status"] == "TRADE_FINISHED" || Request.Form["trade_status"] == "TRADE_SUCCESS")// 判断支付状态_交易成功结束(文档中有枚举表可以参考) { //更新自己数据库的订单语句,请自己填写一下 string strOrderNO = Request.Form["out_trade_no"];//订单号 string strPrice = Request.Form["total_fee"];//金额 } else { //更新自己数据库的订单语句,请自己填写一下 } Response.Write("success"); //返回给支付宝消息,成功,请不要改写这个success //success与fail及其他字符的区别在于,支付宝的服务器若遇到success时,则不再发送请求通知(即不再调用该页面,让该页面再次运行起来), //若不是success,则支付宝默认没有收到成功的信息,则会反复不停地调用该页面直到失效,有效调用时间是24小时以内。 //最好写TXT文件,以记录下是否异步返回记录。 ////写文本,纪录支付宝返回消息,比对md5计算结果(如网站不支持写txt文件,可改成写数据库) //string TOEXCELLR = "MD5结果:mysign=" + mysign + ",sign=" + sign + ",responseTxt=" + responseTxt; //StreamWriter fs = new StreamWriter(Server.MapPath("Notify_DATA/" + DateTime.Now.ToString().Replace(":", "")) + ".txt", false, System.Text.Encoding.Default); //fs.Write(TOEXCELLR); //fs.Close(); } else { Response.Write("fail"); //最好写TXT文件,以记录下是否异步返回记录。 //写文本,纪录支付宝返回消息,比对md5计算结果(如网站不支持写txt文件,可改成写数据库) string TOEXCELLR = "MD5结果:mysign=" + mysign + ",sign=" + sign + ",responseTxt=" + responseTxt; StreamWriter fs = new StreamWriter(Server.MapPath("Notify_DATA/" + DateTime.Now.ToString().Replace(":", "")) + ".txt", false, System.Text.Encoding.Default); fs.Write(TOEXCELLR); fs.Close(); } } }

Return.aspx.cs

代码

using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Text; using System.Collections.Specialized; using System.IO; using Gateway; /// <summary> /// 创建该页面文件时,请留心该页面文件是可以对其进行美工处理的,原因在于支付完成以后,当前窗口会从支付宝的页面跳转回这个页面。 /// 该页面称作“返回页”,是同步被支付宝服务器所调用,可当作是支付完成后的提示信息页,如“您的某某某订单,多少金额已支付成功”。 /// </summary> public partial class Alipay_Return : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { string alipayNotifyURL = "https://www.alipay.com/cooperate/gateway.do?service=notify_verify"; //string alipayNotifyURL = "http://notify.alipay.com/trade/notify_query.do?";//此路径是在上面链接地址无法起作用时替换使用。 string key = ""; //partner 的对应交易安全校验码(必须填写) string partner = ""; //partner合作伙伴id(必须填写) string _input_charset = "utf-8";//编码类型,完全根据客户自身的项目的编码格式而定,千万不要填错。否则极其容易造成MD5加密错误。 alipayNotifyURL = alipayNotifyURL + "&partner=" + partner + "&notify_id=" + Request.QueryString["notify_id"]; //获取支付宝ATN返回结果,true是正确的订单信息,false 是无效的 string responseTxt = AliPay.Get_Http(alipayNotifyURL, 120000); //*******加密签名程序开始//******* int i; NameValueCollection coll; //Load Form variables into NameValueCollection variable. coll = Request.QueryString; // Get names of all forms into a string array. String[] requestarr = coll.AllKeys; //进行排序; string[] Sortedstr = AliPay.BubbleSort(requestarr); //构造待md5摘要字符串 ; StringBuilder prestr = new StringBuilder(); for (i = 0; i < Sortedstr.Length; i++) { if (Request.Form[Sortedstr[i]] != "" && Sortedstr[i] != "sign" && Sortedstr[i] != "sign_type") { if (i == Sortedstr.Length - 1) { prestr.Append(Sortedstr[i] + "=" + Request.QueryString[Sortedstr[i]]); } else { prestr.Append(Sortedstr[i] + "=" + Request.QueryString[Sortedstr[i]] + "&"); } } } prestr.Append(key); //生成Md5摘要; string mysign = AliPay.GetMD5(prestr.ToString(), _input_charset); //*******加密签名程序结束******* string sign = Request.QueryString["sign"]; // Response.Write(prestr.ToString()); //调试用,支付宝服务器返回时的完整路径。 if (mysign == sign && responseTxt == "true") //验证支付发过来的消息,签名是否正确 { //更新自己数据库的订单语句,请自己填写一下 string strOrderNO = Request.QueryString["out_trade_no"];//订单号 string strPrice = Request.QueryString["total_fee"];//金额 string strTradeStatus = Request.QueryString["TRADE_STATUS"];//订单状态 Response.Write("订单号:" + strOrderNO + "<br>金额:" + strPrice); //成功,可美化该页面,提示信息 } else { Response.Write("------------------------------------------"); Response.Write("<br>Result:responseTxt=" + responseTxt); Response.Write("<br>Result:mysign=" + mysign); Response.Write("<br>Result:sign=" + sign); Response.Write("支付失败"); //支付失败,提示信息 } } }

除此之外在Notify.aspx页面和Return.aspx页面公用的一些方法,可以提取出来放在一个公共的类里面(Alipay.cs)

Alipay.cs

代码

using System.Web; using System.Text; using System.Security.Cryptography; using System.IO; using System.Net; using System; /// <summary> /// New Interface for AliPay /// </summary> namespace Gateway { public class AliPay { /// <summary> /// 与ASP兼容的MD5加密算法 /// </summary> public static string GetMD5(string s, string _input_charset) { MD5 md5 = new MD5CryptoServiceProvider(); byte[] t = md5.ComputeHash(Encoding.GetEncoding(_input_charset).GetBytes(s)); StringBuilder sb = new StringBuilder(32); for (int i = 0; i < t.Length; i++) { sb.Append(t[i].ToString("x").PadLeft(2, '0')); } return sb.ToString(); } /// <summary> /// 冒泡排序法 /// 按照字母序列从a到z的顺序排列 /// </summary> public static string[] BubbleSort(string[] r) { int i, j; //交换标志 string temp; bool exchange; for (i = 0; i < r.Length; i++) //最多做R.Length-1趟排序 { exchange = false; //本趟排序开始前,交换标志应为假 for (j = r.Length - 2; j >= i; j--) {//交换条件 if (System.String.CompareOrdinal(r[j + 1], r[j]) < 0) { temp = r[j + 1]; r[j + 1] = r[j]; r[j] = temp; exchange = true; //发生了交换,故将交换标志置为真 } } if (!exchange) //本趟排序未发生交换,提前终止算法 { break; } } return r; } /// <summary> /// 生成URL链接或加密结果 /// </summary> /// <param name="para">参数加密数组</param> /// <param name="_input_charset">编码格式</param> /// <param name="sign_type">加密类型</param> /// <param name="key">安全校验码</param> /// <returns>字符串URL或加密结果</returns> public static string CreatUrl( //string gateway,//GET方式传递参数时请去掉注释 string[] para, string _input_charset, string sign_type, string key ) { int i; //进行排序; string[] Sortedstr = BubbleSort(para); //构造待md5摘要字符串 ; StringBuilder prestr = new StringBuilder(); for (i = 0; i < Sortedstr.Length; i++) { if (i == Sortedstr.Length - 1) { prestr.Append(Sortedstr[i]); } else { prestr.Append(Sortedstr[i] + "&"); } } prestr.Append(key); //生成Md5摘要; string sign = GetMD5(prestr.ToString(), _input_charset); //以下是POST方式传递参数 return sign; //以下是GET方式传递参数 //构造支付Url; // char[] delimiterChars = { '='}; // StringBuilder parameter = new StringBuilder(); // parameter.Append(gateway); // for (i = 0; i < Sortedstr.Length; i++) // {//UTF-8格式的编码转换 // parameter.Append(Sortedstr[i].Split(delimiterChars)[0] + "=" + HttpUtility.UrlEncode(Sortedstr[i].Split(delimiterChars)[1]) + "&"); // } // // parameter.Append("sign=" + sign + "&sign_type=" + sign_type); // // //返回支付Url; // return parameter.ToString(); } //获取远程服务器ATN结果,验证是否是支付宝服务器发来的请求 public static string Get_Http(string a_strUrl, int timeout) { string strResult; try { HttpWebRequest myReq = (HttpWebRequest)HttpWebRequest.Create(a_strUrl); myReq.Timeout = timeout; HttpWebResponse HttpWResp = (HttpWebResponse)myReq.GetResponse(); Stream myStream = HttpWResp.GetResponseStream(); StreamReader sr = new StreamReader(myStream, Encoding.Default); StringBuilder strBuilder = new StringBuilder(); while (-1 != sr.Peek()) { strBuilder.Append(sr.ReadLine()); } strResult = strBuilder.ToString(); } catch (Exception exp) { strResult = "错误:" + exp.Message; } return strResult; } } }

以上三个文件建之后,就可以在需要的地方对支付宝接口进行调用以完成支付宝支付的功能了(Default.aspx.cs)

代码

using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using Gateway; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void BtnAlipay_Click(object sender, EventArgs e) { //业务参数赋值; string gateway = "https://www.alipay.com/cooperate/gateway.do?"; //支付接口 string service = "create_direct_pay_by_user"; //服务名称,这个是识别是何接口实现何功能的标识,请勿修改 string seller_email = ""; //商家签约时的支付宝帐号,即收款的支付宝帐号 string sign_type = "MD5"; //加密类型,签名方式“不用改” string key = ""; //安全校验码,与partner是一组,获取方式是:用签约时支付宝帐号登陆支付宝网站www.alipay.com,在商家服务我的商家里即可查到。 string partner = ""; //商户ID,合作身份者ID,合作伙伴ID string _input_charset = "utf-8"; //编码类型,完全根据客户自身的项目的编码格式而定,千万不要填错。否则极其容易造成MD5加密错误。 string show_url = "http://www.alipay.com/"; //展示地址,即在支付页面时,商品名称旁边的“详情”的链接地址。 string out_trade_no = TxtOrderno.Text.Trim(); //客户自己的订单号,订单号必须在自身订单系统中保持唯一性 string subject = TxtSubject.Text.Trim(); //商品名称,也可称为订单名称,该接口并不是单一的只能买一样东西,可把一次支付当作一次下订单 string body = TxtBody.Text.Trim(); //商品描述,即备注 string total_fee = TxtTotal_fee.Text.Trim(); //商品价格,也可称为订单的总金额 //服务器通知url(Alipay_Notify.aspx文件所在路经),必须是完整的路径地址 string notify_url = "http://localhost:8978/direct_vs2005_utf/Alipay_Notify.aspx"; //服务器返回url(Alipay_Return.aspx文件所在路经),必须是完整的路径地址 string return_url = "http://localhost:8978/direct_vs2005_utf/Alipay_Return.aspx"; //构造数组; //以下数组即是参与加密的参数,若参数的值不允许为空,若该参数为空,则不要成为该数组的元素 string[] para ={ "service="+service, "partner=" + partner, "seller_email=" + seller_email, "out_trade_no=" + out_trade_no, "subject=" + subject, "body=" + body, "total_fee=" + total_fee, "show_url=" + show_url, "payment_type=1", "notify_url=" + notify_url, "return_url=" + return_url, "_input_charset="+_input_charset }; //支付URL生成 string aliay_url = AliPay.CreatUrl( //gateway,//GET方式传递参数时请去掉注释 para, _input_charset, sign_type, key ); //以下是GET方式传递参数 //Response.Redirect(aliay_url); //以下是POST方式传递参数 Response.Write("<form name='alipaysubmit' method='post' action='https://www.alipay.com/cooperate/gateway.do?_input_charset=utf-8'>"); Response.Write("<input type='hidden' name='service' value=" + service + ">"); Response.Write("<input type='hidden' name='partner' value=" + partner + ">"); Response.Write("<input type='hidden' name='seller_email' value=" + seller_email + ">"); Response.Write("<input type='hidden' name='out_trade_no' value=" + out_trade_no + ">"); Response.Write("<input type='hidden' name='subject' value=" + subject + ">"); Response.Write("<input type='hidden' name='body' value=" + body + ">"); Response.Write("<input type='hidden' name='total_fee' value=" + total_fee + ">"); Response.Write("<input type='hidden' name='show_url' value=" + show_url + ">"); Response.Write("<input type='hidden' name='return_url' value=" + return_url + ">"); Response.Write("<input type='hidden' name='notify_url' value=" + notify_url + ">"); Response.Write("<input type='hidden' name='payment_type' value=1>"); Response.Write("<input type='hidden' name='sign' value=" + aliay_url + ">"); Response.Write("<input type='hidden' name='sign_type' value=" + sign_type + ">"); Response.Write("</form>"); Response.Write("<script>"); Response.Write("document.alipaysubmit.submit()"); Response.Write("</script>"); } }

转接到支付宝的付款功能在调试过程中,必须真实的从网站够买商品并跳转到支付宝网

付款。可以将所买商品的单价设为0.01元,从支付宝付款成功并重新跳回本网站后,检查订单信息是否正确,以及对传回的 数据所做的处理操作是否正确。总之,用支付宝付款的功能在调试过程中必须进行实际的支付操作才能发现错误。

[转载]封装jQuery表格插件jqGrid,控件化jqGrid(三):查询,编辑,修改,删除

mikel阅读(1113)

[转载]封装jQuery表格插件jqGrid,控件化jqGrid(三):查询,编辑,修改,删除 – 黑曜石 – 博客园.

上两篇:

封装 jQuery表格插件jqGrid,控件化jqGrid(一):显示

封装 jQuery表格插件jqGrid,控件化jqGrid(二):显示

本文将编码表格的动作功能,查询,编辑,修改和删除,并在文末附上源码,供大家参考,一起讨论,希望可以抛砖引玉!

一,在jqGrid.cs控件主体类中加入属性,用于控制这些功能的开关

#region 动作按钮
        private bool _search = false;
        private bool _add = false;
        private bool _edit = false;
        private bool _del = false;
        [Description("是否可查询,默认否")]
        public bool Search
        {
            get { return _search; }
            set { _search = value; }
        }
        [Description("是否可添加,默认否")]
        public bool Add
        {
            get { return _add; }
            set { _add = value; }
        }
        [Description("是否可编辑,默认否")]
        public bool Edit
        {
            get { return _edit; }
            set { _edit = value; }
        }
        [Description("是否可删除,默认否")]
        public bool Del
        {
            get { return _del; }
            set { _del = value; }
        }
        #endregion




相应的,需要在构造jqGrid页面JS时,构造相应的JS脚本,代码大家自己下源码可以看到。

二,做完了控件类,现在开始数据提供类的编码,这也是最核心的。

1)查询

可以用firebug看到jqGrid查询时,使用的是get方式,所以用Request.QueryString的方式取得控件传过来的值

看到传来的的_search参数控件查询的开关,当值为true时,查询打开

本控件仅使用复合查询的方式,并且默认使用“包含”,“大于等于”和“小于等于”,前者符合大部分的通用查询的要求,后者使得查询处理更简单些

其形式如:{“groupOp”:”AND”,”rules”: [{“field”:”email”,”op”:”cn”,”data”:”1″},{“field”:”orderno”,”op”:”ge”,”data”:”2″}]}

使用复合查询时,传过来的查询值是以json的方式包含在filters参数中的,因此,要使用其值,需要对其值进行JSON的反序列化,这里使用 DataContractJsonSerializer,需要在类中using System.Runtime.Serialization.Json,工程中也要加入其引用,其核心代码如下:

 
另外,还需要新建一个类,用于对应反序列化

 
注意类名上的DataContract声明和参数中的形如[DataMember(Name="groupOp")]的声明,这是反序列化所必须的,具体的大家可以搜索一下,这里就不做详细说明了

以上就完成了查询条件方面的参数分析和清理,现在对这些东东进行数据库交互

 

 
注意到or条件时,searchCase += " and (1<>1"这样的形式,这个小技巧,大家可以在sql中自己试试看,这样可以解决拼接SQL条件时or开头的问题,在第一篇中还有1=1的形式,同样的,也是可以解决and开头的问题。这个是我师傅教的,自认为是一个很牛比的小技巧。

查询至些就OVER了。

2)编辑,删除,添加

在设计控件之初,就为这个编辑伤脑筋,每个表的字段都不一样,类型也不一样,用反射吗,不灵活,也好麻烦,不能控件到细部

用entity framework吧,这个好,自动取到post过来的值,自动更新model,不过要依赖entity,更不通用。

后来想到配置用XML,问题一通百通,不管是表体的呈现灵活还是数据提供类的方面,都可以解决掉。

此三个功能都是使用post方式传递各项值,了解这点以后就很方便了,直接拼接SQL就可以完成这三个功能,很简单

 
文章有点长了,本来再慢慢写的,不过这几天很忙,一次写完吧,还是那句话,希望可以抛砖引玉。

这里是源码下载:http://files.cnblogs.com/bestfc/AspJqGrid.rar

使用方法:

web.config中加入


页面中加入jquery.js和jquery.ui.css后,写入

<AllenJqGrid:JqGrid ID="MyJqGrid" runat="server" TableName="orders" Search="true" Scroll="true" Add="true" Edit="true" Del="true" />,就OK了。

配置文件自动生成在页面文件所在目录,生成会判断是否有配置文件,有则读,无则生成后再读。

[转载]Windows SDK编程(Delphi版) 之 Windows编程概述与框架

mikel阅读(1262)

[转载]Windows SDK编程(Delphi版) 之 Windows编程概述与框架 – 得闲笔记 – 博客园.

Windows的目的是使那些熟悉系统基本知识的人能够坐下来,不必进行任何预训练,就能实际运行任何应用程序。为实现此目的,Windows向用 户提供了一些始终不变的接口。理论上来说,如果用户能运行起Windows,那么也就能运行那些基于那种接口的所有程序。而我们作为程序员的职责就是用 Windows提供的这些接口来开发基于Windows的应用程序。Windows提供的这些接口,我们通俗的称为Win32 SDK API。

为许多操作系统编写程序时,是你的程序使其与操作系统发生作用。比如在DoS程序中,是程序在要求做诸如是输入和输出这样的工作。不同的是,用传统的方法 编写的程序调用操作系统,操作系统却不调用你的程序。不过在大多数情况下,windows以相反的方式进行工作,既Windows会来调用你的程序。其过 程为:Windows一直处于等待状态,直到由Windows发送一条消息,该消息通过由Windows调用的特殊函数传送到用户的程序中。只要程序接收 到一条消息,它就会产生相应的动作,这便是Windows应用程序的的驱动方式,我称它为消息驱动模型。

Windows编程基础

入口函数WinMain:

以前学过C的人都晓得,C中的入口就是main()函数,任何程序开始,都由main进入开始执行,在Windows的编程中,也一样有一个入口函数,本 函数与C的入口函数差不多,不过是WinMain,所有的Windows程序都由Winmain入口开始往下执行。另外,WinMain有一个调用约定, 必须制定为WINAPI约定。和C的main返回一样,返回整形。Windows的标准语言是C,所以这个说明是针对于C版本的,那么我们Delphi程 序员呢?如果有人看过Delphi的工程文件dpr里面的内容的话,就应该晓得,Delphi的入口是由dpr文件的begin end之间入口开始往下执行。这个我们可以看着为Delphi的编译器的一个魔法,它将WinMain函数在Delphi的内部给我们已经制定好了,编译 的时候自动进入Winmain,然后再Winmain中进入begin end之间开始我们的窗口过程,所以我们可以将begin end这个看做为WinMain的一个子部分,那么就可以简单的理解为这个就相当于是一个WinMain了,同时也不用我们自己去声明一个WinMain 这样的入口函数,而只需在工程文件的begin .. end之间写我们的代码就可。

窗口过程

首先 窗口过程是由Windows调用的,而不是由我们程序自己调用的函数。  所有的Windows程序必须包括一些特殊的函数,他们不由我们程序自己调用,而是由Windows操作系统来调用。这个函数通常被称为窗口过程或者窗口 函数。当Windows需要向程序中传递一条消息时,Windows将调用窗口函数来处理这个消息。窗口函数在它的参数内接受消息。所有的窗口函数的返回 类型为LRESULT,调用约定为CALLBACK。LRESULT实际上就是一个整形,在Delphi中可以用LongInt来表示。调用约定 CallBack表示此过程属于系统回调函数,在Delphi中用Stdcall约定就行。实际上凡是关乎操作系统的相关函数都指定为Stdcall调用 约定。一个窗口函数过程内部,我们通常能够看到一个很大的Case end这样的结构。用来标记对不同的消息做不同的处理。Windows的消息有很多很多个,很多时候,我们不必为我们关心的消息提供消息处理过程,此时我 们就可以使用Windows的默认处理过程为DefWindowProc函数,用这个函数就可以按照系统的默认方式进行处理。

窗口类

这里说的窗口类,是指窗口的类型和样式,而不是我们面向对象中所说的那个类了,这个需要分解清楚。Windows的应用程序都是由一个个的窗口组成的。而 要生成这些窗口,我们必须先注册一个窗口类给系统,这样以后创建窗口的时候,就会根据你所给定的样式等信息进行创建了。

消息循环

在前面说了Windows是消息驱动程序运行的,在每个应用程序中,都有一个自己的消息队列,从应用程序的消息队列中不断的取回消息构建了消息循环运转。 所以所有的应用程序都必须在内部建立一个消息循环,此循环从应用程序的消息队列中读取任何未处理的消息,然后将它送还给Windows,这样以该消息作为 参数的就会被对应的窗口过程调用以对该消息进行处理。

通过前面几点的了解,应该大致的晓得了一个Windows应用程序所具备的几个要素和步骤了:

1、声明入口函数(这个在Delphi中没有)

2、定义一个窗口类

3、注册窗口类

4、创建窗口

5、显示窗口

6、开始消息循环。

通过这6个步骤,就基本上能够实现一个Windows的视窗应用程序了。下面我来给一个样本程序

代码

program Project1; uses WIndows, messages; //窗口过程 function WndProc(hwnd: THandle;MsgId: Longint;wParam: WParam;lParam: LParam): LRESULT;stdcall; var dc: HDC; r: TRect; str: string; hi: Word; PaintStruct: TPaintStruct; begin Case msgId of WM_DESTROY: begin PostQuitMessage(0);{释放的时候,发送退出消息循环,程序结束} result := 1; exit; end; result := DefWindowProc(Hwnd,MsgId,WParam,LParam); exit; end; end; procedure WinMain(HthisInstance: LongInt); var msg: Tmsg; MainHwnd: THandle; WndClass: TWNdClassex; begin WndClass.cbSize := Sizeof(WNdClass);//指定结构大小 WndClass.hInstance := HThisInstance;//指定宿主为当前应用程序的实例 WndClass.lpszClassName := 'DxWindow';//指定类名 WndClass.lpfnWndProc := @WndProc;//指定窗口过程 WndClass.style := 0;//CS_VREDRAW or CS_HREDRAW;//指定样式为普通样式 WndClass.hIcon := LoadIcon(0,IDI_Application);//普通图标32*32大小的 WndClass.hIconSm := LoadIcon(0,IDI_WINLogo);//指定小图标16*16的 WndClass.hCursor := LoadCursor(0,IDC_Arrow);//指定光标 WndClass.lpszMenuName := nil;//指定菜单 WndClass.cbClsExtra := 0; WndClass.cbWndExtra := 0; WndClass.hbrBackground := CreateSolidBrush(RGB(236,233,216));//GetStockObject(White_Brush); if RegisterClassex(WndClass) <> 0 then begin MainHwnd := CreateWindowEx(0,WndClass.lpszClassName,'测试窗口标题',WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,0,0,hTHisInstance,nil); if MainHwnd <> 0 then begin ShowWindow(MainHwnd,sw_ShowNormal); UpdateWindow(MainHwnd); while GetMessage(msg,0,0,0) do begin //这里开始消息循环 TranslateMessage(msg); DIspatchMessage(msg); end; ExitCOde := Msg.wParam;//退出 end; end; end; begin WinMain(Hinstance); end.

本程序,就是一个最基本的Windows程序,本程序不包含任何功能,仅仅是显示一个窗口。现在来分解一下这个程序。由于Delphi中不必用 WinMain,所以我自己在内部构建了一个WinMain函数,传递一个参数HthisInstance,本参数表示当前应用程序的实例,实际上 Delphi运行启动程序的时候,这个实例已经通过Delphi内部包装的WinMain函数给反馈回来了,这个实例句柄,我们也可以通过 GetModuleHandle(0)来获得。那么这个参数的目的有什么作用呢?还是需要将WinMain的原型拿过来分析说明一下!WinMain的原 型为:

int WINAPI WinMain(Hinstance hThisInst,Hinstance hPrevInst,LPSTR lpszArgs,int nWinMode);

hThisInst和hPrevInst都是句柄类型,hThisInst指程序的当前实例,因为Windows是个多任务操作系统,一次可以同时 运行相同程序的多个实例,所以用这个实例句柄来标记到底属于哪个。hPrevInst这个我们可以不同管他,在我们的系统中,他始终为nil,他存在的唯 一理由就是与Win3.1时代的程序兼容,意思是指前一个程序实例。lpszArgs指定为命令行参数,就想ping 127.0.0.1这个里面的127.0.0.1这样的就是参数,对应着Delphi的paramstr(1)等。nWinMode参数保存的值决定如何 显示窗口。

定义窗口类。我这里用的是TWndClassEx,在Windows中声明如下

tagWNDCLASSEXA = packed record
cbSize: UINT;  //指定本类结构体大小
style: UINT;  //指定为窗口样式
lpfnWndProc: TFNWndProc;  //指定窗口过程
cbClsExtra: Integer;  //附加的信息
cbWndExtra: Integer;
hInstance: HINST;//所属的应用程序实例
hIcon: HICON; //图标32*32,大图标
hCursor: HCURSOR; //光标
hbrBackground: HBRUSH; //背景画布对象句柄
lpszMenuName: PAnsiChar;//菜单资源名
lpszClassName: PAnsiChar;//类名
hIconSm: HICON;//小图标16*16
end;

每个Windows应用程序都有两个与其相关的图标,一个是标准尺寸(32*32),另一个是小图标,当应用程序被最小化时,使用小图标。当应用程序快捷 方式,以及在硬盘中显示时,显示标准图标。这里我通过了

LoadIcon这个函数来加载一个图标资源。原型为

HICON LoadIcon(Hinsance hinst,LPCSTR IconResName);

本函数将返回一个图标句柄。hInst指定包含图标的实例模块,我这里指定的是0,0表示调用系统的,如果指定为我们本应用程序的实例的话,这个返 回是会失败的哦,因为我们应用程序内部并不包含对应的图标资源。第二个参数指定为图标资源名。系统默认的有

IDI_APPLICATION   缺省图标

IDI_ERROR  错误符号

IDI_INFORMATION  信息

IDI_QUESTION  问号

IDI_WARNING  感叹号

IDI_WINLOGO  窗口标志

读取鼠标光标,用LoadCursor,用法与LoadIcon差不多,一些系统的默认光标样式

IDC_ARROW   缺省箭头指针

IDC_CROSS    十字线

IDC_IBEAM     垂直工字型

IDC_WAIT      沙漏

窗口的背景,通过使用API函数来获得背景画刷的句柄。我这里用的是CreateSolidBrush,目的是创建一个画刷的GDI对 象,CreateSolidBrush中的参数指定为画刷的颜色。另外,还有一种方式就是通过GetStockObject函数来获得一个系统内部的画刷 对象。GetStockObject函数用于获得一些标准显示对象的句柄,包括画刷,画笔和字符字体等。原型为

HGDIOBJ GetStockObject(int Object);

汗水返回object所指定的对象的句柄(HGDIOBJ表示一个GDI句柄)

一些系统的内置画刷:

BLACK_BRUSH  黑色

DKGRAY_BRUSH  黑灰

HOLLOW_BRUSH

LIGRAY_BRUSH  浅灰

WHITE_BRUSH  白色

比如,可以尝试,将我上面的CreateSolidBrush换成GetStockObject(WHITE_BRUSH)这样的看一下效果。

一旦,当窗口类型,指定好了之后,我们就可以通过使用RegisterClassEx来注册一个窗口类了。

ATOM RegisterClassEx(wndclassex: TWNDCLASSEX)

ATOM表示一个原子类型,表示全系统唯一。

创建窗口

通过上面的步骤,注册成功了之后,就可以依据注册的窗口类来创建一个窗口了,创建窗口我用了CreateWindowEX,

function CreateWindowEx(dwExStyle: DWORD; lpClassName: PChar;
lpWindowName: PChar; dwStyle: DWORD; X, Y, nWidth, nHeight: Integer;
hWndParent: HWND; hMenu: HMENU; hInstance: HINST; lpParam: Pointer): HWND;

参数一指定了窗口的扩展样式,我这里啥扩展都没,所以指定为0,参数二指定为类名,这个名字必须要和我们注册的窗口类的名字一样,所以直接指定为 WndClass.lpszClassName,参数三指定为窗口标题,参数4指定为窗口的样式,我这里指定的是层叠样式窗口,其常见类型为:

WS_ORERLAPPED   边框重叠窗口

WS_MAXIMIZEBOX  最大化

WS_MINIMIZEBOX  最小化

WS_SYSMENU  系统菜单

WS_HSCROLL  水平滚动

WS_VSCROLL  垂直滚动

尔后的参数就是指定了窗口的位置,宽度和高度,我这里都是指定了CW_USEDEFAULT采用了系统默认的。如果窗口创建成功,将会返回创建的窗 口句柄,否则返回0。

创建了窗口之后,窗体并不会被显示,要显示窗口,我们需要嗲用ShowWindow函数,本函数参数一指定要显示的窗口句柄,参数2指定为显示方式,显示 方式有下面几种形式:

SW_HIDE     隐藏

SW_MINIMIZE  最小化

SW_MAXIMIZE  最大化

SW_RESTORE  恢复为正常大小

ShowWindows函数,返回窗口的前一个显示状态,如果显示了窗口则返回非0值,如果没显示,则返回0

最后,调用了UpdateWindow函数,本函数的目的是告诉Windows向用户的应用程序发送一条消息,该消息是需要更新一下主窗口。

最后便是消息循环了。消息循环是所有Windows应用程序的组成部分,它的作用就是接受并处理Windows发送的消息。运行程序时,它不断的被系统发 送消息。直到所有这些消息都被读取以及处理。否则就一直保存在应用程序的消息队列里。每次应用程序准备好去读取另一个消息时。就必须调用 GetMessage。原型为

BOOL GETMessage(msg: tagMsg;hwnd: THandle;min,max: UINT);

msg指定消息结构体,这个在windows单元中有定义,在本结构体中有包含hwnd窗口句柄,用来标石采用哪个窗口过程处理。也包含有消息 id,同时表示采用窗口过程的那个消息处理方式处理。其中的WPARAM和lparam是消息的附加信息。time指定了消息的发送时间,以毫秒为单位指 定,pt包含鼠标的坐标。在应用程的消息队列中如果没有一条消息,则GetMessage调用会向Windows回传一个控制命令(这个以后再说)。

GetMessage的hwnd参数将指定所获得的消息传给哪个窗口。一个应用程序可能有很多个窗口,而用户也许只想接收某个具体窗口的消息,如果想接收 指向所有应用程序的所有消息,则指定为0。其他两个参数指定了要接收的消息的范围,通常希望接收所有的消息,所以一般都指定为0.当用户程序结束 时,getmssage返回0,此时消息循环接收。否则返回非0,如果发生错误,则返回-1(至于错误如何处理,请大家思考一下)。然后消息循环段内,有 两个函数

TranslateMessage和DispatchMessage。这两个函数,第一个函数是将系统产生的虚拟键代码转成字符消息。可能并不是所 有程序都需要调用它,但是打部分程序还是需要处理按键信息的。一旦读取并转换了消息,通过DispatchMessage就将消息再派遣回 Windows,于是Windows保存该消息,直到能将其传递给程序的窗口uhanshu为止。一旦消息循环结束,WinMain也就结束,程序也随之 结束。

最后就是窗口函数,我在上面定义的窗口函数为WndProc,可见我在这个函数中仅仅处理了一个唯一的消息就是窗口释放的时候,发送了一个 PostQuitMessage的处理。该函数将发送一个WM_QUIT的消息给应用程序,然后GetMessage获得WM_QUIT的时候,就会为 False,从而退出循环结束消息。其他的消息处理,我全部都是调用的默认处理过程DefWindowProc来对消息进行处理。

到现在为止一个基本的Delphi SDK编程框架就完成了。

作者:不得闲
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必 须保留此段声明,且在文章页面明显位置给出原
文连接,否则保留追究法律责任的权利。

[转载]Visual Studio IDE 实用小技巧(附打包下载)

mikel阅读(1425)

[转载]Visual Studio IDE 实用小技巧(附打包下载) – Lau Yee的.Net地盘 – 博客园.

看到《Visual Studio 2010 实用功能总结》2篇文章大家都比较喜欢,我也来补充一些Visual Studio的实用小技巧。

1、Visual Studio配色方案。

如果你想让你的编辑器换一种风格显示,你可以在工具—〉选项—〉字体和颜色中进行设置,但是最好的方式是下 载精选的这6 套配色方案

使用方法选择工具—〉导入和导出设置,然后按提示进行着设置。

2、代码段

按下Ctrl+K+S 或 Ctrl+K+X 键会出现代码段提示。你可以选择需要的代码段按回车生成。或者直接输入代码段的快捷简写按下 TAB两次生成。例如输入:foreach,按两下 TAB会自动生成代码。可以在这里下载C# 的官方扩充代码段包。还可以在网上搜索网友编辑的代码段包。http://snippetlibcsharp.codeplex.com/ 是 个不错的扩充。当然你可以编辑你自己的代码段。你不需要懂得描述代码段的XML语法。只需要下载这个现成的代码段编辑工具,很高兴它是作为VS的插 件提供。更高兴这个工具是开源的。要使用你下载或自己编辑的代码段,解压他们放到 我的文档\Visual Studio 2XXX\Code Snippets\Visual C#目录下。或者在“工具”—〉“代 码段管理器”进行导入。(我的IDE工具菜单下没有,只能按Ctrl+k+B了)

3、扩展如果你用的不是VS2010便不能使用 VS2010->Tools->Extension Manager的扩展管理功能。但你同样可以在Visual Studio 库找到1,789个扩展的项目。要想自己开发扩展,关注我的Visual Studio 扩展编程-#0:如何开始!系列文章(刚刚开始写),里面介绍了更多资源。

4、其他更多技巧

编辑HTML时的属性值引号自动插入:

这里设置自动排版格式,你可以定制缩进、大括号是否默认在方法括号之后,你可以下载 我的IDE所有设置方案。然后用工具—〉导入和导出设置导入它。

还有另一种代码段:将任意编辑器中的文本字节选定拖到工具箱,在需要的时候再拖下来。如图:

5、将”博客园精华区”加入Visual Studio起始页

如果你是一个开发人员,经常使用VS,和经常登录博客园可能喜欢这个。在你每次打开VS的时候可以方便的看到在博客园当前主页有什么文章更新。你会得到如 图的效果。

要做的这一点我们只需做一下的一个简单的设置。

打开 工具–选项窗口 ,定位到环境–启动项,会出现下图。进行如图设置,就OK了。

只是一个小技巧,但是挺有用处。

附:(VS系统通用快捷键:)

1、自动排版(类似VC6中的Alt+F8)
编辑.格式化选定内容 Ctrl + K,Ctrl + F 根据周围的代码行,正确缩进选定的代码行。

2、注释与去掉注释功能。
编 辑.注释选定内容 Ctrl + K,Ctrl + C 使用编程语言的正确注释语法将代码的当前行标记为注释。
编辑.取消注释选定内容 Ctrl + K,Ctrl + U 从代码的当前行中移除注释语法。
将插入点移动到文档中的下一个大括号处。
编辑.转到大括号 Ctrl + ] 将插入点移动到文档中的下一个大括号处。
编辑.向下滚动一行 Ctrl + 向下键 将文本向下滚动一行。仅可用于文本编辑器。
编辑.向上滚动一行 Ctrl + 向上键 将文本向上滚动一行。仅可用于文本编辑器。

3. 怎么找到解决方案
视图.解决方案资源管理器 Ctrl + Alt + L 显示解决方案资源管理器,它列出当前解决方案中的项目和文件

4.显示“工具箱”
视图.工具 箱 Ctrl + Alt + X 显示“工具箱”,其中包含可包括在代码中或与代码一起使用的控件和其他项。

5. 清除项目中的所有断点
调试.删除所有断点 Ctrl + Shift + F9 清除项目中的所有断点。

调 试.反汇编 Ctrl + Alt + D 显示“反汇编”窗口。
调试.切换断点 F9 在当前行设置或移除断点。
***********************************************
VS2008 快捷键
新建项目 Ctrl+shift+N
新建网站 shift+Alt+N
文件 Ctrl+N
打开项目/解决方案 Ctrl+shift+O
打开网站 shift+Alt+O
打开文件 Ctrl+O
保存当前文件 Ctrl+S
全部保存 Ctrl+Shift+S
添 加新项 Ctrl+Shift+A
添加现有项 Shift+Alt+A
添加类 Shift+Alt+C
撤消 Ctrl+Z
重复 Ctrl+Y
转 到 Ctrl+G
循环应用剪贴板中的复制项Ctrl+Shift+V
设置文档的格 式 Ctrl+E,D
设置选定内容的格式Ctrl+E,F
转换为大写 Ctrl+Shift+U
转换为小写 Ctrl+U
删除水平空白Ctrl+E,\
查 看空白Ctrl+E,S
自动换行Ctrl+E,W
渐进式搜索Ctrl+I
注 释选定内容Ctrl+E,C
取消注释选定内容Ctrl+E,U
快速查找 Ctrl+F
快速替换 Ctrl+H
在文件中查找 Ctrl+Shift+F
查 找下一个 F3
查找上一个 Shift+F3
在文件中替换 Ctrl+Shift+H
查找符号 Alt+F12
切换书签 Ctrl+B,T
启 用书签 Ctrl+B,E
上一书签 Ctrl+B,P
下一书签 Ctrl+B,N
清 除书签 Ctrl+B,C
添加任务列表快捷方式Ctrl+E,T
切换大纲显示展开Ctrl+M,M
切 换所有大纲显示Ctrl+M,L
停止大纲显示 Ctrl+M,P
折叠到定义 Ctrl+M,O
生产方法存根 Ctrl+K,M
列出成员 Ctrl+K,L
参 数信息 Ctrl+K,P
快速信息 Ctrl+K,I
完成单词 Ctrl+K,W
插入代码段 Ctrl+K,X
外侧代码 Ctrl+K,S
代 码 F7
设计器 Shift+F7
服务器资源管理器 Ctrl+W,L
解 决方案资源管理器 Ctrl+W,S
类视图 Ctrl+W,c
代码定义窗口Ctrl+W,D
对 象浏览器 Ctrl+W,J
错误列表 Ctrl+W,E
输出 Ctrl+W,O
属性窗口 Ctrl+W,P
任务列表 Ctrl+W,T
工 具箱 Ctrl+W,X
全屏显示shift+Alt+Enter
向后定位 Ctrl+-
向前定位 Ctrl+Shift+-
属性页 Shift+F4
查 找符号结果 Ctrl+W,Q
书签窗口 Ctrl+W,B
命令窗口 Ctrl+W,A
文档大纲 Ctrl+W,U
资源视图 Ctrl+W,R
宏 资源管理器 Alt+F8
Web浏览器 Ctrl+W,W
重命名 F2
提取方法 Ctrl+R,M
封装字段 Ctrl+R,E
提 取接口 Ctrl+R,I
将局部变量提升为参数 Ctrl+R,P
移除参数 Ctrl+R,V
重新排列参数 Ctrl+R,O
生成解决方案 F6
生 成当前项目 Shift+F6
启动调试 F5
继续 F5
全 部中断 Ctrl+Alt+Break
停止调试 Shift+F5
重新启动 Ctrl+Shift+F5
开始执行(不调试)Ctrl+F5
异常 Ctrl+D,E
逐语句 F11
跳出 Shift+F11
逐 过程 F10
切换断点 F9
删除所有断点 Ctrl+Shift+F9
断点 Ctrl+D,B
即时 Ctrl+D,I
快 速监视 Ctrl+D,Q
监视1 Ctrl+D,W
监视2 Ctrl+D+W,2
监视3 Ctrl+D+W,3
监视4 Ctrl+D+W,4
自 动窗口Ctrl+D,A
局部变量 Ctrl+D,L
调用堆栈 Ctrl+D,C
线程 Ctrl+D,T
切换当前线程标志状态 Ctrl+8
仅 显示标志的线程 Ctrl+9
模块 Ctrl+D,M
进程 Ctrl+D,P
反编译 Ctrl+Alt+D
寄存器 Ctrl+D,R
内 存1 Ctrl+D,Y
内存2 Ctrl+Alt+M,2
内存3 Ctrl+Alt+M,3
内存4 Ctrl+Alt+M,4
附加到进程 Ctrl+Alt+P
代码段管理器 Ctrl+K,Ctrl+B
运行当前宏 Ctrl+Shift+P
记录当前宏 Ctrl+Shift+R
宏IDE Alt+F11
当前上下文中的测试 Ctrl+R,T
解决方案中的所有测试Ctrl+R,A
如 何实现 Ctrl+F1,H
搜索 Ctrl+F1,S
目录 Ctrl+F1,C
索引 Ctrl+F1,I
帮助收藏夹 Ctrl+F1,F
动 态帮助 Ctrl+F1,D
索引结果 Ctrl+F1,T

[转载]NVelocity系列:NVelocity配置详解

mikel阅读(1000)

[转载]NVelocity系列:NVelocity配置详解 – EasyNet – 博客园.

VelocityEngine初始化前,可以通过ExtendedProperties配置NVelocity的运行环境参数,当执行 VelocityEngine的Init(ExtendedProperties)后,NVelocity会合并自定义配置和默认配置。 NVelocity在NVelocity.Runtime.RuntimeConstants中定义了默认配置项的名称,在内嵌资源文件 NVelocity.Runtime.Defaults.nvelocity.properties中定义了所有默认配置项的值。下面列出一些常用配置:

模板编码:

input.encoding=ISO-8859-1     //模板输入编码
output.encoding=ISO-8859-1  //模板输出编码

#foreach配置

directive.foreach.counter.name = velocityCount     //计数器名称
directive.foreach.counter.initial.value = 1               //计数器初始值
directive.foreach.maxloops = -1                           //最大循环次数,-1为默认不限制 directive.foreach.iterator.name = velocityHasNex    //迭代器名称

#set配置

directive.set.null.allowed = false     //是否可设置空值

#include配置

directive.include.output.errormsg.start = <!– include error :     //错误信息提示开始字符串
directive.include.output.errormsg.end   =  see error log –>      //错误信息提示结束字符串

#parse配置

directive.parse.max.depth = 10     //解析深度

模板加载器配置

resource.loader = file     //模板加载器类型,默认为文件,可定义多个

file.resource.loader.description = Velocity File Resource Loader     //加载器描述
file.resource.loader.class = NVelocity.Runtime.Resource.Loader.FileResourceLoader     //加载器类名称
file.resource.loader.path = .            //模板路径
file.resource.loader.cache = false     //是否启用模板缓存
file.resource.loader.modificationCheckInterval = 2     //检查模板更改时间间隔

宏配置

velocimacro.permissions.allow.inline = true                              //是否可以行内定义
velocimacro.permissions.allow.inline.to.replace.global = false     //是否可以用行内定义代替全局定义
velocimacro.permissions.allow.inline.local.scope = false             //行内定义是否只用于局部

velocimacro.context.localscope = false                                    //宏上下文是否只用于局部
velocimacro.max.depth = 20                                                  //解析深度

velocimacro.arguments.strict = false                                       //宏参数是否启用严格模式

资源管理器配置

resource.manager.class = NVelocity.Runtime.Resource.ResourceManagerImpl          //管理器类名称
resource.manager.cache.class = NVelocity.Runtime.Resource.ResourceCacheImpl     //缓存器类名称

解析器池配置

parser.pool.class = NVelocity.Runtime.ParserPoolImpl     //解析池类名称
parser.pool.size = 40                                                  //初始大小

#evaluate配置

directive.evaluate.context.class = NVelocity.VelocityContext     //上下问类名称

可插入introspector配置

runtime.introspector.uberspect = NVelocity.Util.Introspection.UberspectImpl     //默认introspector类名称
在NVelocity中有的配置是可以定义多个的,比 如资源加载器。注意一点在传入的实现类名称一定要采用:class fullname;assembly name格式,比如自定义了一 个资源加载器EasyNet.Mvc.AssemblyResourceLoader,程序集名称为EasyNet.Mvc,那么应该如下示例定义:


VelocityEngine velocity = new VelocityEngine();

ExtendedProperties props = new ExtendedProperties();

//定义资源加载器
props.AddProperty(RuntimeConstants.RESOURCE_LOADER, EasyNet.Mvc.AssemblyResourceLoader;EasyNet.Mvc);

//初始化
velocity.Init(props);

[转载]NVelocity指南:宏和包含

mikel阅读(979)

1. 宏

#macro 脚本元素允许模板设计者在VTL 模板中定义重复的段。 Velocimacros 不管是在复杂还是简单的场合都非常有用。下面这个Velocimacro,仅用来节省击键和减少排版错误,介绍了一些Velocity宏的概念。

#macro( d )

<tr><td></td></tr>

#end

在例子中,Velocimacro定义为d,它可以象调用其他VTL 指令一样的形式来进行调用

#d()

当这个模板被调用时, Velocity#d() 替换为一个单行的空表格。

Velocimacro 可以带一些参数,也可以不带参数(如上例所示)。但在他被调用时,所带的参数必须和其定义时的参数一样。很多Velocimacros 定义为不止一个参数。下面这个宏带有两个参数,一个颜色,一个数组。

#macro( tablerows $color $somelist )

#foreach( $something in $somelist )

<tr><td bgcolor=$color>$something</td></tr>

#end

#end

在这个例子中定义的Velocimacro,名为tablerows, 要求两个参数。 第一个参数代替$color, 第二个代替$somelist

可以写进VTL 模板中的东西都可以写进Velocimacro 的主体部分。tablerows 宏其实是一个foreach 语句。在#tablerows 宏的定义中有两个#ende 语句,第一个属于#foreach, 第二个结束宏定义。

#set( $greatlakes = [“Superior”,”Michigan”,”Huron”,”Erie”,”Ontario”] )

#set( $color = “blue” )

<table>

#tablerows( $color $greatlakes )

</table>

请注意$greatlakes 替换了$somelist。 这样,当#tablerows 宏被调用时,将产生以下输出:

<table>

<tr><td bgcolor=”blue”>Superior</td></tr>

<tr><td bgcolor=”blue”>Michigan</td></tr>

<tr><td bgcolor=”blue”>Huron</td></tr>

<tr><td bgcolor=”blue”>Erie</td></tr>

<tr><td bgcolor=”blue”>Ontario</td></tr>

</table>

Velocimacros 在Velocity 模板语句内定义,这意味着它在同一站点内的其他Velocity 模板中并不有效。定义一个宏,并使其与其他模板共享很具有明显的优点:他减少了在大量的模板内重复定义宏的工作,并减少了出错的机会,并确保对其他宏的改 变对其他所有模板有效。

但如果 #tablerows($color $list) 宏是在一个Velocimacros 模板库内定义的,它就可以被其他常规模板所用。当然,它可以用于各种目的,也可重用多次。在表示所有真菌类(fungi)的mushroom.vm 模板中,#tablerows 宏可以被用来列出典型的蘑菇。

#set( $parts = [“volva”,”stipe”,”annulus”,”gills”,”pileus”] )

#set( $cellbgcol = “#CC00FF” )

<table>

#tablerows( $cellbgcol $parts )

</table>

我们对mushroom.vm执行请求,Velocity 将在模板库内找到#tablerows 宏 (在velocity.properties 文件中定义)并产生以下输出:

<table>

<tr><td bgcolor=”#CC00FF”>volva</td></tr>

<tr><td bgcolor=”#CC00FF”>stipe</td></tr>

<tr><td bgcolor=”#CC00FF”>annulus</td></tr>

<tr><td bgcolor=”#CC00FF”>gills</td></tr>

<tr><td bgcolor=”#CC00FF”>pileus</td></tr>

</table>

Velocimacro 参数

Velocimacros 的参数可以是以下的VTL元素:

引用(Reference): 以 ‘$’ 打头的元素

字面字符串(String literal) : 比如”$foo” 或 ‘hello’

字面数字: 1, 2 ….

整数范围: [ 1..2] 或 [$foo .. $bar]

对象数组: [ “a”, “b”, “c”]

布尔真

布尔假

当把引用作为参数传递给Velocimacros时,请注意引用是按“名字”传递 的。这意味着他们的值在每次使用他们的Velocimacro中产生。这个特性允许你在方法调用是传递引用,并在每次使用时进行方法调用。例如,Fo, 当调用下面的Velocimacro 时,

#macro( callme $a )

$a $a $a

#end

#callme( $foo.bar() )

结果是,在方法bar() 中,引用 $foo 被调用了3次。

咋看时,这个特征让人吃惊,当当你考虑一下Velocimacros的原本动机 – 在VTL模板中避免很多“剪切复制”操作—你就会明白。它允许你将无状态对象,比如在一个颜色表格行内重复产生一些颜色次序的对象,传递给 Velocimacro。

如果你需要使用这个特征,你通常可以从方法内取得一个值,作为一个新的引用传递给 宏:

#set( $myval = $foo.bar() )

#callme( $myval )

Velocimacro 属性

在velocity.properties 文件中有数行定义可以用来灵活实现Velocimacros。详细情况请参见开发指南(Developer Guide)。

velocimacro.library – 是一个逗号分隔的所有Velocimacro 模板库的列表。默认情况下, Velocity 搜寻一个单一的库VM_global_library.vm.。 预先配置的模板路径用来查找Velocimacro 库。

velocimacro.permissions.allow.inline – 这个属性决定Velocimacros 是否可以在常规模板内定义,取值为逻辑True或者False。默认情况下,设置为true,允许设计者在产规模板内定义宏。

velocimacro.permissions.allow.inline.to.replace.global – 逻辑true 或者false,允许标明是否允许在常规模板内定义的Velocimacro 代替在模板库中定义并通过velocimacro.library属 性在启动时装入的全局宏。默认设置为false。

velocimacro.permissions.allow.inline.local.scope – 逻辑true 或者false,默认值为false。 控制是否 在模板内定义的Velocimacros 仅在定义它的模板内可见。换句话说,如果设置为true,一个模板可以定义仅能被他所用的宏。你可以用它来做一些漂亮的宏,如果一个全局调用另一个全局 宏,在局部(inline)范围内,当被一个模板调用时,该模板可以定义一个被第一个全局宏调用的第二个全局宏的私有实现。其他所有模板都不受影响。

velocimacro.context.localscope – 逻辑值true 或者 false,缺省值为false。但设置为true时,所有在Velocimacro 内通过 #set() 进行的修改都将被视为Velocimacro 的本地行为,不会影响到其上下文。

velocimacro.library.autoreload – 此属性控制Velocimacro 库的自动载入。缺省值为false。如果设置为true,被调用的Velocimacro得源库将被检查是否改变,并在必要是重新载入。这将使你可以改变 和测试Velocimacro 库,而不必重新启动应用服务器或者servlet容器,就象你工作在常规模板一样。这个模时仅在资源载入器的缓存模时被关闭的情况下有效 (如 file.resource.loader.cache = false )。此特征为开发时设计,不要在生产模式时使用。

Velocimacro Trivia

当前, Velocimacros 在其首次在模版中使用前必须首先定义它。这意味着, #macro() 宣称应该在使用Velocimacros之前。

如果你想#parse() 一个包含#macro() 指令的模板,记住这个非常重要。因为#parse() 在运行时发生,解析器在解析时要决定是否模版中一个看起来像VM的元素真是VM,所以解析一系列VM 宣称可能并不能如愿地工作的很好。为避免如此,可以简单地使用velocimacro.library 的办法,使Velocity 在启动时载入VM。

2. 转义 VTL 指令

VTL 可以通过反斜杠(“\”)来进行转义,directives can be escaped with the backslash character in a manner similar to valid VTL references.

## #include( “a.txt” ) renders as <contents of a.txt>

#include( “a.txt” )

## \#include( “a.txt” ) renders as \#include( “a.txt” )

\#include( “a.txt” )

## \\#include ( “a.txt” ) renders as \<contents of a.txt>

\\#include ( “a.txt” )

在转义在一个单一指令内包含多个脚本元素(比如f-else-end语句)的指令时 应多加小心。下面是一个典型的VTL if语句;

#if( $jazz )

Vyacheslav Ganelin

#end

如果 $jazz为 true,输出是

Vyacheslav Ganelin

如果 $jazz 为false,将没有输出。转义脚本元素将改变输出。考虑下面的情况;

\#if( $jazz )

Vyacheslav Ganelin

\#end

不管 $jazz 是真或假,输出都是

#if($ jazz )

Vyacheslav Ganelin

#end

事实上,因为所有脚本元素都被转义了, $jazz 永远不会被求值。将设反斜杠在被合法转义的脚本元素之前

\\#if( $jazz )

Vyacheslav Ganelin

\\#end

这时,如果$jazz 为真,输出是

\ Vyacheslav Ganelin

\

为理解这个情况,请注意在一个新行结束是将在输出中忽略新的一行。因此,经 过#if()前的’\\’ 加工后,#if()块紧跟第一个’\’。最后一个\位于新的一行,因为在’Ganelin’后又一个新行,所以,最后的那个位于#end 之前的\\是语句块的一部分

如果 $jazz 为false,这里将没有输出。注意,在开始破坏了if语句的情况将不能被正确转义:

\\\#if( $jazz )

Vyacheslave Ganelin

\\#end

这里,#if 被转义,但有一个#end 被保留了;所以有多个结束语句将导致解析错误。

[转载]web2.0按钮制作教程汇总

mikel阅读(1007)

[转载]web2.0按钮制作教程 | 崔凯,关注网站设计、前端开发.

  1. Boonage: Creating an Orb Button in Photoshop

    Boonage
  2. IRIS Design: Web 2.0 Style Photoshop Button Tutorial

    Iris  Design
  3. AvivaDirectory: Windows Vista Style Nav Bar Buttons

    Aviva
  4. PSCloud: Create a Navigation Bar Like the Current Apple Site

    PSCloud
  5. Talk-Mania: Create a Navigation Bar like the Old Apple Site

    Talk  Mania

  6. VDC: Create iMac Style Buttons in Photoshop

    Visual  Design Core
  7. TutorialWiz: Creating Simple Pixel Micro Buttons

    TutorialWiz
  8. PhotoshopStar: Creating Buttons for a Game Site like the Warcraft III site.

    PhotoshopStar
  9. EVOGfx: Creating an XBox 360 Syle Navigation Bar and Buttons

    EVOGFX
  10. PhotoshopPoint: How to Make an Industrial Strength Button

    PhotoshopPoint
  11. Photoshop-Pack: Simple and Clean Photoshop Menu Bar Buttons

    PhotoshopPack
  12. YourPhotoshopGuide: Transparent Glass Button Photoshop Tutorial
    YourPhotoshopGuide
  13. N-Sane: Glowing Glass Buttons in Photoshop

    N-Sane
  14. PhotoshopStar: Carbon Fibre Navigation Button Tutorial

    PhotoshopStar
  15. TutorialStream: Making Big Attractive Buttons in Photoshop

    TutorialStream
  16. PSDSpy: Small Chrome Micro Button Photoshop Tutorial

    PSDSpy
  17. WipeOut44: Stylish Rounded Gradient Photoshop Button Tutorial

    Wipeout44
  18. PixelDigest: Create an MP3 Player Style Glass Navigation Bar
    PixelDigest
  19. UpUpMedia: Make a Slick Tabbed Navigation Bar in Photoshop

    UpUpMedia
  20. EVOGfx: How to Make a Dark Vertical Navigation Bar and Buttons

    EVOGFX
  21. TutorialPark: Plastic Button Navigation Bar

    TutorialPark
  22. Toxiclab: Creating Web Buttons for Your Site Layout

    ToxicLab
  23. PhotoshopBuddy: Animated Click here Button

    PhotoshopBuddy
  24. SkeletorScope: Brushed Metal Photosho Button Tutorial

    SkeletorScope
  25. RetireAt21: Floral Buttons for a Website Selling Something like Flowers

    RetireAt21
  26. Time2Photoshop: Sleek Submit Button with Grungy Text

    Time2Photoshop
  27. SaberFusion: Create Buttons like the Search button on MSN

    SaberFusion
  28. Veerle: Creating Flexible Buttons Using Photoshop Shapes and Styles

    Veerle
  29. Photoshop-Pack: Making Stylish Buttons in Just a Minute

    PhotoshopPack
  30. ZASDesign: Inset Photoshop Button Tutorial

    ZASDesign
  31. Spoono: How to Create Gradient Micro Buttons

    Spoono
  32. PhotoshopStar: Creating a 2.0 Download Button in Photoshop

    PhotoshopStar
  33. PSDTuts: Make a Button Like the Ones you Pin on a Shirt

    PSDTuts
  34. EmpireDezign: Creating a Button with a Gel Effect

    Empire  Dezign
  35. TutorialSubmitter: Creating a ‘Push Button’ in Photoshop

    TutorialSubmitter
  36. Boonage: Perfect Shine Photoshop Button Tutorial

    Boonage
  37. OKTutorials: Making a Sleek ‘Shut Down’ Button in Photoshop

    OkTutorials
  38. DesignTutorials: How to Make Custom Volume Control Button

    DesignTutorials

[转载]MYSQL中UNIX时间戳与日期的转换

mikel阅读(1114)

[转载]MYSQL中UNIX时间戳与日期的转换_就这样每一天~寻找刺激 -_百度空间.

UNIX时间戳转换为日期用函数: FROM_UNIXTIME()

select FROM_UNIXTIME(1156219870);

日期转换为UNIX时间戳用函数: UNIX_TIMESTAMP()

Select UNIX_TIMESTAMP(’2006-11-04 12:23:00′);

例:mySQL查询当天的记录数:

$SQL=”select * from message Where DATE_FORMAT(FROM_UNIXTIME(chattime),’%Y-%m-%d’) = DATE_FORMAT(NOW(),’%Y-%m-%d’) order by id desc”;

当然大家也可以选择在PHP中进行转换

UNIX时间戳转换为日期用函数: date()

date(‘Y-m-d H:i:s’, 1156219870);

日期转换为UNIX时间戳用函数:strtotime()

strtotime(‘2010-03-24 08:15:42′);

mysql> select FROM_UNIXTIME(1156219870,’%y-%m-%d’);
+————————————–+
| FROM_UNIXTIME(1156219870,’%y-%m-%d’) |
+————————————–+
| 06-08-22                             |
+————————————–+
1 row in set (0.03 sec)

mysql> SELECT UNIX_TIMESTAMP(‘2006-11-04 12:23:00’);
+—————————————+
| UNIX_TIMESTAMP(‘2006-11-04 12:23:00’) |
+—————————————+
|                            1162614180 |
+—————————————+
1 row in set (0.00 sec)

[转载]有关Lucene的问题(7):用Lucene构建实时的索引

mikel阅读(877)

[转载]有关Lucene的问题(7):用Lucene构建实时的索引 – 觉先 – 博客园.

由于前一章所述的Lucene的事务性,使得Lucene可以增量的添加一个段,我们知道,倒排索引是有一定的格式的,而这个格式一旦写入是非常难 以改变的,那么如何能够增量建索引呢?Lucene使用段这个概念解决了这个问题,对于每个已经生成的段,其倒排索引结构不会再改变,而增量添加的文档添 加到新的段中,段之间在一定的时刻进行合并,从而形成新的倒排索引结构。

然而也正因为Lucene的事务性,使得Lucene的索引不够 实时,如果想Lucene实时,则必须新添加的文档后IndexWriter需要commit,在搜索的时候IndexReader需要重新的打开,然而 当索引在硬盘上的时候,尤其是索引非常大的时候,IndexWriter的commit操作和IndexReader的open操作都是非常慢的,根本达 不到实时性的需要。

好在Lucene提供了RAMDirectory,也即内存中的索引,能够很快的commit和open,然而又存在 如果索引很大,内存中不能够放下的问题。

所以要构建实时的索引,就需要内存中的索引RAMDirectory和硬盘上的索引 FSDirectory相互配合来解决问题。

1、初始化阶段

首先假设我们硬盘上 已经有一个索引FileSystemIndex,由于IndexReader打开此索引非常的慢,因而其是需要事先打开的,并且不会时常的重新打开。

我们在内存中有一个索引MemoryIndex,新来的文档全部索引到内存索引中,并且是索引完IndexWriter就 commit,IndexReader就重新打开,这两个操作时非常快的。

如下图,则此时新索引的文档全部能被用户看到,达到实时的目 的。

绘图8

2、合并索引阶段

然而经过一段时间,内存中的索引会比较大了,如果不合并到硬盘上,则可能造成内存不够用,则需要进行合并的过程。

当然在合并的过 程中,我们依然想让我们的搜索是实时的,这是就需要一个过渡的索引,我们称为MergingIndex。

一旦内存索引达到一定的程度,则 我们重新建立一个空的内存索引,用于合并阶段索引新的文档,然后将原来的内存索引称为合并中索引,并启动一个后台线程进行合并的操作。

在 合并的过程中,如果有查询过来,则需要三个IndexReader,一个是内存索引的IndexReader打开,这个过程是很快的,一个是合并中索引的 IndexReader打开,这个过程也是很快的,一个是已经打开的硬盘索引的IndexReader,无需重新打开。这三个IndexReader可以 覆盖所有的文档,唯一有可能重复的是,硬盘索引中已经有一些从合并中索引合并过去的文档了,然而不用担心,根据Lucene的事务性,在硬盘索引的 IndexReader没有重新打开的情况下,背后的合并操作它是看不到的,因而这三个IndexReader所看到的文档应该是既不少也不多。合并使用 IndexWriter(硬盘索引).addIndexes(IndexReader(合并中索引)),合并结束后Commit。

如下 图:

merging

3、重新 打开硬盘索引的IndexReader

当合并结束后,是应该重新打开硬盘索引的时候了,然而这是一个可能比较慢的过 程,在此过程中,我们仍然想保持实时性,因而在此过程中,合并中的索引不能丢弃,硬盘索引的IndexReader也不要动,而是为硬盘索引打开一个临时 的IndexReader,在打开的过程中,如果有搜索进来,返回的仍然是上述的三个IndexReader,仍能够不多不少的看到所有的文档,而将要打 开的临时的IndexReader将能看到合并中索引和原来的硬盘索引所有的文档,此IndexReader并不返回给客户。如下图:

reopen

4、替代 IndexReader

当临时的IndexReader被打开的时候,其看到的是合并中索引的 IndexReader和硬盘索引原来的IndexReader之和,下面要做的是:

(1) 关闭合并中索引的IndexReader

(2) 抛弃合并中索引

(3) 用临时的IndexReader替换硬盘索引原来的IndexReader

(4) 关闭硬盘索引原来的IndexReader。

上面说的这几个操作必须是原子性的,如果做了(2)但没有做(3),如果来一个搜索,则将 少看到一部分数据,如果做了(3)没有做(2)则,多看到一部分数据。

所以在进行上述四步操作的时候,需要加一个锁,如果这个时候有搜索 进来的时候,或者在完全没有做的时候得到所有的IndexReader,或者在完全做好的时候得到所有的IndexReader,这时此搜索可能被 block,但是没有关系,这四步是非常快的,丝毫不影响替代性。

如下图:

replace

经过这几个过程,又达到了第一步的状态,则进行 下一个合并的过程。

5、多个索引

有一点需要注意的是,在上述的合并过程中,新添 加的文档是始终添加到内存索引中的,如果存在如下的情况,索引速度实在太快,在合并过程没有完成的时候,内存索引又满了,或者硬盘上的索引实在太大,合并 和重新打开要花费太长的时间,使得内存索引以及满的情况下,还没有合并完成。

为了处理这种情况,我们可以拥有多个合并中的索引,多个硬盘 上的索引,如下图:

multiple

  • 新添加的文档永远是进入内存索引
  • 当内存索引到达一定的大小的时候,将其加入合并中索引链表
  • 有一个后台线程,每隔一定的时刻,将合并中索引写入 一个新的硬盘索引中取。这样可以避免由于硬盘索引过大而合并较慢的情况。硬盘索引的IndexReader也是写完并重新打开后才替换合并中索引的 IndexReader,新的硬盘索引也可保证打开的过程不会花费太长时间。
  • 这样会造成硬盘索引很多,所以,每隔一定的时刻,将硬 盘索引合并成一个大的索引。也是合并完成后方才替换IndexReader

大家可能会发现,此合并的过程和Lucene的段 的合并很相似。然而Lucene的一个函数IndexReader.reopen一直是没有实现的,也即我们不能选择哪个段是在内存中的,可以被打开,哪 些是硬盘中的,需要在后台打开然后进行替换,而IndexReader.open是会打开所有的内存中的和硬盘上的索引,因而会很慢,从而降低了实时性。

[转载]Net资源泄露(内存泄露,GDI泄露,handle 泄露等)的终极解决方案

mikel阅读(1549)

[转载]Net资源泄露(内存泄露,GDI泄露,handle 泄露等)的终极解决方案 – 云镜 – 博客园.

摘要

什么是.Net内存泄露?如何确定是发生了内存泄露?如何预防内存泄露的发生?

正文

1.dot Net内存泄露简介

刚开始使用Net的读者(甚至做了一两年商业开发的同行)可能对Net的内存泄露不是很了解,甚至会说Net不存在内存泄露,他们会问“不是有GC机制 吗?”恩,是有这么回事,它保证了通常应用时不用考虑头疼的资源释放问题,但很遗憾的是这个机制不保证你开发的程序就不存在内存泄露甚至可以说,Net内 存泄露是很常见的。这是因为: 一方面,GC机制本身的缺陷造成的;另一方面,Net中托管资源和非托管资源的处理是有差异的,托管资源的处理是由GC自动执行的,而非 托管资源 (占少部分,比如文件操作,网络连接等)必须显式地释放,否则就可能造成泄露。综合起来说的话,由于托管资源在Net中占大多数,通常不做显式的资源释放 是可以的,不会造成明显的资源泄露,而非托管资源则不然,是发生问题的主战场,是最需要注意的地方。

另外,照我看来,很多情况下,衰老测试关注的主要是有没有内存泄露的发生,而对其他泄露的重视次之。为什么这样做呢,我认为有两方面的原因,一是内存跟其 他资源是正相关的,也就是说没有内存泄露的发生,其他泄露的发生概率也较小,其根本在于所有的资源最后会反应在内存上;另一个就是很多Net应用开发,用 到的非托管资源,多提供Dispose方法的,而Dispose之后,不光内存及时释放,其他的也做了释放。因此,通常情况下我们主要关注的是内存的使用 情况,而对自定义控件开发等情况则需要关注GDI,handle等其他资源的情况。

2.dot Net内存泄露的确诊

上面说了这么多,那么到底如何判断有没有内存泄露的发生呢?

如果程序报“Out of memory”之类的错误,事实上也占据了很大部分的内存,应该说是典型的内存泄露,这种情况属于彻底的Bug,解决之道就是找到问题点,改正。但我的经 验中,这种三下两下的就明显的泄露的情况较少,除非有人在很困的情况下编码,否则大多是隐性或渐进式地泄露,这种需经过较长时间的衰老测试才能发现,或者 在特定条件下才出现,对这种情况要确定问题比较费劲,有一些工具(详见3.3)可以利用,但我总感觉效果一般,也可能是我不会使用吧,我想大型程序估计得 无可奈何的用这个,详细的参见相关手册。

需要强调的是,判断一个程序是不是出现了”memory leak”,关键不是看它占用的内存有多大,而是放在一个足够长的时期(程序进入稳定运行状态后)内,看内存是不是还是一直往上涨,因此,刚开始的涨动或 者前期的涨动不能做为泄露的充分证据。

以上呢都是些感性的说法,具体的判断是否发生了内存泄露,可以通过一些性能计数器来测定。一般来讲,我测性能时,主要关注Process里 以下几个指标,如果这些量整体来看是持续上升的,基本可以判断是有泄露情况存在的。

A.Handle Count

B.Thread Count

C.Private Bytes

D.Virtual Bytes

E.Working Set

F.另外.NET CLR Memory下的Bytes in all heeps也是我比较关注的。

通关观察,你如果发现这些参数是在一个区间内震荡的,应该是没有大的问题的,如果是一个持续上涨的状态,那你就得注意,很可能存在内存泄露。

3.内存泄露诊断工具

如何测定以上的计数器呢,我大多使用windows自带的perfmon.msc。在此稍微说说改工具的使用。

3.1perfmon.msc 的使用

在Run中输入perfmon.msc,运行,其他的自己摸索,不难。

3.2 一些重要的性能计数器

重要的 计数器1, 重要的计数器2

3.3其他检测工具

我用过的里面CLRProfilerdotTrace 还行(下载地址见链接)windeg也还行。不过坦白的说,准确定位比较费劲,最好还是按常规的该Dispose的加Dispose,也可以加 GC.Collect()。

4.如何制造出健壮的程序

4.1 Dispose()的使用

如果使用的对象提供Dispose()方法,那么当你使用完毕或在必要的地方(比如Exception)调用该方法,特别是对非托管对象,一定要加以调 用,以达到防止泄露的目的。另外很多时候程序提供对Dispose()的扩展,比如Form,在这个扩展的Dispose方法中你可以把大对象的引用什么 的在退出前释放。

对于DB连接,COM组件(比如OLE组件)等必须调用其提供的Dispose方法,没有的话最好自己写一个。

4.2 using的使用

using除了引用Dll的功用外,还可以限制对象的适用范围,当超出这个界限后对象自动释放,比如

4.3 事件的卸载

4.4 API的调用

4.5继承 IDisposable实现自己内存释放接口

Net 如何继承IDisposable接口,实现自己的Dispose()函数

4.6弱引用(WeakReference

通常情况下,一个实例如果被其他实例引用了,那么他就不会被GC回收,而弱引用的意思是,如果一个实例没有被其他实例引用(真实引用),而仅仅是被弱引 用,那么他就会被GC回收。

4.7析构函数(Finalize())

有了前面的 Dispose,一般不推荐自定义析构函数。

5.其他资源泄露

GDI leak,handle leak。

6.几个特例

1.对于使用了Bitmap对象的部分需要调用DestroyIcon来删除对象

[DllImport(“user32”, EntryPoint = “DestroyIcon”)]
private static extern void DestroyIcon(IntPtr handle);
using (System.IO.MemoryStream mstream = new System.IO.MemoryStream())
{
bmp.Save(mstream, System.Drawing.Imaging.ImageFormat.Png);
intP = new Bitmap(mstream).GetHicon(); this.notifyIcon.Icon = Icon.FromHandle(intP);
DestroyIcon(intP);
}

2. 多线程中的GC

//Force garbage collection.
GC.Collect();
//Wait for all finalizers to complete
GC.WaitForPendingFinalizers();

7.参考文献

1.发 现并防止 托管代码中出现内存泄漏

2..NET Memory Leak reader email: Are you really “leaking” .net memory

3.How to detect and avoid memory and resources leaks in .NET applications

4.其 他参考资料

8.扩展阅读

1.实 用.net内存泄露(memory leak)解决方案

2.Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework By Jeffrey Richter  http://msdn.microsoft.com/en-us/magazine/bb985010.aspx

3.OutOfMemoryException and Pinning

https://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx

后记

其实本文更像一篇编写出健壮WinForm程序的指南。

—-全文完—–