搜索
bottom↓
回复: 199

[上位机][C#][学习笔记] 学习如何给自己的软件加后门

[复制链接]

出0入296汤圆

发表于 2010-1-26 19:36:13 | 显示全部楼层 |阅读模式


<font color=red>[说在前面的话]


    不知道有多少朋友没有听说过软件彩蛋,要我说没有几个。经典的比如在Excel得某个单元隔里面OOXX
就可以获得一个赛车游戏之类。这是一种软件彩蛋,纯属娱乐。但是更多的“彩蛋”被用作软件后门。比如
我们提供给客户一个上位机软件,通常是看不到某些调试用的窗口和工具的;当我们被要求给客户提供现场
技术支持的时候,我们往往希望通过某种隐秘的手段来开启这些条使用的工具和窗口,这就是后门。这类后
门中又以按键后门最为常见,下面我们就利用一个已有的第三方函数库Utilities.dll来构建一个后门系统。


[相关下载]

    1、Utilities库
       点击此处下载 ourdev_529297.rar(文件大小:18K) (原文件名:Utilities.rar)
    2、示例工程
       点击此处下载 ourdev_529327.rar(文件大小:92K) (原文件名:BackdoorExample.rar)

出0入296汤圆

 楼主| 发表于 2010-1-26 19:36:29 | 显示全部楼层


<font color=red>[STEP 01] 新建工程


    打开Visual Studio 2008 选择 File->New->Project
    依次选择 Visual C# -> Windows -> Windows Forms Application
    名称假设叫做:BackdoorExample, Solution也是这个名字,
    工程建立成功后的效果如下图所示


(原文件名:1.JPG)

出0入296汤圆

 楼主| 发表于 2010-1-26 19:36:41 | 显示全部楼层

<font color=red>[STEP 02] 加入第三方库Utilities


    在Solution Explorer里面选中工程 BackdoorExample
    单击右键,在菜单中选择Add Reference
    稍等片刻,在Add Reference 窗口中选择Browns,找到Utilities.dll,选中后单击OK(如下图所示)
   


    展开工程的Reference 文件夹,我们应该能看到刚刚加入的库Utilities   
   

    双击Form1,进入代码编辑窗口
    在最开始的地方加入对Utilities库的引用:
    using Utilities;
   

    至此,我们就完成了对第三方库Utilities的引用。

出0入296汤圆

 楼主| 发表于 2010-1-26 19:36:51 | 显示全部楼层

<font color=red>[STEP 03] 初始化后门


    双击Form1,打开代码编辑窗口,在窗体类中添加一个 后门类KeyboardIncantationMonitor
   
    private KeyboardIncantationMonitor m_KeyBackDoor = new KeyboardIncantationMonitor();



    新建一个私有成员函数,并添加两个后门
   
        private void AddBackDoor()
        {
            //! 第一个后门
            do
            {
                //! 申请一个后门暗号
                KeyboardIncantationMonitor.KeysIncantation tInc = m_KeyBackDoor.NewIncantation() as KeyboardIncantationMonitor.KeysIncantation;

                //! 初始化这个暗号为:依次按下 <Esc>HELLO<Enter>
                tInc.AddKey(Keys.Escape);
                tInc.AddKey(Keys.H);
                tInc.AddKey(Keys.E);
                tInc.AddKey(Keys.L);
                tInc.AddKey(Keys.L);
                tInc.AddKey(Keys.O);
                tInc.AddKey(Keys.Enter);

                //! 对上暗号以后的处理程序
                tInc.IncantationCantillatedReport += new IncantationReport(BackdoorHandler_A);

                //! 将这个暗号添加到后门监视器里面
                m_KeyBackDoor.AddIncantation(tInc);
            }
            while (false);

            //! 第一个后门
            do
            {
                //! 申请一个后门暗号
                KeyboardIncantationMonitor.KeysIncantation tInc = m_KeyBackDoor.NewIncantation() as KeyboardIncantationMonitor.KeysIncantation;

                //! 初始化这个暗号为:依次按下 <Esc>Bye<Enter>
                tInc.AddKey(Keys.Escape);
                tInc.AddKey(Keys.B);
                tInc.AddKey(Keys.Y);
                tInc.AddKey(Keys.E);
                tInc.AddKey(Keys.Enter);

                //! 对上暗号以后的处理程序
                tInc.IncantationCantillatedReport += new IncantationReport(BackdoorHandler_B);

                //! 将这个暗号添加到后门监视器里面
                m_KeyBackDoor.AddIncantation(tInc);
            }
            while (false);
        }


        //! 第一个后门的处理程序
        void BackdoorHandler_A(IIncantation tInc)
        {
            throw new NotImplementedException();
        }

        //! 第二个后门的处理程序
        void BackdoorHandler_B(IIncantation tInc)
        {
            throw new NotImplementedException();
        }




      在Form1的构造函数中增加对AddBackDoor方法的调用,这样我们就把后门加好了
      
        public Form1()
        {
            InitializeComponent();

            //! 加入后门
            AddBackDoor();
        }

出0入296汤圆

 楼主| 发表于 2010-1-26 19:37:03 | 显示全部楼层

<font color=red>[STEP 04] 安插后门


        在什么地方安插后门呢?依照个人喜好了,不过既然是一个按键后门,肯定要选择一个能接收KeyDown
    或者KeyUp事件的地方。
        比如我们在Form1上增加一个文本框,将其ReadOnly属性设置为True,并添加一段文字:“版权所有,
    翻版必究”。并在窗体上增加一个按钮,作为我们演示的对象:普通状况下,看不到这个按钮,开启后门
    以后,按钮的visible属性就变为true,我们就能看见了;我们也可以使用另外一个后门重新将按钮的
    visible属性设置为false,然后就可以交给客户了。^_^

   



        选中文本框,在Property窗口中选择Event,双击KeyDown,进入代码编辑窗口,并在KeyDown处理程序中
    加入对后门监视器的处理:也就是把按下的键告诉监视器:
        
        private void textBox1_KeyDown(object sender, KeyEventArgs e)
        {
            //! 告诉后门监视器哪个键被按下了
            m_KeyBackDoor.Append(e.KeyCode);
        }


       在后门处理程序A中将Button的Visible属性设置为True;
        //! 第一个后门的处理程序
        void BackdoorHandler_A(IIncantation tInc)
        {
            button1.Visible = true;
        }

      在后门处理程序A中将Button的Visible属性设置为False;
        //! 第二个后门的处理程序
        void BackdoorHandler_B(IIncantation tInc)
        {
            button1.Visible = false;
        }




     最后,别忘记把Button的visible属性设置为False,否则怎么向我们的客户隐藏这个按钮呢?

出0入296汤圆

 楼主| 发表于 2010-1-26 19:37:15 | 显示全部楼层

<font color=red>[STEP 05] 效果验证


     运行程序,看到如下窗口:果然看不到Button,他被隐藏了
     

     选中写有“翻版必究”的文本框,依次按下:
     <Esc> <H> <E> <L> <L> <O> <Enter> 键
     button1出现了
     


     依次按下:
     <Esc> <B> <Y> <E> <Enter> 键
     button1又消失了
     



     大功告成。
     无论任何时候你都可以从头开始输入后门,不必担心上次从什么地方开始的。当然,也不用考虑大小写。
     Have a good time.

出0入0汤圆

发表于 2010-1-26 19:58:01 | 显示全部楼层
我一般认为是用程序的运行参数来决定程序是运行在客户模式还是调试模式

出0入0汤圆

发表于 2010-1-26 20:08:49 | 显示全部楼层
mark!

出0入0汤圆

发表于 2010-1-26 20:10:56 | 显示全部楼层
LZ厉害,C#也在整!

出0入296汤圆

 楼主| 发表于 2010-1-26 20:11:05 | 显示全部楼层
to 【6楼】 deathbravo
    您说的也是一种常用方法。不过按键后门的操作性更有趣一点。

出200入0汤圆

发表于 2010-1-26 20:21:07 | 显示全部楼层
好久不见傻孩子出没了~~

出0入296汤圆

 楼主| 发表于 2010-1-26 20:34:51 | 显示全部楼层
居然用“出没”这个词……

- -b |||

出0入0汤圆

发表于 2010-1-26 21:00:11 | 显示全部楼层
进来学习下

出0入0汤圆

发表于 2010-1-26 21:09:39 | 显示全部楼层
不太理解,为什么要用

do{

}while(false)

来添加 键盘监视对象

出0入0汤圆

发表于 2010-1-26 21:33:24 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-1-26 21:51:12 | 显示全部楼层
【3楼】 Gorgon Meducer 傻孩子

KeyboardIncantationMonitor.KeysIncantation tInc = m_KeyBackDoor.NewIncantation() as KeyboardIncantationMonitor.KeysIncantation;

==========================================

每次看见某些 Windows 程序员搞的这种犹胜三寸金莲老太太的裹脚布的标识符我就脑袋大

别谈什么匈牙利命名法的优势云云,我就问个问题:

有哪个人不需要熟读手册,只凭 IDE 环境的标识符联想和匈牙利命名法就能搞定 Windows 编程的?

出0入0汤圆

发表于 2010-1-26 22:03:09 | 显示全部楼层
ls说的中肯。

能不能说一下你的标示符命名习惯?

出0入0汤圆

发表于 2010-1-26 22:19:33 | 显示全部楼层
个人认为:

局部变量和局域变量并不需要什么命名习惯,只要在声明时附上必要的注释即可

接口标识符命名时,把变量类型在命名中显式声明即可,至于功用,仍然应以注释为准

说到底,任何命名法,首先考虑的应该是易读,其次才是易懂,而显然,

typea A = funcB() as interfaceC;

要比

KeyboardIncantationMonitor.KeysIncantation tInc = m_KeyBackDoor.NewIncantation() as KeyboardIncantationMonitor.KeysIncantation;

易读得多

而说到易懂,实际上,由于不太可能在对象命名时直接把一整句注释内容直接拿来做标识符,因此,滥用复杂式命名法反而很容易导致使用者的误会——不管哪国程序员,语文水平不足都是通病,因此A程序员自认为简洁明了的命名规则,拿给B程序员,往往会产生驴唇不对马嘴的解读

更遑论这年头,标识符命名中出现大票狗屎不通的英语甚至汉语日语韩语拼音的鬼事儿都所在多有,这种情况下,还要谈什么“匈牙利命名”,就是明摆着添乱了

出0入70汤圆

发表于 2010-1-26 23:03:47 | 显示全部楼层
这个库是标准DLL吗,可以由其它程序如DELPHI,VB引用吗

出0入0汤圆

发表于 2010-1-26 23:05:50 | 显示全部楼层
牛~~mark

出0入0汤圆

发表于 2010-1-26 23:30:21 | 显示全部楼层
MARK

出0入0汤圆

发表于 2010-1-27 08:32:16 | 显示全部楼层
有意思 傻孩子怎么什么都懂啊。。。

出0入0汤圆

发表于 2010-1-27 09:31:51 | 显示全部楼层
Gorgon Meducer 傻孩子,能把Utilities.dll的源代码也一起发出来吗?
反编译的代码有一点小问题。

出0入93汤圆

发表于 2010-1-27 09:37:06 | 显示全部楼层
个人向来不喜欢长的变量名,费劲。

另外,针对本文所用的库Utilities.dll实在不敢恭维,明显是垃圾一堆。

对于【18楼】SkyGz  
这个库是标准DLL吗,可以由其它程序如DELPHI,VB引用吗
-----------------------------------------------------------------------
所问的问题还不如自己写呢,楼主只是提供一种方法而已。不要这个库,可能更好一些吧。直接判断某个控件的KeyDown事件,也不要什么循环查询了。

附Utilities.dll反编译的一段代码,就知道该文件写的有多差劲了。要使用的人最好不要学习这种风格。

public static class Utilities.HEX.HEXBuilder
{
    // Methods
    public static string ByteArrayToHEXString(byte[] Datas)
    {
        if (Datas == null)
        {
            return "";
        }
        if (Datas.Length == 0)
        {
            return "";
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < Datas.Length; i++)
        {
            builder.Append(Datas.ToString("X2"));
            builder.Append(" ");
        }
        return builder.ToString().Trim().ToUpper();
    }

    public static bool HEXStringToByteArray(string strHex, ref byte[] cResult)
    {
        return HEXStringToByteArray(strHex, ref cResult, true);
    }

    public static bool HEXStringToByteArray(string strHex, ref byte[] cResult, bool bStrictCheck)
    {
        uint num = 0;
        uint index = 0;
        bool flag = true;
        if (strHex != null)
        {
            if (cResult == null)
            {
                cResult = new byte[1];
            }
            strHex = strHex.Trim();
            strHex = strHex.ToUpper();
            if (strHex.StartsWith("0X"))
            {
                strHex = strHex.Remove(0, 2);
            }
            if (cResult.Length < 1)
            {
                Array.Resize<byte>(ref cResult, 1);
            }
            cResult[0] = 0;
            if (!(strHex == ""))
            {
            Label_0251:
                if (strHex != "")
                {
                    byte num3 = 0;
                    if (((num % 2) == 0) && (cResult.Length <= index))
                    {
                        Array.Resize<byte>(ref cResult, cResult.Length + 1);
                    }
                    if (strHex.StartsWith("0"))
                    {
                        flag = false;
                        num3 = 0;
                    }
                    else if (strHex.StartsWith("1"))
                    {
                        flag = false;
                        num3 = 1;
                    }
                    else if (strHex.StartsWith("2"))
                    {
                        flag = false;
                        num3 = 2;
                    }
                    else if (strHex.StartsWith("3"))
                    {
                        flag = false;
                        num3 = 3;
                    }
                    else if (strHex.StartsWith("4"))
                    {
                        flag = false;
                        num3 = 4;
                    }
                    else if (strHex.StartsWith("5"))
                    {
                        flag = false;
                        num3 = 5;
                    }
                    else if (strHex.StartsWith("6"))
                    {
                        flag = false;
                        num3 = 6;
                    }
                    else if (strHex.StartsWith("7"))
                    {
                        flag = false;
                        num3 = 7;
                    }
                    else if (strHex.StartsWith("8"))
                    {
                        flag = false;
                        num3 = 8;
                    }
                    else if (strHex.StartsWith("9"))
                    {
                        flag = false;
                        num3 = 9;
                    }
                    else if (strHex.StartsWith("A"))
                    {
                        flag = false;
                        num3 = 10;
                    }
                    else if (strHex.StartsWith("B"))
                    {
                        flag = false;
                        num3 = 11;
                    }
                    else if (strHex.StartsWith("C"))
                    {
                        flag = false;
                        num3 = 12;
                    }
                    else if (strHex.StartsWith("D"))
                    {
                        flag = false;
                        num3 = 13;
                    }
                    else if (strHex.StartsWith("E"))
                    {
                        flag = false;
                        num3 = 14;
                    }
                    else if (strHex.StartsWith("F"))
                    {
                        flag = false;
                        num3 = 15;
                    }
                    else
                    {
                        if (strHex.StartsWith(" "))
                        {
                            if (!flag)
                            {
                                flag = true;
                                num++;
                                index = num >> 1;
                                num = index * 2;
                            }
                            strHex = strHex.Remove(0, 1);
                            goto Label_0251;
                        }
                        return (!bStrictCheck && (num > 0));
                    }
                    cResult[index] = (byte) (cResult[index] << 4);
                    cResult[index] = (byte) (cResult[index] | num3);
                    num++;
                    index = num >> 1;
                    strHex = strHex.Remove(0, 1);
                    goto Label_0251;
                }
                return true;
            }
        }
        return false;
    }

    public static bool HEXStringToU16Array(string strHex, ref ushort[] hwResult)
    {
        return HEXStringToU16Array(strHex, ref hwResult, true);
    }

    public static bool HEXStringToU16Array(string strHex, ref ushort[] hwResult, bool bStrictCheck)
    {
        uint num = 0;
        if (strHex != null)
        {
            if (hwResult == null)
            {
                hwResult = new ushort[1];
            }
            strHex = strHex.Trim();
            strHex = strHex.ToUpper();
            if (strHex.StartsWith("0X"))
            {
                strHex = strHex.Remove(0, 2);
            }
            if (hwResult.Length < 1)
            {
                Array.Resize<ushort>(ref hwResult, 1);
            }
            hwResult[0] = 0;
            if (!(strHex == ""))
            {
                while (strHex != "")
                {
                    if (((num % 4) == 0) && (hwResult.Length <= (num >> 2)))
                    {
                        Array.Resize<ushort>(ref hwResult, hwResult.Length + 1);
                    }
                    byte num2 = 0;
                    if (strHex.StartsWith("0"))
                    {
                        num2 = 0;
                    }
                    else if (strHex.StartsWith("1"))
                    {
                        num2 = 1;
                    }
                    else if (strHex.StartsWith("2"))
                    {
                        num2 = 2;
                    }
                    else if (strHex.StartsWith("3"))
                    {
                        num2 = 3;
                    }
                    else if (strHex.StartsWith("4"))
                    {
                        num2 = 4;
                    }
                    else if (strHex.StartsWith("5"))
                    {
                        num2 = 5;
                    }
                    else if (strHex.StartsWith("6"))
                    {
                        num2 = 6;
                    }
                    else if (strHex.StartsWith("7"))
                    {
                        num2 = 7;
                    }
                    else if (strHex.StartsWith("8"))
                    {
                        num2 = 8;
                    }
                    else if (strHex.StartsWith("9"))
                    {
                        num2 = 9;
                    }
                    else if (strHex.StartsWith("A"))
                    {
                        num2 = 10;
                    }
                    else if (strHex.StartsWith("B"))
                    {
                        num2 = 11;
                    }
                    else if (strHex.StartsWith("C"))
                    {
                        num2 = 12;
                    }
                    else if (strHex.StartsWith("D"))
                    {
                        num2 = 13;
                    }
                    else if (strHex.StartsWith("E"))
                    {
                        num2 = 14;
                    }
                    else if (strHex.StartsWith("F"))
                    {
                        num2 = 15;
                    }
                    else
                    {
                        return (!bStrictCheck && (num > 0));
                    }
                    hwResult[num >> 2] = (ushort) (hwResult[num >> 2] << 4);
                    hwResult[num >> 2] = (ushort) (hwResult[num >> 2] | num2);
                    num++;
                    strHex = strHex.Remove(0, 1);
                }
                return true;
            }
        }
        return false;
    }

    public static bool HEXStringToU32Array(string strHex, ref uint[] hwResult)
    {
        return HEXStringToU32Array(strHex, ref hwResult, true);
    }

    public static bool HEXStringToU32Array(string strHex, ref uint[] wResult, bool bStrictCheck)
    {
        uint num = 0;
        if (strHex != null)
        {
            if (wResult == null)
            {
                wResult = new uint[1];
            }
            strHex = strHex.Trim();
            strHex = strHex.ToUpper();
            if (strHex.StartsWith("0X"))
            {
                strHex = strHex.Remove(0, 2);
            }
            if (wResult.Length < 1)
            {
                Array.Resize<uint>(ref wResult, 1);
            }
            wResult[0] = 0;
            if (!(strHex == ""))
            {
                while (strHex != "")
                {
                    if (((num % 8) == 0) && (wResult.Length <= (num >> 3)))
                    {
                        Array.Resize<uint>(ref wResult, wResult.Length + 1);
                    }
                    byte num2 = 0;
                    if (strHex.StartsWith("0"))
                    {
                        num2 = 0;
                    }
                    else if (strHex.StartsWith("1"))
                    {
                        num2 = 1;
                    }
                    else if (strHex.StartsWith("2"))
                    {
                        num2 = 2;
                    }
                    else if (strHex.StartsWith("3"))
                    {
                        num2 = 3;
                    }
                    else if (strHex.StartsWith("4"))
                    {
                        num2 = 4;
                    }
                    else if (strHex.StartsWith("5"))
                    {
                        num2 = 5;
                    }
                    else if (strHex.StartsWith("6"))
                    {
                        num2 = 6;
                    }
                    else if (strHex.StartsWith("7"))
                    {
                        num2 = 7;
                    }
                    else if (strHex.StartsWith("8"))
                    {
                        num2 = 8;
                    }
                    else if (strHex.StartsWith("9"))
                    {
                        num2 = 9;
                    }
                    else if (strHex.StartsWith("A"))
                    {
                        num2 = 10;
                    }
                    else if (strHex.StartsWith("B"))
                    {
                        num2 = 11;
                    }
                    else if (strHex.StartsWith("C"))
                    {
                        num2 = 12;
                    }
                    else if (strHex.StartsWith("D"))
                    {
                        num2 = 13;
                    }
                    else if (strHex.StartsWith("E"))
                    {
                        num2 = 14;
                    }
                    else if (strHex.StartsWith("F"))
                    {
                        num2 = 15;
                    }
                    else
                    {
                        return (!bStrictCheck && (num > 0));
                    }
                    wResult[num >> 3] = wResult[num >> 3] << 4;
                    wResult[num >> 3] |= num2;
                    num++;
                    strHex = strHex.Remove(0, 1);
                }
                return true;
            }
        }
        return false;
    }

    public static bool HEXStringToU64Array(string strHex, ref ulong[] hwResult)
    {
        return HEXStringToU64Array(strHex, ref hwResult, true);
    }

    public static bool HEXStringToU64Array(string strHex, ref ulong[] dwResult, bool bStrictCheck)
    {
        uint num = 0;
        if (strHex != null)
        {
            if (dwResult == null)
            {
                dwResult = new ulong[1];
            }
            strHex = strHex.Trim();
            strHex = strHex.ToUpper();
            if (strHex.StartsWith("0X"))
            {
                strHex = strHex.Remove(0, 2);
            }
            if (dwResult.Length < 1)
            {
                Array.Resize<ulong>(ref dwResult, 1);
            }
            dwResult[0] = 0L;
            if (!(strHex == ""))
            {
                while (strHex != "")
                {
                    if (((num % 0x10) == 0) && (dwResult.Length <= (num >> 4)))
                    {
                        Array.Resize<ulong>(ref dwResult, dwResult.Length + 1);
                    }
                    byte num2 = 0;
                    if (strHex.StartsWith("0"))
                    {
                        num2 = 0;
                    }
                    else if (strHex.StartsWith("1"))
                    {
                        num2 = 1;
                    }
                    else if (strHex.StartsWith("2"))
                    {
                        num2 = 2;
                    }
                    else if (strHex.StartsWith("3"))
                    {
                        num2 = 3;
                    }
                    else if (strHex.StartsWith("4"))
                    {
                        num2 = 4;
                    }
                    else if (strHex.StartsWith("5"))
                    {
                        num2 = 5;
                    }
                    else if (strHex.StartsWith("6"))
                    {
                        num2 = 6;
                    }
                    else if (strHex.StartsWith("7"))
                    {
                        num2 = 7;
                    }
                    else if (strHex.StartsWith("8"))
                    {
                        num2 = 8;
                    }
                    else if (strHex.StartsWith("9"))
                    {
                        num2 = 9;
                    }
                    else if (strHex.StartsWith("A"))
                    {
                        num2 = 10;
                    }
                    else if (strHex.StartsWith("B"))
                    {
                        num2 = 11;
                    }
                    else if (strHex.StartsWith("C"))
                    {
                        num2 = 12;
                    }
                    else if (strHex.StartsWith("D"))
                    {
                        num2 = 13;
                    }
                    else if (strHex.StartsWith("E"))
                    {
                        num2 = 14;
                    }
                    else if (strHex.StartsWith("F"))
                    {
                        num2 = 15;
                    }
                    else
                    {
                        return (!bStrictCheck && (num > 0));
                    }
                    dwResult[num >> 4] = dwResult[num >> 4] << 4;
                    dwResult[num >> 4] |= num2;
                    num++;
                    strHex = strHex.Remove(0, 1);
                }
                return true;
            }
        }
        return false;
    }
}


真服了,十六进制的字符转成十进制数字有那么复杂么?C#我不太懂,Delphi中StrToInt就搞定了,C中atoi也可以,不调用库函数直接减去'0'或('A'+10)也并不麻烦。

出0入0汤圆

发表于 2010-1-27 09:50:54 | 显示全部楼层
感觉和vb  与bcb差不多

出0入93汤圆

发表于 2010-1-27 09:52:48 | 显示全部楼层
我发一个Delphi版的完整版的,功能和傻孩子的一样。不过个人习惯使用参数方式。

Delphi版本的在此下载(文件大小:160K) (原文件名:Unit1.rar),这么简单的东西我就不注释了。默认的变量名懒得改了。代码贴出来:


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, XPMan;

type
  TForm1 = class(TForm)
    btn1: TButton;
    mmo1: TMemo;
    procedure mmo1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  BackDoor: string;                                        //后门字符串
  IsWatch: Boolean;                                        //是否开始监视后门输入

implementation

{$R *.dfm}

procedure TForm1.mmo1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    BackDoor := UpperCase(BackDoor);
    if BackDoor = 'HELLO' then btn1.Show                       
    else if BackDoor = 'BYE' then btn1.Hide;
    BackDoor := '';
    IsWatch := False;
  end else if Key = VK_ESCAPE then begin
          IsWatch := True;
    BackDoor := '';
  end else if IsWatch then BackDoor := BackDoor + Char(Key)
  else BackDoor := '';
end;

end.

出0入0汤圆

发表于 2010-1-27 20:07:59 | 显示全部楼层
估计微软在其产品中也留有后门,每天每时都在监控着数以亿计的用户。
前段时间potel,delphi什么的版权问题,
天天打恐吓电话来说我在使用盗版的delphi,potel等,还要到法院告我。

============
以下是他们代表处的信息

美国英巴卡迪诺科技有限公司北京代表处

北京朝阳区朝阳门外大街18号丰联广场B座写字楼615室 ,100020

法务直线:010-6451-4305  传真:010-6451-4256  朱江

=====================

这个垃圾公司天天打骚扰电话来,拿他们一点办法都没有了。

出0入0汤圆

发表于 2010-1-27 20:33:31 | 显示全部楼层
时代变了,走后门不用偷偷摸摸的啦.....MARK!

出10入95汤圆

发表于 2010-1-27 22:49:50 | 显示全部楼层
学习...

出0入0汤圆

发表于 2010-1-27 23:33:09 | 显示全部楼层
mark

出0入296汤圆

 楼主| 发表于 2010-1-28 12:09:49 | 显示全部楼层
to 【17楼】 watercat
    感谢您中肯的指出命名习惯的问题。不过习惯就是习惯,不是规范,我还是喜欢用长
命名规则。所以,尽管您的建议的确很实在,在我还没有理解其中根本性的差异之前,还
是会坚持这种做法。

to 【23楼】 takashiki 岚月影
    黑盒子不是拿来反汇编用的,所以看了费劲也没有办法啊。呵呵。不过关于里面HEX
转换函数,我还是要解释一下:
    第一,C#有自己的转换方式,比如Byte.Parse就可以把十六进制字符串转换为实际
的数值(当然是字节型的)。但是问题在于,我希望编写一个函数能在转换的时候将一串
十六进制字符串转换为字节数组;同时符合人们的书写习惯:从右向左写。
    第二,用这种方法有可能在别人书写的十六进制字符串出现非法字符的时候做出一
定的处理:要么直接提取已有的数据(也就是兼容某些十六进制数字后面跟上一些不小心
输入的符号),要么报告给函数的调用者,这样就能实现对textbox中输入十六进制数字
的有效性检测——你可以弹出一个对话框或者干脆清空这个文本框。
    第三、如果使用Parse方法,对于非法字符可能会引发异常,而处理异常的系统开销是比较
大的,在Visual C# 2008 Step by Step书中建议过,在可能的情况下尽可能使用“有效性”
检测来替代异常。
    第四、这段代码确实是我学习C#2个星期后写出来的……想法幼稚,我也是最近才知道
还有Parse方法,呵呵。
    希望多多讨论。
    还有,不要出口都是“垃圾”。

出0入296汤圆

 楼主| 发表于 2010-1-28 12:18:11 | 显示全部楼层
to  【13楼】 zzsoft
do
{

}
while (false);
是一个很有用的方法:
1、限定了括号里面变量的作用域
2、可以在执行某些操作之前进行必要的有效性检测,检测不合格的直接break就可以了,例如
do
{
    if (null == strResult)
    {
        break;
    }
}
while (false);
这种方法可以适应需要避免使用return的场合。

另外
do
{

}
while(true);
在反编译的时候会被当作 goto XXX的结果

出0入0汤圆

发表于 2010-1-28 12:20:23 | 显示全部楼层
做电子工程研发员需要钻研,做通用OS下的应用程序更需要钻研

如果只是作为一种拿来探讨的思路,那么这样的代码当然无所谓

但如果标题和内容字里行间已然体现出一种“诲人不倦”的态度,那么最好还是对自己代码中展现出的种种先斟酌一下比较好

出0入296汤圆

 楼主| 发表于 2010-1-28 12:28:51 | 显示全部楼层
to 【33楼】 watercat
    没有任何证据证明复式命名法一定就是为了别人能读懂。而事实上,很多时候
程序员并不在乎别人怎么命名,觉得不可靠的时候都自己“写一遍”。所以,我意
识到代码只有自己会去读,所以写给以后的自己来看,当然要符合自己的阅读习惯。
    另外,没有一种绝对的证据证明,您的观点是正确的;相反我认为您的观点是
一种平行的观点,并无绝对对错;就好比如果我要提供代码给客户,客户抱怨说麻
烦我会立即用简单的方法来改写一样。
    所谓诲人不倦,这是自己的态度,所以明辨是非,这是读者的态度。提供信息
的人只要自己没有昧着良心,就可以了——因为很多时候,他已经尽力了。反问,
所谓自己一定很认真地检查和审视后的信息,一定是妥当的么?答案是,只是自己
觉得妥当了。

出0入0汤圆

发表于 2010-1-28 12:34:17 | 显示全部楼层
to:
【33楼】 watercat

你说的很对,如果都像你一样,只看不说就不对了,(不是不说:还批评这批评那的)

看看老师,哪个是全懂得,如果等全懂了全对了,还来教你?

谢谢论坛上无私奉献的人们!致以崇高敬意!

出0入296汤圆

 楼主| 发表于 2010-1-28 12:38:16 | 显示全部楼层
实际上,通过这个帖子,99%的人学到的只是知道:
1、需要一个监控程序;
2、需要一种方法能方便地添加新的后门;
3、需要一个KeyDown事件
4、写代码不要拿给别人看,否则就有口水。

如果真有人用Utilities.dll,我真的要谢天谢地了。反正非官方的dll,不要乱用
这是真理,因为你不清楚他是否可靠,是否有后门。

换句话说,本来就是很简单的东西,所需要学的也不过就是上面列举的信息;对于初学者
他们可以先用Utilities来实现功能,然后他们会自己想着如何自己写一个同样功能的东西,
再然后也许就会过来贴自己的代码了。

接引比给一个库更有用。
所以,高手世界的东西很多并不能直接让初学者领会,他们需要过程,需要自己演变。
包括水猫说的复式命名法里面有各种“半通不通”的杂烩,这也是大家模仿的结果,
不能因为酒里面掺了水,就说天下所有的黄酒都不是东西。
我觉得,关于命名规则,妥当的方法应该是:
“能体现编写者的意图”,具体解释为:
1、您的代码自己能读懂么?
2、您的代码需要给别人读么?
3、您的代码希望附加什么信息么?
4、您自己使用的命名规则,自己习惯么?如果费劲,就换一种;如果顺手,就适当吸收
   一点别人说的好的东西。比如,如果别人抱怨名字长,你看看是不是可以适当简化。
   等等。

一切都需要过程,不要拔苗助长。

出0入296汤圆

 楼主| 发表于 2010-1-28 12:46:44 | 显示全部楼层
P.S
    我从大二开始,强迫自己用复式命名法,为了在自己写代码的时候
学习单词——想法虽然可笑,不过还是有一点点用。这也就是我的目的,
很简单很明确,我很开心。
   
想起来一个故事,我瞎编的:
   

    村里有很多人种土豆,于是我也种;有一天,我突发奇想给每一个
土豆都扎了一个蝴蝶结,于是我的土豆田我看起来就很开心了。
    突然有一天有一个人站出来对我说,你给自己家种的土豆上都扎了
一个蝴蝶结明显看着“罗嗦”。我应该如何应对呢?
    是把所有的土豆上的蝴蝶结去掉,然后拉更多人来看,还是仍然过
自己怡然自得的生活?
    至少土豆的买家不在乎你是否有蝴蝶结。

出0入0汤圆

发表于 2010-1-28 13:27:39 | 显示全部楼层
风格之争,从代码的可读性、可维护性方面来说,确实有好坏之分,不过这都是小事。
关键是那种“乐于分享”的精神。勿以善小而不为

出0入0汤圆

发表于 2010-1-28 13:34:33 | 显示全部楼层
向楼主致敬!编自己的代码,乐于分享这就够了,其他的不必想太多。

出0入0汤圆

发表于 2010-1-28 13:58:27 | 显示全部楼层
【37楼】Gorgon Meducer  傻孩子

P.S
    我从大二开始,强迫自己用复式命名法,为了在自己写代码的时候
学习单词——想法虽然可笑,不过还是有一点点用。这也就是我的目的,
很简单很明确,我很开心。
   
想起来一个故事,我瞎编的:
   
    村里有很多人种土豆,于是我也种;有一天,我突发奇想给每一个
土豆都扎了一个蝴蝶结,于是我的土豆田我看起来就很开心了。
    突然有一天有一个人站出来对我说,你给自己家种的土豆上都扎了
一个蝴蝶结明显看着“罗嗦”。我应该如何应对呢?
    是把所有的土豆上的蝴蝶结去掉,然后拉更多人来看,还是仍然过
自己怡然自得的生活?
    至少土豆的买家不在乎你是否有蝴蝶结。

====================================================

自己种土豆自己吃的话,你就算每个土豆上绑条蛇都没人管你

但这土豆如果还拿来卖,恐怕正常买家都会在乎这条多余的蝴蝶结的——因为吃你种的土豆居然除了削皮还要额外解条蝴蝶结,而且这蝴蝶结居然还是复式绑法的,没有几星期的训练经验根本只能解的一团糟……

出0入0汤圆

发表于 2010-1-28 14:02:31 | 显示全部楼层
【35楼】sunmy  初学AVR

to:
【33楼】 watercat
你说的很对,如果都像你一样,只看不说就不对了,(不是不说:还批评这批评那的)
看看老师,哪个是全懂得,如果等全懂了全对了,还来教你?
谢谢论坛上无私奉献的人们!致以崇高敬意!

================================================

直话直说吧,我一向觉得,这类纯粹小窍门式的技巧,随便几句话提出就足够了,还要搞出一大段似是而非的代码给别人“手把手”,那是小学一年级甚至幼儿园大班老师的做法,反而不是平等的讨论态度

当然,有人自己愿意当小学生也没问题,但请不要代表所有人搞什么园丁颂……

说到底,对于正常成年IT技术人员来说,思路理应比代码更重要

出0入296汤圆

 楼主| 发表于 2010-1-28 14:10:07 | 显示全部楼层
to 【41楼】 watercat
    敬仰。不过我觉得我就是小学生。而且我发现,高手都是能产出GDP的,
而我们小学生通常要消耗GDP来渴望有一天能成为高手。所以,高手不要笑话
我们,因为你们生而不是高手。

    所谓夏虫不可语冰,做谓夏虫的悲哀,还希望您能体谅我们一些。

出0入0汤圆

发表于 2010-1-28 14:14:34 | 显示全部楼层
索性把话说开吧

这帖子,标题是[手把手教你给自己的软件加后门],结果不过是用最原始的方法做控件隐藏和显现,首先已经是文不对题了

然后,花大把的篇幅抓图、贴图,而且两三张图就开一个新回帖,如此郑重其事之下,却只是说了“怎么在 MSVS 工程中加入外部 dll 引用”——奇怪了,这是当别人是瞎的还是当别人是傻的?有必要这么多此一举么?举个例子来说,我曾经教过只懂电脑基础操作的人,怎么在 Firefox 浏览器中加入新的 SSL 根证书,我只需要告诉她:

循菜单栏[编辑]->[首选项]->[高级]->[加密]->[查看证书]->[导入]->选择刚下载的证书文件->打开->确定

人家一样做的很好,难道能用 MSVS 的人反而连这点理解能力都没有了?

至于那段代码,我也索性懒得多说什么了,有人愿意在青土豆上绑蝴蝶结出来卖也是他自己的事情,但请不要随意代表买家,就这样

出0入0汤圆

发表于 2010-1-28 14:18:41 | 显示全部楼层
【42楼】Gorgon Meducer  傻孩子

    敬仰。不过我觉得我就是小学生。而且我发现,高手都是能产出GDP的,
而我们小学生通常要消耗GDP来渴望有一天能成为高手。所以,高手不要笑话
我们,因为你们生而不是高手。
    所谓夏虫不可语冰,做谓夏虫的悲哀,还希望您能体谅我们一些。

=============================================================

把话说成这样,我就不会觉得你谦虚,而只会觉得你虚伪了

你在论坛许多人眼中是什么位置,不要告诉我你没有一个基本的概念

你现实中的工作也决定了你没有立场说什么[作为夏虫的悲哀]

夏虫明知自己是夏虫,那叫有自知之明,并站稳夏虫的立场,那叫明心见性

但明明已经是鸟雀了,却还非得说自己是夏虫,那个说得好听点,叫妄自菲薄,说得难听点,就叫矫情

出0入296汤圆

 楼主| 发表于 2010-1-28 14:20:22 | 显示全部楼层
让你笑话了,我最近才弄懂这些,所以我觉得很有必要总结一下。
图文结合是我总结出的经验。
还有:
一部分人习惯于:
1、看到标题的内容觉得是小儿科,就不点了;
2、看到标题感到好奇,点进取,看到很多图片,扫一眼,意识到
   都是水货,关贴
3、看到标题的内容对自己有用,看到贴自有手把手还有工程,会
   下载下来留着备用。

最后,我想说开一点:
论坛是在互联网上公开发表自己观点的地方,我发布了,我效果达到了
您评论了,您也行驶了自己浏览论坛的权利。皆大欢喜。

感谢您的参与^_^
有缘在一起,就是幸运。

出0入296汤圆

 楼主| 发表于 2010-1-28 14:28:30 | 显示全部楼层
to 【44楼】 watercat
    1、该帖子的分量和对错一直是您在评论,您有权力评论。
    2、帖子的内容对您没有用。
    3、帖子的内容对一部分人有用。
    4、您不能剥夺仍然不懂这部分内容人学习的权利。
    5、虽然是小技巧,不懂得时候仍然是不懂。
    6、我只是会摆弄几下AVR,C#对我来说完全是初学,我就是小学生,一点也不奇怪。
    7、人要有自知之明,我不过是喜欢写一点东西,赚一点心理满足;但是我自己是谁
       我还是知道的;我也没有写对所有人都没有用的东西,即便写了,灌水无罪阿。
    8、谁有权利来剥夺我发小白教程的权利?答案:阿莫。方法:删贴或封ID
    9、要说自知之明,我还是觉得,我不过就是习惯从初学者的角度考虑;而不算什么
       高手;绝对不是高手,也永远成不了高手,因为我缺乏一个很重要的东西:灵气。
   10、您的言行让我觉得自己很惭愧,果然还是不应该出来显摆。
   11、这个帖子对我自己来说还是有意义的,希望您能尊重我。
   12、说我虚伪也罢,说我谦虚也罢,难道只有你说,没有我说?
   13、你说我说,哪里是自我?自我是什么?饿了,吃饭;困了,睡觉。

出0入0汤圆

发表于 2010-1-28 14:29:46 | 显示全部楼层
我觉得,类似这种小窍门式的经验,更适合开个博客一一记载,而不是技术论坛上以一种推销知识的态度发帖

出0入0汤圆

发表于 2010-1-28 14:33:10 | 显示全部楼层
术业有专攻,闻道有先后。

两位高手就不要再口水了!

另外再佩服一下水猫,高手。

出0入296汤圆

 楼主| 发表于 2010-1-28 14:35:14 | 显示全部楼层
to 【47楼】 watercat
    恩……其实是受了MSDN的启发,他上面的例子也都很基本,很多也是手把手的。不过,说推销,也
不为过。只不过上位机论坛C#的帖子太少了……于是就想根据自己学习的过程,写一点东西,应该会对
后来者有用吧。唉……如果认真说起来,应该是“一段标题引发的口水”。如改改为:“学习笔记,
如何给上位机添加后门”就摆正位置了吧。
   
    说了你别笑话,类似“从generic里面继承”的技巧,我体会到的时候还会高呼:“我真是天才”。
呵呵……别笑话,通常周围的人都会用鄙视的眼神看我“这家伙居然在公共场合手舞足蹈”^_^

出0入0汤圆

发表于 2010-1-28 14:54:45 | 显示全部楼层
【49楼】Gorgon Meducer  傻孩子

    恩……其实是受了MSDN的启发,他上面的例子也都很基本,很多也是手把手的。不过,说推销,也
不为过。只不过上位机论坛C#的帖子太少了……于是就想根据自己学习的过程,写一点东西,应该会对
后来者有用吧。唉……如果认真说起来,应该是“一段标题引发的口水”。如改改为:“学习笔记,
如何给上位机添加后门”就摆正位置了吧。
   
    说了你别笑话,类似“从generic里面继承”的技巧,我体会到的时候还会高呼:“我真是天才”。
呵呵……别笑话,通常周围的人都会用鄙视的眼神看我“这家伙居然在公共场合手舞足蹈”^_^

================================================

1、改了标题后确实顺眼很多

2、MSDN的立场和你的立场并不一致,MSDN 的目的就是尽最大可能增加 M$ 软件的销量,自然会努力抓住任何可能的编程者,但你也不要忽略了一件事:MSDN 在很多基本的例子之余,更有许多在相当深刻的层次上阐述算法和编程理念的内容

3、展现自我是好事,不介意别人鄙视也并不是什么坏事,但你真的能做到身心内外真如不二么?能做到的话怕也不会在这里废口水了

出0入296汤圆

 楼主| 发表于 2010-1-28 15:05:24 | 显示全部楼层
因为重视您的意见。而且我也知道网络的力量,学习过《竞选州长》。适当的
解释是需要的。而且也并不是无用的口水。这是一种交流。也可以认为是一种
利用,这种口水很赚眼球,也能让帖子不沉。同时也能了解大家的思维方式。
找到机会更正自己孤陋寡闻的地方。

MSDN对深层次的讲解很多都是点到为止,符合高手交流的习惯,不过当自己还
没有Framework开发人员那种水平之前,想要当他们肚子里的蛔虫,工作忙起来
还真的觉得不方便……要么别给我提示,要提示就干脆点嘛……呵呵

好在还有论坛。所以我才想写点东西。因为这些内容的确卡过我……

出0入0汤圆

发表于 2010-1-28 18:58:49 | 显示全部楼层
to Gorgon Meducer 傻孩子:

do{

}while(false)

这个技巧满有意思的,受教了.

------------------------------------------------------------------
to watercat, Gorgon Meducer 傻孩子

两位的讨论真是涉及甚广,我没有太多感触,只想谈谈对于代码风格的个人心得.

我认为采用如何的代码风格是根据代码所用环境,参与的人员和个人环境决定的.

傻孩子的风格比较适合,大的集合开发中,很多人都是同一代码的开发者,

比如cadence美国写算法代码,而cadence中国在编译和debug,这时候相互之间需要很了解代码含义,

傻孩子的风格对于有英文阅读习惯的人来说比注释更方便,更容易理解,相互理解比较容易;


而水猫的风格适合分布式开放,一个人开发debug一个模块代码,一人一模块时,

简单的接口名比较有利于记忆和使用,尤其是不使用IDE的情况下,

而且水猫的风格比较有利于职场的保护,比如开发者不希望代码很快就被了解和解读的话,

采用这种风格,就非常便利了,只要将所有的注释删除,就是人工混淆器了,甚至可以说,就是一篇密码.

所以我认为,代码风格不要拘泥与一种形式,要灵活使用.

出0入0汤圆

发表于 2010-1-28 19:42:35 | 显示全部楼层
又浪费了很多时间看口水。在浪费一点时间表达一下自己的看法。

1)很感谢Gorgon Meducer 傻孩子能把自己做过的东西拿出来说,而且是step by step的。感觉中国很缺这种东西,大家都是转来转去,抄来抄去。很少自己写点东西。我觉得如果每个人能像Gorgon Meducer把自己的心得写出来,哪怕是一个最简单的例子。中国的技术水平就不会是现在的样子。在私企呆了四年多,深感做技术没有积累,大家都是从头开始,学到什么也都不愿意拿出来告诉别人。公司换了一拨人,就基本上从头开始,原来那帮人犯的错误后来的人还要再犯一遍。很喜欢读日本人写的东西,感觉浅显,但是该说的都说了,就像Gorgon Meducer这篇文章的风格。
2)国内做技术的氛围都太浮躁了,其实有时候从这里也能看出来,大家对“爆料”或者所谓的“内部资料”之类的关注远远大于原创的技术文档或者讨论问题的帖子。感觉愿意认真讨论什么问题的很少,大家更愿意直接下载资料放着,看不看又是另外一件事情了。
3)再次感谢Gorgon Meducer ,虽然这篇帖子跟我没什么关系,很佩服你这种精神,希望您能继续下去,期待能在一个共同的领域跟您一起讨论问题。

