使用C语言来扩展Python程序和Zope服务器的教程

有几个原因使您可能想用 C 扩展 Zope。最可能的是您有一个已能帮您做些事的现成的 C 库,但是您对把它转换成 Python 却不感兴趣。此外,由于 Python 是解释性语言,所以任何被大量调用的 Python 代码都将降低您的速度。因此,即使您已经用 Python 写了一些扩展,您仍然要考虑把其中最常被调用的部分改用 C 来写。不论哪种方式,扩展 Zope 都是从扩展 Python 开始。此外,扩展 Python 会给您带来其它的好处,因为您的代码将可以从任何 Python 脚本访问,而不只是从 Zope。这里唯一要提醒的是在写本文的时候,Python 的当前版本是 2.1,但是 Zope 仍然只能和 Python 1.5.2 一起运行。对 C 扩展来说,两个版本并没有什么变化,但如果您有兴趣对您的库进行 Python 包装,又想让它们都能在 Zope 下工作,您就得注意不要使用任何比 1.5.2 更新的东西。
Zope 是什么?

Zope 代表“Z Object Publishing Environment(Z 对象发布环境)”,它是用 Python 实现的应用程序服务器。“太棒了,”您说,“但应用程序服务器的确切含义是什么呢?”应用程序服务器就是一个长期运行的进程,它为“活动的内容”提供服务。Web 服务器在运行期间调用应用程序服务器来构建页面。
扩展 Python:有趣又有益

想扩展 Zope,您首先要扩展 Python。虽然扩展 Python 不像“脑外科手术”那样复杂,但也不像“在公园中散步”那样悠闲。有两个基本组件用于 Python 扩展。第一个显然是 C 代码。我将马上探讨它。 另一个部分是 安装文件。安装文件通过提供模块名称、模块的 C 代码的位置和您可能需要的所有编译器标志来描述模块。该文件被预处理,以创建 makefile(在 UNIX 上)或 MSVC++ 工程文件(MSVC++ project file,在 Windows 上)。先说一下 ― Windows 上的 Python 事实上是用 Microsoft 编译器编译的。Python.org 的人也推荐用 MSVC++ 编译扩展。显然,您应该能够成功说服 GNU 的编译者们,但我本人还没试过。

无论如何,还是让我们来定义一个叫做‘foo'的模块吧。‘foo'模块会有一个叫做‘bar'的函数。当我们要使用时,我们可以用 import foo; 来把这个函数导入到 Python 脚本中,就跟导入任何模块一样。安装文件非常简单:
清单 1. 一个典型的安装文件

# You can include comment lines. The *shared* directive indicates
# that the following module(s) are to be compiled and linked for
# dynamic loading as opposed to static: .so on Unix, .dll on Windows.
*shared*
# Then you can use the variables later using the $(variable) syntax
# that 'make' uses. This next line defines our module and tells
# Python where its source code is.
foo foomain.c

编写代码

那么我们实际上该怎样写 Python 知道如何使用的代码呢,您问? foomain.c (当然,您可以随意命名它)文件包含三项内容:一个方法表,一个初始化函数和其余的代码。方法表简单地将函数名与函数联系起来,并告知 Python 各个函数所使用的参数传递机制(您可以选择使用一般的位置参数列表或位置参数和关键词参数的混合列表)。Python 在模块装入时调用初始化函数。初始化函数将完成模块所要求的所有初始化操作,但更重要的是,它还把一个指向方法表的指针传回给 Python。

那我们就来看看我们的小型 foo 模块的 C 代码。
清单 2. 一个典型的 Python 扩展模块

#include <Python.h>
/* Define the method table. */
static PyObject *foo_bar(PyObject *self, PyObject *args);
static PyMethodDef FooMethods[] = {
  {"bar", foo_bar, METH_VARARGS},
  {NULL, NULL}
};
/* Here's the initialization function. We don't need to do anything
  for our own needs, but Python needs that method table. */
void initfoo()
{
  (void) Py_InitModule("foo", FooMethods);
}
/* Finally, let's do something ... involved ... as an example function. */
static PyObject *foo_bar(PyObject *self, PyObject *args)
{
  char *string;
  int  len;
  if (!PyArg_ParseTuple(args, "s", &string))
    return NULL;
  len = strlen(string);
  return Py_BuildValue("i", len);
}

深入研究

