这个事情就是一个坑,耽误了两周时间,之前并没有做过ActiveX这玩意,现在客户需求如此,只能说是在网上看着教程做了。

  事情是这样的,有一台海康威视的摄像头,客户需要一个ActiveX控件嵌入到网页中,通过点击按钮开始录制和结束录制来进行视频的录制和保存,关于海康摄像头的二次开发在此就不多说了,可以参考SDK中的说明。

  直接上流程:

  1.开发环境:

    VS2010,这个打包方便,之前用VS2013打包的,总是调用不了,不知道原因是什么;SDK是32位的,用64位的在Winform中可以正常使用,在网页中使用控件时会报错。

  2.新建项目:

    新建一个类库项目,如下:

    右键点击项目,添加“用户控件”,如下:

    界面拖控件,如下:

    控件代码如下,其中Guid是“工具”->“创建GUID”自动生成的,#region->#endregion折叠部分是实现的IObjectSafety接口

using System;namespace VideoHelper{    [System.Security.SecuritySafeCritical]    public class Videos    {        private bool m_initSDK = false;        /// <summary>        /// 正在录制        /// </summary>        private bool m_Record = false;        private uint LastErr = 0;        private Int32 m_RealHandle = -1;        private Int32 m_lUserID = -1;        public IntPtr handle { get; set; }        public bool Initialize(string ip = "192.168.1.64", int port = 8000, string username = "admin", string password = "8910jqk#")        {            try            {                m_initSDK = CHCNetSDK.NET_DVR_Init();                if (m_initSDK)                {                    CHCNetSDK.NET_DVR_SetLogToFile(3, "C:\\SdkLog\\", true);                    //设备参数结构体                    CHCNetSDK.NET_DVR_DEVICEINFO_V30 DeviceInfo = new CHCNetSDK.NET_DVR_DEVICEINFO_V30();                    //注册设备                    m_lUserID = CHCNetSDK.NET_DVR_Login_V30(ip, port, username, password, ref DeviceInfo);                    return m_lUserID >= 0;                }                return false;            }            catch (Exception ex)            {                System.Windows.Forms.MessageBox.Show("Initialize:" + ex.Message);                return false;            }        }        public bool Start(IntPtr handle, string filename)        {            try            {                CHCNetSDK.NET_DVR_PREVIEWINFO lpPreviewInfo = new CHCNetSDK.NET_DVR_PREVIEWINFO();                lpPreviewInfo.lChannel = 1;                lpPreviewInfo.dwLinkMode = 0;                lpPreviewInfo.dwStreamType = 0;                lpPreviewInfo.bBlocked = true;                lpPreviewInfo.dwDisplayBufNum = 15;                lpPreviewInfo.hPlayWnd = handle;                IntPtr pUser = IntPtr.Zero;//new IntPtr();                         //获取实时视频流                m_RealHandle = CHCNetSDK.NET_DVR_RealPlay_V40(m_lUserID, ref lpPreviewInfo, null, pUser);                if (m_Record == false)                {                    CHCNetSDK.NET_DVR_MakeKeyFrame(m_lUserID, 1);                    if (!CHCNetSDK.NET_DVR_SaveRealData(m_RealHandle, filename))                    {                        LastErr = CHCNetSDK.NET_DVR_GetLastError();                        return false;                    }                    else                    {                        m_Record = true;                        return true;                    }                }                else                {                    return false;                }            }            catch            {                return false;            }        }        public bool End()        {            if (m_Record)            {                if (!CHCNetSDK.NET_DVR_StopSaveRealData(m_RealHandle))                {                    LastErr = CHCNetSDK.NET_DVR_GetLastError();                    return false;                }                m_Record = false;                m_RealHandle = -1;                return true;            }            else            {                return false;            }        }        public void Dispose()        {            try            {                if (m_lUserID >= 0)                {                    CHCNetSDK.NET_DVR_Logout_V30(m_lUserID);                    m_lUserID = -1;                }                if (m_RealHandle >= 0)                {                    CHCNetSDK.NET_DVR_StopRealPlay(m_RealHandle);                    m_RealHandle = -1;                }                CHCNetSDK.NET_DVR_Cleanup();            }            catch            { }        }    }}录制视频操作类

录制视频操作类

using System;using System.Runtime.InteropServices;namespace VideoHelper{    [ComImport, GuidAttribute("CB5BDC81-93C1-11CF-8F20-00805F2CD064")]    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]    public interface IObjectSafety    {        [PreserveSig]        int GetInterfaceSafetyOptions(ref Guid riid, [MarshalAs(UnmanagedType.U4)] ref int pdwSupportedOptions, [MarshalAs(UnmanagedType.U4)] ref int pdwEnabledOptions);        [PreserveSig()]        int SetInterfaceSafetyOptions(ref Guid riid, [MarshalAs(UnmanagedType.U4)] int dwOptionSetMask, [MarshalAs(UnmanagedType.U4)] int dwEnabledOptions);    }}接口代码
using System;using System.Windows.Forms;using System.IO;using System.Runtime.InteropServices;namespace VideoHelper{    [System.Security.SecuritySafeCritical]    [Guid("79629620-3C0C-4D47-B93B-2D36AEF8EF31")]    public partial class VideoControl : UserControl,IObjectSafety    {        public VideoControl()        {            InitializeComponent();        }        string videopath = Environment.CurrentDirectory;        Videos video;        IntPtr handle;        private void btnLogin_Click(object sender, EventArgs e)        {            if (btnLogin.Text == "登录")            {                try                {                    if (string.IsNullOrWhiteSpace(this.txtIP.Text))                    {                        MessageBox.Show("IP地址不能为空!");                        return;                    }                    if (string.IsNullOrWhiteSpace(this.txtUserID.Text))                    {                        MessageBox.Show("用户名不能为空!");                        return;                    }                    if (string.IsNullOrWhiteSpace(this.txtPwd.Text))                    {                        MessageBox.Show("密码不能为空!");                        return;                    }                    video = new Videos();                    if (video.Initialize(this.txtIP.Text, Convert.ToInt32(this.numericUpDown1.Value), this.txtUserID.Text, this.txtPwd.Text))                    {                        this.btnLogin.Text = "注销";                        MessageBox.Show("登录成功!");                        this.btnStart.Enabled = true;                        this.btnSave.Enabled = true;                    }                    else                    {                        MessageBox.Show("登录失败!");                    }                }                catch (Exception ee)                {                    MessageBox.Show("登录异常:" + ee.Message);                }            }            else if (btnLogin.Text == "注销")            {                try                {                    video.Dispose();                    this.btnLogin.Text = "登录";                    this.btnStart.Enabled = false;                    this.btnSave.Enabled = false;                }                catch (Exception ee)                {                    MessageBox.Show("注销异常:" + ee.Message);                }            }        }        private void btnStart_Click(object sender, EventArgs e)        {            try            {                string filename = txtFile.Text.Trim();                if (filename.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || string.IsNullOrWhiteSpace(filename))                {                    MessageBox.Show("文件名含有非法字符或空格,请重新输入");                    txtFile.Focus();                    return;                }                video.Start(handle, filename + comboBox1.SelectedItem.ToString());                this.btnStart.Enabled = false;                this.btnSave.Enabled = true;            }            catch (Exception ee)            {                MessageBox.Show("异常:" + ee.Message);            }        }        private void btnSave_Click(object sender, EventArgs e)        {            try            {                if (video.End())                {                    MessageBox.Show("视频已保存!");                    this.btnStart.Enabled = true;                    this.btnSave.Enabled = false;                }                else                {                    MessageBox.Show("保存失败!");                    this.btnStart.Enabled = true;                    this.btnSave.Enabled = true;                }            }            catch (Exception ee)            { MessageBox.Show("异常:" + ee.Message); }        }        private void button1_Click(object sender, EventArgs e)        {            try            {                System.Diagnostics.Process.Start(videopath);            }            catch            { }        }        private void VideoControl_Load(object sender, EventArgs e)        {            this.comboBox1.SelectedItem = ".mp4";            this.handle = pictureBox1.Handle;            this.btnStart.Enabled = false;            this.btnSave.Enabled = false;        }        #region IObjectSafety 成员        private const string _IID_IDispatch = "{00020400-0000-0000-C000-000000000046}";        private const string _IID_IDispatchEx = "{a6ef9860-c720-11d0-9337-00a0c90dcaa9}";        private const string _IID_IPersistStorage = "{0000010A-0000-0000-C000-000000000046}";        private const string _IID_IPersistStream = "{00000109-0000-0000-C000-000000000046}";        private const string _IID_IPersistPropertyBag = "{37D84F60-42CB-11CE-8135-00AA004BB851}";        private const int INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001;        private const int INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002;        private const int S_OK = 0;        private const int E_FAIL = unchecked((int)0x80004005);        private const int E_NOINTERFACE = unchecked((int)0x80004002);        private bool _fSafeForScripting = true;        private bool _fSafeForInitializing = true;        public int GetInterfaceSafetyOptions(ref Guid riid, ref int pdwSupportedOptions, ref int pdwEnabledOptions)        {            int Rslt = E_FAIL;            string strGUID = riid.ToString("B");            pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA;            switch (strGUID)            {                case _IID_IDispatch:                case _IID_IDispatchEx:                    Rslt = S_OK;                    pdwEnabledOptions = 0;                    if (_fSafeForScripting == true)                        pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER;                    break;                case _IID_IPersistStorage:                case _IID_IPersistStream:                case _IID_IPersistPropertyBag:                    Rslt = S_OK;                    pdwEnabledOptions = 0;                    if (_fSafeForInitializing == true)                        pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA;                    break;                default:                    Rslt = E_NOINTERFACE;                    break;            }            return Rslt;        }        public int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask, int dwEnabledOptions)        {            int Rslt = E_FAIL;            string strGUID = riid.ToString("B");            switch (strGUID)            {                case _IID_IDispatch:                case _IID_IDispatchEx:                    if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_CALLER) && (_fSafeForScripting == true))                        Rslt = S_OK;                    break;                case _IID_IPersistStorage:                case _IID_IPersistStream:                case _IID_IPersistPropertyBag:                    if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_DATA) && (_fSafeForInitializing == true))                        Rslt = S_OK;                    break;                default:                    Rslt = E_NOINTERFACE;                    break;            }            return Rslt;        }        #endregion    }}控件代码
namespace VideoHelper{    partial class VideoControl    {        /// <summary>         /// 必需的设计器变量。        /// </summary>        private System.ComponentModel.IContainer components = null;        /// <summary>         /// 清理所有正在使用的资源。        /// </summary>        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>        protected override void Dispose(bool disposing)        {            if (disposing && (components != null))            {                components.Dispose();            }            base.Dispose(disposing);        }        #region 组件设计器生成的代码        /// <summary>         /// 设计器支持所需的方法 - 不要        /// 使用代码编辑器修改此方法的内容。        /// </summary>        private void InitializeComponent()        {            this.button1 = new System.Windows.Forms.Button();            this.comboBox1 = new System.Windows.Forms.ComboBox();            this.label4 = new System.Windows.Forms.Label();            this.txtFile = new System.Windows.Forms.TextBox();            this.btnSave = new System.Windows.Forms.Button();            this.btnStart = new System.Windows.Forms.Button();            this.btnLogin = new System.Windows.Forms.Button();            this.label3 = new System.Windows.Forms.Label();            this.txtPwd = new System.Windows.Forms.TextBox();            this.label2 = new System.Windows.Forms.Label();            this.txtUserID = new System.Windows.Forms.TextBox();            this.label1 = new System.Windows.Forms.Label();            this.numericUpDown1 = new System.Windows.Forms.NumericUpDown();            this.IP = new System.Windows.Forms.Label();            this.txtIP = new System.Windows.Forms.TextBox();            this.pictureBox1 = new System.Windows.Forms.PictureBox();            ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();            ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();            this.SuspendLayout();            //             // button1            //             this.button1.Cursor = System.Windows.Forms.Cursors.Hand;            this.button1.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));            this.button1.Location = new System.Drawing.Point(377, 360);            this.button1.Name = "button1";            this.button1.Size = new System.Drawing.Size(138, 22);            this.button1.TabIndex = 58;            this.button1.Text = "打开视频存放位置";            this.button1.UseVisualStyleBackColor = true;            this.button1.Click += new System.EventHandler(this.button1_Click);            //             // comboBox1            //             this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;            this.comboBox1.FlatStyle = System.Windows.Forms.FlatStyle.Flat;            this.comboBox1.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));            this.comboBox1.FormattingEnabled = true;            this.comboBox1.Items.AddRange(new object[] {            ".mp4",            ".avi",            ".wmv",            ".3gp",            ".flv"});            this.comboBox1.Location = new System.Drawing.Point(303, 361);            this.comboBox1.Name = "comboBox1";            this.comboBox1.Size = new System.Drawing.Size(55, 25);            this.comboBox1.TabIndex = 57;            //             // label4            //             this.label4.AutoSize = true;            this.label4.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));            this.label4.Location = new System.Drawing.Point(14, 360);            this.label4.Name = "label4";            this.label4.Size = new System.Drawing.Size(116, 17);            this.label4.TabIndex = 56;            this.label4.Text = "请输入视频文件名:";            //             // txtFile            //             this.txtFile.Location = new System.Drawing.Point(136, 360);            this.txtFile.Name = "txtFile";            this.txtFile.Size = new System.Drawing.Size(161, 21);            this.txtFile.TabIndex = 55;            //             // btnSave            //             this.btnSave.Cursor = System.Windows.Forms.Cursors.Hand;            this.btnSave.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));            this.btnSave.Location = new System.Drawing.Point(490, 298);            this.btnSave.Name = "btnSave";            this.btnSave.Size = new System.Drawing.Size(57, 45);            this.btnSave.TabIndex = 54;            this.btnSave.Text = "保存";            this.btnSave.UseVisualStyleBackColor = true;            this.btnSave.Click += new System.EventHandler(this.btnSave_Click);            //             // btnStart            //             this.btnStart.Cursor = System.Windows.Forms.Cursors.Hand;            this.btnStart.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));            this.btnStart.Location = new System.Drawing.Point(421, 298);            this.btnStart.Name = "btnStart";            this.btnStart.Size = new System.Drawing.Size(57, 45);            this.btnStart.TabIndex = 53;            this.btnStart.Text = "录制";            this.btnStart.UseVisualStyleBackColor = true;            this.btnStart.Click += new System.EventHandler(this.btnStart_Click);            //             // btnLogin            //             this.btnLogin.Cursor = System.Windows.Forms.Cursors.Hand;            this.btnLogin.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));            this.btnLogin.Location = new System.Drawing.Point(352, 298);            this.btnLogin.Name = "btnLogin";            this.btnLogin.Size = new System.Drawing.Size(57, 45);            this.btnLogin.TabIndex = 52;            this.btnLogin.Text = "登录";            this.btnLogin.UseVisualStyleBackColor = true;            this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click);            //             // label3            //             this.label3.AutoSize = true;            this.label3.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));            this.label3.Location = new System.Drawing.Point(172, 325);            this.label3.Name = "label3";            this.label3.Size = new System.Drawing.Size(44, 17);            this.label3.TabIndex = 51;            this.label3.Text = "密码:";            //             // txtPwd            //             this.txtPwd.Location = new System.Drawing.Point(221, 322);            this.txtPwd.Name = "txtPwd";            this.txtPwd.PasswordChar = '*';            this.txtPwd.Size = new System.Drawing.Size(115, 21);            this.txtPwd.TabIndex = 50;            this.txtPwd.Text = "8910jqk#";            this.txtPwd.UseSystemPasswordChar = true;            //             // label2            //             this.label2.AutoSize = true;            this.label2.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));            this.label2.Location = new System.Drawing.Point(8, 322);            this.label2.Name = "label2";            this.label2.Size = new System.Drawing.Size(44, 17);            this.label2.TabIndex = 49;            this.label2.Text = "用户名";            //             // txtUserID            //             this.txtUserID.Location = new System.Drawing.Point(66, 322);            this.txtUserID.Name = "txtUserID";            this.txtUserID.Size = new System.Drawing.Size(100, 21);            this.txtUserID.TabIndex = 48;            this.txtUserID.Text = "admin";            //             // label1            //             this.label1.AutoSize = true;            this.label1.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));            this.label1.Location = new System.Drawing.Point(172, 295);            this.label1.Name = "label1";            this.label1.Size = new System.Drawing.Size(44, 17);            this.label1.TabIndex = 47;            this.label1.Text = "端口:";            //             // numericUpDown1            //             this.numericUpDown1.Location = new System.Drawing.Point(222, 295);            this.numericUpDown1.Maximum = new decimal(new int[] {            65535,            0,            0,            0});            this.numericUpDown1.Minimum = new decimal(new int[] {            1,            0,            0,            0});            this.numericUpDown1.Name = "numericUpDown1";            this.numericUpDown1.Size = new System.Drawing.Size(114, 21);            this.numericUpDown1.TabIndex = 46;            this.numericUpDown1.Value = new decimal(new int[] {            8000,            0,            0,            0});            //             // IP            //             this.IP.AutoSize = true;            this.IP.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));            this.IP.Location = new System.Drawing.Point(20, 295);            this.IP.Name = "IP";            this.IP.Size = new System.Drawing.Size(19, 17);            this.IP.TabIndex = 45;            this.IP.Text = "IP";            //             // txtIP            //             this.txtIP.Location = new System.Drawing.Point(66, 295);            this.txtIP.Name = "txtIP";            this.txtIP.Size = new System.Drawing.Size(100, 21);            this.txtIP.TabIndex = 44;            this.txtIP.Text = "192.168.1.64";            //             // pictureBox1            //             this.pictureBox1.Location = new System.Drawing.Point(5, 5);            this.pictureBox1.Name = "pictureBox1";            this.pictureBox1.Size = new System.Drawing.Size(542, 269);            this.pictureBox1.TabIndex = 43;            this.pictureBox1.TabStop = false;            //             // VideoControl            //             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;            this.Controls.Add(this.button1);            this.Controls.Add(this.comboBox1);            this.Controls.Add(this.label4);            this.Controls.Add(this.txtFile);            this.Controls.Add(this.btnSave);            this.Controls.Add(this.btnStart);            this.Controls.Add(this.btnLogin);            this.Controls.Add(this.label3);            this.Controls.Add(this.txtPwd);            this.Controls.Add(this.label2);            this.Controls.Add(this.txtUserID);            this.Controls.Add(this.label1);            this.Controls.Add(this.numericUpDown1);            this.Controls.Add(this.IP);            this.Controls.Add(this.txtIP);            this.Controls.Add(this.pictureBox1);            this.Name = "VideoControl";            this.Size = new System.Drawing.Size(556, 398);            this.Load += new System.EventHandler(this.VideoControl_Load);            ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit();            ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();            this.ResumeLayout(false);            this.PerformLayout();        }        #endregion        private System.Windows.Forms.Button button1;        private System.Windows.Forms.ComboBox comboBox1;        private System.Windows.Forms.Label label4;        private System.Windows.Forms.TextBox txtFile;        private System.Windows.Forms.Button btnSave;        private System.Windows.Forms.Button btnStart;        private System.Windows.Forms.Button btnLogin;        private System.Windows.Forms.Label label3;        private System.Windows.Forms.TextBox txtPwd;        private System.Windows.Forms.Label label2;        private System.Windows.Forms.TextBox txtUserID;        private System.Windows.Forms.Label label1;        private System.Windows.Forms.NumericUpDown numericUpDown1;        private System.Windows.Forms.Label IP;        private System.Windows.Forms.TextBox txtIP;        private System.Windows.Forms.PictureBox pictureBox1;    }}控件设计器代码

控件设计器代码

    至此,此项目结束。

    右键点击解决方案,添加新项目,如下,至于为什么建立两个项目,我一会儿在下面解释,

    在HkHelper项目中添加类CHCNetSDK.cs,此类是海康提供的,可以在官网找到

    接下来,最重要的,项目属性设置如下,两个项目都要设置:

    至此,自定义控件已经完成,接下来就是打包,新建一个安装项目:

    右键点击安装项目,“添加”->“项目输出”,并选择自定义控件的项目,然后确定

    然后添加海康提供的SDK的库文件文件夹下的所有文件和文件夹到项目中,如下:

    然后生成项目,会生成setup.exe和SetupVideo.msi两个文件,然后用打包文件,把这两个文件打包称cab文件就OK了

打包文件一共三个cabarc.exe、build.bat、install.inf

build.bat文件:


"cabarc.exe"  n VideoSetup.cab SetupVideo.msi install.inf

install.inf文件:


[version]signature="$CHICAGO$"AdvancedINF=2.0[Setup Hooks]hook1=hook1[hook1]run=msiexec.exe /i "%EXTRACT_DIR%\SetupVideo.msi" /qn

cabarc.exe是微软提供的工具

最后说一下为什么要分为两个项目去实现控件,那是因为如果在一个项目中的话,调用海康动态库的类CHCNetSDK.cs不能进行COM注册

更多相关文章

  1. C# WinForm跨线程访问控件的图文详解
  2. UWP中设置控件样式四种方法
  3. C#调用的三维地球控件,看三维地球构建过程
  4. textbox控件属性有哪些
  5. 广告控件中xml文件的写法
  6. 具体分析微软的xml解析器
  7. JavaScript 创意卡通滑杆拖动控件
  8. 继 GitHub 后微软又收购了 npm
  9. 想来微软实习吗?

随机推荐

  1. 程序员用python给了女友一个七夕惊喜!
  2. GitHub 热门:实用 Python 编程
  3. 使用Nginx 代理应用服务的端口,以及ssh连
  4. LeetCode 上最难的链表算法题,没有之一!
  5. Stack Overflow 热帖:如何用 Python 调用
  6. 干货 | 滴滴 数据分析原来是这样做的!
  7. 几道 BAT 算法面试中经常问的「字符串」
  8. 11 月编程排行榜:Python “打败”Java 成
  9. 【代码实战】华为应用市场专家在线直播Ap
  10. 大新闻!Python 之父重新出山,加入微软开发