aaa1982

出0入296汤圆

 楼主| 发表于 2010-1-28 23:55:15 | 显示全部楼层

出0入0汤圆

发表于 2010-1-29 05:21:36 | 显示全部楼层
呵呵,一个例子几乎涵盖了所有C#的特性和面向对象的例子,很厉害。

但是有两点,我不是太明了,听听你的意见。

1-,C#模版,我一直不太理解C#为什么要加入模版。

如果没记错,C#的类根是 Object, 也就是说,并不存在未知类型对象,所以模版根本没意义。

2-,lock 是保证,一个线程访问这个代码,

你这里添加了lock,可以解释一下是为了预防那种情况?因为看不出你这个类可以多线程使用,如果你预想了多线程,那么怎样使用这个类?

谢谢。

出0入296汤圆

 楼主| 发表于 2010-1-29 09:44:51 | 显示全部楼层
to 【55楼】 zzsoft
    用模板的原因很简单,模板提供了一个框架,根据这个框架,可以写出
很多具有类似结构的代码,比如这个后门监视,实际上还可以通过从TIncantationMonitor<String>
继承的方法实现一个对字符串的匹配。当然也可以改为TIncantationMonitor<Byte>
实现从字节流中匹配出自己感兴趣的数据结构,比如自己定义的通讯数据包。总之,你
可以把TIncantationMonitor<TType>看成一个指令系统或者消息系统的基础类模板。

