OCX开发手记

来源:互联网 发布:怎么变成大小为1矩阵 编辑:程序博客网 时间:2024/05/16 07:04

1.  OCX

开始干活。首先得做出一个可以在网页中看得到的OCX

1.1     准备工作

打开BCB6,看着那熟悉的LOGO,轻轻抚摸它一下,乖,今天争点气哈。

 

以下的步骤,轻车熟路,驾轻就熟,嘿嘿,没什么好说的。

第一步,当然是新建一个ActiveForm

 

再下来,改ActiveX名字为CbwScreen,选中Make Control LicensedInclude Version Information选项,About Box没啥用,也就不选Include About Box选项了。

 

 

OK之后,全部保存,改一堆文件名以及工程名,改成下面这个效果就好了。

 

1.2     工程设置

下一步,设置工程的属性,为打个包准备。也就是在Packages标签页面下不选中Build with runtime packages,在Linker标签下不选中Use dynamic RTL选项,这样编译出来的EXEDLL才可以单独运行,而不需要这个那个DLL

 

为使编译结果与源代码文件不混杂在一起,设置一下各个输出目录。

 

现在,编译,运行,对前者,BCB当然无条件支持的了,但一运行,它不干了

 

需要设置一下才能直接运行。

1.3     发布相关

当然,这还需要做一点铺垫,因为OCX要嵌入到网页中,所以得先有个网页。

这个网页需要自己设计吗?先还是免了吧,BCB能为我们做这件事。

选择菜单项ProjectàWeb Deployment Options,按如下设置

 

其中,10.210.9.2是我本机的IP地址。注意URL地址设置为http://10.210.9.2/DoubleScreen/,其中有个虚拟目录DoubleScreen,稍后设置。

然后Web Deploy进行发布,到目标目录下一看,嘿,一堆文件

 

为了刚才的URLhttp://10.210.9.2/DoubleScreen/起作用,需要设置一下虚拟目录DoubleScreen,在目录DoubleScreen上右键点击,选择“共享和安全”,在Web共享页面中共添加共享。

 

当然,这点需要有IIS的支持,在此不对其多加说明。

现在进行IIS信息服务窗口中,可以看到DoubleScreen虚拟目录

 

右键选择其属性,设置其身份验证方法为匿名访问。

 

 

 

 

 

现在到资源管理器中,双击打开CbwScreen.htm,网页出来了哈

 

一切顺利,点击提示项,选择“允许阻止的内容”,IE没办法了,只好再提示一下

 

还有什么说的,确定Y,这下该出来了吧。

 

这是有点傻眼了。哪里做得不对呢。网上有一大堆的建议。花了大约15分钟,才想起来可以参考一下我原来做的OCX,一看,哦,需要屏蔽一下LICENSE,在CbwScreenImpl1.h中,屏蔽以下代码

/*

//$$---- activex control license support (stActiveXControlLicensing)

 // Licensing support

 //

 typedef TLicenseString<TCbwScreenImpl> TLicenseClassImpl;

 DECLARE_CLASSFACTORY2(TLicenseClassImpl)

 

 // Add logic to determine whether this Control is properly licensed on this machine

 // in the following method..

 //

 static const WCHAR* GetLicenseString()

 {

   return L"{9DEB874C-56CF-4A4E-B247-CEB9152488DC}";

 }

 

 static const TCHAR* GetLicenseFileName()

 {

   return _T("DoubleScreen.lic");

 }

 

 static BOOL IsLicenseValid()

 {

   // By default we validate the license by verifying that the

   // license string GUID is in the .LIC file generated by the Wizard.

   //

   // You may replace the logic of this routine to implement another

   // method to verify that your control is properly licensed.

   //

   return TValidateLicense::IsGUIDInFile(GetLicenseString(), GetLicenseFileName());

 }

*/

再次发布,打开CbwScreen.htmOK

 

1.4     提高效率

哦,每次都要发布再打开文件看,多麻烦,设置一下吧,提高效率。

RunàParameters调出对话框中,设置如下

 

现在在窗口中加一个按钮,直接按F9,嘿嘿,又出来了。

 

这下,工作应该完成1/3了,OCX已经能显示了。哦,等等,还需要有个什么安全性的处理,呃,参考以前OCX的代码,哦,IObjectSafety接口,加入几句代码就OK了。也就是CbwScreenImpl1.h中要修改成以下模样,怎么修改呢,自己琢磨哈。

