摘要: 在做一个WinForm登录框时,突然想到,如果有黑客帝国中字符雨的特效做背景,那应该蛮Cool的,所以就有了如下代码,随意写的,有点乱。[代码]测试程序下载使用的时候,先设定ShowWindow,这个属性决定在哪个控件上显示字符雨,然后还可以设置如下属性:MaxLength:每条字符雨的最大字符数量MinLength:每条字符雨的最小字符数量RainBodyColor:字符雨中间的颜色RainCh... 阅读全文
posted @ 2008-09-28 16:56 AndyHai 阅读(931) | 评论 (12)编辑

谁动了我的构造函数?

——由DBNull引发的……

  总所周知,DBNull只有一个实例——DBNull.Value,我们不可能通过new DBNull()方法来创建一个新的DBNull实例,这是因为:DBNull的构造函数是私有的,大概就如同下面这样。

 

public sealed class DBNull
{
    
public static readonly DBNull Value = new DBNull();
    
private DBNull()
    {
    }
}

 

一个典型的单实例模式,那么也就是说,两个非null的DBNull对象实例间的==比较,一定会返回true咯?看起来……似乎……确实是这样的,因为这个对象只有一个实例嘛,它们的引用比较一定是相等的。

慢着,在下这个结论前,先给大家看一段代码

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.IO;

namespace ConsoleApplication2008
{
    
public class Program
    {

        
public class TestClass
        {
            [XmlElement()]
            
public DBNull MyValue = DBNull.Value;
        }

        
static void Main(string[] args)
        {
            TestClass v1 
= new TestClass();

            XmlSerializer xs 
= new XmlSerializer(typeof(TestClass));
            MemoryStream ms 
= new MemoryStream();
            xs.Serialize(ms, v1);
            
//string xml = Encoding.UTF8.GetString(ms.GetBuffer());
            
//Console.WriteLine(xml);// 此处两行可要可不要

            ms.Seek(
0, SeekOrigin.Begin);
            TestClass v2 
= xs.Deserialize(ms) as TestClass;

            Console.WriteLine(v1.MyValue 
== v2.MyValue ? "Match" : "Unmatch");

            Console.ReadKey();
        }
    }
}

 

  没什么花哨的地方,无非是xml序列化,然后再反序列化而已,运行一次看看……Oh,为什么?结果怎么会是Unmatch?难道说DBNull对象可以有两个不同的实例?这和它的声明可不一致啊!它的构造函数可是private的,除了它自身,不应该有其它对象可以调用的,难道说……
  整理一下思路,TestClass对象在反序列化时,到底发生了什么?当然是调用TestClass的构造函数,生成一个实例咯,然后呢?调用每个公共成员(属性和成员变量)的类型的构造函数,将实例赋给相应的成员,如此反复下去……咦?等等,DBNull的构造函数是private的,要想取得实例,必须使用DBNull.Value来获得,.NET框架不可能聪明到会自动去使用DBNull.Value吧?既然这样,那DBNull是如何被反序列化出实例来的?一个私有的构造函数是怎么会被调用的?

  不妨再做个例子:

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.IO;

namespace ConsoleApplication2008
{
    
public class Program
    {
        
public sealed class SingletoneClass
        {
            
private static int i = 0;
            
public static readonly SingletoneClass Value = new SingletoneClass();

            
private SingletoneClass()
            {
                Console.WriteLine(
"SingletoneClass 被第 {0} 次实例化"++i);
            }
        }

        
public class TestClass
        {
            [XmlElement()]
            
public SingletoneClass MyValue = SingletoneClass.Value;
        }

        
static void Main(string[] args)
        {
            Console.WriteLine(
"构造TestClass.");
            TestClass v1 
= new TestClass();

            XmlSerializer xs 
= new XmlSerializer(typeof(TestClass));
            MemoryStream ms 
= new MemoryStream();
            Console.WriteLine(
"v1序列化.");
            xs.Serialize(ms, v1);

            ms.Seek(
0, SeekOrigin.Begin);
            Console.WriteLine(
"反序列化出v2.");
            TestClass v2 
= xs.Deserialize(ms) as TestClass;

            Console.WriteLine(v1.MyValue 
== v2.MyValue ? "Match" : "Unmatch");

            Console.ReadKey();
        }
    }
}

 