由于队列的存在,只要有多个访问队列的方法,并且无法限定用户只在一个线程里面
调用这些方法,就需要加入多线程保护机制。你看我的代码应该可以发现,我是一个
小心翼翼的人,很多地方都要加入多重限定。以前认识一些黑客,他们给我的忠告就
是一定要注意“变量检查”,不要只检查一次,越接近核心,越要小心。因为你的代
码很容易被别人crack。

出0入0汤圆

发表于 2010-1-29 11:08:23 | 显示全部楼层
老实说……代码还是一样的“难看”,长变量名依旧,而且这次还没注释了……

至于内容,虽然目前看有点多此一举,不过循着这个路子,未来应该有机会能做出不错的东西,拭目以待,也就不多说什么了

最后,关于那个[黑客的忠告]云云,个人认为,不能说他错,不过,如果有可能,则

[将安全相关的资源访问集中于单点、并分离为不同程序,然后由此程序在资源访问程序中依照“最小授权原则”设置权限,并对此程序进行严格的代码审查]

整体效果会更好一些……因为没人能保证一个大系统绝对不会有 BUG,这个不是你编程者有多少次代码检查就能解决的问题,举个例子,当初的 JPEG 漏洞,就是系统通用的 JPEG 处理库发生了问题,这种情况下,只要你的代码用到了 JPEG 图形,你就必然中招,你这个代码运行的进程空间和同一 UID 下的所有进程就必然不安全

