搜索
首页后端开发php教程 [翻译][php扩展开发和嵌入式]第6章-返回值

返回值

用户空间函数利用return关键字向它的调用空间回传信息, 这一点和C语言的语法相同.

例如:

function sample_long() {  
  return 42;  
}  
$bar = sample_long();

当sample_long()被调用时, 返回42并设置到$bar变量中. 在C语言中的等价代码如下:

int sample_long(void) {  
  return 42;  
}  
void main(void) {  
  int bar = sample_long();  
}

当然, 在C语言中你总是知道被调用的函数是什么, 并且基于函数原型返回, 因此相应的你要定义返回结果存储的变量. 在php用户空间处理时, 变量类型是动态的, 转而依赖于第2章"变量的里里外外"中介绍的zval的类型.

return_value变量

你可能认为你的内部函数应该直接返回一个zval, 或者说分配一个zval的内存空间并如下返回zval *.

PHP_FUNCTION(sample_long_wrong)  
{  
    zval *retval;  
  
    MAKE_STD_ZVAL(retval);  
    ZVAL_LONG(retval, 42);  
  
    return retval;  
}

不幸的是, 这样做是不正确的. 并不是强制每个函数实现分配zval并返回它. 而是Zend引擎在函数调用之前预先分配这个空间. 接着将zval的类型初始化为IS_NULL, 并将值作为参数名return_value传递. 下面是正确的做法:

PHP_FUNCTION(sample_long)  
{  
    ZVAL_LONG(return_value, 42);  
    return;  
}

要注意的是PHP_FUNCTION()实现并不会直接返回任何值. 取而代之的是直接将恰当的数据弹出到return_value参数中, Zend引擎会在内部函数执行完成后处理它.

友情提示: ZVAL_LONG()宏是对多个赋值操作的一个封装:

Z_TYPE_P(return_value) = IS_LONG;  
Z_LVAL_P(return_value) = 42;

或者更直接点:

return_value->type = IS_LONG;  
return_value->value.lval = 42;

return_value的is_ref和refcount属性不应该被内部函数直接修改. 这些值由Zend引擎在调用你的函数时初始化并处理.

现在我们来看看这个特殊的函数, 将它增加到第5章"你的第一个扩展"中创建的sample扩展中. 只需要在sample_hello_world()函数下面添加这个函数, 并将sample_long()加入到php_sample_functions结构体中:

static function_entry php_sample_functions[] = {  
    PHP_FE(sample_hello_world, NULL)  
    PHP_FE(sample_long, NULL)  
    { NULL, NULL, NULL }  
};

现在我们就可以执行make重新构建扩展了.

如果一切OK, 可以运行php并测试新函数:

$ php -r 'var_dump(sample_long());

包装更紧凑的宏

在代码可读性和可维护性方面, ZVAL_*()宏中有重复的部分: return_value变量. 这种情况下, 将宏的ZVAL替换为RETVAL, 我们就可以在调用时省略return_value了.

前面的例子中, sample_long()的实现代码可以缩减到下面这样:

PHP_FUNCTION(sample_long)  
{  
    RETVAL_LONG(42);  
    return;  
}

下表列出了Zend引擎中RETVAL一族的宏. 除了两个特殊的, RETVAL宏除了删除了return_value参数之外, 和对应的ZVAL族宏相同.

1019.png


要注意到, TRUE和FALSE宏没有括号. 这是考虑到了Zend/PHP代码标准的偏差, 保留了主要的一种以保持向后兼容. 如果你构建扩展失败, 收到了错误消息undefined macro RETVAL_TRUE(), 请确认你是否在代码中写这两个宏时误写了括号.

通常, 在你的函数处理返回值的时候, 它已经准备好退出并将控制返回给调用作用域了. 由于这个原因, 为内部函数设计了另外一些宏用于返回: RETURN_*()族宏.

PHP_FUNCTION(sample_long)  
{  
    RETURN_LONG(42);  
}

尽管看不到, 但这个函数在RETURN_LONG()宏调用完后的确会返回. 我们可以在函数末尾增加php_printf()进行测试:

PHP_FUNCTION(sample_long)  
{  
    RETURN_LONG(42);  
    php_printf("I will never be reached.\n");  
}

php_printf(), 如内容所描述的, 因为RETURN_LONG()调用隐式的结束了函数.

和RETVAL系列一样, 前面表中列出的每个简单类型都有对应的RETURN宏. 同样和RETVAL系列一样, RETURN_TRUE和RETURN_FALSE宏不使用括号.