哦~运行结果是这样:

 

构造TestClass.
SingletoneClass 被第 
1 次实例化
v1序列化
.
反序列化出v2
.
SingletoneClass 被第 
2 次实例化
Unmatch

 

SingletoneClass的第1次实例化很容易理解,那是它自身的静态只读成员Value造成的,第二次是什么原因造成的呢?想来想去,只能是在反序列化过程中调用的,也就是说“.NET框架可以调用我们的私有构造函数”。那么问题就产生了,我期望的只有一个实例的对象现在出现了两个实例,这样的话==操作符判断的结果就肯定会是false,如果你的代码中出现了大量的:

if (v == DBNull.Value)
{
}


这样的代码,而你又对这个对象做了XML序列化/反序列化,那么很显然,这段代码不能如我们所期望的那样工作。怎么办?

如果是如SingletoneClass一样的自定义对象,那么也好解决,重载==操作符即可:

 

public sealed class SingletoneClass
{
    
private static int i = 0;
    
public static readonly SingletoneClass Value = new SingletoneClass();

    
private SingletoneClass()
    {
        Console.WriteLine(
"SingletoneClass 被第 {0} 次实例化"++i);
    }

    
public override bool Equals(object obj)
    {
        
if (obj == null || obj.GetType() == this.GetType())
            
return true;
        
else
            
return false;
    }

    
public static bool operator ==(SingletoneClass a, SingletoneClass b)
    {
        
// If both are null, or both are same instance, return true.
        if (System.Object.ReferenceEquals(a, b))
            
return true;

        
return a.Equals(b);
    }

    
public static bool operator !=(SingletoneClass a, SingletoneClass b)
    {
        
return !(a == b);
    }
}

 

  这样做虽然有违==操作符的本意,却也是无可奈何的解决办法,你总不能拒绝.NET框架调用你的私有构造函数吧?那样的话,反序列化又如何进行呢?不过DBNull又该如何解决呢?我们不能通过修改.NET框架本身来达到我们的目的吧?(或者让微软出补丁?这算是BUG吗?)哪位看官能帮我解答?

posted @ 2008-07-26 16:18 AndyHai 阅读(1051) | 评论 (12)编辑
  做语音通讯时通常会碰到需要将某种格式的音频信号转换成其它格式的音频信号和将两个或多个音频信号混合的情况,参考网上搜寻到的一些资料,我做了一个示例程序,此程序可以将两个 8Bit 8000Sample 1Channel PCM A-Law 格式的音频文件转换成为容易混音的16Bit 8000Sample 1Channel PCM Line格式的音频数据,然后对两个信号进行混音处理,最后将混音结果再转换为8Bit 8000Sample 1Channel PCM A-Law格式保存。
  格式转换使用了ACM,参考了《VC下调用ACM音频编程接口压缩Wave音频》(原文不知何处,GOOGLE一下一大把),混音则采用最简便的线性累加的方法进行累加。
  我提供了两个文件A1.PCM和A2.PCM,点击Convert按钮会将这两个文件转换为B1.PCM与B2.PCM,生成这两个文件后,点击MIX按钮会将B1.PCM与B2.PCM混合并生成M.PCM。

  A1.PCM、A2.PCM、M.PCM格式均为 8Bit 8000Sample 1Channel PCM A-Law格式
  B1.PCM、B2.PCM格式为16Bit 8000Sample 1Channel PCM Line格式
  以上文件可以使用CoolEdit或Adobe Audition打开。

  工程基于CodeGear C++ Builder 2007,在Windows Server 2003和Vista下均调试通过。