class ATL_NO_VTABLE TCbwScreenImpl:

 VCLCONTROL_IMPL(TCbwScreenImpl, CbwScreen, TCbwScreen, ICbwScreen, DIID_ICbwScreenEvents)

 ,public IObjectSafetyImpl<TCbwScreenImpl, INTERFACESAFE_FOR_UNTRUSTED_CALLER>

。。。。。。。。。

BEGIN_CATEGORY_MAP(TCbwScreenImpl)

  IMPLEMENTED_CATEGORY(CATID_SafeForScripting)

  IMPLEMENTED_CATEGORY(CATID_SafeForInitializing)

END_CATEGORY_MAP()  

BEGIN_COM_MAP(TCbwScreenImpl)

 VCL_CONTROL_COM_INTERFACE_ENTRIES(ICbwScreen)  

 COM_INTERFACE_ENTRY(IObjectSafety)

END_COM_MAP()

时间嘀嗒嘀嗒不停转,一抬头,已经8:30了。过去了半个小时,唉,要是不上网转那10多分钟就好了。起来转会吧。

2.  拷屏与显示

2.1     拷屏

拷屏,也就是将屏幕内容拷贝到一个MemoryStream中,这个,呃,还是上网搜一下吧,确实一大堆代码。

整理一下,核心代码如下:

if(HDC hdcScreen = GetWindowDC(GetDesktopWindow()))

{

     int w = Screen->Monitors[0]->Width;

     int h = Screen->Monitors[0]->Height;

     BitBlt(MoniterBitmap->Canvas->Handle, 0, 0, w, h, hdcScreen, 0, 0, SRCCOPY);

     ReleaseDC(GetDesktopWindow(), hdcScreen);

     BmpStream->Clear();

     MoniterBitmap->SaveToStream(BmpStream);

}

呵呵,上面代码要运行,需要声明一些辅助变量,主要是MoniterBitmapBmpStream,这点工作应该能搞定吧。

2.2     检验

效果如何呢?需要检验一下,用什么来检验呢?图形化效果那是最好了,干脆,自己先显示一下,看看对不对,放置一个Image1alClient,拉伸效果,然后代码为

if(HDC hdcScreen = GetWindowDC(GetDesktopWindow()))

{

     int w = Screen->Monitors[0]->Width;

     int h = Screen->Monitors[0]->Height;

     BitBlt(MoniterBitmap->Canvas->Handle, 0, 0, w, h, hdcScreen, 0, 0, SRCCOPY);

     ReleaseDC(GetDesktopWindow(), hdcScreen);

     BmpStream->Clear();

     MoniterBitmap->SaveToStream(BmpStream);

        Image1->Picture->Bitmap->LoadFromStream(BmpStream);  

}

运行后,居然没变化!!什么问题,调试看看BmpStreamSize,啊?居然每次的值不一样!!

想来想去,不得其解。

难道拷屏方法有误?翻来覆去,辗转反侧,没发现什么问题。

查看一下TMemoryStream的帮助,随手点开其属性,简单至极

Derived from TCustomMemoryStream

Memory

Derived from TStream

Position

Size

猛然想起,其Position是位置,LoadFromStream是从当前位置开始读的,是不是需要手动从头开始呢。先不管了,强制置其位置为0试试:

MoniterBitmap->SaveToStream(BmpStream);

BmpStream->Position = 0;

Image1->Picture->Bitmap->LoadFromStream(BmpStream);     

F9运行一下,啊哈,期望结果出来了

 

 

 

 

 

 

 

 

2.3     OCX大小调整

好象有点美中不足哈,窗口太小了,这点可在网页中修改,现在的网页源文本内容为:

<HTML>

<H1> C++Builder 6 ActiveX Test Page </H1><p>

You should see your C++Builder 6 forms or controls embedded in the form below.

<HR><center><P>

<OBJECT

        classid="clsid:0680890D-4D0A-45FB-BF44-3AF41DC04165"

        codebase="http://10.210.9.2/DoubleScreen/CbwScreen.inf"

        width=350

        height=250

        align=center

        hspace=0

        vspace=0

</OBJECT>

</HTML>

widthheight改成100%OK了,即

<HTML>

<H1> C++Builder 6 ActiveX Test Page </H1><p>