出0入0汤圆

发表于 2010-1-29 11:41:18 | 显示全部楼层
mark,谢谢分享

出0入296汤圆

 楼主| 发表于 2010-1-29 12:09:22 | 显示全部楼层
to 【57楼】 watercat
    风格上的事情就不讨论了,因为还在摸索。在C#上用我在C上用的风格我自己也觉得只是
临时之举。有点怀念VB的with块。

    至于安全的问题,你说的我会牢牢记住。不过多做限定在程序效率允许的情况下应该是一个好习惯。
您的方法理解为:鸡蛋不放在一个篮子里面,并且每个藏鸡蛋的地方都用不同的密码,并且仅仅给与有限
的权限,对否?
   
    受教了。谢谢。

出0入0汤圆

发表于 2010-1-29 12:52:12 | 显示全部楼层
学习中,非常感谢分享

傻孩子搞啥秘密核武器?看起来又是一个通吃的模块要诞生了

出0入0汤圆

发表于 2010-1-29 18:26:28 | 显示全部楼层
【59楼】Gorgon Meducer  傻孩子

    风格上的事情就不讨论了,因为还在摸索。在C#上用我在C上用的风格我自己也觉得只是
临时之举。有点怀念VB的with块。
    至于安全的问题,你说的我会牢牢记住。不过多做限定在程序效率允许的情况下应该是一个好习惯。