代码下载:点此下载
posted @ 2008-06-26 11:27 AndyHai 阅读(699) | 评论 (3)编辑

  有人说人与人的距离是一个绝对值,如果超过了这个绝对值,那么必然会出现矛盾。

  不一定说互相憎恨,互相讨厌,才会使两个人产生矛盾的,当你和她的距离太近,太爱她,太关心她的时候,也会出现矛盾,这就是所谓的关心则乱吧,因为关心,她的一切一切在你的眼中变的如此的重要,一点变化,一个举动,一个眼神,都能让你思索良久.因为关心,你会去想这些变化究竟是为了什么,你希望她能一切都好,所谓当你想的太多,一切都会往坏处去想,焦虑是免不了的,而当她认为一切只是小事,把你的关心当做是多余,那么就有矛盾。

  因为关心,你会希望你所爱的人一切都是最好的,因此,她身上的每个小缺点都会被无限扩大,因为你的眼睛是始终盯着她的,当一个缺点落到你眼睛里的时候,你会极力的希望这个缺点能成为优点,为了这个,你会不惜一切,因此,也会有矛盾,因为,人自己毕竟是不愿意去面对太多的缺点的,包括我自己。

  然而,我依然不愿为了这些而牺牲我的关心,我依然希望去一如既往的关心她,爱护她,也希望天下所有人都能有这份关心,也能有这份胸怀去享受这份关心。

posted @ 2008-06-19 11:23 AndyHai 阅读(78) | 评论 (0)编辑
  经常有一些小的WEB服务项目,不想在IIS中部署,于是就从微软网站找到了这个例程,稍微做了下修改,可以正常运行了。

下载源码
posted @ 2007-11-28 11:08 AndyHai 阅读(771) | 评论 (11)编辑
  近日在工作中,遇到一个项目,需要将SQL Server中的纪录拆分显示,也就是将一条纪录根据某种方式拆分成多条纪录。比如说在某个帐单系统中,记帐时,按照常规方式按条记;出帐时,要以0.5每纪录的方式进行拆分,即3元的帐单,要拆分成6条纪录,每条0.5元,除金额外,其它字段保持不变。
  这是个很有趣的问题,最简陋的方法莫过于使用游标,一条纪录一条纪录的分析并插入。可是,如果纪录数很多(比如上十万条帐单),那肯定是一场恶梦。如何拆分才能使效率最佳化呢?我在网上搜索了一下“SQL 纪录拆分”,竟然还真让我找到了,他用的是JOIN的方式达到拆分效果,看起来效率应该比游标高许多,不过对方是将VarChar字段用","分割进行拆分,我采取拿来主义的方法,对他的方法进行了修改,下面以上面说的帐单系统为例,细说一下:
  设定一个帐单基本值,此值即每条出帐纪录的金额。在SELECT中,将帐单的金额减去此基本值乘以一个有限序列数i,其结果可用来表示第i条出帐单的剩余金额,如果得数大于等于0,表示还有剩余的金额,出帐纪录有效;若是小于0,则意味着剩余金额不足,出帐纪录无效。有效的出帐纪录中的金额自然等于帐单基本值,不过这里面还有几个问题:
1、这里只考虑了帐单金额可以被帐单基本值整除的情况,这种情况下,假如帐单基本值是0.5元,自然是每条出帐单0.5元;而假若帐单是3.2元,帐单基本值是0.5元,那么最后一条帐单纪录肯定不是0.5元而是0.2元,所以还需要判断一下剩余金额是否大于0小于帐单基本值,如果是,则剩余金额就是该条出帐单的金额。
2、有限的序列数i,如何产生有限序列数?方法很多,创建一个临时表(或者表变量),1、2、3、4、5……一条一条插入即可产生序列数啦。另一个方法比较有趣,使用某个系统表中的纪录做引子,产生序列数。
  下面是SQL示例:
DECLARE @BaseValue AS INT --帐单基本值

SET @BaseValue = 50

DECLARE @Fee TABLE (ID INT IDENTITY(11), Name VARCHAR(32), Fee INT, FeeTime DATETIME)
INSERT INTO @Fee VALUES ('Andy'300'2007-01-01 10:22:42')
INSERT INTO @Fee VALUES ('John'300'2007-01-01 10:22:32')
INSERT INTO @Fee VALUES ('Lara'310'2007-01-01 10:22:22')
INSERT INTO @Fee VALUES ('Philip'240'2007-01-01 10:22:52')