更加复杂的类型, 比如对象和数组, 同样是通过return_value参数返回的; 然而, 它们天生就不能通过简单的宏创建. 即便是资源类型, 虽然它有RETVAL宏, 但是真正要返回资源类型还需要额外的工作. 你将在第8章到第11章看到怎样返回这些类型.

值得这么麻烦吗?

一个尚未使用的Zend内部函数特性是return_value_used参数. 考虑下面的用户空间代码:

function sample_array_range() {  
    $ret = array();  
    for($i = 0; $i < 1000; $i++) {  
        $ret[] = $i;  
    }  
    return $ret;  
}  
sample_array_range();

因为sample_array_range()调用的时候并没有将结果存储到变量中, 这里创建使用的1000个元素的数组空间将被完全浪费. 当然, 这样调用sample_array_range()是愚蠢的, 但是没有很好的办法预知未来你又能怎么样呢?

虽然无法访问用户空间函数, 内部函数可以依赖于所有内部函数公共的return_value_used参数设置, 条件式的跳开这样的无意义行为.

PHP_FUNCTION(sample_array_range)  
{  
    if (return_value_used) {  
        int i;  
        /* 返回从0到999的数组 */  
        array_init(return_value);  
        for(i = 0; i < 1000; i++) {  
            add_next_index_long(return_value, i);  
        }  
        return;  
    } else {  
        /* 提示错误 */  
        php_error_docref(NULL TSRMLS_CC, E_NOTICE,  
               "Static return-only function called without processing output");  
        RETURN_NULL();  
    }  
}

要看到这个函数的操作, 只需要在你的sample.c源文件中增加这个函数, 并将它暴露在php_sample_functions结构中:

PHP_FE(sample_array_range, NULL)

译注: 关于用户空间不使用的函数返回值怎么处理, 可以阅读Zend/zend_vm_execute.h中的zend_do_fcall_common_helper_SPEC函数, 它在处理完内部函数调用后, 会检查该函数的返回值是否被使用, 如果不使用, 则进行了相应的释放.

返回引用值

从用户空间的php工作中你可能已经知道了, php函数还可以以引用方式返回值. 由于实现问题, 在php 5.1之前应该避免在内部函数中返回引用, 因为它不能工作. 考虑下面的用户空间代码片段:

function &sample_reference_a() {  
    /* 如果全局空间没有变量$a, 就以初始值NULL创建它 */  
    if (!isset($GLOBALS[&#39;a&#39;])) {  
        $GLOBALS[&#39;a&#39;] = NULL;  
    }  
    return $GLOBALS[&#39;a&#39;];  
}  
$a = &#39;Foo&#39;;  
$b = sample_reference_a();  
$b = &#39;Bar&#39;;

在这个代码片段中, 就像使用$b = &$GLOBALS['a];或者由于在全局空间, 使用$b = &$a; 将$b创建为$a的一个引用.

回顾第3章"内存管理", 在到达最后一行时, $a和$b实际上包含相同的值'Bar'. 现在我们看看内部实现这个函数:

#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && \  
                          PHP_MINOR_VERSION > 0)  
PHP_FUNCTION(sample_reference_a)  
{  
    zval **a_ptr, *a;  
  
  
    /* 从全局符号表查找变量$a */  
    if (zend_hash_find(&EG(symbol_table), "a", sizeof("a"),  
                                          (void**)&a_ptr) == SUCCESS) {  
        a = *a_ptr;  
    } else {  
        /* 如果不存在$GLOBALS[&#39;a&#39;]则创建它 */  
        ALLOC_INIT_ZVAL(a);  
        zend_hash_add(&EG(symbol_table), "a", sizeof("a"), &a,  
                                              sizeof(zval*), NULL);  
    }  
    /* 废弃旧的返回值 */  
    zval_ptr_dtor(return_value_ptr);  
    if (!a->is_ref && a->refcount > 1) {  
        /* $a需要写时复制, 在使用之前, 必选先隔离 */  
        zval *newa;  
        MAKE_STD_ZVAL(newa);  
        *newa = *a;  
        zval_copy_ctor(newa);  
        newa->is_ref = 0;  
        newa->refcount = 1;  
        zend_hash_update(&EG(symbol_table), "a", sizeof("a"), &newa,  
                                                 sizeof(zval*), NULL);  
        a = newa;  
    }  
    /* 将新的返回值设置为引用方式并增加refcount */  
    a->is_ref = 1;  
    a->refcount++;  
    *return_value_ptr = a;  
}  
#endif /* PHP >= 5.1.0 */

return_value_ptr参数是所有内部函数都会传递的另外一个公共参数, 它是zval **类型, 包含了指向return_value的指针. 通过在它上面调用zval_ptr_dtor(), 默认的return_value的zval *将被释放. 接着可以自由的选择一个新的zval *去替代它, 在这里选择了变量$a, 选择性的进行zval隔离后, 将它的is_ref设置为1, 设置到return_value_ptr中.

如果现在编译运行这段代码, 无论如何你会得到一个段错误. 为了使它可以工作, 你需要在php_sample.h中增加下面的代码:

#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && \  
                          PHP_MINOR_VERSION > 0)  
static  
    ZEND_BEGIN_ARG_INFO_EX(php_sample_retref_arginfo, 0, 1, 0)  
    ZEND_END_ARG_INFO ()  
#endif /* PHP >= 5.1.0 */

译注: 译者使用的php-5.4.9中, ZEND_BEGIN_ARG_INFO_EX的宏定义中已经包含了static修饰符, 因此本书示例中相应的需要进行修改, 请读者在阅读过程中注意这一点.

接着, 在php_sample_functions中声明你的函数时使用这个结构:

#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && \  
                          PHP_MINOR_VERSION > 0)  
    PHP_FE(sample_reference_a, php_sample_retref_arginfo)  
#endif /* PHP >= 5.1.0 */

这个结构你将在本章后面详细学习, 用来向Zend引擎提供函数调用的重要暗示信息. 这里它告诉Zend引擎return_value需要被覆写, 应该从return_value_ptr中得到正确的地址. 如果没有这个暗示, Zend引擎会简单的在return_value_ptr中设置NULL, 这可能使得在执行到zval_ptr_dtor()时程序崩溃.

这段代码每一个片段都包裹在#if块中, 它指示编译器只有在PHP版本大于等于5.1时才启用这个支持. 如果没有这些条件指令, 这个扩展将不能在php4上编译(因为在return_value_ptr中包含的一些元素不存在), 在php5.0中不能提供正确的功能(有一个bug导致返回的引用被以值的方式拷贝)

引用方式返回值

使用return(语法)结构将值和变量回传给调用方是没有问题的, 但是, 有时你需要从一个函数返回多个值. 你可以使用数组(我们将在第8章"使用数组和哈希表工作")达到这个目的, 或者你可以使用参数栈返回值.

调用时引用传值

一种简单的引用传递变量方式是要求调用时在参数变量名前使用取地址符(&), 如下用户空间代码:

function sample_byref_calltime($a) {  
    $a .= &#39; (modified by ref!)&#39;;  
}  
$foo = &#39;I am a string&#39;;  
sample_byref_calltime(&$foo);  
echo $foo;

参数变量名前的取地址符(&)使得发送给函数的是$foo实际的zval, 而不是它的内容拷贝.这就使得函数可以通过传递的这个参数修改这个值来返回信息. 如果调用sample_byref_calltime()时没有在$foo前面使用取地址符(&), 则函数内的修改并不会影响原来的变量.

在C层面重演这个行为并不需要特殊的编码. 在你的sample.c源码文件中sample_long()后面创建下面的函数:

PHP_FUNCTION(sample_byref_calltime)  
{  
    zval *a;  
    int addtl_len = sizeof(" (modified by ref!)") - 1;  
  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &a) == FAILURE) {  
        RETURN_NULL();  
    }  
    if (!a->is_ref) {  
        /* 不是以引用方式传值则不做任何事离开函数 */  
        return;  
    }  
    /* 确保变量是字符串 */  
    convert_to_string(a);  
    /* 扩大a的缓冲区以使可以保存要追加的数据 */  
    Z_STRVAL_P(a) = erealloc(Z_STRVAL_P(a),  
        Z_STRLEN_P(a) + addtl_len + 1);  
    memcpy(Z_STRVAL_P(a) + Z_STRLEN_P(a),  
    " (modified by ref!)", addtl_len + 1);  
    Z_STRLEN_P(a) += addtl_len;  
}

和往常一样, 这个函数需要增加到php_sample_functions结构中.

PHP_FE(sample_byref_calltime,        NULL)

译注: 运行时引用传值在译者使用的版本(php-5.4.9 ZendEngine 2.4.0)中已经被完全废弃. 早前的版本可以在php.ini中使用allow_call_time_pass_reference指令启用. 测试时请注意版本问题.

编译期引用传值