您的方法理解为:鸡蛋不放在一个篮子里面,并且每个藏鸡蛋的地方都用不同的密码,并且仅仅给与有限
的权限,对否?
   
    受教了。谢谢。

===============================================================

C# 应该用 Java 的风格,你看看阿莫编的这个论坛代码,偶尔 SQL 连接出错时的那些提示(:D),我觉得那种风格就不错

至于安全,其实你理解不是很准确,我的意思如果说的形象化一点,就是:

把不同的蛋放在不同的保险箱里,然后所有保险箱放在一间铁房子里,而这些保险箱和铁房子都是通过稳妥的渠道有线遥控的,这样,只要这个人本身可以放心,则可以避免绝大多数问题的发生

举个现实点的例子:

我做一个论坛程序,该程序整体有对下层数据库的访问,也有对用户上传的图片、文档、压缩包进行分析和归类的功能,而这些功能,本质上都必须使用第三方的外部库实现,所以一旦任何库或者程序本身出现漏洞,这个漏洞肯定会影响到本进程空间,也很可能会影响到与本进程相同 UID 的所有进程

而我对此的处理措施就是:首先分析安全风险,得知绝大多数安全问题,归根到底必须通过对数据库的非法访问发生作用,则此时,我将数据库操作单独提取出来,由一个独立的中间件进程实现,该进程运行在独立的 UID + chroot 空间,对上提供一个 socket (本机或 tcp)端口,接收并转发数据库操作请求,同时对“数据”本身中出现的分号和 SQL 保留字进行过滤,再重新定义“删除”操作,这样,就可以规避几乎所有 SQL 注入类攻击,同时,这个中间件还可以根据需要过滤数据中出现的可能引入二义性的 html 字符,这样,绝大多数跨站脚本(XSS)攻击也将归于无形

而一旦 SQL 注入和 XSS 攻击都被无效化,再加上程序本身代码文件经由合理的安全设置进行防护,此时只要这个中间件程序的代码经过了严格审核,则这个论坛程序作为一个独立的服务,其已经可以认为是[自身安全]了——哪怕论坛程序代码自身仍然有漏洞也是如此

出0入296汤圆

 楼主| 发表于 2010-1-29 23:22:22 | 显示全部楼层
抓其必经之路,并迫使所有的操作都从某一关口通过,而针对这一关口进行特殊的隔离操作。
嗯……我会慢慢体会的。

出0入0汤圆

发表于 2010-1-30 18:36:15 | 显示全部楼层
【62楼】Gorgon Meducer  傻孩子

抓其必经之路,并迫使所有的操作都从某一关口通过,而针对这一关口进行特殊的隔离操作。
嗯……我会慢慢体会的。

==========================================

认真说起来,Windows NT 体系从基本架构上讲,就是以这套理论为基础的,所以严格说来,Windows NT 体系完全有机会做到极高的安全性

然而,M$ 无数软件开发员为了自己的方便,几乎在 Windows 系统所有部分都制造了“后门”以绕开这套规则,于是最终把 Windows 安全机制做成了多孔奶酪

从这个意义上讲,M$ 的研发体系显然至少从技术上讲是不合格的

出0入0汤圆

发表于 2010-1-31 20:21:04 | 显示全部楼层
to 【62楼】Gorgon Meducer  傻孩子

1)虽然c++常用模板,那是因为没有根类,所以需要TDataType确认存储空间;

而c#秉承java从根类继承,也就是所有的对象都可以转成根类对象,并不需要TDataType确认存储空间;

这样就不需要让Collection去了解存储的内容。

我分析了你的代码中需要识别 TDataType 的函数,并没发现需要区分 TDataType 的意义。

所以我还是那样认为的,java (GJ)和C#引入模板只是为了方便c++用户,有点画蛇添足;我不建议使用模板和模板类;

当然,这是个人意见。

2)lock 是为了解决多线程并发的锁,

所以,如果你的类的对象,会被多线程共享,是应该添加 lock 保护 list 在某一时刻只被一个线程读写,

但是,我认为 KeysIncantation 对象不会被多线程共享,所以 list 不需要被保护

另外,添加 lock 是否能起到你说的安全检查倒是个问题,而且lock有时会出错的,

例如代码 tostring 就好像 lock 作用域短了,因该 覆盖 foreach

public override String ToString()
            {
                StringBuilder sbKeys = new StringBuilder();
                lock (((ICollection)m_KeysList).SyncRoot)
                {
                    if (0 == m_KeysList.Count)
                    {
                        return "<NO KEY>";
                    }
// 不应该在这里 }

                    foreach (Keys tKey in m_KeysList)
                    {
                        sbKeys.Append('<');
                        sbKeys.Append(tKey.ToString());
                        sbKeys.Append('>');
                     }
                     return sbKeys.ToString();
// 应该在这里   }
            
            }
        }

出0入296汤圆

 楼主| 发表于 2010-2-1 11:48:58 | 显示全部楼层
to 【64楼】 zzsoft  
    我用这个模板的作用仅仅是为了代码复用,不准备把不同类型的monitor放在一个
collection里面。换句话说,我只是把它当作一种“代码宏”的变种在用。
   
    你说的覆盖问题,的确是一个失误,谢谢指出。

出0入296汤圆

 楼主| 发表于 2010-2-1 11:51:58 | 显示全部楼层
to 【63楼】 watercat
    哎M$……我想当初设计内核的时候,他们的初衷是好的,不过执行的时候
多半是向工期和$妥协了……

出0入0汤圆

发表于 2010-2-2 01:29:23 | 显示全部楼层
回复【65楼】Gorgon Meducer  傻孩子
-----------------------------------------------------------------------

谢谢你才对,许久没有用C#了,你发的东西让我学到很多.

出0入0汤圆

发表于 2010-2-2 11:15:47 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-2-4 20:49:19 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-2-4 21:18:23 | 显示全部楼层
我在想,这样做的菜单,reflector不是一下就看见了,楼主把它封装到dll里,打乱了,造福大家。

出0入0汤圆

发表于 2010-2-10 11:38:34 | 显示全部楼层
标记,学到了很多!

出0入0汤圆

发表于 2010-2-10 14:36:12 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-2-19 00:00:42 | 显示全部楼层
不错

出0入0汤圆

发表于 2010-2-21 02:36:20 | 显示全部楼层
很多时候真理不辨不明,看两大高手论道,长了很多知识。
想多说两句,傻孩子的code里面用了<TDataType>模板,其实就是对应java5中的泛型
其实用这个的妙处在于
1。傻孩子提出的,适应各种类型数据,很灵活。
2。用<TDataType>模板,能防止数据类型转换和错误。要想做一套通用的能处理几种类型数据的类,你不用模板,你要getter类里面这个通用数据是,不可避免地要用到强制转换,比喻你原来传到这个类是string数据,不用<TDataType>模板,那回头你去拿回时必定要做强制转换回string。其实对不用模板的通用object向下类型转换而言都是潜在的危险,别人或你再回头去拿这个这个通用类里的数据时可能忘了前面送的是string类型,而把它当一个byte类型,结果在后续代码里面去拿这个数据时,强制转换成byte.而这时compiler是不会报错的,而运行后结果可想而知,错大了。

而用不用锁,在多线程里面不用锁,肯定不是线程安全的。加了这个锁,CLR就会保障我们的数据不会被另外的线程调用时弄脏。(买个保险)
因为在现代的的SMP (Symmetric Multiprocessing) CPU 下,会有内存和cache的同步问题。

出0入0汤圆

发表于 2010-2-21 04:58:27 | 显示全部楼层
回复【74楼】fightman  
-----------------------------------------------------------------------

针对你的话的分析

1.) 不用<TDataType> 只用基类 object, 适应所有类型数据,更灵活

2.) 防止数据类型转换和错误,要靠程序员的良好习惯,而不是模板,不要以为有了TDataType就不需要强制转型了。
需不需要强制转型,要根据看程序来的。要注意C++引入模板是为了确认存储格式和空间,C#不存在这个问题。

3.) 锁是需要用的时候才用的,不需要的时候还要添加,这本身是坏习惯。

4.) SMP (Symmetric Multiprocessing) CPU 下的内存和cache的同步问题。
根本不是软件要考虑的问题,那是硬件设计问题,Cache coherence对于OS是不可见的。

出0入0汤圆

发表于 2010-2-21 09:44:02 | 显示全部楼层
再冒个泡,
1,2)<TDataType>用还是不用,见仁见智。只要在downcast小心使用,也不是不好。

3)其实如果只用在我们所这个STA(Single Threaded Apartmenet)的例程中,是有点多余。

4)其实C#的程序运行也离不开JIT,JIT对C#的执行,还是逃不托cpu和os的环境。不加锁,在更多的时候,你还真不知道JIT怎么帮你在多线程时处理这段代码里面的值,会不会帮你cache起来。

http://stackoverflow.com/questions/395232/we-need-to-lock-a-net-int32-when-reading-it-in-a-multithreaded-code

Locking accomplishes two things:
It acts as a mutex, so you can make sure only one thread modifies a set of values at a time.
It provides memory barriers (acquire/release semantics) which ensures that memory writes made by one thread are visible in another.
Most people understand the first point, but not the second. Suppose you used the code in the question from two different threads, with one thread calling Add repeatedly and another thread calling Read. Atomicity on its own would ensure that you only ended up reading a multiple of 8 - and if there were two threads calling Add your lock would ensure that you didn't "lose" any additions. However, it's quite possible that your Read thread would only ever read 0, even after Add had been called several times. Without any memory barriers, the JIT could just cache the value in a register and assume it hadn't changed between reads. The point of a memory barrier is to either make sure something is really written to main memory, or really read from main memory.

现在的程序开发,依赖的东西太多了,编程语言,编译器,操作系统,库函数,设备驱动,CPUcache和指令集优化,甚至还有语言本身内置数据结构比如浮点,整型的限制,内存收集机制等等。 程序员几乎如履薄冰,每一步都要小心犯错,这个时候,程序员的个人修养和开发规范成为我们的保护伞,防止我们迈进bug的深渊。

出0入0汤圆

发表于 2010-2-21 22:27:01 | 显示全部楼层
回复【76楼】fightman  
-----------------------------------------------------------------------

你说的问题4)和这里的代码没关系,你给的引用无法证明 “在现代的的SMP (Symmetric Multiprocessing) CPU 下,会有内存和cache的同步问题”。

我看了Jon Skeet的文章 http://www.yoda.arachsys.com/csharp/threads/volatility.shtml。

Jon Skeet的建议是针对 read, write 在 多线程中如何防止脏数据出现,里面泛泛的说

Memory in modern computers is a very complicated business, with registers, multiple levels of cache, and multiple processors sharing main memory but possibly not caches, etc.

以后如果你看到类似的话语,就可以忽略,因为这样混淆问题的语句,只能说明作者根本不懂这个问题。

你可以注意他的结论:

A "strong" memory model is one which guarantees a lot; a "weak" model is one which doesn't guarantee much at all,
often giving better performance but requiring more work on the part of the developer.
"x86" processors have a stronger memory model than the CLR itself,
which is one reason problems such as seeing stale data are relatively hard to demonstrate.

所以不要怀疑硬件,出错99.99999%是软件设计问题。

他的文章假设了一种情况,两个线程可能同时操作一个数据,一个做write, 一个做read,他说如果只是lock write 而不 lock read,

很可能一个write之后,另一个read的还是上一次的值。

- 如果这个数据是被cache的,不用怀疑,现代的SMP不会出现这种情况,因为到目前,大部分商用的SMP CPU都是共享最后一级cache;

其它不是共享的cache,就是coherence的。

- 如果这个数据是没有被cache,可能会出现这样的状况,但是你仔细想想,这并不是一个错,也不是必须的。而是一个需求。需不需数据更新即可见。

综上所诉,.net层的软件设计是硬件无关的,如果这个思想得不到贯彻是不可能设计好软件的。

个人认为,Jon Skeet的文章引导方向有问题,而且会说一些无所谓的话。

出0入0汤圆

发表于 2010-2-22 13:48:48 | 显示全部楼层
定定定定定定定定定定定

出0入0汤圆