我们来看会儿这些代码。首先,请注意您必须包含 Python.h 。除非您已在包含路径(include path)中设置了该文件的路径,否则您可能需要在安装文件中包含 -I 标志以指向该文件。

初始化函数必须命名为 init <模块名>,在我们的例子中是 initfoo 。初始化函数的名称,毫无疑问,是 Python 在装入模块时所知道的关于模块的全部信息,这也是初始化函数的名称如此死板的原因。顺便说一下,初始化函数必须是文件中唯一未被声明为 static 的全局标识符。这对静态链接比对动态链接更重要,因为非 static 标识符将是全局可见的。对动态链接来说,这不是一个很大的问题,但如果您打算在编译期间链接所有东西,又没有把所有可以声明为 static 的东西声明为 static ,那么您很可能就会碰到名称冲突的问题。

现在我们来观察实际的代码,看看参数是怎样被处理的,返回值又是怎样被传递的。当然,一切都是 PyObject ― Python 堆上的对象。您从参数中得到的是一个对“this”对象的引用(this 用于对象方法,对类似 bar() 这样的无参数的老式函数来说是 NULL)和一个存储在 args 中的参数元组。您用 PyArg_ParseTuple 找回您的参数,然后用 Py_BuildValue 把结果传回去。这些函数(还有更多)都归档在 Python 文档的“Python/C API”部分中。不幸的是,没有按名称排列的简单的函数清单,文档是按主题排列的。

另请注意,函数在出错的情况下返回 NULL。返回 NULL 表示出错了;如果想让 Python 做得更好,您应该抛出异常。我会指点您去查阅关于如何做这件事的文档。

编译扩展

现在剩下的全部问题是编译模块。您可以通过两种方式进行。第一种是按照文档中的指导,运行 make -f Makefile.pre.in boot ,这样将会使用您的 Setup 来编译一个 Makefile。然后您就用该 Makefile 编译您的工程。这种方式只适用于 UNIX。对 Windows 来说,存在一个叫“compile.py”的脚本(请参阅本文后面的 参考资料)。原始脚本很难找到;我从一个邮件列表中找到了一个来自 Robin Dunn(wxPython 的幕后工作者)的被大量改动了的副本。这个脚本能在 UNIX 和 Windows 上工作;在 Windows 上,它将从您的 Setup 开始编译 MSVC++ 工程文件。

要进行编译,您必须使包含的文件和库都可用。Python 的标准 Zope 安装没有包含这些文件,因此您需要从 www.python.org(请参阅 参考资料)安装 Python 的常规安装。在 Windows 上,您还必须从源代码安装的 PC 目录中获取 config.h 文件;它是 UNIX 安装为您编译的 config.h 的手工版。因此,在 UNIX 上,您应该已经拥有它了。

一旦这些都完成后,您就会得到一个以“.pyd”为扩展名的文件。把这个文件放到 Python 安装目录下的“lib”目录(在 Zope 下,Python 位于“bin”目录,因此您的扩展得结束于“bin/lib”目录,奇怪吧。)然后您就可以调用它了,就像调用任何源生的 Python 模块一样。

 >>> import foo;
 >>> foo.bar ("This is a test");
 14

做到这里时,我的第一个问题是问自己该如何用 C 定义从 Python 中可见的 类。事实上,我可能问了一个错误的问题。在我已研究的示例中,特定于 Python 的一切都只 用 Python 来完成,也都只调用从您的扩展中导出的 C 函数。

把它带到 Zope 中去

一旦完成了您的 Python 扩展,下一步就是使 Zope 能和它一起工作。您有几种方式可以选择,但在一定程度上,您希望您的扩展以什么方式与 Zope 一起工作将首先影响到您编译扩展的方式。从 Zope 内使用 Python(以及用 C 所做的扩展)代码的基本方式是:

  • 如果函数很简单,您可以把它当作一个变量。这些被叫做“外部方法”。
  • 更复杂的类,可以从 Zope 脚本中调用(这是 Zope 2.3 的一个新功能)。
  • 您可以定义一个 Zope Product,然后可以用 ZClass(一组已做好的、Web 可访问的对象)扩展它,在脚本中使用它,根据它的自有权限发布它(它的实例被当作页来对待)。

当然,您自己的应用程序可以使用这些方式的组合。

创建外部方法