DECLARE @Tmp TABLE(ID INT IDENTITY(01), A INT--序列数表(必须从0开始)

INSERT INTO @Tmp (A)
    
SELECT NULL FROM sys.columns --用系统表做引子,产生序列数

SELECT 
F.ID, F.Name, 
'Fee' = 
    
CASE
        
WHEN Fee - @BaseValue * T.ID >= @BaseValue THEN @BaseValue
        
ELSE Fee - @BaseValue * T.ID
    
END,
FeeTime
FROM @Fee AS F INNER JOIN @Tmp AS T ON Fee - @BaseValue * T.ID > 0 ORDER BY F.ID, T.ID
posted @ 2007-10-13 09:23 AndyHai 阅读(162) | 评论 (3)编辑
     摘要: 看到很多网站上的输入框都有空值提示,即:输入框中没有内容且没有焦点时,输入框中显示的是提示文字;如果有内容或者拥有焦点,则正常显示。我觉得这东西很有意思,在某些应用中,可以减少界面排版上的麻烦,可惜WinForm中的TextBox没有此功能,于是自己做了一个,效果嘛,还算满意的:)publicclassTEditBox:System.Windows.Forms.TextBox{publicTEdi... 阅读全文
posted @ 2007-09-19 14:04 AndyHai 阅读(319) | 评论 (2)编辑

第一部分: NAT介绍

各种不同类型的NAT(according to RFC)

Full Cone NAT:

内网主机建立一个UDP socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,任何外部主机只要知道这个(PublicIP:PublicPort)就可以发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包

Restricted Cone NAT:

内网主机建立一个UDP socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,如果任何外部主机想要发送数据给这个内网主机,只要知道这个(PublicIP:PublicPort)并且内网主机之前用这个socket曾向这个外部主机IP发送过数据。只要满足这两个条件,这个外部主机就可以用自己的(IP,任何端口)发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包

Port Restricted Cone NAT:

内网主机建立一个UDP socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,如果任何外部主机想要发送数据给这个内网主机,只要知道这个(PublicIP:PublicPort)并且内网主机之前用这个socket曾向这个外部主机(IP,Port)发送过数据。只要满足这两个条件,这个外部主机就可以用自己的(IP,Port)发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包

Symmetric NAT:

内网主机建立一个UDP socket(LocalIP,LocalPort),当用这个socket第一次发数据给外部主机1时,NAT为其映射一个(PublicIP-1,Port-1),以后内网主机发送给外部主机1的所有数据都是用这个(PublicIP-1,Port-1),如果内网主机同时用这个socket给外部主机2发送数据,第一次发送时,NAT会为其分配一个(PublicIP-2,Port-2), 以后内网主机发送给外部主机2的所有数据都是用这个(PublicIP-2,Port-2).如果NAT有多于一个公网IP,则PublicIP-1和PublicIP-2可能不同,如果NAT只有一个公网IP,则Port-1和Port-2肯定不同,也就是说一定不能是PublicIP-1等于 PublicIP-2且Port-1等于Port-2。此外,如果任何外部主机想要发送数据给这个内网主机,那么它首先应该收到内网主机发给他的数据,然后才能往回发送,否则即使他知道内网主机的一个(PublicIP,Port)也不能发送数据给内网主机,这种NAT无法实现UDP-P2P通信。

第二部:NAT类型检测

前提条件:有一个公网的Server并且绑定了两个公网IP(IP-1,IP-2)。这个Server做UDP监听(IP-1,Port-1),(IP-2,Port-2)并根据客户端的要求进行应答。

第一步:检测客户端是否有能力进行UDP通信以及客户端是否位于NAT后?

客户端建立UDP socket然后用这个socket向服务器的(IP-1,Port-1)发送数据包要求服务器返回客户端的IP和Port, 客户端发送请求后立即开始接受数据包,要设定socket Timeout(300ms),防止无限堵塞. 重复这个过程若干次。如果每次都超时,无法接受到服务器的回应,则说明客户端无法进行UDP通信,可能是防火墙或NAT阻止UDP通信,这样的客户端也就不能P2P了(检测停止)。
当客户端能够接收到服务器的回应时,需要把服务器返回的客户端(IP,Port)和这个客户端socket的(LocalIP,LocalPort)比较。如果完全相同则客户端不在NAT后,这样的客户端具有公网IP可以直接监听UDP端口接收数据进行通信(检测停止)。否则客户端在NAT后要做进一步的NAT类型检测(继续)。

第二步:检测客户端NAT是否是Full Cone NAT?

客户端建立UDP socket然后用这个socket向服务器的(IP-1,Port-1)发送数据包要求服务器用另一对(IP-2,Port-2)响应客户端的请求往回发一个数据包,客户端发送请求后立即开始接受数据包,要设定socket Timeout(300ms),防止无限堵塞. 重复这个过程若干次。如果每次都超时,无法接受到服务器的回应,则说明客户端的NAT不是一个Full Cone NAT,具体类型有待下一步检测(继续)。如果能够接受到服务器从(IP-2,Port-2)返回的应答UDP包,则说明客户端是一个Full Cone NAT,这样的客户端能够进行UDP-P2P通信(检测停止)。

第三步:检测客户端NAT是否是Symmetric NAT?

客户端建立UDP socket然后用这个socket向服务器的(IP-1,Port-1)发送数据包要求服务器返回客户端的IP和Port, 客户端发送请求后立即开始接受数据包,要设定socket Timeout(300ms),防止无限堵塞. 重复这个过程直到收到回应(一定能够收到,因为第一步保证了这个客户端可以进行UDP通信)。
用同样的方法用一个socket向服务器的(IP-2,Port-2)发送数据包要求服务器返回客户端的IP和Port。
比较上面两个过程从服务器返回的客户端(IP,Port),如果两个过程返回的(IP,Port)有一对不同则说明客户端为Symmetric NAT,这样的客户端无法进行UDP-P2P通信(检测停止)。否则是Restricted Cone NAT,是否为Port Restricted Cone NAT有待检测(继续)。

第四步:检测客户端NAT是否是Restricted Cone NAT还是Port Restricted Cone NAT?

客户端建立UDP socket然后用这个socket向服务器的(IP-1,Port-1)发送数据包要求服务器用IP-1和一个不同于Port-1的端口发送一个UDP数据包响应客户端, 客户端发送请求后立即开始接受数据包,要设定socket Timeout(300ms),防止无限堵塞. 重复这个过程若干次。如果每次都超时,无法接受到服务器的回应,则说明客户端是一个Port Restricted Cone NAT,如果能够收到服务器的响应则说明客户端是一个Restricted Cone NAT。以上两种NAT都可以进行UDP-P2P通信。

注:以上检测过程中只说明了可否进行UDP-P2P的打洞通信,具体怎么通信一般要借助于Rendezvous Server。另外对于Symmetric NAT不是说完全不能进行UDP-P2P达洞通信,可以进行端口预测打洞,不过不能保证成功。

posted @ 2007-08-17 16:39 AndyHai 阅读(458) | 评论 (2)编辑
     摘要: 有同学向我问这个问题,于是就Google了一下找到答案,不过是C下的,我将其改编成了C#的。  当设备被插入/拔出的时候,WINDOWS会向每个窗体发送WM_DEVICECHANGE 消息,当消息的wParam 值等于 DBT_DEVICEARRIVAL 时,表示Media设备被插入并且已经可用;如果wParam值等于DBT_DEVICEREMOVECOMPLETE,表示Media设备已经被移出。... 阅读全文
posted @ 2007-07-25 09:09 AndyHai 阅读(890) | 评论 (11)编辑
     摘要: 中国移动与各SP之间的用户订购关系同步是在MISC1.6系统中的DSMP中通过Provision接口完成的,其实看过Provision接口之后都知道,它就是一个WebService,不过很多地方的移动公司并没有开启WEB引用发现,所以大多数情况下,无法使用“添加WEB引用”的方法来写这个反向接口,因而很多SP都是直接用WebRequest去处理,不过仔细分析MISC1.6文... 阅读全文
posted @ 2007-07-19 10:17 AndyHai 阅读(862) | 评论 (0)编辑
QQ: 2369537