发表于 2010-2-24 19:35:11 | 显示全部楼层
回复【77楼】 zzsoft
    Jon Skeet也许解释得不是很合理。暂时放下SMP CPU的RAM和Cache同步问题的争论。抛砖引玉,我们来上一段有趣的code,看看JVM(Java虚拟机)虽然提供了synchronized块(类似如C#的lock的功能),但仍然不能保证synchronized保护的代码段在多线程情况下有个显式的内存屏障explicit memory barriers。

(附注:synchronized的一个作用是保证主存内容和线程的工作内存中的数据的一致性。如果多个线程同时执行一段未经synchronized保护的代码段,很有可能某条线程已经改动了变量的值,但是其他线程却无法看到这个改动,依然在旧的变量值上进行运算,最终导致不可预料的运算结果。)


如果你pc装了jdk5你们可以编译这一段代码看看。(见上传附件)Checked Locking is bokenourdev_535117.rar(文件大小:2K) (原文件名:DoubleCheckTest.rar)

运行时加两个参数,参数I线程数 参数II创建单例实体数。运行java DoubleCheckTest 5 1000, 这时出来的结果很简单
waiting to join 0
waiting to join 1
...
waiting to join 4
看一下这段code
if (o.reference == null) {
        synchronized (o) {
        if (o.reference == null) {
        o.reference = new Singleton();
        o.reference.setA(A_VALUE);
        o.reference.setB(B_VALUE);
        o.reference.setC(C_VALUE);
        o.reference.setDummy(dummyObject);
        recentSingleton = i;
        }
        // shouldn't have to check singelton here
        // mutex should provide consistent view
        }
} else {
...
checkSingleton()
}
想创建的1000个单例实体被我们的5个线程并发的创建了出来,在这个争先恐后地创建1000个单例实体的竞赛中,有落后的竞赛线程,他们没有抢到建设项目,而被分配到执行检查别人建设项目是否偷工减料的质检活 checkSingleton()。结果是这个5人的团队还不赖,活干又快又好。

再放大团队到20和项目规模数目到80000看看。运行java DoubleCheckTest 20 80000, 质量问题暴露出来了
[1xxx] Singleton.c not intialized 0
[1xxx] Singleton.dummy not initialized, value is null
[1xxx] Singleton.c not intialized 0
[1xxx] Singleton.dummy not initialized, value is null
[1xxx] Singleton.dummy not initialized, value is null

有的竞赛线程干活偷工减料,被执行质检的线程给检查出来了。

为什么会出现这个问题。被synchronized 封闭起来的一段创建和初始化单例实体的代码都没有被完整执行,有的竞赛线程做了一半的话,就报告大家说我活做完了。


  

出0入0汤圆

发表于 2010-2-24 19:47:37 | 显示全部楼层
mark

出0入296汤圆

 楼主| 发表于 2010-2-24 20:21:31 | 显示全部楼层
出人意料的热闹……
不过我解释一下,为什么我一定要加线程锁定
public abstract partial class TIncantationMonitor<TDataType>
这个类是一个抽象类,其根本作用是为了解决流处理的匹配问题,未来会派生出很多
子类,比如:使用正则表达式从字符串流中提取所需的子串,从公共通讯总线中提取
自己的数据包(允许不同的数据包格式同时存在于一个总线中)

如此种种……
既然牵涉到通讯,就很有可能有多线程问题
所以加入保护……

出0入0汤圆

发表于 2010-2-24 20:52:30 | 显示全部楼层
学习了,做个记号,呵呵

出0入0汤圆

发表于 2010-2-24 21:47:13 | 显示全部楼层
学习了, 欣赏楼主的共享精神和谦虚的学习态度

出0入0汤圆

发表于 2010-2-26 01:05:34 | 显示全部楼层
接【79楼】盖楼,
   为什么会出现被synchronized (o) { ...}筑起围墙圈起地来盖的楼,还没封顶。就被在外面游荡的质监队给闯进来给给判了个质量不合格。
   
ObjectHolder对象的Object o中的成员reference被构造函数o.reference = new Singleton()构造出来; 后续的一些初始化其内部成员变量还没有开始前。但是已经能够通过singletons.reference获得Singleton对象实例的引用。

如果把代码放到多线程环境下运行,某线程在执行到该行代码的时候JVM或者操作系统进行了一次线程切换,其他线程显然会发现reference对象已经不为空,导致前面一个Lazy load的判断语句if(o.reference == null)不成立。线程认为对象已经建立成功,随之可能会使用对象的成员变量或者调用该对象实例的方法,而实际上前一线程对该对象的后续一些初始化还没有做完,最终导致不可预测的错。

我们改进一下代码,把synchronized(o) {...}移到Lazy load的判断语句if(o.reference == null)的上面
                synchronized(o) {                    
                        if (o.reference == null) {         
                                o.reference = new Singleton();   
                                o.reference.setA(A_VALUE);        
                                o.reference.setB(B_VALUE);        
                                o.reference.setC(C_VALUE);        
                                o.reference.setDummy(dummyObject);
                                recentSingleton = i;              
                        }                                   
                        else {                              
                                checkSingleton(o.reference, i);   
                                int j = recentSingleton - 1;      
                                if (j > i)                        
                                        i = j;                          
                                }                                 
                                                           
                }
你再用java DoubleCheckTest 20 80000跑跑,80000个Singleton里面没有一个Singleton存在初始化不完全的错误了。

其实上面这段例子是Singleton lazy load模式的范例

class Foo
{
  private Resource res = null;
  public Resource getResource()
  {
   if (res == null) res = new Resource();
   return res;
  }
}

由于LazyLoad可以有效的减少系统资源消耗,提高程序整体的性能,所以被广泛的使用,连Java的缺省类加载器也采用这种方法来加载Java类。

在单线程环境下,一切都相安无事,但如果把上面的代码放到多线程环境下运行,那么就可能会出现问题。假设有2条线程,同时执行到了if(res == null),那么很有可能res被初始化2次,为了避免这样的Race Condition,得用synchronized关键字把上面的方法同步起来。代码如下:

代码2

Class Foo
{
  Private Resource res = null;
  Public synchronized Resource getResource()
  {
   If (res == null) res = new Resource();
   return res;
  }
}

现在Race Condition解决了,一切都很好。

N天过后,好学的你偶然看了一本Refactoring的魔书,深深为之打动,准备自己尝试这重构一些以前写过的程序,于是找到了上面这段代码。你已经不再是以前的Java菜鸟,深知synchronized过的方法在速度上要比未同步的方法慢上100倍,同时你也发现,只有第一次调用该方法的时候才需要同步,而一旦res初始化完成,同步完全没必要。所以你很快就把代码重构成了下面的样子:

代码3

Class Foo
{
  Private Resource res = null;
  Public Resource getResource()
  {
   If (res == null)
   {
    synchronized(this)
    {
     if(res == null)
     {
      res = new Resource();
     }
    }
   }
   return res;
  }
}

这种看起来很完美的优化技巧就是Double-Checked Locking。但是很遗憾,根据Java的语言规范,上面的代码是不可靠的。

变化回代码2,也是我所举那个例子最后的变化
代码4
Class Foo
{
  Private Resource res = null;
  Public Resource getResource()
  {
     synchronized(this)
     {
     If (res == null)
     {
     res = new Resource();
    }
    return res;
    }
   }
}

其实我盖的79楼的例子是取自于
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
因为原例所用的symantec jit compiler已经找不到了,所以对原来例子的code (http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckTest.java)做了点变化,使得在JDK5的JVM上也能实现同样的一些效果。

还想说会现代的的SMP (Symmetric Multiprocessing) CPU 下,会有内存和cache的同步问题。
http://www.cs.umd.edu/~pugh/java/memoryModel/AlphaReordering.html
文章说的是Compaq Alpha SMP机器可能会即使有显式的memoryBarrier,向在另一个processor 2执行的代码通知y的值发生变化, process 2却在没有真正同步自己的cache和主内存,就发一个acknowledged给processor 1说我知道了。如是processor 1开始把y值变化和p指针指向这个变化写到主内存里。而这时的processor 2由于只是把要同步cache和主内存的coherence放在队列里,埋头执行流水线上dereference p指针,一看p指针指向的y在自己cache有拷贝,拿来就用了,浑然不知道自己的cache已经和主内存失步了。

出0入296汤圆

 楼主| 发表于 2010-2-26 09:27:32 | 显示全部楼层
瓦卡卡……楼上有代码有讲解,好文

出0入0汤圆

发表于 2010-2-26 09:45:38 | 显示全部楼层
谦虚的学习态度值得学习!

出0入0汤圆

发表于 2010-2-27 06:40:25 | 显示全部楼层
回复【84楼】fightman  
-----------------------------------------------------------------------

最近很忙,没有时间详细看你给的代码。

但是,针对你说的

http://www.cs.umd.edu/~pugh/java/memoryModel/AlphaReordering.html

“现代的的SMP (Symmetric Multiprocessing) CPU 下,会有内存和cache的同步问题”

我正在于 Kourosh Gharachorloo 和 Bill Pugh 讨论中,如果有进一步的消息,我再发上来。

出0入0汤圆

发表于 2010-3-1 07:20:00 | 显示全部楼层
讨论问题不能把所有的问题都混在一起,那样只会让人越来越不明了,最终得出“有问题”的结论。

我改了例子,分析这个问题:

--------------------------------------------
Initially: p = & x, x = 1, y = 0
Thread 1        | Thread 2
y = 2                |  i = *p
memoryBarrier   |
p = & y        |
Can result in: i = 0
--------------------------------------------

如果说 result 是 i=1,i=2 是我们可以接受的,但是 i = 0, 是不能接受的。

造成 i=0 的原因是在现代计算机 尤其是服务器,为了运算优化,设计优化的 memory model。

Alpha 21264 就是这样。

(The Alpha memory model (architectural specification) allows for a ton of important optimizations.)
Kourosh Gharachorloo 的 email 的话。

在这个例子中,Thread 2 里面的 2 read 可能被打乱了。

(Under the Alpha memory model, you need a memory barrier on Thread 2 between
the read of a pointer (p) and reading of the location the pointer points to (dereferencing p).
There are obviously two reads involved when you do this on Thread 2.
In memory models that don't guarantee read ordering on a thread,
you need a special relationship between those two reads to guarantee ordering. Kourosh Gharachorloo)

所以,2 个 read 之间要加 MB 来保证顺序,这样就不会出现 i = 0 的结果。

但是为什么cache没有立即更新呢?

是因为我之前说的:“但是你仔细想想,这并不是一个错,也不是必须的。而是一个需求。需不需数据更新即可见。”

Alpha 认为 MB 既然提供了显示的 cache coherence 处理方式,需不需要用是用户来决定 “数据是不是更新即可见”。

The 21264 flushes its incoming probe queue (i.e., services any pending messages in there) at every MB。

当然了,这种设计方式不够智能,但是却有很好的性能。:)

我认为的智能方式是X86的监视方式,出现一个MB,其他的P都要看看是不是要即可更新自己的cache,但是这样会堵塞其他的P。

综上说诉,这个问题跟 cache 是没有关系的,是 out-of-order 造成的。

所以某些程序必须 reordering,这就是 MB 的作用,但是 Kourosh Gharachorloo 在 Compaq 建议过 非 MB 的解决方法。

严格的说,MB是 hardware 层次的 instruction,添加 MB 是 compilation 应该考虑的问题还是 程序员的问题

就像 i = *p 是 2 个 read 数据关联的, 中间加一个 MB 并不可能是程序员应该去做的。

但是很多高级语言还是提供了 MemoryBarrier 的方式,是为了

"在弱顺序多处理器系统 (例如,使用多个 Intel Itanium 处理器的系统)

上 MemoryBarrier 才是必需的。在大多数情况下,C# 中的 lock 语句、Visual Basic 中的

SyncLock 语句或 Monitor 类提供了更简单的方式来同步数据。"

http://msdn.microsoft.com/zh-cn/library/system.threading.thread.memorybarrier.aspx