You should see your C++Builder 6 forms or controls embedded in the form below.

<HR><center><P>

<OBJECT

        classid="clsid:0680890D-4D0A-45FB-BF44-3AF41DC04165"

        codebase="http://10.210.9.2/DoubleScreen/CbwScreen.inf"

        width=100%

        height=100%

        align=center

        hspace=0

        vspace=0

</OBJECT>

</HTML>

再次运行,这下满意了

 

3.  网络传输

对于网络传输的需求是早就想好了的,直接用INDY

3.1     基本元件

在员工机端,放置一个TIdTCPClient

object IdTCPClient: TIdTCPClient

 Host = '10.210.9.1'

 Port = 6001

 Left = 424

 Top = 232

end

再用一个TIdAntiFreeze增加其健壮性

object IdAntiFreeze1: TIdAntiFreeze

 Left = 464

 Top = 232

end

而在客户端,也就是目标计算机上,放置一个TIdTCPServer

object IdTCPServer: TIdTCPServer

 Active = True

 Bindings = <10.210.9.2:6001>

 DefaultPort = 6001

 OnExecute = IdTCPServerExecute

 Left = 40

 Top = 152

end

为了在同一个OCX中支持发送与接收端,因此可加入两个基本FORM,分别为处理员工端与客户端。

先固定端口为6001吧,其修改留待后续开放属性时再处理

同样,目标计算机的IP地址即Host也先固定为10.210.9.1,开放属性后由网页的JS进行修改为宜。

3.2     通信

在实现通信之前,我想是否需要先实现一个文本方式通信呢?这时,头脑里出现正反双方两个声音:

“需要吗?”

“不需要吗?”

“需要吗?”

“不需要吗?”

“需要吗?”

“不需要吗?”

“呃,研究研究嘛,何必那么认真呢?需要吗?”

嗨,哪跟哪呀。就俺目前这水平,还需要这点小玩意来验证吗?不做也该知道做出来的结果是什么样子的了。直接上图片,不然不足以显示出水平,上!

自己YY得热血沸腾,顺手就写了个发送端:

     AnsiString errorPrompt = "";

     try

     {  // 各种容错处理

        IdTCPClient->Connect();

        try

        {

           int length = BmpStream->Size;

 

           AnsiString protocol = bmpProtocol;     // 为避免错误,再加一层握手协议

           IdTCPClient->WriteLn(protocol);       // 以流的方式进行发送,将减小出错的概率

           IdTCPClient->WriteInteger(length);

           IdTCPClient->WriteStream(BmpStream, true, false);

           IdTCPClient->Disconnect();

        }

        catch(Exception& ex)

        {

           IdTCPClient->Disconnect();

           if(!SameText(ex.Message, "Connection Closed Gracefully."))

           {

              errorPrompt.printf("通信出错 ==>向目的服务器 '%s'发送数据失败!/n/n网络信息:/n", cPromptHost.c_str());

              errorPrompt += ex.Message;

              throw Exception(errorPrompt);

           }

        }

     }

     catch(Exception& ex)

     {

        errorPrompt.printf("网络连接出错,无法发送数据,请检查!/

           /r/n/r/n%s/

           /r/n/r/n可能出现的情况:/

           /r/n/r/n/t1. %s的地址或端口设置出错;/

           /r/n/r/n/t2. %s程序没有运行;/

           /r/n/r/n/t3. 网络不通 /

           /n/n网络信息:/n",

           (cPromptHost.Length() ?

              (AnsiString("目的服务器名: ") + cPromptHost).c_str() :

              (AnsiString("目的主机地址: ") + IdTCPClient->Host).c_str()),

           (cPromptHost.Length() ?

              cPromptHost.c_str() : "目的主机"),

           (cPromptHost.Length() ?

              cPromptHost.c_str() : "服务器端")

           );

        errorPrompt += ex.Message;

        throw Exception(errorPrompt);

     }

手随心动,看着手指头在键盘上飞舞,编程和打字一样惬意,心里那个爽啊。一边敲一边想,幸亏咱经验多,还知道try…catch来捕捉异常哈。又想,这个小项目,收人家的费用是不是有点多了呢。猛地,一首歌调浮现了出来:您看这道菜,群英荟萃,收您老八十一点都不多。呵呵,摇头兼晃脑,正想着间,代码写完了,按个Ctrl+F9编译一下,看看有没有手误敲错的。糊了。

