新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 计算机科学论坛计算机技术与应用『 C/C++编程思想 』 → COM组件设计与应用(十三、十四) - 事件和通知(VC6.0) 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 6474 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: COM组件设计与应用(十三、十四) - 事件和通知(VC6.0) 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客楼主
    发贴心情 COM组件设计与应用(十三、十四) - 事件和通知(VC6.0)

    一、前言[/B]
      我的 COM 组件运行时产生一个窗口,当用户双击该窗口的时候,我需要通知调用者;
      我的 COM 组件用线程方式下载网络上的一个文件,当我完成任务后,需要通知调用者;
      我的 COM 组件完成一个钟表的功能,当预定时间到达的时候,我需要通知调用者;
      ... ... ... ...
      本回书开始话说 COM 的事件、通知、连接点......这些内容比较多,我分两次(共四回)来介绍。

    二、通知的方法
      当程序甲方内部发生了某个事件的时候,需要通知乙方,无非使用几个方法:



    此主题相关图片如下:
    按此在新窗口浏览图片

    在 COM 的时代,以上这些方法就基本上不能玩转了,因为...您想呀 COM 组件是运行在分布式环境中的,地球另一边计算机上运行的组件,怎么可能给你的窗口发消息那?当然不能!(但话又说回来,对于 ActiveX 这样只能在本地运行的组件,当然也可以发送窗口消息的啦。)
      回调函数的方式,是设计 COM 通知方法的基础。回调函数,本质上是预先把某一函数的指针告诉我,当我有必要的时候,就直接呼叫该函数了,而这个回调函数做了什么,怎么做的,我是根本不关心的。好了,问你个问题:啥是 COM 的接口?接口其实就是一组相关函数的集合(这个定义不严谨,但你可以这么理解哈)。因此,在COM中不使用“回调函数”而是使用“回调接口”(说的再清楚一些,就是使用一大堆包装好的“回调函数”集) ,回调接口,我们也叫“接收器接口”。

    按此在新窗口浏览图片
    图一、客户端传递接收器接口指针给COM。当发生事件时,COM调用接收器接口函数完成通知

    本回示例程序完成的功能是:
      客户端启动组件(Simple11.IEvent1.1)并得到接口指针 IEvent1 *;
      调用接口方法 IEvent1::Advise() 把客户端内部的一个接收器(sink)接口指针(ICallBack *)传递到组件服务器中;
      调用 IEvent1::Add() 去计算两个整数的和;
      但是计算结果并不通过该函数返回,而是通过 ICallBack::Fire_Result() 返回给客户端;
      当客户端不再需要接受事件的时候,调用 IEvent1::Unadvise() 断开和组件的联系。

    三、组件实现步骤
    1、建立一个工作区(WorkSpace)
    2、在工作区中,建立一个 ATL 工程(Project)。示例程序中工程名称叫 Simple11,接受全部默认选项。
    3、ClassView 中,执行鼠标右键菜单命令 New Atl Object...,添加 ALT 类。
       3-1、左侧分类 Category 选择 Objects,右侧 Objects 选择 SimpleObject(其实就是默认项目)
       3-2、名称 Name 卡片中,输入组件名称。示例程序中是 Event1(注1)
       3-3、属性 Attributes 卡片中,修改接口类型 Interface 为定制的 Custom(注2)
    4、ClassView 中,选择接口(IEvent1),鼠标右键菜单添加函数 Add Method...

    按此在新窗口浏览图片
    图二、增加接口函数 Add([in] long n1,[in] long n2)

    按此在新窗口浏览图片
    图三、增加接口函数 Advise([in] ICallBack *pCallBack,[out] long *pdwCookie)

    按此在新窗口浏览图片
    图四、增加接口函数 Unadvise([in] long dwCookie)

      你应该注意到了,在Add()函数中,并没有[out]、[retval] 这样的 IDL 属性,嘿嘿,因为我们本来就不打算通过 Add() 函数直接得到计算结果。不然怎么演示回调接口呀:-) 另外,在函数 Advise()中,需要返回一个整数 dwCookie,这是干什么?道理很简单,因为我们的组件想同时支持多个对象的回调连接。因此当客户端传递一个接口给我们组件的时候,我返回给它唯一的一个 cookie 号码来表示身份,将来断开连接的时候 Unadvise(),它需要把这个 cookie 身份号再给我,这样我就知道是谁想断开了。
    5、增加回调接口 ICallBack 的 IDL 定义。打开 IDL 文件并手工输入(黑体字部分为手工输入的) ,然后保存:
    import "oaidl.idl";
    import "ocidl.idl";
    [
     object,
     uuid(7E659BB1-FB79-4188-9661-65CA22B6A3E6), // 这个 IID 可以用 GUDIGEN.EXE 产生
     
     helpstring("ICallBack Interface"),
     pointer_default(unique)
    ]
    interface ICallBack : IUnknown
    {

    };

    [
     object,  // 以下内容同示例程序,当然如果是你自己生成的程序就肯定有差别的啦
     uuid(7E659BB0-FB79-4188-9661-65CA22B6A3E6),
     
     helpstring("IEvent1 Interface"),
     pointer_default(unique)
    ]
    interface IEvent1 : IUnknown
    {
     [helpstring("method Add")] HRESULT Add([in] long n1, [in] long n2);
     [helpstring("method Advise")] HRESULT Advise([in] ICallBack * pCallBack, [out] long * pdwCookie);
     [helpstring("method Unadvise")] HRESULT Unadvise([in] long dwCookie);
    };

    [
     uuid(695C9BB2-2AE9-4232-8225-17AB8BD3BABC),
     version(1.0),
     helpstring("Simple11 1.0 Type Library")
    ]
    library SIMPLE11Lib
    {
     importlib("stdole32.tlb");
     importlib("stdole2.tlb");

     [
      uuid(6FCF997C-C811-49DB-9D16-46FAF8D24822),
      helpstring("Event1 Class")
     ]
     coclass Event1
     {
      [default] interface IEvent1;
      // 需要手工输入,据说 VB 使用的话,不能有 [source,default] 属性
      [source, default] interface ICallBack; 
     };
    };
    6、增加回调接口函数

    按此在新窗口浏览图片
    图五、增加回调接口函数

    其实和以前的方法一样,只要注意别选错了接口就好。

    按此在新窗口浏览图片
    图六、增加接口函数 Fire_Result([in] long nResult)

    我们计算整数和,得到结果后,就是要靠这个回调接口函数去反馈给客户端呀。

    7、添加组件内部保存回调接口指针的数组
      刚才已经说过,我们这个组件打算支持多个对象的回调连接,因此我们要使用一个数组来保存。在 ClassView 中,选择 CEvent1 类,增加成员变量 Add Member Variable...

    按此在新窗口浏览图片
    图七、增加保存 ICallBack * 的数组

      当然,保存一个数组可以有多种方式。示例程序比较简单,定义了一个10个元素空间的成员数组变量。如果你已经学会了使用 STL,那么你也可以用 vector 等容器来实现。注意!注意!注意!在构造函数中别忘了初始化数组元素为 NULL

    8、好了,下面开始完成所有代码
    STDMETHODIMP CEvent1::Add(long n1, long n2)
    {
     long nResult = n1 + n2;
     for( int i=0; i<10; i++)
     {
      if( m_pCallBack[i] )  // 如果回调接口有效
       m_pCallBack[i]->Fire_Result( nResult ); // 则发出事件/通知
     }

     return S_OK;
    }

    STDMETHODIMP CEvent1::Advise(ICallBack *pCallBack, long *pdwCookie)
    {
     if( NULL == pCallBack ) // 居然给我一个空指针?!
      return E_INVALIDARG;

     for( int i=0; i<10; i++) // 寻找一个保存该接口指针的位置
     {
      if( NULL == m_pCallBack[i] ) // 找到了
      {
       m_pCallBack[i] = pCallBack; // 保存到数组中
       m_pCallBack[i]->AddRef(); // 指针计数器 +1

       *pdwCookie = i + 1; // cookie 就是数组下标
        // +1 的目的是避免使用0,因为0表示无效

       return S_OK;
      }
     }
     return E_OUTOFMEMORY; // 超过10个连接,内存不够用啦
    }

    STDMETHODIMP CEvent1::Unadvise(long dwCookie)
    {
     if( dwCookie<1 || dwCookie>10 ) // 这是谁干的呀?乱给参数
      return E_INVALIDARG;

     if( NULL == m_pCallBack[ dwCookie - 1 ] ) // 参数错误,或该接口指针已经无效了
      return E_INVALIDARG;

     m_pCallBack[ dwCookie -1 ]->Release(); // 指针计数器 -1
     m_pCallBack[ dwCookie -1 ] = NULL;  // 空出该下标的数组元素

     return S_OK;
    }

    四、客户端实现步骤
      大家下载示例程序后,去浏览客户端的实现程序吧。这里我只说明一下关于接收器是如何构造的:

    按此在新窗口浏览图片
    图八、从 ICallBack 派生接收器类 CSink

      从 ICallBack 派生一个类 CSink。确认后 IDE 会有一个警告,说它找不到 ICallBack 的头文件,不用理它,因为只有当编译的时候,#import 才会为我们生成 xxxx.tlh、xxxx.tli 文件,这些文件就有 ICallBack 的声明啦。
      这里 ICallBack 是 COM 接口,因此 CSink 是不能事例化的,如果你去编译,会得到一坨一坨(注3)的错误,报告说你没有实现 virtual 函数。然后,我们可以按照错误报告,去实现所有的虚函数:
    // STDMETHODIMP 是宏,等价于 long __stdcall
    STDMETHODIMP CSink::QueryInterface(const struct _GUID &iid,void ** ppv)
    {
     *ppv=this; // 不管想得到什么接口,其实都是对象本身
     return S_OK;
    }

    ULONG __stdcall CSink::AddRef(void)
    { return 1; }// 做个假的就可以,因为反正这个对象在程序结束前是不会退出的

    ULONG __stdcall CSink::Release(void)
    { return 0; }// 做个假的就可以,因为反正这个对象在程序结束前是不会退出的

    STDMETHODIMP CSink::raw_Fire_Result(long nResult)
    {
     ... ... // 把计算结果显示在窗口中
     return S_OK;
    }

    、小结
      COM 组件实现事件、通知这样的功能有两个基本方法。今天介绍的回调接口方式非常好,速度快、结构清晰、实现也不复杂;下回书介绍连接点方式(Support Connection Points),连接点方法其实并不太好,速度慢(如果是远程DCOM方式,要谨慎选择它)、结构复杂、唯一的好处就是 ATL 对它进行了包装,所以实现起来反而比较简单。不介绍又不行,因为微软绝大数支持事件的组件都是用连接点实现的,咳......讨厌的微软(注4)。
    --------------------------------------------------------------------------------
    注1:本来设想多举几个例子,因此第一个叫 Event1,可写完后,感觉程序已经比较复杂了,就没继续再做了。
    注2:当然,你选择使用双接口 Dual 也没有问题。但要注意到在下面的步骤,增加回调接口修改 IDL 文件的时候,我们是要使用 Custom(从IUnknown派生,而不是从IDispatch派生)的。
    注3:一坨一坨经常用来形容一堆一堆的狗屎。
    注4:微软的同志们,玩笑话不要当真呀!我还靠着你来吃饭那。
    [B]


    [此贴子已经被作者于2007-10-30 9:49:41编辑过]

       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/29 11:14:00
     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客2
    发贴心情 

    一、前言[/B]
      我的 COM 组件运行时产生一个窗口,当用户双击该窗口的时候,我需要通知调用者;
      我的 COM 组件用线程方式下载网络上的一个文件,当我完成任务后,需要通知调用者;
      我的 COM 组件完成一个钟表的功能,当预定时间到达的时候,我需要通知调用者;
      ... ... ... ...
      本回书开始话说 COM 的事件、通知、连接点......这些内容比较多,我分两次(共四回)来介绍。

    二、通知的方法
      当程序甲方内部发生了某个事件的时候,需要通知乙方,无非使用几个方法

    在 COM 的时代,以上这些方法就基本上不能玩转了,因为...您想呀 COM 组件是运行在分布式环境中的,地球另一边计算机上运行的组件,怎么可能给你的窗口发消息那?当然不能!(但话又说回来,对于 ActiveX 这样只能在本地运行的组件,当然也可以发送窗口消息的啦。)
      回调函数的方式,是设计 COM 通知方法的基础。回调函数,本质上是预先把某一函数的指针告诉我,当我有必要的时候,就直接呼叫该函数了,而这个回调函数做了什么,怎么做的,我是根本不关心的。好了,问你个问题:啥是 COM 的接口?接口其实就是一组相关函数的集合(这个定义不严谨,但你可以这么理解哈)。因此,在COM中不使用“回调函数”而是使用“回调接口”(说的再清楚一些,就是使用一大堆包装好的“回调函数”集) ,回调接口,我们也叫“接收器接口”。

    按此在新窗口浏览图片
    图一、客户端传递接收器接口指针给COM。当发生事件时,COM调用接收器接口函数完成通知

    本回示例程序完成的功能是:
      客户端启动组件(Simple11.IEvent1.1)并得到接口指针 IEvent1 *;
      调用接口方法 IEvent1::Advise() 把客户端内部的一个接收器(sink)接口指针(ICallBack *)传递到组件服务器中;
      调用 IEvent1::Add() 去计算两个整数的和;
      但是计算结果并不通过该函数返回,而是通过 ICallBack::Fire_Result() 返回给客户端;
      当客户端不再需要接受事件的时候,调用 IEvent1::Unadvise() 断开和组件的联系。

    三、组件实现步骤
    1、建立一个解决方案
    2、在解决方案中,建立一个 ATL 项目。示例程序中项目名称叫 Simple12,取消“属性化”,其它接受默认选项。
    3、选择项目,执行鼠标右键菜单命令“添加\添加类”。
       3-1、左侧分类选择 ATL,右侧模板选择 Atl 简单对象
       3-2、名称卡片中,输入组件名称。示例程序中是 Event1(注1)
       3-3、选项卡片中,修改接口类型“自定义”(注2)
    4、选择 IEnvent1 接口,鼠标右键菜单“添加\添加方法”

    按此在新窗口浏览图片
    图二、增加接口函数 Add([in] long n1,[in] long n2)

    按此在新窗口浏览图片
    图三、增加接口函数 Advise([in] ICallBack *pCallBack,[out] long *pdwCookie)

    按此在新窗口浏览图片
    图四、增加接口函数 Unadvise([in] long dwCookie)

      你应该注意到了,在Add()函数中,并没有[out]、[retval] 这样的 IDL 属性,嘿嘿,因为我们本来就不打算通过 Add() 函数直接得到计算结果。不然怎么演示回调接口呀:-) 另外,在函数 Advise()中,需要返回一个整数 dwCookie,这是干什么?道理很简单,因为我们的组件想同时支持多个对象的回调连接。因此当客户端传递一个接口给我们组件的时候,我返回给它唯一的一个 cookie 号码来表示身份,将来断开连接的时候 Unadvise(),它需要把这个 cookie 身份号再给我,这样我就知道是谁想断开了。
    5、增加回调接口 ICallBack 的 IDL 定义。打开 IDL 文件并手工输入(黑体字部分为手工输入的) ,然后保存:
    import "oaidl.idl";
    import "ocidl.idl";
    [
     object,
     uuid(DB72DF86-70E9-4ABC-B2F8-5E04062D3B2E), // 这个 IID 可以用 GUDIGEN.EXE 产生
     helpstring("ICallBack 接口"),
     pointer_default(unique)
    ]
    interface ICallBack : IUnknown
    {

    };

    [
     object,  // 以下内容同示例程序,当然如果是你自己生成的程序就肯定有差别的啦
     uuid(DB72DF85-70E9-4ABC-B2F8-5E04062D3B2E),
     
     helpstring("IEvent1 Interface"),
     pointer_default(unique)
    ]
    interface IEvent1 : IUnknown
    {
     [helpstring("method Add")] HRESULT Add([in] long n1, [in] long n2);
     [helpstring("method Advise")] HRESULT Advise([in] ICallBack * pCallBack, [out] long * pdwCookie);
     [helpstring("method Unadvise")] HRESULT Unadvise([in] long dwCookie);
    };

    [
     uuid(FBA1E0F0-49CD-4B77-B9B1-4DC066AF8A8E),
     version(1.0),
     helpstring("Simple12 1.0 类型库")
    ]
    library SIMPLE11Lib
    {
     importlib("stdole32.tlb");
     importlib("stdole2.tlb");

     [
      uuid(53E00126-B1A0-4510-B9BC-75ED87CE2DB7),
      helpstring("Event1 Class")
     ]
     coclass Event1
     {
      [default] interface IEvent1;
      // 需要手工输入,据说 VB 使用的话,不能有 [source,default] 属性
      [source, default] interface ICallBack; 
     };
    };
    6、增加回调接口函数

    按此在新窗口浏览图片
    图五、增加回调接口函数

      其实和以前的方法一样,只要注意别选错了接口就好。

    按此在新窗口浏览图片
    图六、增加接口函数 Fire_Result([in] long nResult)

      我们计算整数和,得到结果后,就是要靠这个回调接口函数去反馈给客户端呀。

    7、添加组件内部保存回调接口指针的数组
      刚才已经说过,我们这个组件打算支持多个对象的回调连接,因此我们要使用一个数组来保存。由于 vc.net 无法用向导来添加数组形式的成员变量,我们还是打开 CEvent1 类的头文件,手工输入吧:
    ......
    private:
     ICallBack * m_pCallBack[10];
    ......
      保存一个数组可以有多种方式。示例程序比较简单,定义了一个10个元素空间的成员数组变量。如果你已经学会了使用 STL,那么你也可以用 vector 等容器来实现。注意!注意!注意!在构造函数中别忘了初始化数组元素为 NULL

    8、好了,下面开始完成所有代码
    STDMETHODIMP CEvent1::Add(long n1, long n2)
    {
     long nResult = n1 + n2;
     for( int i=0; i<10; i++)
     {
      if( m_pCallBack[i] )  // 如果回调接口有效
       m_pCallBack[i]->Fire_Result( nResult ); // 则发出事件/通知
     }

     return S_OK;
    }

    STDMETHODIMP CEvent1::Advise(ICallBack *pCallBack, long *pdwCookie)
    {
     if( NULL == pCallBack ) // 居然给我一个空指针?!
      return E_INVALIDARG;

     for( int i=0; i<10; i++) // 寻找一个保存该接口指针的位置
     {
      if( NULL == m_pCallBack[i] ) // 找到了
      {
       m_pCallBack[i] = pCallBack; // 保存到数组中
       m_pCallBack[i]->AddRef(); // 指针计数器 +1

       *pdwCookie = i + 1; // cookie 就是数组下标
        // +1 的目的是避免使用0,因为0表示无效

       return S_OK;
      }
     }
     return E_OUTOFMEMORY; // 超过10个连接,内存不够用啦
    }

    STDMETHODIMP CEvent1::Unadvise(long dwCookie)
    {
     if( dwCookie<1 || dwCookie>10 ) // 这是谁干的呀?乱给参数
      return E_INVALIDARG;

     if( NULL == m_pCallBack[ dwCookie - 1 ] ) // 参数错误,或该接口指针已经无效了
      return E_INVALIDARG;

     m_pCallBack[ dwCookie -1 ]->Release(); // 指针计数器 -1
     m_pCallBack[ dwCookie -1 ] = NULL;  // 空出该下标的数组元素

     return S_OK;
    }

    四、客户端实现步骤
      大家下载示例程序后,去浏览客户端的实现程序吧。这里我只说明一下关于接收器是如何构造的:

    按此在新窗口浏览图片
    图七、从 ICallBack 派生接收器类 CSink

      这里 ICallBack 是 COM 接口,因此 CSink 是不能事例化的,如果你去编译,会得到一坨一坨(注3)的错误,报告说你没有实现 virtual 函数。然后,我们可以按照错误报告,去实现所有的虚函数:
    // STDMETHODIMP 是宏,等价于 long __stdcall
    STDMETHODIMP CSink::QueryInterface(const struct _GUID &iid,void ** ppv)
    {
     *ppv=this; // 不管想得到什么接口,其实都是对象本身
     return S_OK;
    }

    ULONG __stdcall CSink::AddRef(void)
    { return 1; }// 做个假的就可以,因为反正这个对象在程序结束前是不会退出的

    ULONG __stdcall CSink::Release(void)
    { return 0; }// 做个假的就可以,因为反正这个对象在程序结束前是不会退出的

    STDMETHODIMP CSink::raw_Fire_Result(long nResult)
    {
     ... ... // 把计算结果显示在窗口中
     return S_OK;
    }

    、小结
      COM 组件实现事件、通知这样的功能有两个基本方法。今天介绍的回调接口方式非常好,速度快、结构清晰、实现也不复杂;下回书介绍连接点方式(Support Connection Points),连接点方法其实并不太好,速度慢(如果是远程DCOM方式,要谨慎选择它)、结构复杂、唯一的好处就是 ATL 对它进行了包装,所以实现起来反而比较简单。不介绍又不行,因为微软绝大数支持事件的组件都是用连接点实现的,咳......讨厌的微软(注4)。
    --------------------------------------------------------------------------------
    注1:本来设想多举几个例子,因此第一个叫 Event1,可写完后,感觉程序已经比较复杂了,就没继续再做了。
    注2:当然,你选择使用双接口 Dual 也没有问题。但要注意到在下面的步骤,增加回调接口修改 IDL 文件的时候,我们是要使用 Custom(从IUnknown派生,而不是从IDispatch派生)的。
    注3:一坨一坨经常用来形容一堆一堆的狗屎。
    注4:微软的同志们,玩笑话不要当真呀!我还靠着你来吃饭那。[B]

    [此贴子已经被作者于2007-10-30 9:50:26编辑过]

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/29 11:15:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/7/14 10:55:27

    本主题贴数2,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    78.125ms