你给出的例子,(没时间没仔细看)应该是如何正确使用 lock (C#), synchronized (java)。

养成好习惯,就会写出好的程序。但是硬件的设计问题,不应该在软件的设计中考虑。

出0入0汤圆

发表于 2010-3-3 19:42:43 | 显示全部楼层
为了优化代码,没把p反到MB里面
Initially: p = & x, x = 1, y = 0
Thread 1        | Thread 2
y = 2          |  i = *p
memoryBarrier   |
p = & y        |
Can result in: i = 0  

把p放到MB里面
Initially: p = & x, x = 1, y = 0
Thread 1        | Thread 2
y = 2          |
p = & y        |
memoryBarrier   | i= *p
Can result in: i = 2



其实这个问题,悖谬之处在于我们总是希望优化代码,把本应该可以放在MemoryBarrier的保护罩下xy变量的一个外引用指针p移出了保护罩。因为P不再在MemoryBarrier下,CPU可以优化p所联系的代码(乱序执行),不等xy的变化所请求的cache同步操作,就执行解引用p的操作。这也不能怪cpu设计错误,P被移出线程安全的MemoryBarrier体系之外的,我们为什么强求p的dereference一定要等我们的xy的密室交易完才开始执行呢?(以下我们把MB保护罩下所执行的代码段换个通俗的名字,叫密室交易)  
就算是x86的体系比较高明,有比其他RISC体系更强的智能监视方式。(“我认为的智能方式是X86的监视方式,出现一个MB,其他的P都要看看是不是要即可更新自己的cache,但是这样会堵塞其他的P。综上说诉,这个问题跟 cache 是没有关系的,是 out-of-order 造成的。” ), 智能到察觉到p一定要等我们的xy的密室交易完才开始执行。 那同样一段代码,因为我们写的时候,把p这个外引用指针放在C# lock {}, Java synchronized (obj) {}之外
或C++  asm ("memoryBarrier")之前,谁保证编译器一定会保证会把把执行p dereference的执行代码刻意在我们的线程安全的密室交易代码之后呢。p在安全体系之外,编译器就不能可以调整执行顺序,打乱执行代码顺序把p dereference安排在密室交易的进行当中执行? 编译器优化了代码,把p的dereference插到xy密室交易当中执行,x86大神不会也能察觉到p这时不能deference吧。这是p = & y ,而y=2密室交易还没有执行,p dereference得到0也没有out of order啊。 优化代码,乱序执行非相关代码,是编译器为优化并行计算所做的孜孜不倦地努力啊!

看看 http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html里面写的
跟底层机器码很接近的c/c++代码,它对这个Lazy initialization经典模式的Double Check Locking的实现代码。
template <class TYPE, class LOCK> TYPE *
Singleton<TYPE, LOCK>::instance (void) {
    // First check
    TYPE* tmp = instance_;
    // Insert the CPU-specific memory barrier instruction
    // to synchronize the cache lines on multi-processor.
    asm ("memoryBarrier");
    if (tmp == 0) {
        // Ensure serialization (guard
        // constructor acquires lock_).
        Guard<LOCK> guard (lock_);
        // Double check.
        tmp = instance_;
        if (tmp == 0) {
                tmp = new TYPE;
                // Insert the CPU-specific memory barrier instruction
                // to synchronize the cache lines on multi-processor.
                asm ("memoryBarrier");
                instance_ = tmp;
        }
    return tmp;
tmp这个外引用指针,在MB密室交易体系外,用了严格的双层检查体系,严格地第二层确定tmp引用的对象为空时,给tmp初始化了一个单例实体。而且在做第二层的初始化前,严格地向操作系统申请了一个线程互斥锁,保证别的线程那不到本身class的实例_instance, 然后tmp = _instance. 即使这个时候,切换到别的线程,应为线程互斥锁拿不到这个_instance, 也reference不成这时的tmp。    接着再看第二层执行tmp = new TYPE;  跟着就是调asm ("memoryBarrier"); 同步各个processor的cache和主内存,大声宣布MB密室交易结束了。
难道上面写的注释
//Insert the CPU-specific memory barrier instruction
    to synchronize the cache lines on multi-processor.
asm ("memoryBarrier");

还不明白解释了
现代的的SMP (Symmetric Multiprocessing) CPU 在多线程下,会有内存和cache的同步问题。(我前面可能忘了写在多线程下会有同步问题上下文了)

出0入0汤圆

发表于 2010-3-3 23:19:07 | 显示全部楼层
膜拜高手

出0入0汤圆

发表于 2010-3-4 01:21:52 | 显示全部楼层
准备写篇blog,再看了看zzsoft给的http://msdn.microsoft.com/library/system.threading.thread.memorybarrier.aspx这个链接。觉得M$给的那段中文翻译也没有给个好的上下文例子。
用这个Lazy Initialization例子就完美了
Class Foo
{
  Private Resource res = null;
  Public Resource getResource()
  {
   If (res == null)
   {
       // call CLI system MemoryBarrier()
       //可惜M$没给J#实现这个MB函数
       xxx.MemoryBarrier()

       synchronized(this)
    {
     if(res == null)
     {
      res = new Resource();
     }
    }
       //MB作用域结束,同步刚刚MB密室内实例化的Resouce的引用res和MB密室外部的指向空值的引用res
   }
      //指向Resouce的res可以被别的线程访问得到了
   return res;
  }
}

参阅了下原来的英文版链接http://msdn.microsoft.com/en-us/library/system.threading.thread.memorybarrier.aspx和我对Alhpha问题的一些讨论。

那么msdn上thread memorybarrier中文翻译这样比较妥当:按如下方式同步内存访问,执行当前线程的处理器在对指令不能乱序执行,不能把MemoryBarrier 调用之前的内存访问,乱序安排到 MemoryBarrier 调用之后的内存访问的之后执行。

还有备注里面的中文翻译加上段解释也比较贴切

只有在弱顺序(喜欢优化指令乱序执行的)多处理器系统(例如,使用多个 Intel Itanium 处理器的系统)上 MemoryBarrier 才是必需的。

后话,在跑hpux的itanium 2 cpu 4core的小型机上,
跑我79楼的例程,放2个线程去race to construct 500个单例体,结果就报错跑趴了。有条件的可以试验下在Itanium, Sparc, PowerPC上跑下看看最多能跑几个线程构建几个单例体不出错。 为什么RISC体系的CPU跑几个线程建千个以下单例体就跑趴了。

出0入0汤圆

发表于 2010-3-4 01:42:47 | 显示全部楼层
----- 代码0 -----
Initially: p = & x, x = 1, y = 0
Thread 1        | Thread 2
y = 2          |  i = *p
memoryBarrier   |
p = & y        |
Can result in: i = 0   

----- 代码1 -----
y = 2      
memoryBarrier
p = & y
-----------------

这里MB保证 y = 2 先于 p = & y 执行。

所以你提供的代码2,MB 没有任何意义。

----- 代码2 -----
y = 2
p = & y  
memoryBarrier
-----------------

在乱序的CPU中,代码2以下面的顺序执行,

p = & y
y = 2

明显与程序语义不同,会产生脏数据。
  
所以 Thread 1 必须用 MB 保证顺序。

我们再看 Thread 2,

就算因为“失效”的信息还在队列中没有被处理 而导致 cache 没有即时的与内存同步。

这其实不是问题,是对cache coherence延时的“容忍”。

我用代码3解释:

----- 代码3 -----
Initially: i = 1, y = 0  
Thread 1        | Thread 2  
y = 2          |  i = y  
memoryBarrier   |
Can result in: i = 0   
-----------------

当进行多线程的时候,如果 Thread 1 和 Thread 2 之间没有任何强制性的时间关系,(不要求更新即可见)。

即使 P1 运行了 y = 1 并通过 MB 通知 P2 更新 cache, 但 P2 的 cache 与 内存不同步时也没有关系,

这时 i = 0 是(语义)有效结果,不是脏数据。

所以内存和cache的同步问题不成立。

但是在代码0中,为什么 i = 0 是脏数据呢?

是因为乱序所引起的语义变化, 如果

i = *p 不被打破顺序,

read of a pointer (p)
dereferencing p

就不会出现 i = 0 的情况。

脏数据根本的原因就是乱序造成的。

MB 用途:1) 为什么 MB 要实现更新 cache (副产品)?

//Insert the CPU-specific memory barrier instruction
    to synchronize the cache lines on multi-processor.
asm ("memoryBarrier");

其实用 MB 不仅更新 cache,还要挂起 cpu, 同步流水线内的寄存器,用来保证没有脏数据。

最后说明一下:

“优化代码,乱序执行非相关代码,是编译器为优化并行计算所做的孜孜不倦地努力啊!”

没错,但是如果程序有上下文相关的话,必须保证语义不被打破,这时优化的前提。

而且,切勿用软件的思维去考虑硬件问题,硬件中的很多“容忍”本身不是问题,而是一种优化设计。

出0入0汤圆

发表于 2010-3-4 13:51:56 | 显示全部楼层
我给得代码2有个地方遗漏,忘记指定这个密室交易块的范围,
代码2
   把p放到MB里面
Initially: p = & x, x = 1, y = 0  
Thread 1        | Thread 2
memoryBarrier   |
lock<?>
y = 2          |   
p = & y
memoryBarrier   | i= *p  
Can result in: i = 2

其实这个你还得回头看看C++那段代码,MB块里面还得申请个线程互斥锁,保证外引用的p在Thread1做完密室交易再刷新cache前不能解引用dereference p。其实在密室交易进行中,操作系统从thread 1切到thread 2,由于有个线程互斥锁锁着,你Thread 2也没有办法去dereference p.

那么在thread 1里面密室操作x,y。我们的代码光有互斥锁,没有MB;或光有MB,没有互斥锁.解引用dereference p都有可能出错。 前者可能拿到i=0, 后者可能拿到i=1. 想引用完x再引用y, 你不能说我x,y本体在密室交易,工作内存没有跟主内存和在另一个cpu跑的另一个thread的模块或类显示同步,前者情况下你就近在cache里拿个原来的值0, 后者情况下访y是给你个x的值(谁叫你先引用x)。拿得不对还得说你懂不懂,这叫cache coherence冗余度?
  

出0入0汤圆

发表于 2010-3-4 19:50:47 | 显示全部楼层
回复【93楼】fightman  

----------------------------------------------------------------------------

----- 代码0 -----
Initially: p = & x, x = 1, y = 0
Thread 1        | Thread 2
y = 2          |  i = *p
memoryBarrier   |
p = & y        |
Can result in: i = 0   

----- 代码3 -----
Initially: i = 1, y = 0  
Thread 1        | Thread 2  
y = 2          |  i = y  
Can result in: i = 0, i = 1, i = 2   
-----------------

----- 代码4 -----
Initially: p = & x, x = 1, y = 0  
Thread 1        | Thread 2
memoryBarrier   |
lock<?>
y = 2          |   
p = & y
memoryBarrier   | i= *p  
Can result in: i = 2
-----------------

我逐句分析你的话, 让你明白你思维的错误。

“其实这个你还得回头看看C++那段代码,MB块里面还得申请个线程互斥锁,保证外引用的p在Thread1 做完密室交易再刷新cache前不能解引用dereference p。”

你给出的C++代码是出自 http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

我们讨论的代码出自 http://www.cs.umd.edu/~pugh/java/memoryModel/AlphaReordering.html

你始终没有理解什么是 memoryBarrier,所以你才会 代码4 这样的 MB 主功能无用代码, 这里的两个 MB 没有起到 reordering 的作用。

如果在 乱序的 SMP 中, y=2 和 p = & y 还是会被打乱 产生违反语义的情况。

“其实在密室交易进行中,操作系统从thread 1切到thread 2,由于有个线程互斥锁锁着,你Thread 2也没有办法去dereference p.”

在 SMP 中 没有切换的概念: Thread 1 和 Thread 2 在 P1 和 P2 中 各自 欢快的运行着。

有个线程互斥锁锁着,是为了同步 Thread 1 和 Thread 2 。

而 代码0 中 是 Thread 1 和 Thread 2 异步 的, 这里不需要 lock 。

“那么在thread 1里面密室操作x,y。我们的代码光有互斥锁,没有MB;或光有MB,没有互斥锁.解引用dereference p都有可能出错。 前者可能拿到i=0, 后者可能拿到i=1.”

Thread 1 必须像 代码0 一样 用 MB 保护 y=2 和 p = & y 的顺序。

如果用你的代码4,有 lock,MB, 解引用dereference p都有可能出错。

“想引用完x再引用y, 你不能说我x,y本体在密室交易,工作内存没有跟主内存和在另一个cpu 跑的另一个thread的模块或类显示同步,前者情况下你就近在cache里拿个原来的值0,
后者情况下访y是给你个x的值(谁叫你先引用x)。拿得不对还得说你懂不懂,这叫cache coherence冗余度?”

你的中文很乱:

错误一 : “工作内存” 这个话不准确,你的意思是当前 P 的 cache。

错误二 : “当前 P 的 cache没有跟主内存和在另一个cpu 跑的另一个thread的模块或类显示同步,前者情况下你就近在cache里拿个原来的值0,后者情况下访y是给你个x的值(谁叫你先引用x)。”

如果没有同步, 为什么 取 p 地址是指向 y ?????? 而不是 x ??????, 显然已经同步了。但是 p 同步了有用吗?

你终于明白了,因为乱序,造成了 "dereference p" 先于 "取 p 地址" 进流水线,

造成 "dereference p" 使用 p 旧值, 而 "取 p 地址" 用了 p 的新值了,

这样就造成了 p 指向 y, 而 i 确实 x 值, 这是也是脏数据。

所以 如果 取 p 地址 MB 再 dereference p, 就不会有错误发生了。

而 MB 的显示更新 p 和 流水线的 p 值,也保证了 y 被更新。

其实 y 的同步也是一样的, 如果 y 的同步发生在 "dereference p" 之后, "取 p 地址" 之前,

y 的同步也被没用了。

所以没有乱序,就不会把 cache coherence 延时 展现出来,也就用不到 MB 的主功能。

这就是为什么 代码 3 中,没有 MB ,没有 lock 依然不错的原因。

-----------------------------------------------------------------------

不要想当然的产生实时同步的想法。

要记得,CPU (SMP) 要不要容忍 cache coherence 时延是一种设计。

(“冗余度” 用在这里是错误的,“冗余度”是容错里的指标量。这里不是容错)

如果不容忍 cache coherence 延时,就必须像 X86 一样, 当遇到一个 MB 时,每个 CPU 挂起,

去监视是否更新 自己的 cache,但是对于多核(2 - 8),极多核(9 - 100)这样效能太低。

再次谨记 以 代码0 为例 :

1)MB 是 乱序 的 SMP 的一种 reordering 的方式。

2)MB 是 底层 instruction : 保证 y= 2 先于 p = &y