这不,打铁要趁热,俺手一抖,服务端代码就跃然于屏幕之上了。

void __fastcall TCbwScreen::IdTCPServerExecute(

     TIdPeerThread *AThread)

{

  AnsiString protocol = AThread->Connection->ReadLn(EOL);

  bool bmpFlag = SameText(protocol, bmpProtocol);

  if(!bmpFlag)   return;

     // 以流的方式读取

  int number = AThread->Connection->ReadInteger();

  AThread->Connection->ReadStream(MemoryStream, number, false);

  if(MemoryStream->Size == 0)

     return;

 

     // 获取通信内容

  try

  {

     EnterCriticalSection(&g_CriticalSection); // 串行处理

     MemoryStream->Position = 0;

     Image1->Picture->Bitmap->LoadFromStream(MemoryStream);   

  }

  __finally

  {

     LeaveCriticalSection(&g_CriticalSection); // 可以处理下一个请求了

  }

我的思绪飞呀飞,歌曲调调已经是笑脸中的有情人千里共婵娟了。顺便把CriticalSection处理完毕。

运行,OK,啊啊,太不把人当腕了。

4.  添加OCX属性

剩下的事情是不是该扫尾了,发布一下OCX属性,供网页中的JS调用以方便设置,要不然,部署到不同的计算机上还要改一下源代码,可羞死人了。

 

BCB中,添加OCX属性是如此的简单,当然首先需要知道要添加什么属性,想干什么?这个很简单,一个属性Client,表示是否为客户机,一个属性为Host,表示客户机的IP地址,一个属性为Monitor,表示启动/停止发送桌面。

呃,你说,上面各属性的类型都是些什么呢,用脚趾头想,使劲想。

添加的结果如上图所示。

其实现代码没啥好说的,直接看吧。

STDMETHODIMP TCbwScreenImpl::set_Monitor(VARIANT_BOOL Value)

{

 try

 {

  bool v = Value;

  GlobalCbwScreen->Timer1->Enabled = v;

 }

 catch(Exception &e)

 {

   return Error(e.Message.c_str(), IID_ICbwScreen);

 }

 return S_OK;

};

 

STDMETHODIMP TCbwScreenImpl::set_Host(BSTR Value)

{

 try

 {

    GlobalCbwScreen->IdTCPClient->Host = Value;

    GlobalCbwScreen->Label1->Caption = Format("机型:%s/r/n发送目标IP: %s, 端口:%d/r/n本机监听端口:%d,激活态:%s",

     ARRAYOFCONST(((ClientFlag ? "客户机" : "员工机"),

     GlobalCbwScreen->IdTCPClient->Host.c_str(),

     GlobalCbwScreen->IdTCPClient->Port,

     GlobalCbwScreen->IdTCPServer->DefaultPort,

     GlobalCbwScreen->IdTCPServer->Active ? "true" : "false")));

 }

 catch(Exception &e)

 {

   return Error(e.Message.c_str(), IID_ICbwScreen);

 }

 return S_OK;

};

 

STDMETHODIMP TCbwScreenImpl::set_Client(VARIANT_BOOL Value)

{

 try

 {

     ClientFlag = Value;

     int sendPort = 6001;

     int recievePort = 6010;

     if(ClientFlag)

     {  // 客户机

        int temp = sendPort;

        sendPort = recievePort;

        recievePort = temp;

     }

     GlobalCbwScreen->IdTCPClient->Port = sendPort;

     GlobalCbwScreen->IdTCPServer->DefaultPort = recievePort;

 }

 catch(Exception &e)

 {

   return Error(e.Message.c_str(), IID_ICbwScreen);

 }

 return S_OK;

};

5.  部署OCX

5.1     服务端与客户端网页

为了完成项目需求,还需要设计一个服务端网页(部署在员工机上)和一个客户端网页(部署在客户机上)。服务端负责向客户端发送桌面。

OCX嵌入网页嘛,具体可参照BCB替我们生成的标准网页。

下面是员工机的网页ServerScreen.htm

<HTML>

<OBJECT

        classid="clsid:0680890D-4D0A-45FB-BF44-3AF41DC04165"

        codebase="DoubleScreen/CbwScreen.inf"

        id="doublescreen"

        width=0

        height=0

        align=center

        hspace=0

        vspace=0

</OBJECT>

<Body onload=Init()     >

</Body>

<script language="javascript">

function Init()

{//设置图形控件访问的WebService地址,其最重要的是要对图形控件设置其访问的WebService地址

      doublescreen.Client=0;                                      // 1为客户机,0为员工机

      doublescreen.Host="10.210.9.2";                // 客户机IP地址

}

</script>

<form name="form1">

      <input type="button" onclick='doublescreen.Monitor=true' value="发送">

</form>

</HTML>

其中,OCX的宽度高度设置为0,表示该OCX不可见。嗯,笨就一个字,我只说一次。

直接在网页onload事件中调用Init()初始化函数,主要是设置OCX工作在员工机模式(Client=0),目标客户机IP地址为10.210.9.2(本机,呵呵,测试一下)。发送按钮将触发发送事件。

客户机网页ClientScreen.htm如下:

<HTML>

<OBJECT

        classid="clsid:0680890D-4D0A-45FB-BF44-3AF41DC04165"

        codebase=" DoubleScreen/CbwScreen.inf"

        id="doublescreen"

        width=100%

        height=100%

        align=center

        hspace=0

        vspace=0

</OBJECT>

<Body onload=SetClient()     >

</Body>

<script language="javascript">

function SetClient()

{    // 设置图形控件访问的WebService地址,其最重要的是要对图形控件设置其访问的WebService地址

      doublescreen.Client=1;          // 1为客户机,0为员工机

}

</script>

</HTML>

6.  测试

万事俱备了,想直接拷贝给朋友,作一了断,转念一想,还是简单测试一下吧。

换台计算机,环境配置完毕,测试。

没想到啊没想到,真傻眼了,那个小小的红叉叉它硬是要出来。

嗨,这不是还没有配置新计算机的IE吗。立马设置一下。

首先,IE的菜单工具àInternet选项,设置Internet的安全级别,简单点,直接设置为低安全级吧。啊?人倒霉了,喝凉水都塞牙,那个该死的Windows不让我设置更低的级别,气得我连图都懒得贴出来了。

内事不决问百度,外事不决找GOOGLE,这是微软的事,那就G吧。

找了一大圈,找到了解决方法:修改注册表,额的神啊。

不过改注册表这种行为,俺也不是一次两次的了,进入HKEY_CURRENT_USERSOFTWAREMICROSOFTWINDOWSCURRENTVERSIONINTERNET SETTINGSZONES3(迷糊了吗?没迷糊我再讲一遍哈),修改CurrentLevel值为10000,确定。

嘿嘿,可以设置为低安全级了。脸上都笑开了花。

刷新一下,笑容就僵在脸上了,可要了亲命了,还是不行。

重来

这次老子也不信安全级别了,直接进入自定义级别,呃,要是你不清楚怎么整出个自定义级别的话,说明你是新来的,还是直接从这段才开始看的。

 

瞪圆了双眼,愣是把Active控件和插件下的所有选项一个一个地、挨个地、逐个地设置为启用了。再次打开网页,OMG,外甥打灯笼了。

GOOGLE,你这次再不好好表现一下,以后你就给我爬一边去。

GOOGLE还是好啊,它也不生气,告诉了我另外一个方式:Internet选项中还有个高级标签呢,在安全下面的复选框中,有一项,它名叫,哦,专业一点,应该是标题是“允许运行或安装软件,即使签名无效”。我一看,哟,果真呢,我这项没有选,选上,赶紧的。

 

办法都想尽了,这下该好了吧。想咱没有功劳也有苦劳,没有苦劳也有疲劳。唉,又是一个杨白劳。

GOOGLE又说,可以设置一下本地Intranet的安全级别,呜呜呜呜,结果呢,地球人都知道了。

 

GOOGLE还说,应该加入受信任的站点,我也加了的呀。

没办法了,再G一下吧,GOOGLE也没啥新意了,东说西说,都是上面的这些解决方法,唉,中文的博客、文章之类的,抄来抄去的,再看个开头就知道结尾,不看也罢。

突然,眼前一亮,发现一个新方法,说是系统属性中有个硬件,通过“驱动程序签名”按钮进行签名选项设置,应选择为“忽略安装软件,不用征求我的同意”。哦,我什么时候说过需要我的同意了,你赶紧出来吧。

 

我想,我当时的眼睛可能都亮得发出绿光了,但过了5秒钟,正常了。

我都有点想撞墙了,换换脑子吧。前两天听一个小朋友成天给我吹风说现在蜗居很热很真实,呃,要不看上一集?先上网看看评论,嗬,那是人山人海,锣鼓喧天,相当的热闹。有两个标题党,一个说是史上最淫荡台词的电视剧,附了一大堆的截图,另一个说是让80后有当二奶的冲动。啊?这哪跟哪呀?看一集吧。

结果看了三集,中间吃了中午饭。我看时间不早了,肠子都悔青了。今天咋给朋友交待呢。

一不做,二不休,再上网溜溜,一会儿就跑题了,看到了北大两名学生关机山事件的各种版本,笑死俺了。在此借用一下易中天版本哈。

上一讲我们说到了OCX控件在IE中无法显示的问题。为什么无法显示呢?史书上没有记载,但我们可以从一些野史上找到一些蛛丝马迹。根据我们的分析,这其中的原因可能有三个,一是IE安全级别设置得不对,导致OCX无法下载;二是OCX的包没打全;三是OCX行了一些苟且之事,编得有问题。对于第一个原因,基本可以排除,因为我们已经系统地试验过了。第二个原因倒是有可能,因为在开发的计算机上可以运行,换台机子就不行了。让我们参考一下《三国志》OCX传裴松注中的说法,“橘生南国为橘,北国为桔”,换台机子后它水土不服,这只是表象,究其根本原因在于自己的毛还没有长全,打包的时候少打了些东东。但仔细一想,不对呀,已经全部设置清楚了呀,该干的事都做了,偷鸡摸狗的事一件没做呀,因此,这第二个原因也可以排除。那现在只剩下第三个原因,也就是说,OCX行了一些苟且之事,编得有问题。

哦,易中天教授,您太伟大了,按照您的思路,我一下子就找到了问题的根源。我知道该怎么做了。

首先,我就只做一个空白的OCX,经过接口处理,发布,部署,拷贝到新的计算机上,一看,哟嗬,你猜怎么着,小红叉叉不见了,OCX控件出来了。呃,有戏。

然后,我就逐个模块往上添加,添加一个,发布一版,只见那文件版本,从1.0.1.0噌噌上升到了1.0.52.0,这中间,我就象个机器人,修改、编译、发布、插U盘、拷贝文件、拔U盘、拷贝文件、修改版本号、拔U盘,一系列工作,我把它流程化了,幸亏我的椅子带有滚轮,不然,我能跑个5公里

你又猜怎么着,嘿,我找到问题所在了,是IdTCPServer出错了,我反复试了下,把原来的IdTCPServer复制粘贴到新的OCX中就不行,小红叉叉如约守候在网页中,不要它就OK

呃,不要可就不行了,网络传输还要靠它呢,怎么办呢?

仔细看了看IdTCPServer

object IdTCPServer: TIdTCPServer

 Active = True

 Bindings = <10.210.9.2:6001>

 DefaultPort = 6001

 OnExecute = IdTCPServerExecute

 Left = 40

 Top = 152

end

哦,把它的Active改为false,并将Bindings清空试试。

天可怜见,一切正常了。

原来,在本机上,Bindings绑定了本机IP与端口,所以初始化时,置其Activetrue,它能正确处理。但换到另一台计算机上,它不在本局域网内,10.210.9.2这个IP地址它找不到,所以IdTCPServertrue时将会失败,可能抛出异常,导致OCX不能正常初始化,结果失败。将其Active属性改为false并将Bindings清空后,它的初始化过程不会出错,一切正常,OCXOK了。

我恍然大悟。

我想我快是什么教徒了:“主啊,原谅我的无知吧,我再也不妄自菲薄了。”

再一看时间,都下午5点了,赶紧的,把一切弄得看起来专业一点,把版本定为1.1.1.0,打了个包,发给了朋友。呃,不好意思,顺便再把账号也发了一遍。

朋友在下班前测试了一下,一切正常,说,明天就去给客户演示。我说,好,你去吧。

本来想总结一下的,结果一想,值得总结吗?至少在一段时间内,我需要谦虚了,谨慎了,低调了,这是今天的最大收获。你呢?

0 0
原创粉丝点击