更常用的方式是编译期引用传值. 这里函数的参数定义为只能以引用方式使用, 传递常量或其他中间值(比如函数调用的结果)将导致错误, 因为那样函数就没有地方可以存储结果值去回传了. 用户空间的编译期引用传值代码如下:

function sample_byref_compiletime(&$a) {  
    $a .= &#39; (modified by ref!)&#39;;  
}  
$foo = &#39;I am a string&#39;;  
sample_byref_compiletime($foo);  
echo $foo;

如你所见, 这和调用时引用传值的差别仅在于取地址符的位置不同. 在C的层面上去看这个函数时, 函数代码上是完全相同的. 唯一的区别在于php_sample_functions块中对函数的声明:

PHP_FE(sample_byref_compiletime, php_sample_byref_arginfo)

php_sample_byref_arginfo是一个常量结构, 你需要在使用之前先定义它.

实际上, 编译期引用传值中, 对is_ref检查的代码可以删除, 因为总能保证它是引用方式的. 不过这里留着它也不会有什么危害.

在Zend引擎1(php4)中, 这个结构是一个简单的char *列表, 第一个元素指定了长度, 接下来是描述每个函数参数的标记集合.

static unsigned char php_sample_byref_arginfo[] =  
                                { 1, BYREF_FORCE };

这里, 1表示向量只包含了一个参数的信息. 后面的元素就顺次描述参数特有的标记信息, 第二个元素描述第一个参数. 如果涉及到第二个或第三个参数, 对应的就需要在这个向量中增加第三个和第四个元素. 参数信息的可选值如下表:

1020.png

在Zend引擎2(php 5+)中, 你将使用一种更加可扩展的结构, 它包含类更多的信息, 比如最小和最大参数要求, 类型暗示, 是否强制引用等.

首先, 参数信息结构使用两个宏中的一个定义. 较简单的一个是ZEND_BEGIN_ARG_INFO(), 它需要两个参数:

ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference)

name非常简单, 就是扩展中其他地方使用这个结构时的名字, 当前这个例子我们使用的名字是: php_sample_byref_arginfo

pass_rest_by_reference的含义和BYREF_FORCE_REST用在Zend引擎1的参数信息向量最后一个元素时一致. 如果这个参数设置为1, 所有没有在结构中显式描述的参数都被认为是编译期引用传值的参数.

还有一种可选的开始宏, 它引入了两个Zend引擎1没有的新选项, 它是ZEND_BEGIN_ARG_INFO_EX():

ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference,  
                             required_num_args)

当然, name和pass_rest_by_reference和前面所说的是相同的含义. 本章前面也提到了, return_reference是告诉Zend你的函数需要用自己的zval覆写return_value_ptr.

最后一个参数, required_num_args, 它是另外一种类型的暗示, 用来告诉Zend在某种被认为是不完整的调用时完全的跳过函数调用.

在你拥有了一个恰当的开始宏后, 接下来就可以是0个或多个ZEND_ARG_*INFO元素. 这些宏的类型和用法如下表. 

1021.png

最后, 所有使用Zend引擎2的宏设置的参数信息结构必须使用ZEND_END_ARG_INFO()结束. 对于你的sample函数, 你需要选择一个如下的结构:

ZEND_BEGIN_ARG_INFO(php_sample_byref_arginfo, 0)  
    ZEND_ARG_PASS_INFO(1)  
ZEND_END_ARG_INFO()

为了让扩展兼容Zend引擎1和2, 需要使用#ifdef语句为两者均定义arg_info结构:

#ifdef ZEND_ENGINE_2  
static  
    ZEND_BEGIN_ARG_INFO(php_sample_byref_arginfo, 0)  
        ZEND_ARG_PASS_INFO(1)  
    ZEND_END_ARG_INFO()  
#else /* ZE 1 */  
static unsigned char php_sample_byref_arginfo[] =  
                                { 1, BYREF_FORCE };  
#endif

注意, 这些代码片段是集中在一起的, 现在是时候创建一个真正的编译期引用传值实现了. 首先, 我们将为Zend引擎1和2定义的php_sample_byref_arginfo块放到头文件php_sample.h中.

接下来, 可以有两种选择, 一种是将PHP_FUNCTION(sample_byref_calltime)拷贝一份, 并重命名为PHP_FUNCTION(sample_byref_compiletime), 接着在php_sample_functions中增加一行PHP_FE(sample_byref_compiletime, php_sample_byref_arginfo)

这种方式简单移动, 并且在一段时候后修改时, 更加不容易产生混淆. 因为这里只是示例代码, 因此, 我们可以稍微放松点, 使用你在上一章学的PHP_FALIAS()避免代码重复.