3)MB 用法是 放到两个要保持循序的 代码中间,不是两边。

4)而 lock 是高级 函数模块 是用来同步线程的,就是实现 “更新即可见”: 与 MB 用意都不同,两者没有必然联系。这里是异步线程,没有必要 lock.

以 代码3 为例 :

5) 在 代码 3 里 没有互斥锁,没有 MB (因为没有乱序),结果从语义上讲  i = 0, i = 1 ,i = 2 都是正确的.

建议你仔细认真的看看 http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

和 http://www.cs.umd.edu/~pugh/java/memoryModel/AlphaReordering.html

出0入0汤圆

发表于 2010-3-5 08:48:40 | 显示全部楼层
哈哈。学习了。

出0入0汤圆

发表于 2010-3-5 11:02:51 | 显示全部楼层
感谢傻孩子的精神,每次看到一个原创帖总是会有好多口水,但请指出帖中的问题以及改进的思路,而不是进行人身方面的攻击,对于高手们来说或许这个帖很“垃圾”,很原始,没什么含量,那么请点击你的鼠标让它关闭,对于我这个小学生来说仍然还是可以学到不少东西的,我依然觉得它很有价值,至于命名方面的问题我想这只是个人风格的问题,不必长篇大论

出0入296汤圆

 楼主| 发表于 2010-3-8 00:12:16 | 显示全部楼层
近期将公布一个 HexFileStream类,用于对HEX格式的文件进行存取。
该类直接从FileStream继承。 届时将贴出dll和源代码,还请大家多多提出改进意见。

出0入0汤圆

发表于 2010-3-8 01:40:59 | 显示全部楼层
先顶一下傻孩子新的code发布。再开始以下码楼。

我逐句分析你的话, 让你明白你思维的错误。
********************
开头就高屋建瓯,占据这么高的地方进攻。看不下去了,赶个周末晚场也想发表下自己的浅见。


“其实这个你还得回头看看C++那段代码,MB块里面还得申请个线程互斥锁,保证外引用的p在Thread1 做完密室交易再刷新cache前不能解引用dereference p。”
你给出的C++代码是出自 http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
我们讨论的代码出自 http://www.cs.umd.edu/~pugh/java/memoryModel/AlphaReordering.html  
你始终没有理解什么是 memoryBarrier,所以你才会 代码4 这样的 MB 主功能无用代码, 这里的两个 MB 没有起到 reordering 的作用。

********************************
MB是什么,wiki上有解释
http://en.wikipedia.org/wiki/Memory_barrier,wiki的解释可以做名词时指一个内存藩篱,或内存边界,也可以做动词就是指一组代码去完成对原始基础物件或锁无关的数据结构在多处理器系统和跟硬件相关的一些设备的同步。

请参看上文给的第一个链接的文章. java的每一个sychronized {}块肯定被JVM整出一个mb出来.只是我们不知道边界在哪个地方。看似我们可以简单的把块结束的}之处看做mb的显式切分点,但是诡异的地方是上文给的第一个链接的文章的这些大牛们作者都
说JAVA synchronized块没有显式(Explicit)的MB.不信大家从头读到尾,从尾读到首.除了敬畏地重温下各位大牛的名号外,真的找不到.细心的同学可能解读到显式(Explicit)这个关键:这个synchronized块还是有个MB的边界,只是不是Explicit的. 边界在哪,请各位往下看。

如果在 乱序的 SMP 中, y=2 和 p = & y 还是会被打乱 产生违反语义的情况。

*****************************
当朝毛太祖说过"大乱才能大治". 伟人的眼光和心境真是异于常人. 发明Java的James Gosling也很神,sychronized{}这个块被描叙成一个蟑螂汽车旅馆(Roach Motel),块外围的语句可以(指令被优化)移进来,块内的语句却不可以(指令优化)移出去。(出处http://www.cs.umd.edu/~pugh/java/memoryModel/BidirectionalMemoryBarrier.html)。估计这个旅店可能就像我们中国的黑旅店,里面蟑螂老鼠出没,进去的没住够时间的不准你出来。zzsoft同学看样子很细心,看到Gorgon Meducer傻孩子把代码往C# lock(相似于Java Sychronized{})里送,连忙提醒要下心,这是家黑店,别把自己的孩子往里送。


引入正题,JVM和Compiler随时可以把synchronized{}块外前后的代码优化进synchronized{}块里面,那哪里才是synchronized{}块的MB边界?答案其实要表叙出来还得分两种情况分析。
在有基于方法体(函数)上的
   Integer x, y;
  Public synchronized Integer getP()
  {
       y=0;
       Integer p=y;
       return p;
  }

没什么悬念,p被整成synchronized 块内的变量,多线程下任何线程调用getP()取回返回值那个地方就是MB的边界。借着函数体,划了个MB的楚河汉界。注意这个调用这个方法是阻塞型同步(blocking synchronization)
  

再看第二种情况,基于声明块的

public class Foo2 {
    private IntWrapper x=1, y=0;
    private IntWrapper p=x;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

public IntWrapper getLazyInitP() {
  if (p ==x)  {
                 synchronized(p) {
                         y.setIntValue(1);
                         p=y
                  }
       }
       Return p;
}
}

Thread1:  xxx=getLazyInitP();
Thread2:  i=getLazyInitP();

这段代码就是alpha的问题起源的一个场景

如果Java Compiler发飚把代码乱序优化成这种情况。

  synchronized(p) {
            p=y;
            y.setIntValue(2);
  }
Thread 1 一完成p=y, p本来是x的引用,变成y的引用。因为这是的synchronized(p) {}
这个基于p的声明块,因为p已经完成引用赋值,进入块是获取(acquire)到p这个object的隐含锁,就会被释放出来。这时,已经改成对引用y的p,对thread2来说,不等y 的int值被改赋值成2这部分代码完成。对这种情况,MB的边界就是p=y这个地方。运行完这段p改赋为y的引用后,获取(acquire)到p的隐含锁就会被释放, 同实调用这个函数时的Thread2, 可以在这时获取(acquire)到p的隐含锁,原后执行 i=getLazyInitP()而可能拿到i.intValue = 0.





再看下这段代码
Class Foo {
….
public IntWrapper getLazyInitP() {
    ….
  synchronized (this) {
              y.setIntValue(2);
              p=y;
        }
    }

}
注意这是改成对this的声明块了,这时如何Java Compiler老老实实的不对代码进行优化。
那么执行到y.setIntValue(2),往y里面塞个新的值2, 因为这时是获取整个Foo类的实例体的隐含锁,y.setIntValue(2)这句话改变了Foo 的类成员的值,所以这时也会有个MB(动词),要求别的线程同步他的cache或工作内存的y的值。注意这个别的线程一定指Thread2也可能是3啊4的。也为它们可能Foo别的方法,只要访问y啊。还有就是这个MB还有个保序的作用,要求他后面的代码,不要没等他完成就开始执行。
但是这时也调用getLazyInitP()的另一个线程得等,因为他拿不到this的隐含锁。非得等到前一个线程运行完y=p. 至于你觉不觉得运行完y=p会不会有个MB,可以自己思考。
y=p之后,所有访问的Foo成员都完成了访问,释放this隐含锁,这时到达MB的边界。



附注:
java中每个object都有跟自己关联的一个隐含锁,
请看一下sun的原文http://java.sun.com/docs/books/tutorial/essential/concurrency/locksync.htm
一个线程要想独占性访问一个object里面的成员的话,都必须得到这个object的隐含锁,访问完后还得释放这个锁。

在 SMP 中 没有切换的概念: Thread 1 和 Thread 2 在 P1 和 P2 中 各自 欢快的运行着。
有个线程互斥锁锁着,是为了同步 Thread 1 和 Thread 2 。
而 代码0 中 是 Thread 1 和 Thread 2 异步 的, 这里不需要 lock 。
*********************
弱弱地问一句:
 P1中的Thread1就不能被OS挂起,而转去执行Thread3啊4的指令.当然这时P2里面的thread 2可能正欢快的运行着。既然是多线程,就就要这种thread1被人家3踢掉, thread2正粉抹登场的场景考虑到吧。除非你是用什么non preemptive operation system.



要记得,CPU (SMP) 要不要容忍 cache coherence 时延是一种设计。
(“冗余度” 用在这里是错误的,“冗余度”是容错里的指标量。这里不是容错)
如果不容忍 cache coherence 延时,就必须像 X86 一样, 当遇到一个 MB 时,每个 CPU 挂起,  
去监视是否更新 自己的 cache,但是对于多核(2 - 8),极多核(9 - 100)这样效能太低。
再次谨记 以 代码0 为例 :
1)MB 是 乱序 的 SMP 的一种 reordering 的方式。
2)MB 是 底层 instruction : 保证 y= 2 先于 p = &y
3)MB 用法是 放到两个要保持循序的 代码中间,不是两边。  
4)而 lock 是高级 函数模块 是用来同步线程的,就是实现 “更新即可见”: 与 MB 用意都不同,两者没有必然联系。这里是异步线程,没有必要 lock.
以 代码3 为例 :
5) 在 代码 3 里 没有互斥锁,没有 MB (因为没有乱序),结果从语义上讲  i = 0, i = 1 ,i = 2 都是正确的.  

*******************
感觉synchronised等高级 函数模块没有MB。要想MB怎么办,只能搞搞c/cc++或M$ .NET或等x86 的高级智能判断机制了。感觉高级函数模块模块是左手,MB 是 底层 instruction是右手。左手碰不了右手,不能上下其手.我等做程序还不能觉得左手右手冗余,得学会容忍。

出0入0汤圆

发表于 2010-3-8 01:49:49 | 显示全部楼层
忘了帖上文我给的代码用到到的自己封装的类。

就是简单的把一个int值包成一个object里面。java里面本有个Integer类可以用,但是它是immutable的,不能初始化后改里面的int值。

public class IntWrapper {
    private int  intValue;
   
    public IntWrapper() {
            super();
            intValue = 0;
    }

        public IntWrapper(int intValue) {
                super();
                this.intValue = intValue;
        }

        public int getIntValue() {
                return intValue;
        }

        public void setIntValue(int intValue) {
                this.intValue = intValue;
        }
       
        public int intValue() {
                return intValue;
        }
       
        public String toString() {
                return String.valueOf(intValue);
        }

        @Override
        public int hashCode() {
                final int prime = 31;
                int result = 1;
                result = prime * result + intValue;
                return result;
        }

        @Override
        public boolean equals(Object obj) {
                if (this == obj)
                        return true;
                if (obj == null)
                        return false;
                if (getClass() != obj.getClass())
                        return false;
                IntWrapper other = (IntWrapper) obj;
                if (intValue != other.intValue)
                        return false;
                return true;
        }
   
}

出0入0汤圆

发表于 2010-3-8 03:13:47 | 显示全部楼层
MARK
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-29 06:47

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表