从 Zope 调用 Python 的最简单的方式是把您的 Python 代码做成 外部方法。外部方法是被放到 Zope 安装目录下的“Extensions”目录中的 Python 函数。一旦那里有了这样一个 Python 文件,您就可以转到任意文件夹,选择“添加外部方法”,并添加调用要使用的函数的变量。然后您就可以往该文件夹中显示调用结果的任意页添加 DTML 字段。我们来看一个使用了上面所定义的 Python 扩展 ― foo.bar ― 的简单示例。

首先,来看扩展本身:我们把它放到一个例如叫 foo.pyd 的文件中。记住,这个文件位于 Zope 下的 Extensions 目录。为了能够顺利进行,当然,我们在上面创建的 foo.pyd 必须在位于 bin/lib 的 Python 库中。一个出于这个目的的、简单的包看起来可能像这样:
清单 3. 一个简单的外部方法(文件:Extensions/foo.py)

import foo
def bar(self,arg):
  """A simple external method."""
  return 'Arg length: %d' % foo.bar(arg)

很简单,不是吗?它定义了一个可以用 Zope 管理界面附加到任意文件夹的外部方法“bar”。要从该文件夹中的任何页中调用我们的扩展,我们只需简单地插入一个 DTML 变量引用,如下所示:

 <dtml-var bar('This is a test')>

当用户查看我们的页时,DTML 字段将被文本“Arg length: 14”代替。我们就这样用 C 扩展了 Zope。

Zope 脚本:Cliff Notes 版

Zope 脚本是 Python 2.3 的一个想用来代替外部方法的新功能。外部方法能做到的,它都能做到,而且它能和安全性及管理系统更好地集成,在集成方面提供更多的灵活性,它还有很多对 Zope API 中公开的全部 Zope 功能的访问。

一个脚本基本上就是一个短小的 Python 程序。它可以定义类或函数,但不是必须的。它被作为对象安装在 Zope 文件夹中,然后就可以把它当作 DTML 变量或调用(就像一个外部方法)来调用或者“从 Web 中”(在 Zope 中的意思就是它将被当作页来调用)调用它。当然,这意味着脚本可以像 CGI 程序那样生成对表单提交的响应,但却没有 CGI 的开销。确实是一个很棒的功能。此外,脚本有权访问被调用者或调用者对象(通过“context”对象)、对象所在的文件夹(通过“container”对象)和其他一些零碎信息。要获得更多关于脚本的知识,请参阅 Zope 手册(请参阅 参考资料)中的“高级 Zope 脚本编制(Advanced Zope Scripting)”那一章。

您可能会错误地认为可以直接从脚本简单地导入 foo 并使用 foo.bar(我知道我确实犯过这种错误)。但事实并非如此。由于安全性限制,只有 Product 可以被导入,而不是什么模块都可以。一般而言,Zope 的设计者们认为任何脚本编制都需要访问文件系统,既然脚本对象是由 Web 使用 Zope 管理界面来管理,所以它们不是完全可信的。所以我打算就此打住,不给您展示示例脚本了,而是来讨论 Product 和基础类。

专注于 Product

Product 是扩展 Zope 的强大工具方法。从安装目录的级别来看,Product 就是位于 Zope 目录下的“lib/python/Products”目录中的一个目录。在您自己的 Zope 安装目录中,您可以看到很多 product 示例,但本质上,最小的 Product 只由位于该目录的两个文件组成:一个可任意命名的代码文件和一个 Zope 在启动时调用来初始化 Product 的称为 __init__.py 的文件。(请注意:Zope 只在启动时读取 Product 文件,这意味着为了测试,您必须能够停止和重新启动 Zope 进程)。本文只是尽量多提供一些您能通过使用 Zope Product 做到的事的提示。

要知道的是 Product 封装了一个或多个可从 ZClass、脚本或直接从 Web 上的 URL 使用的类。(当然,在最后一种情况下,Product 的实例被当作文件夹看待;那么 URL 的最后部分指定了将被调用的方法,该方法返回任意的 HTML。)您不必一定要把 Product 当作“可添加的”对象来对待,虽然这是它的主要目的。要看一个优秀的、现实存在的示例,可以去看 ZCatalog 实现,它是标准 Zope 分发的一部分。那里您可以在 __init__.py 中看到一个非常简单的安装脚本,可以在 ZCatalog.py 中看到 ZCatalog 类,该类提供了很多发布方法。请注意 Zope 采用一种奇怪的约定来确定哪些方法可以通过 Web 访问 ― 如果一个方法包含有一个 doc 字符串,那么该方法可通过 Web 访问;否则,就被认为是私有的。