这样, 就不是赋值PHP_FUNCTION(sample_byref_calltime), 而是在php_sample_functions中直接增加一行:

PHP_FALIAS(sample_byref_compiletime, sample_byref_calltime,  
    php_sample_byref_arginfo)

回顾第5章, 这将创建一个名为sample_byref_compiletime()的用户空间函数, 它对应的内部实现是sample_byref_calltime()的代码. php_sample_byref_arginfo是这个版本的特殊之处.

小结

本章你看到了怎样从一个内部函数返回值, 包括直接返回值和引用方式返回, 以及通过参数栈引用返回. 此外还简单了解了Zend引擎2的参数类型暗示结构zend_arg_info.

下一章你将会继续探究接受基本的zval参数以及使用zend_parse_parameters()强大的类型戏法.

以上就是 [翻译][php扩展开发和嵌入式]第6章-返回值的内容,更多相关内容请关注PHP中文网(www.php.cn)!


声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
PHP的目的:构建动态网站PHP的目的:构建动态网站Apr 15, 2025 am 12:18 AM

PHP用于构建动态网站,其核心功能包括:1.生成动态内容,通过与数据库对接实时生成网页;2.处理用户交互和表单提交,验证输入并响应操作;3.管理会话和用户认证,提供个性化体验;4.优化性能和遵循最佳实践,提升网站效率和安全性。

PHP:处理数据库和服务器端逻辑PHP:处理数据库和服务器端逻辑Apr 15, 2025 am 12:15 AM

PHP在数据库操作和服务器端逻辑处理中使用MySQLi和PDO扩展进行数据库交互,并通过会话管理等功能处理服务器端逻辑。1)使用MySQLi或PDO连接数据库,执行SQL查询。2)通过会话管理等功能处理HTTP请求和用户状态。3)使用事务确保数据库操作的原子性。4)防止SQL注入,使用异常处理和关闭连接来调试。5)通过索引和缓存优化性能,编写可读性高的代码并进行错误处理。

您如何防止PHP中的SQL注入? (准备的陈述,PDO)您如何防止PHP中的SQL注入? (准备的陈述,PDO)Apr 15, 2025 am 12:15 AM

在PHP中使用预处理语句和PDO可以有效防范SQL注入攻击。1)使用PDO连接数据库并设置错误模式。2)通过prepare方法创建预处理语句,使用占位符和execute方法传递数据。3)处理查询结果并确保代码的安全性和性能。

PHP和Python:代码示例和比较PHP和Python:代码示例和比较Apr 15, 2025 am 12:07 AM

PHP和Python各有优劣,选择取决于项目需求和个人偏好。1.PHP适合快速开发和维护大型Web应用。2.Python在数据科学和机器学习领域占据主导地位。

PHP行动:现实世界中的示例和应用程序PHP行动:现实世界中的示例和应用程序Apr 14, 2025 am 12:19 AM

PHP在电子商务、内容管理系统和API开发中广泛应用。1)电子商务:用于购物车功能和支付处理。2)内容管理系统:用于动态内容生成和用户管理。3)API开发:用于RESTfulAPI开发和API安全性。通过性能优化和最佳实践,PHP应用的效率和可维护性得以提升。

PHP:轻松创建交互式Web内容PHP:轻松创建交互式Web内容Apr 14, 2025 am 12:15 AM

PHP可以轻松创建互动网页内容。1)通过嵌入HTML动态生成内容,根据用户输入或数据库数据实时展示。2)处理表单提交并生成动态输出,确保使用htmlspecialchars防XSS。3)结合MySQL创建用户注册系统,使用password_hash和预处理语句增强安全性。掌握这些技巧将提升Web开发效率。

PHP和Python:比较两种流行的编程语言PHP和Python:比较两种流行的编程语言Apr 14, 2025 am 12:13 AM

PHP和Python各有优势,选择依据项目需求。1.PHP适合web开发,尤其快速开发和维护网站。2.Python适用于数据科学、机器学习和人工智能,语法简洁,适合初学者。

PHP的持久相关性:它还活着吗?PHP的持久相关性:它还活着吗?Apr 14, 2025 am 12:12 AM

PHP仍然具有活力,其在现代编程领域中依然占据重要地位。1)PHP的简单易学和强大社区支持使其在Web开发中广泛应用;2)其灵活性和稳定性使其在处理Web表单、数据库操作和文件处理等方面表现出色;3)PHP不断进化和优化,适用于初学者和经验丰富的开发者。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
4 周前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。