无论如何,我们还是来看一个使用了 C 模块(我们在上面定义了它)的非常简单的 Product。首先来看非常简单的 __init__.py;请注意它只做了一件事,即告诉 Zope 我们正在安装的类的名称。更复杂的初始化脚本能做 更多的事,包括声明由服务器维护的全局变量以及设置访问权限等等。欲了解更多详细信息,请参阅在线文档中的 Zope 开发者指南,也请研究您的 Zope 安装目录中现成的 Product。您或许已经猜到了,我们的示例 Product 被称为“Foo”。这样您就将在 lib/python/Products 目录下创建一个 Foo 子目录。
清单 4. 基本的 Product 初始化脚本

import Foo
def initialize(context):
  context.registerClass(
    Foo.Foo,
    permission='Add Foo',
    constructors=Foo.manage_addFoo
    )

现在请注意这个初始化脚本不仅导入了那个类,使它可被 Zope 的其它部件访问,而且还将该类注册成具有“可添加性”。 context.registerClass 调用通过首先命名我们所导入的类,然后指定可被用于添加实例的方法名称(这个方法必须显示一个管理页面,且该方法将自动与 Zope 管理界面集成)的名称来完成这项工作。酷。

我们来小结一下这个短小、简单的 Product。它会把我们的 foo.bar 函数公开给脚本和 ZClass,并且还有一个作为“可添加的”对象的小接口,这就是全部内容。
清单 5. 一个简单的 Zope Product

import foo
class Foo(SimpleItem.Item):
 "A Foo Product"
 meta_type = 'foo'
 def bar(self, string):
   return foo.bar(string)
 def __init__(self, id):
   "Initialize an instance"
   self.id = id
 def index_html(self):
   "Basic view of object"
   return '
My id is %s and its length is %d.
' % (self.id, foo.bar(self.id))
 def manage_addFoo(self, RESPONSE):
   "Management handler to add an instance to a folder."
   self._setObject('Foo_id', Foo('Foo_id'))
   RESPONSE.redirect('index_html')

这只是一个最简单的 Product。不能绝对地说它是可能的 Product 中最小的一个,但已经很接近了。不过,它确实说明了 Product 的一些关键特征。首先,请注意“index_html”方法:它被调用来显示一个对象实例,这是通过构建 HTML 完成的。它实际上是一个页面。 manage_addFoo 方法是 Zope 对象管理的接口;我们在上面的 __init__.py 中引用了它。“__init__”方法初始化对象;实际上它 必须做的全部工作就是记录实例的唯一标识符。

这个微型的 Product 不和 Zope 安全性进行交互操作。它不做很多管理工作。它没有交互功能。所以您可以给它添加很多东西(甚至连很有用的功能它也没有)。我希望这对您是一个很好的开始。

以后该做什么

对 Zope Product 的简单介绍已经告诉您如何把 C 语言函数从 C 代码变为 Zope 中可用的。要学会怎么写 Product,您还得阅读更多文档(其中有很多仍在完善之中),坦率地说,还要研究已有的 Product,看看它们是怎么做的。Zope 模型有很强大的功能和很大的灵活性,它们都很值得探究。

我目前正在做集成 C 和 Zope 的大工程:集成我的工作流工具包(workflow toolkit)。在本文发表之前,我希望能看到它的雏形。它已被列在下面的参考资料中,去看看吧;到您阅读本文时,应该已经能够从中找到一个扩展示例。祝我好运。

(0)

相关推荐

  • 使用Protocol Buffers的C语言拓展提速Python程序的示例

    Protocol Buffers (类似XML的一种数据描述语言)最新版本2.3里,protoc-py_out命令只生成原生的Python代码. 尽管PB(Protocol Buffers)可以为C++语言生成快速解析和序列化代码,但是这种方式对于Python不适用,并且手动生成的已包装的代码需要非常大的维护工作.在讨论组里,这是一个常见的功能要求,由于一个必备的客户端组件-AppEngine(根据团队介绍名称为AppEngine),生成原生的Python代码有更高的优先级. 幸运的是, PB

  • Python调用C语言开发的共享库方法实例

    在helloworld工程中,编写了一个简单的两个数值相加的程序,编译成为共享库后,如何使用python对其进行调用呢? 使用ll命令列出当前目录下的共享库,其中共享库名为libhelloworld.so.0.0.0 复制代码 代码如下: ufo@ufo:~/helloworld/.libs$ ll 总用量 32 drwxr-xr-x 2 ufo ufo 4096  1月 29 14:54 ./ drwxr-xr-x 6 ufo ufo 4096  1月 29 16:08 ../ -rw-r--

  • 如何用C语言、Python实现栈及典型应用

    前言 栈是什么,你可以理解为一种先入后出的数据结构(First In Last Out),一种操作受限的线性表... C实现 借助与C语言中的void指针及函数指针,我们可以实现一个链式通用栈: /* stack.h */ #ifndef _STACK_H_ #define _STACK_H_ typedef struct stackNode { void *value; struct stackNode *next; } stackNode; typedef struct stack { st

  • 通过实例浅析Python对比C语言的编程思想差异

    我一直使用 Python,用它处理各种数据科学项目. Python 以易用闻名.有编码经验者学习数天就能上手(或有效使用它). 听起来很不错,不过,如果你既用 Python,同时也是用其他语言,比如说 C 的话,或许会存在一些问题. 给你举个我自己经历的例子吧. 我精通命令式语言,如 C 和 C++.对古老经典的语言如 Lisp 和 Prolog 能熟练使用.另外,我也用过 Java,Javascript 和 PHP 一段时间.(那么,学习) Python 对我来讲不是很简单吗?事实上,只是看起

  • 用C语言模仿Python函数的一种简单实现方法

    首先得说明一点,C 语言不是函数式编程语言,要想进行完全的函数式编程,还得先写个虚拟机,然后再写个解释器才行(相当于 CPython ). 下面我们提供一个例子,说明 C 语言函数可以"适度地模仿" Python 函数. 我们有如下的 Python 程序: def line_conf(a, b): def line(x): return a*x + b return line line1 = line_conf(1, 1) line2 = line_conf(4, 5) print(l

  • python和C语言混合编程实例

    最近为了测试网速情况怎么样,由于部分业务服务器需要关闭icmp,这样的话采用普通的ping就无法适应我的需求,于是自己简单的写了一个基于tcp端口的ping的程序,由于c执行效率比较的不错,但是开发效率低下,而python是开发效率高,但是执行效率不如C,由于需要大规模的使用,于是用C实现核心部分的代码,并把这部分实现成一个python的模块,由python调用c的模块,下面就贴代码吧 复制代码 代码如下: /* tcpportping.c */#include <Python.h>#incl

  • 使用C语言扩展Python程序的简单入门指引

    一.简介 Python是一门功能强大的高级脚本语言,它的强大不仅表现在其自身的功能上,而且还表现在其良好的可扩展性上,正因如此,Python已经开始受到越来越多人的青睐,并且被屡屡成功地应用于各类大型软件系统的开发过程中. 与其它普通脚本语言有所不同,Python程序员可以借助Python语言提供的API,使用C或者C++来对Python进行功能性扩展,从而即可以利用Python方便灵活的语法和功能,又可以获得与C或者C++几乎相同的执行性能.执行速度慢是几乎所有脚本语言都具有的共性,也是倍受人

  • 使用C语言来扩展Python程序和Zope服务器的教程

    有几个原因使您可能想用 C 扩展 Zope.最可能的是您有一个已能帮您做些事的现成的 C 库,但是您对把它转换成 Python 却不感兴趣.此外,由于 Python 是解释性语言,所以任何被大量调用的 Python 代码都将降低您的速度.因此,即使您已经用 Python 写了一些扩展,您仍然要考虑把其中最常被调用的部分改用 C 来写.不论哪种方式,扩展 Zope 都是从扩展 Python 开始.此外,扩展 Python 会给您带来其它的好处,因为您的代码将可以从任何 Python 脚本访问,而不

  • 用Python编写分析Python程序性能的工具的教程

    虽然并非你编写的每个 Python 程序都要求一个严格的性能分析,但是让人放心的是,当问题发生的时候,Python 生态圈有各种各样的工具可以处理这类问题. 分析程序的性能可以归结为回答四个基本问题: 正运行的多快 速度瓶颈在哪里 内存使用率是多少 内存泄露在哪里 下面,我们将用一些神奇的工具深入到这些问题的答案中去. 用 time 粗粒度的计算时间 让我们开始通过使用一个快速和粗暴的方法计算我们的代码:传统的 unix time 工具. $ time python yourprogram.py

  • 使用优化器来提升Python程序的执行效率的教程

    如果不首先想想这句Knuth的名言,就开始进行优化工作是不明智的.可是,你很快写出来加入一些特性的代码,可能会很丑陋,你需要注意了.这篇文章就是为这时候准备的. 那么接下来就是一些很有用的工具和模式来快速优化Python.它的主要目的很简单:尽快发现瓶颈,修复它们并且确认你修复了它们. 写一个测试 在你开始优化前,写一个高级测试来证明原来代码很慢.你可能需要采用一些最小值数据集来复现它足够慢.通常一两个显示运行时秒的程序就足够处理一些改进的地方了. 有一些基础测试来保证你的优化没有改变原有代码的

  • 详解在Python程序中使用Cookie的教程

    大家好哈,上一节我们研究了一下爬虫的异常处理问题,那么接下来我们一起来看一下Cookie的使用. 为什么要使用Cookie呢? Cookie,指某些网站为了辨别用户身份.进行session跟踪而储存在用户本地终端上的数据(通常经过加密) 比如说有些网站需要登录后才能访问某个页面,在登录之前,你想抓取某个页面内容是不允许的.那么我们可以利用Urllib2库保存我们登录的Cookie,然后再抓取其他页面就达到目的了. 在此之前呢,我们必须先介绍一个opener的概念. 1.Opener 当你获取一个

  • 使用Pyrex来扩展和加速Python程序的教程

    Pyrex 是一种专门设计用来编写 Python 扩展模块的语言.根据 Pyrex Web 站点的介绍,"它被设计用来在友好易用的高级 Python 世界和凌乱的低级 C 世界之间搭建一个桥梁."虽然几乎所有的 Python 代码都可以作为有效的 Pyrex 代码使用,但是您可以在 Pyrex 代码中添加可选的静态类型声明,从而使得这些声明过的对象以 C 语言的速度运行. 加速 Python 从某种意义上来说,Pyrex 只是不断发展的 Python 类语言系列的一个部分:Jython

  • python程序文件扩展名知识点详解

    python程序文件的扩展名称是什么 python程序的扩展名有.py..pyc..pyo和.pyd..py是源文件,.pyc是源文件编译后的文件,.pyo是源文件优化编译后的文件,.pyd是其他语言写的python库. 扩展名 在写Python程序时我们常见的扩展名是py, pyc,其实还有其他几种扩展名.下面是几种扩展名的用法. py:py就是最基本的源码扩展名.windows下直接双击运行会调用python.exe执行. pyw:pyw是另一种源码扩展名,跟py唯一的区别是在windows

  • Python程序语言快速上手教程

    本来打算从网上找一篇入门教程,但因为Python很少是程序员的第一次接触程序所学的语言,所以网上现有的教程多不是很基础,还是决定自己写下这些. 如果没有程序基础的话,可能会觉得本文涵盖的内容有点多.对照大学里面常教的C语言的教学速度,本文大约有四五个课时的内容:对照网上程序类的视频 教程,大致相当于两三个小时的内容:对于翻一本程序书籍,大约相当于翻一个小时书.也因此,如果有深入学习的打算的话,为了效率还是推荐看书. 如果暂时不能理解本文中的一些内容也没关系,因为都是一些经常会用到的基础知识,在实

  • 提升Python程序运行效率的6个方法

    Python是一个很酷的语言,因为你可以在很短的时间内利用很少的代码做很多事情.不仅如此,它还能轻松地支持多任务,比如多进程等.Python批评者有时会说Python执行缓慢.本文将尝试介绍6个技巧,可加速你的Python应用程序. 1.让关键代码依赖于外部包 虽然Python让许多编程任务变得容易,但它可能并不总能为紧急的任务提供最佳性能.你可以为紧急的任务使用C.C++或机器语言编写的外部包,这样可以提高应用程序的性能.这些包都是不能跨平台的,这意味着你需要根据你正在使用的平台,寻找合适的包

随机推荐