from: http://lzyy.org/blog/archives/38

首先来说说,为什么要用javascript模板。以PHP为例,PHP本身就可以穿插于HTML之间,所以也算是一种模板语言,缺点就是代码看起来会有点乱,尤其是融合了各种循环,判断,赋值等等操作(也不方便格式化),没有做到结构和数据分离。当然PHP也有很多的模板引擎,使用这些模板引擎的一个缺陷就是,效率不如原生的PHP高。没错,可以生成缓存文件,但对于更新相对频繁的站点就不行了。这个时候或许可以考虑javascript模板技术了。
使用了javascript模板,就把解析压力交给了浏览器,服务端只需要提供要用到的数据即可。

这里要介绍的是JST(JAVASCRIPT TEMPLATE),它有什么特点呢?

  • 文件体积小压缩后只有6K
  • 兼容主流浏览器包括IE6
  • 使用方便待会看下面的demo就知道了
  • 功能齐全,使用灵活如果用过php的模板,那么对JST的用法应该就会比较熟悉了

使用方法

无论是数据还是模板,JST都是把这些放在textarea里的,当然你可以设置为隐藏,这样textarea就相当于一个容器了,为什么是textarea?因为textarea的innerHTML能够非常好地保持数据结构,而且不会被浏览器解析。所以是理想的藏身之所。
要使用JST,第一步当然是载入对应的js文件

<html>
<head>
<script language="javascript" src="trimpath/template.js"></script>
...
</head>
...
</html>

然后创建数据,一些objet和array

<script language="javascript">
var data = {
products : [ { name: "mac", desc: "computer",
price: 1000, quantity: 100, alert:null },
{ name: "ipod", desc: "music player",
price:  200, quantity: 200, alert:"on sale now!" },
{ name: "cinema display", desc: "screen",
price:  800, quantity: 300, alert:"best deal!" } ],
customer : { first: "John", last: "Public", level: "gold" }
};
</script>

接下来就是待解析的模板了,放在了一个id为cart_jst的textarea里

<textarea id="cart_jst" style="display:none;">
Hello ${customer.first} ${customer.last}.<br/>
Your shopping cart has ${products.length} item(s):
<table>
<tr><td>Name</td><td>Description</td>
<td>Price</td><td>Quantity &amp;amp; Alert</td></tr>
{for p in products}
<tr><td>${p.name|capitalize}</td><td>${p.desc}</td>
<td>$${p.price}</td><td>${p.quantity} : ${p.alert|default:""|capitalize}</td>
</tr>
{forelse}
<tr><td colspan="4">No products in your cart.</tr>
{/for}
</table>
{if customer.level == "gold"}
We love you!  Please check out our Gold Customer specials!
{else}
Become a Gold Customer by buying more stuff here.
{/if}
</textarea>

下面这个就是关键的解析语句了,还是很简单的

<script language="javascript">
//一句话搞定解析并赋值,cart_jst是textarea的id,data是之前定义的数据
//现在result已经包含解析过的模板文件内容了
var result = TrimPath.processDOMTemplate("cart_jst", data);
 
//也可以分两步走,先解析,再赋值
var myTemplateObj = TrimPath.parseDOMTemplate("cart_jst");
var result  = myTemplateObj.process(data);
//也可以给这个模板一个新的数据源
var result2 = myTemplateObj.process(differentData);
 
//输出结果
someOutputDiv.innerHTML = result;
</script>

最后的内容就像这样

Hello John Public.<br/>
Your shopping cart has 3 item(s):
<table>
<tr><td>Name</td><td>Description</td>
<td>Price</td><td>Quantity &amp;amp; Alert</td></tr>
<tr><td>MAC</td><td>computer</td>
<td>$1000</td><td>100 : </td>
</tr>
<tr><td>IPOD</td><td>music player</td>
<td>$200</td><td>200 : ON SALE NOW!</td>
</tr>
<tr><td>CINEMA DISPLAY</td><td>screen</td>
<td>$800</td><td>300 : BEST DEAL!</td>
</tr>
</table>
We love you!  Please check out our Gold Customer specials!

也可以不把模板放到textarea里,直接解析String也是可以的

<script language="javascript">
var myStr = "Hello ${customer.first} ${customer.last}, Welcome back!";
result = myStr.process(data);
 
//或者直接使用process方法,还省去了一个中间变量
result = "Hello ${customer.first} ${customer.last}, Welcome back!".process(data);
//The result: "Hello John Public, Welcome back!"
//跟下面的效果是一样
result = "Hello " + customer.first + " " + customer.last + ", Welcome back!";
 
//或者先解析,再赋值
var myTemplateObj = TrimPath.parseTemplate(myStr);
var result  = myTemplateObj.process(data);
var result2 = myTemplateObj.process(differentData);
</script>

提示:也可以把jst模板文件放到服务端,等要用时再去load,如$.getScript()

模板语言

真是麻雀虽小,五脏俱全啊,看看下面的例子就知道了

跟Smarty一样,JST也可以通过在变量后面加|在模板里”包装”变量

${customer.firstName}
${customer.firstName|capitalize}
${customer.firstName|default:"no name"|capitalize}

if else判断语句

{if customer != null &amp;amp;&amp;amp; customer.balance > 1000}
We love you!
{/if}
 
{if user.karma > 100}
Welcome to the Black Sun.
{elseif user.isHero}
Sir, yes sir!  Welcome!
{if user.lastName == "Yen"}
Fancy some apple pie, sir?
{/if}
{/if}
 
<a href="/login{if returnURL != null &amp;amp;&amp;amp; returnURL != 'main'}?goto=${returnURL}{/if}">Login</a>

循环输出

{for x in customer.getRecentOrders()}
//这里的x_index指代循环的次数,使用方法为"变量名_index"
${x_index} : ${x.orderNumber} <br/>
//如果customer.getRecentOrders()长度为0或为null
{forelse}
You have no recent orders.
{/for}

自定义变量

{var temp = crypto.generateRandomPrime(4096)}
Your prime is ${temp}

cdata,不接受解析,保持原来的数据,就跟xml里的CDATA一样,有两种使用方法,效果都一样

{cdata}
...text emitted without JST processing...
{/cdata}
 
{cdata EOF}
...text emitted without JST processing...
EOF

在模板内执行javascript

{eval}
//str是从js处传过来的参数
//如TrimPath.parseDOMTemplate('ev').process({str:'hello JST'});
alert(str);
{/eval
 
{eval EOF}
跟上面的一样
EOF

以上演示代码直接从官方网站拿来的,为了照顾像我一样看见E文就头大的同学

注意
如果在textarea的模板里又套了一个textarea会出错,可以通过自定义变量解决,或者避免这样的情况发生。

<textarea id="myTemplate" style="display:none">
{var textarea = "textarea"}
<form>
<${textarea} name="myField">
Some stuff here
</${textarea}>
</form>
</textarea>
posted @ 2009-11-10 10:06 小马歌 阅读(1544) | 评论 (1)编辑 收藏
 
Kohana是一款基于CodeIgniter的 PHP5 框架,其与 CI 最大的区别便是Kohana完全采用 PHP5。

2007 年 6 月 1 日,CI 社区用户 Todd Wilson (Tido)发布了 BlueFlame 1.0 的帖子。BlueFlame 1.0 正式发布,团队成员有九人,其中包括 Todd(原团队 lead developer)、现在 Kohana 的 lead developer、以及本人。该发布贴引起了 CI 社区用户的广泛关注。然而由于 BlueFlame 团队事前没有与 CodeIgniter 团队进行沟通,导致一些理解上的小插曲,包括 Rick (CI 的祖父)要求 BlueFlame 团队停止使用 CI 的社区资源来发布 BlueFlame 相关的信息,以及对 BlueFlame 源代码中的授权部分表示疑义。

最终通过 BlueFlame 与 CodeIgniter 团队的协调沟通,双方的小小误解很快便被解化。Rick 在此期间提到 BlueFlame 名称的潜在版权问题,于是 BlueFlame 几天后便正式更名为 Kohana,并注册了现在的官方网站:kohanaphp.com

在 BlueFlame 1.0 之后,Kohana 团队一直没有发布新的版本,之后整个项目一度处于僵化期。尽管如此,trac 上还是有更新进度。在 Todd Wilson “消失”了一阵子后,成员之一 Woody Gilk (Shadow Hand) 接下重任,担当团队 leader。

之后 Woody 联络本人,希望本人承担 Kohana 官方网站以及 logo 的设计制作。论坛上仍然可以找到当时我发的帖子(内有网站以及 logo 的草案)。可惜由于时间的因素,我最终没有将设计草案转化为 HTML。可喜的是,Geert De Deckere 之后设计了一个相当出色的方案,这也就是大家现在所见到的 Kohana 的官方网站。 :)

2007 年 11 月,经过了团队成员以及社区用户的努力,Kohana 终于发布了 2.0 版本。

下面我来翻译一下 Wikipedia 对于 Kohana 的介绍。

历史

Kohana 是基于 CodeIgniter PHP 框架开发的。

开发 Kohana 最主要的原因是 CI 对于 bug 的修复和处理用户提交的建议所需的时间过长。许多 bug 在用户报告后很久才会纳入到新版本中,甚至一些 bug 一直没有被纳入。另外,由于 EllisLab 是 CodeIgniter 唯一开发者,部分用户对于框架的开放性产生异议,他们希望框架可以更自由开放,从而使社区对框架的发展产生一定的影响。

Kohana 与 CodeIgniter 的区别


  • 严谨的 PHP5 面向对象编程。优势:可见性保护、自动类装载、超载、接口类、抽象类以及单件类。
  • 延续 CodeIgniter 的设计模式。CodeIgniter 的用户能迅速的理解 Kohana 的架构和设计模式。
  • 社区向,而非商业向。Kohana 是一款基于社区的作品。Kohana 的开发者们来自世界各地,有着各自的天赋。这使得开发速度得以提高,并在短时间内提供bug修复以及反馈用户提出的建议。
  • GET、POST、COOKIE 以及 SESSION 数组得到改进。Kohana 不对全局数据做读取限制,但依旧提供与 CodeIgniter 相同的数据过滤以及 XSS 防护。
  • 层叠式资源、模块以及类继承。控制器、数据模型、库、助手以及视图均能够在系统中的任何地方进行载入。程序的配置选项可被继承或覆盖。
  • 无命名空间的冲突。类均添加了如“_Controller”之类的后缀,从而使得用户的控制器和数据模型可被同时同地装载。
  • 真正的自动类装载。这包括库、控制器、数据模型以及助手。与 CodeIgniter 不同,Kohana 的自动装载是真正意义上的动态装载,而并非预先装载。
  • 助手为静态类,而非函数。例如,在 CI 中使用的 form_open(),在 Kohana 中为 form::open()。
  • 库驱动以及 API 的一致性。库能够使用不同的驱动来处理不同的外部 API。例如,session 的储存有数据库、cookie 和 native 几种,但它们均使用相同的接口。这使得库可以不断的添加新的驱动,但不会影响到 API 的一致性。
  • 强大的事件处理器。Kohana 的事件可被动态的添加、替换或删除。这使得用户能在 Kohana 执行的过程中动态做更改,而不影响原有的系统代码。


特性


  • 高安全性
  • 轻量级代码
  • 学习周期短
  • MVC 设计模式
  • 100% UTF-8 兼容
  • 松弛耦合架构
  • 易扩展性


技术


  • 严谨的 PHP5 面向对象编程
  • 用 SQL 助手实现简单的数据库抽象层
  • 多 session 驱动(native、数据库、cookie)
  • 动态事件处理器
posted @ 2009-11-10 09:54 小马歌 阅读(635) | 评论 (0)编辑 收藏
 
from :http://lzyy.org/blog/archives/42

对于PHP的异常和错误总是有点迷糊,于是花了半个下午的时间,总算是明白了大概。

异常(Exception)是从PHP5开始引入的,一个异常可以通过try{}catch{}的方式,被抛出,被捕获。异常被抛出后,包含在try里面的异常后面的代码就不会被执行,然后会去寻找第一个匹配的catch。如果一个异常没有被捕获,那么就会触发PHP Fatal Error,类似:’Uncaught Exception …’。


难免会有意想不到的异常,所以这时就可以使用set_exception_handler()来统一管理。比如下面这段代码

<?php
function exception_handler($exception) {
echo "Uncaught exception: " , $exception->getMessage(), "\n";
}
 
set_exception_handler('exception_handler');
 
throw new Exception('Uncaught Exception');
echo "Not Executed\n";
?>

没有捕获抛出的异常,但设置了异常处理函数’exception_handler’,这样就可以在函数里做文章了,比如显示一个错误页面,同时记录下异常的内容,方面日后查看。异常处理函数执行完后,PHP就会停止执行后面的语句。

如果是php内部函数执行时出现异常,不会抛出Exception,而是通过trigger_error来显示错误。好在有set_error_handler,可以设置默认错误处理函数,我们在函数里抛出一个异常,这样就可以达到统一处理的效果。

<?php
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
throw new Exception($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler("exception_error_handler");
 
/* Trigger exception */
strpos();
?>

或者直接把异常和错误的处理都用一个函数来实现,这样就需要在函数里判断错误的来源是Exception还是error,可以通过参数个数来判断(error总是会有5个参数)

设置error

比较重要的两个参数是:error_reporting和display_errors。在PHP4和PHP5里,error_reporting默认都是E_ALL & ~E_NOTICE(显示所有的错误,但不显示NOTICE类型的)。但在PHP5里又有了一个新的error类型:E_STRICT,它并没有被包含到E_ALL里面,所以需要手动添加,如E_ALL | E_STRICT。

注意:下面的代码里error_reporting是没有效果的

ini_set("display_errors", 1);
//最后少了一个分号
echo 'hello world'

无论是设置display_errors为0或者1,都没有效果,因为PHP是先解析后执行,解析过程中出错,会根据php.ini里的display_errors的设置来决定是否显示错误。可以改为这样

<?php
error_reporting(E_ALL);
ini_set("display_errors", 1);
include("file_with_errors.php");
?>
posted @ 2009-11-10 09:52 小马歌 阅读(713) | 评论 (0)编辑 收藏
 

玩过一段时间电脑后,体会到经常会用“记事本”打开多种类型未知文件,以便查看编辑其中内容。所以直接在右键菜单中添加“用记事本打开”这一
功能是比较省事方便的。通过修改注册表可以实现:

1、打开注册表,找到
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\*\shell 主键(项)(如果没有新建一个),
2、单击shell这个主键,在下再新建一个名为 notepad 的主键,在右面的窗口中,把默认项的值改成:用记事本打开 或者其他你喜欢的字样。
3、再在notepad主键下新建一个 command 主键。在右面的窗口中,把默认项的值改成:
notepad %1 (注意notepad和%符号之间要空一格)关闭注册表。
现在在任意一个文件上单击鼠标右键就有“用记事本打开”这一功能了。经体验是一个很好的方法。当然用其它软件也可添加这一功能。(收集学习)

另可以:打开注册表编辑器,找到下面的键值:HKEY_CLASSES_ROOT\*,接着新建一个名为 shell的子项,接着再它的下方新建名为“Notepad”,然后在右侧窗口中将其默认值改为“使用记事本打开”,最后在“Notepad”项下新建名为“command”的子项,在右侧窗口中将其值改为:notepad.exe %1就可以了。

保存一下内容为*.reg运行即可!

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\*\shell]
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\*\shell\playboyjin]
@="用记事本打开"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\*\shell\playboyjin\command]
@="notepad.exe \"%1\""

如何添加鼠标右键DOS命令
A:(1)打开注册表编辑器。 (2)打开HKEY_LOCAL_MACHINE\Software\classes\directory\shell主键并选中它。在右侧窗口中新建一个项,命名为CommandPrompt,然后把它的数值数据改为command。 (3)在CommandPrompt下新建一个项,命名为command,改其数值数据为cmd /k cd %1,退出OK了。 (4)鼠标右键单击任何文件夹,在弹出的菜单中,你就可以选择command命令了。

B:保存下边的代码到一个记事本文件,并把扩展名改为“.bat”,执行一次这个文件(以后就没用了),找一个文件夹,打开鼠标右键看看吧!

reg add "HKCR\*\shell\ms-dos" /ve /d ms-dos /f
reg add "HKCR\*\shell\ms-dos\command" /ve /d "cmd.exe /k cd %%1" /f
reg add "HKCR\Folder\shell\ms-dos" /ve /d ms-dos /f
reg add "HKCR\Folder\shell\ms-dos\command" /ve /d "cmd.exe /k cd %%1" /f


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/bhsky/archive/2009/02/02/3858883.aspx

posted @ 2009-10-27 09:42 小马歌 阅读(609) | 评论 (0)编辑 收藏
 
from : http://codeigniter.org.cn/forums/thread-1224-1-1.html

参考URL:http://codeigniter.com/forums/viewthread/54019/

之前想要做一个搜索的东西,需要把关键词放到URL上面,对用户更加的友好,而且我感觉这样也容易做分页,避免不断的post和用会话保存关键词。
比如下面的路径:
       http://www.example.com/search/name/我是中文
       但是一直报错:Disallowed Key Characters.
       之前一直以为是system/library/URI.php的问题。不断的去修改系统的$config['permitted_uri_chars'] = 'a-z A-Z 0-9~%.:_\-';和URI.php里面的_filter_uri($str)函数。也参照了之前几位仁兄的经验,结果还是没有成功。
       无奈之下,搜索这句报错信息的来源。结果发现是在system/library/URI.php里面的_clean_input_keys($str)这个函数。因为中文的编码在这个地方没有通过正则表达式的匹配,只能被无情的挂掉。
       现在看来,CI过滤URI中的字符有两道防线,第一道防线,是Input.php类里面的_clean_input_keys($str)函数,通过正则匹配,及时卡掉非法字符,第二道防线是URI.php里面的_filter_uri($str),通过系统设定的$config['permitted_uri_chars']来进行正则的匹配。卡掉非法的字符。但是我又感到了CI框架的一个缺陷。就是Input.php和URI.php里面的过滤函数各为其主,采用不同的正则表达式进行匹配,URI.php里面采用的是正统的$config['permitted_uri_chars'],Input.php采用的是固定的”/^[a-z0-9:_\/-]+$/i“。这样,即便在$config['permitted_uri_chars']里面设置了中文的匹配条件,也会在Input.php层被卡掉。
       我的解决方法是:扩展Input类。
PHP
function _clean_input_keys($str)
    {
         if ( ! preg_match("/^[a-z0-9:_/-]+$/i", $str))
         {
             exit('Disallowed Key Characters.');
         }
        return $str;
    }
复制代码
修改成
PHP
function _clean_input_keys($str)
    {
         $config = &get_config('config');
        if ( ! preg_match("/^[".$config['permitted_uri_chars']."]+$/i", rawurlencode($str)))
        {
            exit('Disallowed Key Characters.');
        }
        return $str;
    }
复制代码
这样就能够在URI传送中文了。我的CI版本是1.6.3
posted @ 2009-10-23 16:22 小马歌 阅读(198) | 评论 (0)编辑 收藏
 
from:http://www.afen.cn/blog/?p=129

codeIgniter默认的配置下是不允许URL中包含非ASCII字符的,如果我们有这样一个字符串:
http://www.example.com/photo/北京/鸟巢.jpg
那么CI会毫不客气的告诉你:
The URI you submitted has disallowed characters.
你 可能会说,那我把这个URL使用函数urlencode一下呢?不行。因为Web Server会在接收到一个被urlencode的URL后自动将其decode,然后在PHP里得到的这些字符串转换成他原来所代表的含义,并使用 Web Server自己的URL编码字符集(IIS6 中文版是GBK,Apache 2.2是UTF-8)传送给应用程序,这就使得CI得到的URL已经是一个解码过的,无论你有没有对URL进行urlencode,浏览器在发出请求时会 自动检测,若没有,则会自动进行编码。所以,手动的进行urlencode并不能解决问题。那么我们应该怎么做来解决这个问题呢?

对于CI这种框架,用到现在,我的观点是尽量不要去修改它,而是去扩展他,CI提供了很好的扩展机制,我们只需要在application/libraries/下增加一个文件MY_URI.php,其内容为:

 

<?php 
class MY_URI extends CI_URI {
    function _filter_uri($str)
    
{
        
if ($str != '' AND $this->config->item("permitted_uri_chars"!= '')
        
{
            $str 
= urlencode($str);
            
if ( ! preg_match("|^[".preg_quote($this->config->item("permitted_uri_chars"))."]+$|i", $str))
            
{
                exit(
"The URI you submitted has disallowed characters.");
            }

            $str 
= urldecode($str);
        }

        
return $str;
    }


}


?>

 

编码解码的两行是我新加入的代码,我覆盖了原来CI_URI中的_filter_uri方法,这样就可以使得中文的URL通过检测。但是,如果URL里有空格, 也不行了,怎么办呢?原来,urlencode会将空格转换成+,而CI的默认配置中是不允许+出现在URL里的,OK,把$config ['permitted_uri_chars'] = ‘a-z 0-9~%.:_\-’;改成$config['permitted_uri_chars'] = ‘a-z 0-9~%.:_\+\-’;就可以了。

posted @ 2009-10-23 16:21 小马歌 阅读(288) | 评论 (0)编辑 收藏
 
1、上步国商那边有家韩国料理叫“相约烤吧”还不错价格也挺实惠。

2、华强北民间瓦罐旁有一家四川小吃店,店不大但东东挺不错价格也还可以,口水鸡好象是10元一份。

3、地王附近的工商局后面一条小巷有家湘菜馆也不错,好象叫“湖南人家”那的腊肠做的不错。

4、草原情餐厅,电话:26974816.地点:南山区中山园路188号.正宗蒙古羊肉,价格便宜。

5、南山区政府对面的富豪城,好像主要是粤菜吧,不记得了。只记得在一楼大厅吃饭,啤酒不要钱任喝。

6、南圆路加洲红对面的那家顺德蛇城喝茶挺便宜的,大、中、小都是3.00元,味道最正的是那里的烤乳鸽,又香又便宜。

7、佳和城大酒楼,中航路与深南中路交界的地方.喜欢喝酒的朋友们可以上那里去,菜也不太贵,每次下来每人20元左右。

8、八登街有一家西安菜,做的十分不错,价钱又便宜,强烈推荐:羊肉拌面,10块银子,天上绝色。

9、八登街的鱼香米坊,便宜而且最主要那二楼的环境顺德人开的,广东风味

10、蛇口风华剧院那有家陕西风味不错哦价格巨便宜,土豆饼才3元/张,绿豆稀饭2元一大碗。

11、八卦三路有个明记烧鹅王还不错,新开的,送餐电话82450696

12、著名的潮州打冷,这里只提供最正宗且干净的地点,就在晒布路的深运大酒店。

13、南油酒店中午喝茶是很便宜啊,半价!还有盐插虾8元/斤,片皮鸭18元/只,但二者只能选一。

14、蛇口风华剧院那有家陕西风味不错哦价格巨便宜,土豆饼才3元/张,绿豆稀饭2元一大碗,撑死了别找我

15、银湖车站有个金湖大酒楼,门前车总是满满的,基围虾1元一斤,炒菜9元,酒免费,但会收服务费若干,如果聚会这里也是个不错的选择,吃完后可上银湖公园遛遛。

16、振兴路自上步路口起,到华富路口止,大小食肆林立。自东向西依次是:稻香村 军分区旁边的小巴站处 有海鲜,含部分徽菜口味 中午吃饭花上18元吃一条清蒸鲈鱼,真是很不错耶。

17、醉翁亭 在稻香村后面 这是总店,徽菜口味。诚意推荐菜式:野鸭,鱼头煲,炒菜心,鸭黄豆腐,炒猪肝,炒腰花。物廉价美,尤其适合安徽,江浙一带,非常下饭,每次都吃的很饱才走。

18、巴蜀风 在燕南路和振兴路的交界处 深圳最火爆的川菜馆,其中的牛杆菌炒双椒大漠风沙牛肉,冒菜什么的都很好吃,小心它的泡椒鱼头煲啊,我的嘴就是这样吃肿的。主食里的西红柿鸡蛋面也是不错的,就是要提前一点点,否则等好久。

19、永和大王 巴蜀风西侧 不用我说了,一般无所事事,会去小吃一顿,干净卫生。

20、家常饭 永和大王西侧 江西口味。不要点它的汤喝,反正我不喜欢。井冈豆皮,黄豆猪蹄,干焗大肠,糯米排骨牛肉都是上好的东东。

21、乌江活鱼一条街 在巴蜀风后面的一条路上,没有路名。上面有很多家专吃鱼的店家,什么都一处啊,正宗乌江活鱼啊。四五个人进去选条三斤多的鱼,吃的很开心了。小心上火喔!

22、外婆桥1 沿着活鱼一条街,往前走点,就可以看到一家外婆桥。这一家是专吃小吃的,没有炒菜,只有煲啊什么的。里面的蒜泥白肉手撕饼味道很不错,糖醋排骨也是美味的, 就是没什么肉,而且我觉得做的焦了一点。

23、外婆桥2 这里外婆桥有两家,往南走一些,左拐到底就是另一家。这一家主要做炒菜,我喜欢吃它的鸭血煲,说实话,这是我吃过的最鲜美的鸭血煲。!!!三星推荐。还有,千万不要吃它的全家福,味道还可以,就是太咸。

24、麦当劳 外婆桥重新拐到振兴路上,就是麦当劳了,这个不用我介绍啦。不过吃起来,似乎深圳麦当劳比别地的要好吃。经常会在路过的时候,买个蛋筒吃,自然比起哈根达斯是差多了。

25、潮州大碗粥 沿着振兴路往西走,过了招商银行,就有一家粥城。里面随便点吧,反正很便宜就是了。潮州的粥味道很不错的,他们用大米炒过之后才下锅,配一条红鱼,吃起来有 滋有味的。以后我会介绍更好的一个专门吃粥的地方给大家。

26、香辣蟹 过了大碗粥,拐进去就到了振兴食街,有三家做香辣蟹的。三家香辣蟹味道都不错,不过我喜欢中间那家,虽然右边那家最早味道也最好,但是中间那家态度好,吃的舒服。还可以点黄骨鱼吃,就是浑身黄色斑点的一种鱼,大小大概三个手指这么大,烫火锅吃真的很美味。

27、东北春饼店 在香辣蟹对面,进去就是死吃活吃。不过我最怕韭菜,忌口,所以很少去。记得份量不要点太多,否则吃不完。

28、名典及热带雨林 回到振兴路,就可以看到了。是两家咖啡屋,名典里面的生果沙拉和菠萝饭我还是马虎爱吃,但是跟好的西餐比起来就差劲多了。几个朋友聚会一起,倒是不错 的地方,海阔天空的聊。热带雨林挺差劲的,虽然当初上当有了它的贵宾卡,不如不要。过了华强北,就是振兴西路了,我这里把中航路也给包括进去一起说了。

29、沿着振兴路西行,逐渐就超出单位500米圆范围了,所以就不是很熟悉了。路的中段西侧有一个南昌菜馆,吃过两次,具体吃的什么也就忘记了。但是这里是一个很重要的交界口,因为有好几家好吃的地方。

30、华神火锅那个辣哟!天!店名好像应该是华神川菜馆,但是最著名的该是它的火锅。 下了班要是晚到了,就只有等位的份。店有两层,一群人围炉来个火锅,最是惬意不过。 只不过俺也算是逐渐成长起来的一个辣子手,到了那里就只有甘拜下风。不过同样的是, 几个四川、湖南的同事同样是对开锅汤不敢下嘴,冰冻可乐倒是去的飞快。反而是一个东北妹妹,刺溜刺溜吃个利索。我们是战战兢兢等她把汤面的辣油几乎扫光了,才开始呲牙 咧嘴的吃。星级推荐:猪肝,烫火锅从来没有这么好吃过。

31、香辣蟹 前面介绍了振兴食街上的三家,怎么这里又来了。唉,看官,这家自有它的妙处。里面所有的服务员,不论男女都不洗头的 ,因为都是和尚头,呵呵。这里的香辣蟹又是别种风味,不是肉蟹,而是一种比较小的螃蟹,但是肉却是非常的嫩,可以一试。进去点那种最便宜的螃蟹吃就行了,因为其他几种贵的我都吃过,没感觉出来有什么特别味道好的地方。

32、东北人 一句:翠花,上酸菜。着实把生意本来就红火的东北人,又给捧了一下。里面的姑娘小伙都是民族服装,啥民族服装,就是翠花和翠华那汉子呗。里面东西吃起来自然是大鱼大肉,吃的就是一个俗字,味道呢,个人觉得,酱骨架还是面点王的好吃。不过只要人多,随便点个什么吃,都是非常香。而且姑娘们一句一个大哥,叫得人心里暖呼呼的。

33、面点王 始终觉得面点王还是开办的很成功的,无论如何一个石油企业,竟然开办起一个风牛马不相及的饮食行业,还是心生佩服。面点王的酱骨架,水饺,鸡蛋炒面,桂圆红枣粥,都是闲来吃饭的好选择。经常会在值班的时候去打包一份回来。华强北地区有两家,一家在燕南路振兴路交界沿着燕南路往南走20米左右,一家在红荔路和华强北交界处,友情提醒,午饭或晚饭时间,请估计好具体人数再去,否则等位。

34、家家鸡煲 在振华路和燕南路交界口往南走10米。原来有两家鸡煲店,后来家家味道好,服务也不错,就把另一家给吞并了。进去自然是吃它的传统奇香鸡煲,配上炸的金黄的腐竹金针菇,味道很好。尤其是它的炸腐竹,是腐竹泡水后又沥干水分之后炸的,不像有些火锅店,不泡水就干炸,怎么煮都煮不开腐竹的皮,真是太没专业精神了。

35、天天渔港 振中路上,铜锣湾量贩的对面。说实话,这是我们老百姓最能体现物廉价美的海鲜吃处。环境好,服务好,海鲜味道做的也很不错。其他不说了,就推荐它的点心--菠萝包, 里面的馅心真的有菠萝的喔,全鹏城独此一家。我每次有同学来要吃海鲜,都是带他们去那里吃。

36、海上皇 振中路和华发北路交界处。是吃早茶,午茶,下午茶的绝佳去处。最喜欢吃的是它的牛杂,皮蛋瘦肉粥,白灼菜心(吃的时候淋上调好的蚝油)。其实还有一个很好吃,就是它的点心拼盘。不过一般不是熟客它不卖,你可以理直气壮的点,虽然菜牌上不写,坚持坚持,它就给做了,20元一份,每份是八种点心一起上笼蒸。在这里吃晚餐也不错,推荐它的芝士焗生蚝,才8元一个,每次都去吃两个,HOHO.

37、小肥羊 振华路与燕南路交界,不过实在振华路口。羊的火锅。之所以这么说,是因为里面几乎都是羊的天下,看名字就知道路。什么羊肉,羊肝,羊蛋什么的,不过我坚决只 吃羊肉。这里吃火锅,没有小碗调料的。火锅的汤味道很好,不知道放了什么东西调味的,据说还不准打包,免得外泄。吃东西就是点羊肉吃咯,外加火锅通常都有的,不过味道真是很不错,可以理解我两星期内竟然去吃了四次,要预订。

38、漓江又一轩 沿着小肥羊西行,就看见远远的一块绿色招牌,就是它了。这里是广西干锅口味,鸡煲鱼煲和鸭煲,不过我还是爱吃鸡煲,每种都试过了。两三个人去吃点个鸡煲就够了,青菜和豆腐是送的。它的吃饭是先吃干锅,然后快吃完了才加入汤水煮锅。千万不要吃它的任何鱼,反正我从来没有爱吃过。点心里面倒是有很多好的,对了它的糯米牛肉不错的喔,还有土豆片,香芋饼,可以一顿吃好几个。要是上火,还可以点个龟苓膏吃 ,降火的,据说是梧州的。其实我吃过正宗的梧州龟苓膏,苦死了,我还是喜欢吃甜点的。

39、北海渔村 深圳这个渔村那个渔村是铺天盖地,我喜欢来这家吃。随便点些海鲜吃,但 是最主要的是我爱吃这里的鲍鱼粥。不是那种大的,而是特价三元一个地,点30个小鲍鱼,然后煮粥喝,要半个多小时才能上桌。上次献血完元气大伤,狠狠吃了一顿鲍鱼粥,仿佛心理上才恢复过来。

40、穆斯林 去了很多次,还是不知道叫什么名字,反正那里就是穆斯林吃的地方。羊肉,羊肉。位置在南园路和爱华路的交界处二楼。里面都是新疆姑娘喔,好吃东西很多啦,比 如大盘鸡,羊肉汤等等。最好吃的当属羊腿,15元一个,这么便宜,吃的时候人手一腿,抓起来大啃特啃,不是一般的好。友情提醒:别点多了,吃个羊腿已经打倒一半了。

41、巴香鱼头 沿着穆斯林店,往爱华路南走几步,就看见了。里面的鱼头做法很有特色,是炸过之后再放进汤里煮,配火锅吃的汤料也是干货,吃的时候勺火锅的汤进去搅和,然后再吃。味道奇特,值得一吃。

41、图们烧烤 就在巴香鱼头的马路对面拉。爱吃它的烤鸡架子,就是爪子的筋,吃起来嘎本嘎本特别脆。其他的爱吃啥就点啥吧,可以让店员帮你们烤,免得自己回去都灰头土脸的。

42、山天 沿着穆斯林店,朝南园路的东面走100米吧,就看见山天了。我们喊它三个一, 因为商标就是这样的。里面也是吃火锅的,但是是吃菌类的,进去不用人指 点,爱吃啥就点啥吃吧,火锅的汤底都是菌汤熬的,很鲜。菌丝炒饭是很不错的主食。最喜欢菌汤煮的鱼头了,一顿可以吃四五个,还意犹未尽。牛杆菌是我的最爱。

43、沅绿回转寿司店,在振兴东路靠近华强北的地方,对面就是紫荆城。寿司倒不是非常感兴趣,爱吃的是它的鳗鱼饭,可惜我的肚子有限,每次都只能吃一份就饱了。不过配上的豆豉汤,真不是一般的难喝,不过有同事每次去都是帮我包的。

44、江之鹤 振兴东路中段有个建设银行,旁边一拐就看见了。哇塞,好贵的说。晚餐每个人如果是吃自助,要168。吃过三次,最好吃的就是烤鳗鱼条,味道比沅绿好多了。尤其 是它的银鳕鱼西京烧,天哪,我有一回疯狂的点了四份吃,小姐肯定对我有意见了。第二次去的时候,他们就开始限制吃饭时间了,只给三个小时的点菜时间,其他随便。到第三个小时的时候,我已经饱的不能动了。

45、舞鹤 又是一个广告贼多的料理店,在上海宾馆。不过说实话,自从上次自费去过一次之后,打死我都不会再去了,估计都是补偿广告费去了,不过骗妹妹不错喔。贵,而且我觉得味道不如江之鹤的好。不过料理吃过了,也就这么个样。罗湖商业城下面有一家料理店,是深圳味道最好的地方,有空大家可以去试一下,名字忘记了。

46、罐罐鸡 这是一家小吃。就在我刚才说过的海上皇旁边,就吃的是一个一个砂锅的面或者粉。自然主打的是鸡,可以配猪肝,或者其他的菜牌上有的东西。吃的时候,从罐子里捞面条出来到小碗里,配以咸菜花生,汤本就是鲜的,很是爽口的说。

47、家家长沙米粉店 唉,挤死了,每次去都是人山人海的,空调又不足,还这么多人爱吃。华强北地区就有三家,两家在华发北路上,靠近红荔路的地方。老板一定发财了,本来就只有一家的,一年之内在深圳市内连开四家分店。里面卖粉,也卖面条。什么大肉面啊,冬菇肉饼蒸鸡蛋面啊,酸辣面啊,什么什么的,都挂墙上,大家爱吃什么点什么。粉是宽粉,我不爱吃粉。面是碱水面,很筋斗的,唉,热就热吧,谁让你爱吃呢。每次吃完都是大汗淋漓。吃的时候还可以点红烧猪蹄豆干或者绿豆汤喝,都是不错的。

48、深运粥城 名字是不是叫这个忘记了,位置在红荔路靠近华富路的地方。反正你沿着红荔路往华富路走就是了,在路的北侧。这里的粥集合了潮州粥的大全,不会点不要紧, 吃几次就总结出经验来了。

49、香积世界素菜馆华强北丽人世界的背后,振兴西路靠近华强北的路口。里面都是吃素的,偶尔换个口味,去吃一下,也是挺不错滴,就是吃完了容易饿,毕竟现在的人都是肉食动物。

50、深井烧鹅 据说是香港同胞过来开的。门面不大,不注意还找不着。在家家长沙米粉的对面,注意,这里说的家家是在华发北路东侧的那家,也就是说烧鹅店在华发北西侧。 进去如果一个人的话,可以点个叉鹅饭,意思就是烧鹅和叉烧,如果两个人以上,可以来个叉鹅例盘,吃个痛快。味道很正宗的说,的确如此。不过价格也不菲喔,简单一个饭要15,如果是吃一个烧鹅腿,要25。它用来蘸烧鹅的小碟汁,真是爽死了。

51、大树脚 福田区上步南路(国商旁边)

52、老北京炸酱面福田区百花三路艺术学校西侧

53、长沙米粉福田区华发北路

54、和家长沙米粉店 福田区八卦三路鹏益花园楼下 
posted @ 2009-10-12 16:28 小马歌 阅读(167) | 评论 (0)编辑 收藏
 
翻译:taft at wjl.cn
校对:laruence at yahoo.com.cn
最后更新日期:2009/04/29

简 介

PHP取得成功的一个主要原因之一是她拥有大量的可用扩展。web开发者无论有何种需求,这种需求最有可能在PHP发行包里找到。PHP发行包包括支持各种数据库,图形文件格式,压缩,XML技术扩展在内的许多扩展。

扩展API的引入使PHP3取得了巨大的进展,扩展API机制使PHP开发社区很容易的开发出几十种扩展。现在,两个版本过去了,API仍然和PHP3时的非常相似。扩展主要的思想是:尽可能的从扩展编写者那里隐藏PHP的内部机制和脚本引擎本身,仅仅需要开发者熟悉API。

有两个理由需要自己编写PHP扩展。第一个理由是:PHP需要支持一项她还未支持的技术。这通常包括包裹一些现成的C函数库,以便提供PHP接口。例如,如果一个叫FooBase的数据库已推出市场,你需要建立一个PHP扩展帮助你从PHP里调用FooBase的C函数库。这个工作可能仅由一个人完成,然后被整个PHP社区共享(如果你愿意的话)。第二个不是很普遍的理由是:你需要从性能或功能的原因考虑来编写一些商业逻辑。

如果以上的两个理由都和你没什么关系,同时你感觉自己没有冒险精神,那么你可以跳过本章。

本章教你如何编写相对简单的PHP扩展,使用一部分扩展API函数。对于大多数打算开发自定义PHP扩展开发者而言,它含概了足够的资料。学习一门编程课程的最好方法之一就是动手做一些极其简单的例子,这些例子正是本章的线索。一旦你明白了基础的东西,你就可以在互联网上通过阅读文挡、原代码或参加邮件列表新闻组讨论来丰富自己。因此,本章集中在让你如何开始的话题。在UNIX下一个叫ext_skel的脚本被用于建立扩展的骨架,骨架信息从一个描述扩展接口的定义文件中取得。因此你需要利用UNIX来建立一个骨架。Windows开发者可以使用Windows ext_skel_win32.php代替ext_skel。

然而,本章关于用你开发的扩展编译PHP的指导仅涉及UNIX编译系统。本章中所有的对API的解释与UNIX和Windows下开发的扩展都有联系。

当你阅读完这章,你能学会如何

  • 建立一个简单的商业逻辑扩展。
  • 建议个C函数库的包裹扩展,尤其是有些标准C文件操作函数比如fopen()

快速开始

本节没有介绍关于脚本引擎基本构造的一些知识,而是直接进入扩展的编码讲解中,因此不要担心你无法立刻获得对扩展整体把握的感觉。假设你正在开发一个网站,需要一个把字符串重复n次的函数。下面是用PHP写的例子:

  1. function self_concat($string, $n){
  2.     $result = "";
  3. for($i = 0; $i < $n; $i++){
  4.     $result .= $string;
  5. }
  6.     return $result;
  7. }
  8.  
  9. self_concat("One", 3) returns "OneOneOne".
  10.  
  11. self_concat("One", 1) returns "One".

假设由于一些奇怪的原因,你需要时常调用这个函数,而且还要传给函数很长的字符串和大值n。这意味着在脚本里有相当巨大的字符串连接量和内存重新分配过程,以至显著地降低脚本执行速度。如果有一个函数能够更快地分配大量且足够的内存来存放结果字符串,然后把$string重复n次,就不需要在每次循环迭代中分配内存。

为扩展建立函数的第一步是写一个函数定义文件,该函数定义文件定义了扩展对外提供的函数原形。该例中,定义函数只有一行函数原形self_concat() :

  1. string self_concat(string str, int n)

函数定义文件的一般格式是一个函数一行。你可以定义可选参数和使用大量的PHP类型,包括: bool, float, int, array等。

保存为myfunctions.def文件至PHP原代码目录树下。

该是通过扩展骨架(skeleton)构造器运行函数定义文件的时机了。该构造器脚本叫ext_skel,放在PHP原代码目录树的ext/目录下(PHP原码主目录下的README.EXT_SKEL提供了更多的信息)。假设你把函数定义保存在一个叫做myfunctions.def的文件里,而且你希望把扩展取名为myfunctions,运行下面的命令来建立扩展骨架

  1. ./ext_skel --extname=myfunctions --proto=myfunctions.def

这个命令在ext/目录下建立了一个myfunctions/目录。你要做的第一件事情也许就是编译该骨架,以便编写和测试实际的C代码。编译扩展有两种方法:

  • 作为一个可装载模块或者DSO(动态共享对象)
  • 静态编译到PHP
PHP扩展开发导图

PHP扩展开发导图

因为第二种方法比较容易上手,所以本章采用静态编译。如果你对编译可装载扩展模块感兴趣,可以阅读PHP原代码根目录下的README.SELF-CONTAINED_EXTENSIONS文件。为了使扩展能够被编译,需要修改扩展目录ext/myfunctions/下的config.m4文件。扩展没有包裹任何外部的C库,你需要添加支持–enable-myfunctions配置开关到PHP编译系统里(–with-extension 开关用于那些需要用户指定相关C库路径的扩展)。可以去掉自动生成的下面两行的注释来开启这个配置。

  1. ./ext_skel --extname=myfunctions --proto=myfunctions.def
  2. PHP_ARG_ENABLE(myfunctions, whether to enable myfunctions support,
  3.  
  4. [ --enable-myfunctions                Include myfunctions support])

 

现在剩下的事情就是在PHP原代码树根目录下运行./buildconf,该命令会生成一个新的配置脚本。通过查看./configure –help输出信息,可以检查新的配置选项是否被包含到配置文件中。现在,打开你喜好的配置选项开关和–enable-myfunctions重新配置一下PHP。最后的但不是最次要的是,用make来重新编译PHP。

ext_skel应该把两个PHP函数添加到你的扩展骨架了:打算实现的self_concat()函数和用于检测myfunctions 是否编译到PHP的confirm_myfunctions_compiled()函数。完成PHP的扩展开发后,可以把后者去掉。

  1. <?php
  2. print confirm_myfunctions_compiled("myextension");
  3. ?>

运行这个脚本会出现类似下面的输出:

  1. "Congratulations! You have successfully modified ext/myfunctions
  2.  
  3. config.m4. Module myfunctions is now compiled into PHP."

另外,ext_skel脚本生成一个叫myfunctions.php的脚本,你也可以利用它来验证扩展是否被成功地编译到PHP。它会列出该扩展所支持的所有函数。

现在你学会如何编译扩展了,该是真正地研究self_concat()函数的时候了。
下面就是ext_skel脚本生成的骨架结构:

  1. /* {{{ proto string self_concat(string str, int n)
  2.  
  3. */
  4.  
  5. PHP_FUNCTION(self_concat)
  6.  
  7. }
  8.  
  9. char *str = NULL;
  10.  
  11. int argc = ZEND_NUM_ARGS();
  12.  
  13. int str_len;
  14.  
  15. long n;
  16.  
  17. if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
  18.  
  19. return;
  20.  
  21. php_error(E_WARNING, "self_concat: not yet implemented");
  22.  
  23. }
  24.  
  25. /* }}} */

 

自动生成的PHP函数周围包含了一些注释,这些注释用于自动生成代码文档和vi、Emacs等编辑器的代码折叠。函数自身的定义使用了宏PHP_FUNCTION(),该宏可以生成一个适合于Zend引擎的函数原型。逻辑本身分成语义各部分,取得调用函数的参数和逻辑本身。

为了获得函数传递的参数,可以使用zend_parse_parameters()API函数。下面是该函数的原型:

  1. zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, …);

第一个参数是传递给函数的参数个数。通常的做法是传给它ZEND_NUM_ARGS()。这是一个表示传递给函数参数总个数的宏。第二个参数是为了线程安全,总是传递TSRMLS_CC宏,后面会讲到。第三个参数是一个字符串,指定了函数期望的参数类型,后面紧跟着需要随参数值更新的变量列表。因为PHP采用松散的变量定义和动态的类型判断,这样做就使得把不同类型的参数转化为期望的类型成为可能。例如,如果用户传递一个整数变量,可函数需要一个浮点数,那么zend_parse_parameters()就会自动地把整数转换为相应的浮点数。如果实际值无法转换成期望类型(比如整形到数组形),会触发一个警告。

下表列出了可能指定的类型。我们从完整性考虑也列出了一些没有讨论到的类型。

类型指定符 对应的C类型 描述
l long 符号整数
d double 浮点数
s char *, int 二进制字符串,长度
b zend_bool 逻辑型(1或0)
r zval * 资源(文件指针,数据库连接等)
a zval * 联合数组
o zval * 任何类型的对象
O zval * 指定类型的对象。需要提供目标对象的类类型
z zval * 无任何操作的zval

为了容易地理解最后几个选项的含义,你需要知道zval是Zend引擎的值容器[1]。无论这个变量是布尔型,字符串型或者其他任何类型,其信息总会包含在一个zval联合体中。本章中我们不直接存取zval,而是通过一些附加的宏来操作。下面的是或多或少在C中的zval, 以便我们能更好地理解接下来的代码。

  1. typedef union _zval{
  2. long lval;
  3. double dval;
  4. struct {
  5. char *val;
  6. int len;
  7. }str;
  8.  
  9. HashTable *ht;
  10. zend_object_value obj;
  11.  
  12. }zval;

 

在我们的例子中,我们用基本类型调用zend_parse_parameters(),以本地C类型的方式取得函数参数的值,而不是用zval容器。

为了让zend_parse_parameters()能够改变传递给它的参数的值,并返回这个改变值,需要传递一个引用。仔细查看一下self_concat():

  1. if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
  2. return;

注意到自动生成的代码会检测函数的返回值FAILUER(成功即SUCCESS)来判断是否成功。如果没有成功则立即返回,并且由zend_parse_parameters()负责触发警告信息。因为函数打算接收一个字符串l和一个整数n,所以指定 ”sl” 作为其类型指示符。s需要两个参数,所以我们传递参考char * 和 int (str 和 str_len)给zend_parse_parameters()函数。无论什么时候,记得总是在代码中使用字符串长度str_len来确保函数工作在二进制安全的环境中。不要使用strlen()和strcpy(),除非你不介意函数在二进制字符串下不能工作。二进制字符串是包含有nulls的字符串。二进制格式包括图象文件,压缩文件,可执行文件和更多的其他文件。”l” 只需要一个参数,所以我们传递给它n的引用。尽管为了清晰起见,骨架脚本生成的C变量名与在函数原型定义文件中的参数名一样;这样做不是必须的,尽管在实践中鼓励这样做。

回到转换规则中来。下面三个对self_concat()函数的调用使str, str_len和n得到同样的值:

  1. self_concat("321", 5);
  2.  
  3. self_concat(321, "5");
  4.  
  5. self_concat("321", "5");
  6.  
  7. str points to the string "321", str_len equals 3, and n equals 5.
  8.  
  9. str 指向字符串"321",str_len等于3,n等于5。

 

在我们编写代码来实现连接字符串返回给PHP的函数前,还得谈谈两个重要的话题:内存管理、从PHP内部返回函数值所使用的API。

内存管理

用于从堆中分配内存的PHP API几乎和标准C API一样。在编写扩展的时候,使用下面与C对应(因此不必再解释)的API函数:

emalloc(size_t size);

efree(void *ptr);

ecalloc(size_t nmemb, size_t size);

erealloc(void *ptr, size_t size);

estrdup(const char *s);

estrndup(const char *s, unsigned int length);

在这一点上,任何一位有经验的C程序员应该象这样思考一下:“什么?标准C没有strndup()?”是的,这是正确的,因为GNU扩展通常在Linux下可用。estrndup()只是PHP下的一个特殊函数。它的行为与estrdup()相似,但是可以指定字符串重复的次数(不需要结束空字符),同时是二进制安全的。这是推荐使用estrndup()而不是estrdup()的原因。

在几乎所有的情况下,你应该使用这些内存分配函数。有一些情况,即扩展需要分配在请求中永久存在的内存,从而不得不使用malloc(),但是除非你知道你在做什么,你应该始终使用以上的函数。如果没有使用这些内存函数,而相反使用标准C函数分配的内存返回给脚本引擎,那么PHP会崩溃。

这些函数的优点是:任何分配的内存在偶然情况下如果没有被释放,则会在页面请求的最后被释放。因此,真正的内存泄漏不会产生。然而,不要依赖这一机制,从调试和性能两个原因来考虑,应当确保释放应该释放的内存。剩下的优点是在多线程环境下性能的提高,调试模式下检测内存错误等。

还有一个重要的原因,你不需要检查这些内存分配函数的返回值是否为null。当内存分配失败,它们会发出E_ERROR错误,从而决不会返回到扩展。

从PHP函数中返回值

扩展API包含丰富的用于从函数中返回值的宏。这些宏有两种主要风格:第一种是RETVAL_type()形式,它设置了返回值但C代码继续执行。这通常使用在把控制交给脚本引擎前还希望做的一些清理工作的时候使用,然后再使用C的返回声明 ”return” 返回到PHP;后一个宏更加普遍,其形式是RETURN_type(),他设置了返回类型,同时返回控制到PHP。下表解释了大多数存在的宏。

设置返回值并且结束函数 设置返回值 宏返回类型和参数
RETURN_LONG(l) RETVAL_LONG(l) 整数
RETURN_BOOL(b) RETVAL_BOOL(b) 布尔数(1或0)
RETURN_NULL() RETVAL_NULL() NULL
RETURN_DOUBLE(d) RETVAL_DOUBLE(d) 浮点数
RETURN_STRING(s, dup) RETVAL_STRING(s, dup) 字符串。如果dup为1,引擎会调用estrdup()重复s,使用拷贝。如果dup为0,就使用s
RETURN_STRINGL(s, l, dup) RETVAL_STRINGL(s, l, dup) 长度为l的字符串值。与上一个宏一样,但因为s的长度被指定,所以速度更快。
RETURN_TRUE RETVAL_TRUE 返回布尔值true。注意到这个宏没有括号。
RETURN_FALSE RETVAL_FALSE 返回布尔值false。注意到这个宏没有括号。
RETURN_RESOURCE(r) RETVAL_RESOURCE(r) 资源句柄。

完成self_concat()

现在你已经学会了如何分配内存和从PHP扩展函数里返回函数值,那么我们就能够完成self_concat()的编码:

  1. /* {{{ proto string self_concat(string str, int n)
  2.  
  3. */
  4.  
  5. PHP_FUNCTION(self_concat)
  6.  
  7. }
  8.  
  9. char *str = NULL;
  10.  
  11. int argc = ZEND_NUM_ARGS();
  12.  
  13. int str_len;
  14.  
  15. long n;
  16.  
  17. char *result; /* Points to resulting string */
  18.  
  19. char *ptr; /* Points at the next location we want to copy to */
  20.  
  21. int result_length; /* Length of resulting string */
  22.  
  23. if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
  24.  
  25. return;
  26.  
  27. /* Calculate length of result */
  28.  
  29. result_length = (str_len * n);
  30.  
  31. /* Allocate memory for result */
  32.  
  33. result = (char *) emalloc(result_length + 1);
  34.  
  35. /* Point at the beginning of the result */
  36.  
  37. ptr = result;
  38.  
  39. while (n--) {
  40.  
  41. /* Copy str to the result */
  42.  
  43. memcpy(ptr, str, str_len);
  44.  
  45. /* Increment ptr to point at the next position we want to write to */
  46.  
  47. ptr += str_len;
  48.  
  49. }
  50.  
  51. /* Null terminate the result. Always null-terminate your strings
  52.  
  53. even if they are binary strings */
  54.  
  55. *ptr = '\0';
  56.  
  57. /* Return result to the scripting engine without duplicating it*/
  58.  
  59. RETURN_STRINGL(result, result_length, 0);
  60.  
  61. }
  62.  
  63. /* }}} */

现在要做的就是重新编译一下PHP,这样就完成了第一个PHP函数。

让我门检查函数是否真的工作。在最新编译过的PHP树下执行[2]下面的脚本:

  1. <?php
  2. for ($i = 1; $i <= 3; $i++){
  3.     print self_concat("ThisIsUseless", $i);
  4.     print "\n";
  5. }
  6. ?>

你应该得到下面的结果:

  1. ThisIsUseless
  2.  
  3. ThisIsUselessThisIsUseless
  4.  
  5. ThisIsUselessThisIsUselessThisIsUseless

 

实例小结

你已经学会如何编写一个简单的PHP函数。回到本章的开头,我们提到用C编写PHP功能函数的两个主要的动机。第一个动机是用C实现一些算法来提高性能和扩展功能。前一个例子应该能够指导你快速上手这种类型扩展的开发。第二个动机是包裹三方函数库。我们将在下一步讨论。

包裹第三方的扩展

本节中你将学到如何编写更有用和更完善的扩展。该节的扩展包裹了一个C库,展示了如何编写一个含有多个互相依赖的PHP函数扩展。

动机

也许最常见的PHP扩展是那些包裹第三方C库的扩展。这些扩展包括MySQL或Oracle的数据库服务库,libxml2的 XML技术库,ImageMagick 或GD的图形操纵库。

在本节中,我们编写一个扩展,同样使用脚本来生成骨架扩展,因为这能节省许多工作量。这个扩展包裹了标准C函数fopen(), fclose(), fread(), fwrite()和 feof().

扩展使用一个被叫做资源的抽象数据类型,用于代表已打开的文件FILE*。你会注意到大多数处理比如数据库连接、文件句柄等的PHP扩展使用了资源类型,这是因为引擎自己无法直接“理解”它们。我们计划在PHP扩展中实现的C API列表如下:

FILE *fopen(const char *path, const char *mode);

int fclose(FILE *stream);

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

int feof(FILE *stream);

 

我们实现这些函数,使它们在命名习惯和简单性上符合PHP脚本。如果你曾经向PHP社区贡献过代码,你被期望遵循一些公共习俗,而不是跟随C库里的API。并不是所有的习俗都写在PHP代码树的CODING_STANDARDS文件里。这即是说,此功能已经从PHP发展的很早阶段即被包含在PHP中,并且与C库API类似。PHP安装已经支持fopen(), fclose()和更多的PHP函数。

以下是PHP风格的API:

resource file_open(string filename, string mode)

file_open() //接收两个字符串(文件名和模式),返回一个文件的资源句柄。

bool file_close(resource filehandle)

file_close() //接收一个资源句柄,返回真/假指示是否操作成功。

string file_read(resource filehandle, int size)

file_read() //接收一个资源句柄和读入的总字节数,返回读入的字符串。

bool file_write(resource filehandle, string buffer)

file_write()   //接收一个资源句柄和被写入的字符串,返回真/假指示是否操作成功。

bool file_eof(resource filehandle)

file_eof() //接收一个资源句柄,返回真/假指示是否到达文件的尾部。

因此,我们的函数定义文件——保存为ext/目录下的myfile.def——内容如下:

resource file_open(string filename, string mode)

bool file_close(resource filehandle)

string file_read(resource filehandle, int size)

bool file_write(resource filehandle, string buffer)

bool file_eof(resource filehandle)

 

下一步,利用ext_skel脚本在ext./ 原代码目录执行下面的命令:

./ext_skel --extname=myfile --proto=myfile.def

然后,按照前一个例子的关于编译新建立脚本的步骤操作。你会得到一些包含FETCH_RESOURCE()宏行的编译错误,这样骨架脚本就无法顺利完成编译。为了让骨架扩展顺利通过编译,把那些出错行[3]注释掉即可。

资源

资源是一个能容纳任何信息的抽象数据结构。正如前面提到的,这个信息通常包括例如文件句柄、数据库连接结构和其他一些复杂类型的数据。

使用资源的主要原因是因为:资源被一个集中的队列所管理,该队列可以在PHP开发人员没有在脚本里面显式地释放时可以自动地被释放。

举个例子,考虑到编写一个脚本,在脚本里调用mysql_connect()打开一个MySQL连接,可是当该数据库连接资源不再使用时却没有调用mysql_close()。在PHP里,资源机制能够检测什么时候这个资源应当被释放,然后在当前请求的结尾或通常情况下更早地释放资源。这就为减少内存泄漏赋予了一个“防弹”机制。如果没有这样一个机制,经过几次web请求后,web服务器也许会潜在地泄漏许多内存资源,从而导致服务器当机或出错。

注册资源类型

如何使用资源?Zend引擎让使用资源变地非常容易。你要做的第一件事就是把资源注册到引擎中去。使用这个API函数:

int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number)

这个函数返回一个资源类型id,该id应当被作为全局变量保存在扩展里,以便在必要的时候传递给其他资源API。ld:该资源释放时调用的函数。pld用于在不同请求中始终存在的永久资源,本章不会涉及。type_name是一个具有描述性类型名称的字符串,module_number为引擎内部使用,当我们调用这个函数时,我们只需要传递一个已经定义好的module_number变量。

回到我们的例子中来:我们会添加下面的代码到myfile.c原文件中。该文件包括了资源释放函数的定义,此资源函数被传递给zend_register_list_destructors_ex()注册函数(资源释放函数应该提早添加到文件中,以便在调用zend_register_list_destructors_ex()时该函数已被定义):

  1. static void myfile_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC){
  2. FILE *fp = (FILE *) rsrc->ptr;
  3. fclose(fp);
  4. }

把注册行添加到PHP_MINIT_FUNCTION()后,看起来应该如下面的代码:

  1. PHP_MINIT_FUNCTION(myfile){
  2. /* If you have INI entries, uncomment these lines
  3. ZEND_INIT_MODULE_GLOBALS(myfile, php_myfile_init_globals,NULL);
  4.  
  5. REGISTER_INI_ENTRIES();
  6. */
  7.  
  8. le_myfile = zend_register_list_destructors_ex(myfile_dtor,NULL,"standard-c-file", module_number);
  9.  
  10. return SUCCESS;
  11. }

l 注意到le_myfile是一个已经被ext_skel脚本定义好的全局变量。

PHP_MINIT_FUNCTION()是一个先于模块(扩展)的启动函数,是暴露给扩展的一部分API。下表提供可用函数简要的说明。

函数声明宏 语义
PHP_MINIT_FUNCTION() 当PHP被装载时,模块启动函数即被引擎调用。这使得引擎做一些例如资源类型,注册INI变量等的一次初始化。
PHP_MSHUTDOWN_FUNCTION() 当PHP完全关闭时,模块关闭函数即被引擎调用。通常用于注销INI条目
PHP_RINIT_FUNCTION() 在每次PHP请求开始,请求前启动函数被调用。通常用于管理请求前逻辑。
PHP_RSHUTDOWN_FUNCTION() 在每次PHP请求结束后,请求前关闭函数被调用。经常应用在清理请求前启动函数的逻辑。
PHP_MINFO_FUNCTION() 调用phpinfo()时模块信息函数被呼叫,从而打印出模块信息。

新建和注册新资源 我们准备实现file_open()函数。当我们打开文件得到一个FILE *,我们需要利用资源机制注册它。下面的主要宏实现注册功能:

  1. ZEND_REGISTER_RESOURCE(rsrc_result, rsrc_pointer, rsrc_type);

参考表格对宏参数的解释

ZEND_REGISTER_RESOURCE 宏参数

宏参数 参数类型
rsrc_result zval *, which should be set with the registered resource information. zval * 设置为已注册资源信息
rsrc_pointer Pointer to our resource data. 资源数据指针
rsrc_type The resource id obtained when registering the resource type. 注册资源类型时获得的资源id

文件函数

现在你知道了如何使用ZEND_REGISTER_RESOURCE()宏,并且准备好了开始编写file_open()函数。还有一个主题我们需要讲述。

当PHP运行在多线程服务器上,不能使用标准的C文件存取函数。这是因为在一个线程里正在运行的PHP脚本会改变当前工作目录,因此另外一个线程里的脚本使用相对路径则无法打开目标文件。为了阻止这种错误发生,PHP框架提供了称作VCWD (virtual current working directory 虚拟当前工作目录)宏,用来代替任何依赖当前工作目录的存取函数。这些宏与被替代的函数具备同样的功能,同时是被透明地处理。在某些没有标准C函数库平台的情况下,VCWD框架则不会得到支持。例如,Win32下不存在chown(),就不会有相应的VCWD_CHOWN()宏被定义。

VCWD列表

标准C库 VCWD宏
getcwd() VCWD_GETCWD()
fopen() VCWD_FOPEN
open() VCWD_OPEN() //用于两个参数的版本
open() VCWD_OPEN_MODE() //用于三个参数的open()版本
creat() VCWD_CREAT()
chdir() VCWD_CHDIR()
getwd() VCWD_GETWD()
realpath() VCWD_REALPATH()
rename() VCWD_RENAME()
stat() VCWD_STAT()
lstat() VCWD_LSTAT()
unlink() VCWD_UNLINK()
mkdir() VCWD_MKDIR()
rmdir() VCWD_RMDIR()
opendir() VCWD_OPENDIR()
popen() VCWD_POPEN()
access() VCWD_ACCESS()
utime() VCWD_UTIME()
chmod() VCWD_CHMOD()
chown() VCWD_CHOWN()

编写利用资源的第一个PHP函数

实现file_open()应该非常简单,看起来像下面的样子:

  1. PHP_FUNCTION(file_open){
  2. char *filename = NULL;
  3. char *mode = NULL;
  4. int argc = ZEND_NUM_ARGS();
  5. int filename_len;
  6. int mode_len;
  7. FILE *fp;
  8.  
  9. if (zend_parse_parameters(argc TSRMLS_CC, "ss", &filename,&filename_len, &mode, &mode_len) == FAILURE) {
  10. return;
  11. }
  12.  
  13. fp = VCWD_FOPEN(filename, mode);
  14.  
  15. if (fp == NULL) {
  16. RETURN_FALSE;
  17. }
  18.  
  19. ZEND_REGISTER_RESOURCE(return_value, fp, le_myfile);
  20. }

 

你可能会注意到资源注册宏的第一个参数return_value,可此地找不到它的定义。这个变量自动的被扩展框架定义为zval * 类型的函数返回值。先前讨论的、能够影响返回值的RETURN_LONG() 和RETVAL_BOOL()宏确实改变了return_value的值。因此很容易猜到程序注册了我们取得的文件指针fp,同时设置return_value为该注册资源。

访问资源 需要使用下面的宏访问资源(参看表对宏参数的解释)

ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type);

ZEND_FETCH_RESOURCE 宏参数

参数 含义
rsrc 资源值保存到的变量名。它应该和资源有相同类型。
rsrc_type rsrc的类型,用于在内部把资源转换成正确的类型
passed_id 寻找的资源值(例如zval **)
default_id 如果该值不为-1,就使用这个id。用于实现资源的默认值。
resource_type_name 资源的一个简短名称,用于错误信息。
resource_type 注册资源的资源类型id

使用这个宏,我们现在能够实现file_eof():

  1. PHP_FUNCTION(file_eof){
  2. int argc = ZEND_NUM_ARGS();
  3. zval *filehandle = NULL;
  4. FILE *fp;
  5.  
  6. if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) ==FAILURE) {
  7. return;
  8. }
  9.  
  10. ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-c-file",le_myfile);
  11.  
  12. if (fp == NULL){
  13. RETURN_FALSE;
  14. }
  15.  
  16. if (feof(fp) <= 0) {
  17. /* Return eof also if there was an error */
  18. RETURN_TRUE;
  19. }
  20.  
  21. RETURN_FALSE;
  22. }

 

 

删除一个资源

通常使用下面这个宏删除一个资源:

int zend_list_delete(int id)

传递给宏一个资源id,返回SUCCESS或者FAILURE。如果资源存在,优先从Zend资源列队中删除,该过程中会调用该资源类型的已注册资源清理函数。因此,在我们的例子中,不必取得文件指针,调用fclose()关闭文件,然后再删除资源。直接把资源删除掉即可。

使用这个宏,我们能够实现file_close():

  1. PHP_FUNCTION(file_close){
  2. int argc = ZEND_NUM_ARGS();
  3. zval *filehandle = NULL;
  4.  
  5. if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) == FAILURE) {
  6. return;
  7. }
  8.  
  9. if (zend_list_delete(Z_RESVAL_P(filehandle)) == FAILURE) {
  10. RETURN_FALSE;
  11. }
  12.  
  13. RETURN_TRUE;
  14. }

 

你肯定会问自己Z_RESVAL_P()是做什么的。当我们使用zend_parse_parameters()从参数列表中取得资源的时候,得到的是zval的形式。为了获得资源id,我们使用Z_RESVAL_P()宏得到id,然后把id传递给zend_list_delete()。
有一系列宏用于访问存储于zval值(参考表的宏列表)。尽管在大多数情况下zend_parse_parameters()返回与c类型相应的值,我们仍希望直接处理zval,包括资源这一情况。

Zval访问宏

访问对象 C 类型
Z_LVAL, Z_LVAL_P, Z_LVAL_PP 整型值 long
Z_BVAL, Z_BVAL_P, Z_BVAL_PP 布尔值 zend_bool
Z_DVAL, Z_DVAL_P, Z_DVAL_PP 浮点值 double
Z_STRVAL, Z_STRVAL_P, Z_STRVAL_PP 字符串值 char *
Z_STRLEN, Z_STRLEN_P, Z_STRLEN_PP 字符串长度值 int
Z_RESVAL, Z_RESVAL_P,Z_RESVAL_PP 资源值 long
Z_ARRVAL, Z_ARRVAL_P, Z_ARRVAL_PP 联合数组 HashTable *
Z_TYPE, Z_TYPE_P, Z_TYPE_PP Zval类型 Enumeration (IS_NULL, IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_BOOL, IS_RESOURCE)
Z_OBJPROP, Z_OBJPROP_P, Z_OBJPROP_PP 对象属性hash(本章不会谈到) HashTable *
Z_OBJCE, Z_OBJCE_P, Z_OBJCE_PP 对象的类信息 zend_class_entry

用于访问zval值的宏

所有的宏都有三种形式:一个是接受zval s,另外一个接受zval *s,最后一个接受zval **s。它们的区别是在命名上,第一个没有后缀,zval *有后缀_P(代表一个指针),最后一个 zval **有后缀_PP(代表两个指针)。
现在,你有足够的信息来独立完成 file_read()和 file_write()函数。这里是一个可能的实现:

  1. PHP_FUNCTION(file_read){
  2. int argc = ZEND_NUM_ARGS();
  3. long size;
  4. zval *filehandle = NULL;
  5. FILE *fp;
  6. char *result;
  7. size_t bytes_read;
  8.  
  9. if (zend_parse_parameters(argc TSRMLS_CC, "rl", &filehandle,&size) == FAILURE) {
  10. return;
  11. }
  12.  
  13. ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-cfile", le_myfile);
  14.  
  15. result = (char *) emalloc(size+1);
  16.  
  17. bytes_read = fread(result, 1, size, fp);
  18.  
  19. result[bytes_read] = '\0';
  20.  
  21. RETURN_STRING(result, 0);
  22. }
  23.  
  24. PHP_FUNCTION(file_write){
  25. char *buffer = NULL;
  26. int argc = ZEND_NUM_ARGS();
  27. int buffer_len;
  28. zval *filehandle = NULL;
  29. FILE *fp;
  30.  
  31. if (zend_parse_parameters(argc TSRMLS_CC, "rs", &filehandle,&buffer, &buffer_len) == FAILURE) {
  32. return;
  33. }
  34.  
  35. ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-cfile", le_myfile);
  36.  
  37. if (fwrite(buffer, 1, buffer_len, fp) != buffer_len) {
  38. RETURN_FALSE;
  39. }
  40.  
  41. RETURN_TRUE;
  42. }

测试扩展

你现在可以编写一个测试脚本来检测扩展是否工作正常。下面是一个示例脚本,该脚本打开文件test.txt,输出文件类容到标准输出,建立一个拷贝test.txt.new。

  1. <?php
  2. $fp_in = file_open("test.txt", "r") or die("Unable to open input file\n");
  3.  
  4. $fp_out = file_open("test.txt.new", "w") or die("Unable to open output file\n");
  5.  
  6. while (!file_eof($fp_in)) {
  7.     $str = file_read($fp_in, 1024);
  8.     print($str);
  9.     file_write($fp_out, $str);
  10. }
  11.  
  12. file_close($fp_in);
  13. file_close($fp_out);
  14. ?>

全局变量

你可能希望在扩展里使用全局C变量,无论是独自在内部使用或访问php.ini文件中的INI扩展注册标记(INI在下一节中讨论)。因为PHP是为多线程环境而设计,所以不必定义全局变量。PHP提供了一个创建全局变量的机制,可以同时应用在线程和非线程环境中。我们应当始终利用这个机制,而不要自主地定义全局变量。用一个宏访问这些全局变量,使用起来就像普通全局变量一样。

用于生成myfile工程骨架文件的ext_skel脚本创建了必要的代码来支持全局变量。通过检查php_myfile.h文件,你应当发现类似下面的被注释掉的一节,

  1. ZEND_BEGIN_MODULE_GLOBALS(myfile)
  2.  
  3. int global_value;
  4. char *global_string;
  5.  
  6. ZEND_END_MODULE_GLOBALS(myfile)

你可以把这一节的注释去掉,同时添加任何其他全局变量于这两个宏之间。文件后部的几行,骨架脚本自动地定义一个MYFILE_G(v)宏。这个宏应当被用于所有的代码,以便访问这些全局变量。这就确保在多线程环境中,访问的全局变量仅是一个线程的拷贝,而不需要互斥的操作。

为了使全局变量有效,最后需要做的是把myfile.c:

ZEND_DECLARE_MODULE_GLOBALS(myfile)

注释去掉。

你也许希望在每次PHP请求的开始初始化全局变量。另外,做为一个例子,全局变量已指向了一个已分配的内存,在每次PHP请求结束时需要释放内存。为了达到这些目的,全局变量机制提供了一个特殊的宏,用于注册全局变量的构造和析构函数(参考表对宏参数的说明):

ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor, globals_dtor)

表 ZEND_INIT_MODULE_GLOBALS 宏参数

参数 含义
module_name 与传递给ZEND_BEGIN_MODULE_GLOBALS()宏相同的扩展名称。
globals_ctor 构造函数指针。在myfile扩展里,函数原形与void php_myfile_init_globals(zend_myfile_globals *myfile_globals)类似
globals_dtor 析构函数指针。例如,php_myfile_init_globals(zend_myfile_globals *myfile_globals)

你可以在myfile.c里看到如何使用构造函数和ZEND_INIT_MODULE_GLOBALS()宏的示例。

添加自定义INI指令

INI文件(php.ini)的实现使得PHP扩展注册和监听各自的INI条目。如果这些INI条目由php.ini、Apache的htaccess或其他配置方法来赋值,注册的INI变量总是更新到正确的值。整个INI框架有许多不同的选项以实现其灵活性。我们涉及一些基本的(也是个好的开端),借助本章的其他材料,我们就能够应付日常开发工作的需要。

通过在PHP_INI_BEGIN()/PHP_INI_END()宏之间的STD_PHP_INI_ENTRY()宏注册PHP INI指令。例如在我们的例子里,myfile.c中的注册过程应当如下:

  1. PHP_INI_BEGIN()
  2.  
  3. STD_PHP_INI_ENTRY("myfile.global_value", "42", PHP_INI_ALL, OnUpdateInt, global_value, zend_myfile_globals, myfile_globals)
  4.  
  5. STD_PHP_INI_ENTRY("myfile.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_myfile_globals, myfile_globals)
  6.  
  7. PHP_INI_END()

 

除了STD_PHP_INI_ENTRY()其他宏也能够使用,但这个宏是最常用的,可以满足大多数需要(参看表对宏参数的说明):

STD_PHP_INI_ENTRY(name, default_value, modifiable, on_modify, property_name, struct_type, struct_ptr)

STD_PHP_INI_ENTRY 宏参数表

参数 含义
name INI条目名
default_value 如果没有在INI文件中指定,条目的默认值。默认值始终是一个字符串。
modifiable 设定在何种环境下INI条目可以被更改的位域。可以的值是:
• PHP_INI_SYSTEM. 能够在php.ini或http.conf等系统文件更改
• PHP_INI_PERDIR. 能够在 .htaccess中更改
• PHP_INI_USER. 能够被用户脚本更改
• PHP_INI_ALL. 能够在所有地方更改
on_modify 处理INI条目更改的回调函数。你不需自己编写处理程序,使用下面提供的函数。包括:
• OnUpdateInt
• OnUpdateString
• OnUpdateBool
• OnUpdateStringUnempty
• OnUpdateReal
property_name 应当被更新的变量名
struct_type 变量驻留的结构类型。因为通常使用全局变量机制,所以这个类型自动被定义,类似于zend_myfile_globals。
struct_ptr 全局结构名。如果使用全局变量机制,该名为myfile_globals。

最后,为了使自定义INI条目机制正常工作,你需要分别去掉PHP_MINIT_FUNCTION(myfile)中的REGISTER_INI_ENTRIES()调用和PHP_MSHUTDOWN_FUNCTION(myfile)中的UNREGISTER_INI_ENTRIES()的注释。

访问两个示例全局变量中的一个与在扩展里编写MYFILE_G(global_value) 和MYFILE_G(global_string)一样简单。

如果你把下面的两行放在php.ini中,MYFILE_G(global_value)的值会变为99。

; php.ini – The following line sets the INI entry myfile.global_value to 99.
myfile.global_value = 99

 

线程安全资源管理宏

现在,你肯定注意到以TSRM(线程安全资源管理器)开头的宏随处使用。这些宏提供给扩展拥有独自的全局变量的可能,正如前面提到的。

当编写PHP扩展时,无论是在多进程或多线程环境中,都是依靠这一机制访问扩展自己的全局变量。如果使用全局变量访问宏(例如MYFILE_G()宏),需要确保TSRM上下文信息出现在当前函数中。基于性能的原因,Zend引擎试图把这个上下文信息作为参数传递到更多的地方,包括PHP_FUNCTION()的定义。正因为这样,在PHP_FUNCTION()内当编写的代码使用访问宏(例如MYFILE_G()宏)时,不需要做任何特殊的声明。然而,如果PHP函数调用其他需要访问全局变量的C函数,要么把上下文作为一个额外的参数传递给C函数,要么提取上下文(要慢点)。

在需要访问全局变量的代码块开头使用TSRMLS_FETCH()来提取上下文。例如:

  1. void myfunc(){
  2. TSRMLS_FETCH();
  3.  
  4. MYFILE_G(myglobal) = 2;
  5. }

如果希望让代码更加优化,更好的办法是直接传递上下文给函数(正如前面叙述的,PHP_FUNCTION()范围内自动可用)。可以使用TSRMLS_C(C表示调用Call)和TSRMLS_CC(CC边式调用Call和逗号Comma)宏。前者应当用于仅当上下文作为一个单独的参数,后者应用于接受多个参数的函数。在后一种情况中,因为根据取名,逗号在上下文的前面,所以TSRMLS_CC不能是第一个函数参。

在函数原形中,可以分别使用TSRMLS_D和TSRMLS_DC宏声名正在接收上下文。

下面是前一例子的重写,利用了参数传递上下文。

  1. void myfunc(TSRMLS_D){
  2. MYFILE_G(myglobal) = 2;
  3. }
  4.  
  5. PHP_FUNCTION(my_php_function)
  6. {
  7. myfunc(TSRMLS_C);
  8. }

 

总 结

现在,你已经学到了足够的东西来创建自己的扩展。本章讲述了一些重要的基础来编写和理解PHP扩展。Zend引擎提供的扩展API相当丰富,使你能够开发面向对象的扩展。几乎没有文档谈几许多高级特性。当然,依靠本章所学的基础知识,你可以通过浏览现有的原码学到很多。

更多关于信息可以在PHP手册的扩展PHP章节http://www.php.net/manual/en/zend.php中找到。另外,你也可以考虑加入PHP开发者邮件列表internals@ lists.php.net,该邮件列表围绕开发PHP 本身。你还可以查看一下新的扩展生成工具——PECL_Gen(http://pear.php.net/package/PECL_Gen),这个工具正在开发之中,比起本章使用的ext_skel有更多的特性。

此外你还可以关注风雪之隅, 会有更多相关知识更新.

词汇表

binary safe 二进制安全
context 上下文
extensions 扩展
entry 条目
skeleton 骨架

Thread-Safe Resource Manager TSRM 线程安全资源管理器

Contact info:
Email: taft at wjl.cn / laruence at yahoo.com.cn
http://www.laruence.com

——————————————————————————–
[1] 可参考译者写的
[2] 译者:可以使用phpcli程序在控制台里执行php文件。
[3] 译者:可以查看到生成的FETCH_RESOURCE()宏参数是一些’???’。

posted @ 2009-09-01 12:21 小马歌 阅读(1292) | 评论 (0)编辑 收藏
 
下面是一份对PHP5.1.4底层的研究,是一位从事PHP很长时间并有较深入研究的来自PHPChina.com上的PHPer。在此向他表示感谢!
PHP源代码分析(针对版本PHP5.1.4)
PHP源代码分析 1
1. 目录结构 1
2. PHP使用Lex和Yacc对语法进行解析。 1
3. PHP如何使用Mysql? 2
4. 安全模式? 2
5. 那些是 PHP 的标准函数,那些是扩展函数? 2
6. PHP 源代码中的PHP_FUNCTION(xx) 宏。 2
7. 那些函数集是标准的? 2
8. 一些函数的实现过程 2
9. PHP 函数集注册过程 3
10. 有趣的Zend LOGO图片 3
11. PHP的语法树? 3
12. 从 CVS 获取 PHP 源代码 5

1. 目录结构
1. build 和编译有关的目录。
2. ext 扩展库代码,例如 Mysql、zlib、iconv 等我们熟悉的扩展库。
3. main 主目录。
4. sapi 和各种服务器的接口调用,例如apache、IIS等,也包含一般的fastcgi、cgi等。
5. win32 和 Windows 下编译 PHP 有关的脚本。用了 WSH。
6. Zend 文件夹核心的引擎。
7. scripts Linux 下的脚本目录。
8. tests 测试脚本目录
9. sapi 各类 Web 服务器的接口。
2. PHP使用Lex和Yacc对语法进行解析。
在 Zend 目录下有两个文件 zend_language_parser.y 与 zend_language_scanner.l 他们是Lex和Yacc的脚本文件,通过这两个脚本文件生成对应的.c和.h文件,实际上这在 linux 下非常普遍,gcc 也使用它们产生语树。
3. PHP如何使用Mysql?
ext 目录下有一个 mysql 子目录,这个目录中的php_mysql.c 和 php_mysql.h 负责 PHP 与 Mysql 操作。使用了 Mysql 手册中的 C 语言 API。
4. 安全模式?
main 文件夹下的safe_mode.h 和 safe_mode.c 文件负责PHP的安全模式。
5. 那些是 PHP 的标准函数,那些是扩展函数?
ext 目录下英文意思是扩展,而在 ext 下还是有一个 standard 文件夹,存放着 PHP 中的标准函数,例如 explode 这个函数是在./ext/standard/string.c 下定义的。
6. PHP 源代码中的PHP_FUNCTION(xx) 宏。
这个宏用来检验一个函数名称是否合法。合法的函数名称应该由小写字母及下划线组成。
7. 那些函数集是标准的?
通过 ./ext/standard/ 目录我们可以看到以下常用函数集是标准的。字符串函数集、数组函数集、文件及目录操作函数集、md5算法等。
8. 一些函数的实现过程
1. fsockopen, pfsockopen 的实现
这两个函数的实现离不开 ./ext/standard/fsock.c 文件中的 php_fsockopen_stream 函数。具体的socket都在./main/network.c 中实
现。
9. PHP 函数集注册过程
在./main/internal_functi****.c 中有一个数组 php_builtin_extensi**** 默认下有以下成员:
1. phpext_bcmath_ptr
2. phpext_calendar_ptr
3. phpext_com_dotnet_ptr
4. phpext_ctype_ptr
5. phpext_date_ptr
6. phpext_ftp_ptr
7. phpext_hash_ptr
8. phpext_odbc_ptr
9. phpext_pcre_ptr
10. phpext_reflection_ptr
11. phpext_session_ptr
12. phpext_spl_ptr
13. phpext_standard_ptr
14. phpext_tokenizer_ptr
15. phpext_zlib_ptr
接着 php_register_extensi****(php_builtin_extensi****, EXTCOUNT TSRMLS_CC) 进行注册
10. 有趣的Zend LOGO图片
./main/logos.h 文件中,用 zend_logo 与 php_logo 数组保存了 PHP 标志和 Zend 标志。所以你根本在发行包里找不到zend.gif。
【小知识:Zend 公司创建于 1999 年,之所以命名为 Zend,是取其公司两位始创者Zeev Suraski 和Andi Gutmans 姓名的近似合成发音(Zeev & Andi),Zend 作为 PHP 语言的缔造者和延续着在 PHP 社区中发挥着极为重要的作用,Zend公司一直具备PHP技术的设想和创新能力,并因此保持PHP独一无二的技术领先地位!】
11. PHP的语法树?
1. Lex与Yacc
市面上有这本书。大家可以买来看看,包括GCC都是用它们兄弟生成的语法树。如果对编译器感兴趣。可以翻阅市面上关于这方面的书,并不多就几本。
2. y语法树文件
./Zend/zend_language_scanner.l与./Zend/zend_language_parser.y 规定了PHP的语法。从字面意义上scanner表示语法初步扫描, parser表示语法解析。根据这两个文件lex与yacc可以生成对应的c代码。所以相对来说生成语法是很方便的。
3. 如何定义一个符号
例如 if($language='php') 这一句中的if 就是一个token 语法中我们用T_IF表示。具体在.l文件中如下定义了:
<ST_IN_SCRIPTING>"if" {
return T_IF;
}
这样.php文件中的if就会被翻译成内置符号T_IF。’(单引号)被如下定义:
<ST_SINGLE_QUOTE>['] {
BEGIN(ST_IN_SCRIPTING);
return '\'';
}
4. 复合符号例如最常见的变量命名$discuz_user, $submit 等。
<ST_IN_SCRIPTING,ST_DOUBLE_QUOTES,ST_HEREDOC,ST_BACKQUOTE>"$"{LABEL} {
zend_copy_value(zendlval, (yytext+1), (yyleng-1));
zendlval->type = IS_STRING;
return T_VARIABLE;
}
5. 一个有效的if语句过程
这个定义在zend_language_parser.y 189行:
T_IF '(' expr ')' {
zend_do_if_cond(&$3, &$4 TSRMLS_CC);
} statement {
zend_do_if_after_statement(&$4, 1 TSRMLS_CC);
} elseif_list else_single {
zend_do_if_end(TSRMLS_C);
}
  T_IF '(' expr ')' ':' {
zend_do_if_cond(&$3, &$4 TSRMLS_CC);
} inner_statement_list {
zend_do_if_after_statement(&$4, 1 TSRMLS_CC);
} new_elseif_list new_else_single T_ENDIF ';' {
zend_do_if_end(TSRMLS_C);
}
if 后面必须存在(),圆括弧里面是表达式 expr 表达式在734行被定义:
expr:
r_variable { $$ = $1; }
  expr_without_variable { $$ = $1; }
;
if 后面可以跟 elseif 语句及 else 语句。
从语法树里面我们看出 if () 后面是可以跟 : 的,这一般很少被使用吧。
6. 优先级和左右结合性
一般情况下.y文件中最先定义的操作符优先级相对低,并且可以使用%left、%right 进行描述左右结合性,例如:
%left '+' '-' '.'
%left '*' '/' '%'
%right '!'
这说明'!'在 PHP 语法中是右结合的, '*' '/' '%' '+' '-' '.' 是左结合的,并且'!'的优先级更高
例如语法 !$a + $b 要先计算 !$a 在进行加法操作%left ',' 被放在最上面定义,说明他的优先级最低,因为我们知道','可以等同一个语句。
7. php.ini的解析
1. 如果规定数值正负?
<INITIAL>[ ]*("true" "on" "yes")[ ]* {
ini_lval->value.str.val = zend_strndup("1", 1);
ini_lval->value.str.len = 1;
ini_lval->type = IS_STRING;
return CFG_TRUE;
}
<INITIAL>[ ]*("false" "off" "no" "none")[ ]* {
ini_lval->value.str.val = zend_strndup("", 0);
ini_lval->value.str.len = 0;
ini_lval->type = IS_STRING;
return CFG_FALSE;
}
  
  T_IF '(' expr ')' ':' {
zend_do_if_cond(&$3, &$4 TSRMLS_CC);
} inner_statement_list {
zend_do_if_after_statement(&$4, 1 TSRMLS_CC);
} new_elseif_list new_else_single T_ENDIF ';' {
zend_do_if_end(TSRMLS_C);
}
if 后面必须存在(),圆括弧里面是表达式 expr 表达式在734行被定义:
expr:
r_variable { $$ = $1; }
  expr_without_variable { $$ = $1; }
;
if 后面可以跟 elseif 语句及 else 语句。
从语法树里面我们看出 if () 后面是可以跟 : 的,这一般很少被使用吧。
posted @ 2009-09-01 10:23 小马歌 阅读(149) | 评论 (0)编辑 收藏
 

(一):
此篇文章准备分2个部分来讲述:
第一部分主要详细讲述一下怎么构建一个完成的C++应用扩展模块;
第二部分主要讲述在PHP及Zend框架下怎么使用Zend API和C++语言来实现自己所要的功能以及项目的开发;
此篇文章所运用的环境在Linux 2.4.21-4.ELsmp(Red Hat Linux 3.2.3-20),Apache/2.2.8,gcc version 3.2.3 20030502,PHP 5.2.5 (cli),Zend Engine v2.2.0下进行。

一、前言
以前写过一些使用C语言来扩展PHP的应用[1]。在淘宝使用C++做PHP的扩展做项目的过程中,遇到了一些问题,从Google中查找,使用C++来开发PHP的中文文章少之又少,而且没有一个手册来告诉用户怎么写m4[2]文件,怎么使用zend[3]引擎的一套api函数去写相关PHP的接口,这里就怎么用C++语言来开发PHP的一些心得介绍给大家,希望有心人能够有所收获;
二、为什么要用C++开发PHP
使用C++比用C语言开发PHP主要有2个好处:
使用C++能够很方便的操作string类型,本身的一些容器和模板[4]、以及面对对象的功能让开发者能够节省大量开发的时间,这是比较重要的一点;
C++可以直接使用C的库,只需要extern “C” {}将其C的头文件和库定义包含起来就可以,不需要太多的移植工作,可以重复利用前人的代码或者库进行后续的工作;
用C++开发PHP是快速、迅捷的,熟悉了相关的定义以及语法,相信开发PHP不是难事。

三、书写config文件

config.m4[5]或config.w32[6]文件是编译基础中最核心的文件,这个文件主要是用autoconf[7]来产生configure[8]配置文件,继而自动生成大家所熟悉的Makefile文件,以Linux系统为例:

你可以自己书写config.m4文件,也可以由Shell脚本 ext_skel[9] 来生成样板:
[cnangel@localhost ~]$wget http://docs.php.net/get/php-5.2.5.tar.bz2/from/cn.php.net/mirror
[cnangel@localhost ~]$tar -jxf php-5.2.5.tar.bz2
[cnangel@localhost ~]$cd php-5.2.6/ext
[cnangel@localhost ext]./ext_skel –extname=extern_name

接着你会发现在ext目录下多了一个叫extern_name的目录。进入该目录,会发现目录下有几个文件:
[cnangel@localhost ext_name]$ls -l
总计 32
-rw-r–r– 1 cnangel cnangel 2103 06-29 19:00 config.m4
-rw-r–r– 1 cnangel cnangel 310 06-29 19:00 config.w32
-rw-r–r– 1 cnangel cnangel 8 06-29 19:00 CREDITS
-rw-r–r– 1 cnangel cnangel 0 06-29 19:00 EXPERIMENTAL
-rw-r–r– 1 cnangel cnangel 5305 06-29 19:00 ext_name.c
-rw-r–r– 1 cnangel cnangel 508 06-29 19:00 ext_name.php
-rw-r–r– 1 cnangel cnangel 2766 06-29 19:00 php_ext_name.h
drwxr-xr-x 2 cnangel cnangel 4096 06-29 19:00 tests

然后可以根据提示来修改config.m4文件,这里有几个重要的宏命令如下:
dnl 是注释;
PHP_ARG_WITH 或者 PHP_ARG_ENABLE 指定了PHP扩展模块的工作方式,前者意味着不需要第三方库,后者正好相反;
PHP_REQUIRE_CXX 用于指定这个扩展用到了C++;
PHP_ADD_INCLUDE 指定PHP扩展模块用到的头文件目录;
PHP_CHECK_LIBRARY 指定PHP扩展模块PHP_ADD_LIBRARY_WITH_PATH定义以及库连接错误信息等;
PHP_ADD_LIBRARY(stdc++,”",EXTERN_NAME_LIBADD)用于将标准C++库链接进入扩展
PHP_SUBST(EXTERN_NAME_SHARED_LIBADD) 用于说明这个扩展编译成动态链接库的形式;
PHP_NEW_EXTENSION 用于指定有哪些源文件应该被编译,文件和文件之间用空格隔开;

ext_skel默认生成的模块框架是针对C的,我们要使用C++进行PHP扩展, 那除以上的PHP_REQUIRE_CXX, PHP_ADD_LIBRARY两个宏必需外,还要把extern_name.c改名成extern_name.cpp。

需要注意的是,在config.m4里面可以使用类似的Makefile语法,片段如下:
PHP_REQUIRE_CXX()
INCLUDES=”$INCLUDES `mysql_config –cflags`”
PHP_ADD_LIBRARY(stdc++, “”, EXTRA_LDFLAGS)
EXTRA_LDFLAGS=”$EXTRA_LDFLAGS `mysql_config –libs` -lmemcached”
AC_CHECK_HEADERS([mysql/mysql.h])
CPPFILE=”ext_name.cpp antiForbitWord.cpp antiBaseDict.cpp Trie.cpp Logger.cpp antiEncodeConverter.cpp strnormalize.cpp”
PHP_NEW_EXTENSION(ext_name, $CPPFILE, $ext_shared)
四、书写.h文件

这里指修改php_ext_name.h这个头文件。

由于TSRM.h这个文件所包含的函数和类都是用纯C语言写的,故应该使用extern来说明如下:
extern “C” {
#ifdef ZTS
#include “TSRM.h”
#endif
}

如果该php_ext_name.h头文件或者ext_name.cpp文件用到了C++语言中的一些容器或者一些函数,则需要在头文件中包含相应的c++库的头文件,否则会出现找不到相应的C++函数错误。
五、书写.cpp文件

这里指修改ext_name.cpp这个cpp文件。

由于config.h、php.h、php_ini.h和ext/standard/info.h中包含的函数和类如TSRM.h一样,都是用纯C语言写的,所以也需要用extern说明如下:
extern “C” {
#ifdef HAVE_CONFIG_H
#include “config.h”
#endif
#include “php.h”
#include “php_ini.h”
#include “ext/standard/info.h”
}

而 #include “php_ext_name.h” 这句则已经不需要包含在extern “C”内,另外,ZEND_GET_MODULE这个宏命令也是需要特别申明如下:
#ifdef COMPILE_DL_EXT_NAME
BEGIN_EXTERN_C()
ZEND_GET_MODULE(ext_name)
END_EXTERN_C()
#endif

总之,把一些C写的库或轰用兼容的方式给解决。
六、初步执行

这里需要用到一个命令:phpize[10],命令如下:
[cnangel@localhost ext_name]$phpize
[cnangel@localhost ext_name]$./configure
[cnangel@localhost ext_name]$make

注意:可以使用用phpize生成configure执行文件后,可以使用./configure –help查看帮助信息,修改config.m4文件可以修改configure的帮助信息。每次修改了config.m4文件,需要使用清除临时文件 命令phpize –clean来完成消除configure。
七、初步应用

怎么应用到php上,把刚才的扩展模块当作一个普通的php函数调用呢?简单的应用直接使用命令:
[cnangel@localhost ext_name]$sudo make install

如果有多个php版本,则寻找扩展库目录显得没有那么好找了,比如,你的php执行文件的路径在/usr/local/php/bin/目录下,想知道php扩展模块所在的目录的话,那么执行(PHP5.0以上):
[cnangel@localhost ext_name]$/usr/local/php/bin/php-config | grep extension-dir | sed ’s/.*\[\(.*\)]/\1/’`

PHP5.0以下执行:
[cnangel@localhost ext_name]$/usr/local/php/bin/php-config –extension-dir

这样你可以发现你的扩展库的路径:
/usr/local/php/lib/php/extensions/no-debug-non-zts-20060613

当然,你可以修改php.ini,找到php安装的配置文件,修改extension_dir的值为你想要的一个路径另外,需要将你的扩展写入php.ini,像这样:

extension=ext_name.so

最后,找到扩展库的路径后,将modules下面的extern_name.so文件复制到扩展库的目录下,重新启动一下Apache进程:
[cnangel@localhost ext_name]$which httpd
/usr/bin/httpd
[cnangel@localhost ext_name]$sudo /usr/bin/httpd -k stop
[cnangel@localhost ext_name]$sudo /usr/bin/httpd -k start

把这个样例ext_name.php复制到web路径上去,看看是否好使啦?下一节我们将详细讲一些Zend API的宏在ext_name.cpp中的一些复杂应用。

——————————————————————————-
(二)

这里主要讲述在PHP及Zend框架下怎么使用Zend API和C++语言来实现自己所要的功能以及项目的开发。
此篇文章所运用的环境在Linux 2.4.21-4.ELsmp(Red Hat Linux
3.2.3-20),Apache/2.2.8,gcc version 3.2.3 20030502,PHP 5.2.5 (cli),Zend
Engine v2.2.0下进行。
前言

上次我们说到使用c++写一个完整的php扩展,这里以ext_name模块为例复习一下:

首先仍然修改config.m4文件,由于没有引用外面的模块或者相关库,所以不需要使用PHP_ARG_WITH的方式,使用PHP_ARG_ENABLE方式。找到
PHP_NEW_EXTENSION(ext_name, ext_name.c, $ext_shared)

修改成
PHP_REQUIRE_CXX()
PHP_ADD_LIBRARY(stdc++, “”, EXTRA_LDFLAGS)
PHP_NEW_EXTENSION(ext_name, ext_name.cpp, $ext_shared)

并将ext_name.c重新命名为ext_name.cpp,接着修改其内容,将
#include “php.h”
#include “php_ini.h”
#include “ext/standard/info.h”

用extern “C”将其用大括号括起来,修改
ZEND_GET_MODULE(ext_name)


BEGIN_EXTERN_C()
ZEND_GET_MODULE(ext_name)
END_EXTERN_C()

到此为止,这就是我们第一章内容,第二章比较庞大,这里还是分节来叙述吧。
概述

概述里面主要简单介绍PHP扩展中的一些大致结构和需要注意的事项,做过C扩展PHP的都会知道 PHP_FE是一个宏把这个宏标识的函数,例如:helloworld,这个函数可以直接作用于PHP解释器,比如
< ?php
helloworld();
?>

安装ext_name样板后,系统会自动有一个函数confirm_ext_name_compiled,这个函数是可以自行修改的,当然,PHP_FE可以定义多个函数,这些函数都必须在之前进行申明,一般在php_ext_name.h头文件进行申明。

我们还知道,仅仅有头文件和PHP_FE宏来申明这个函数是不行的,这个函数还没有内容,怎么编写这个函数的内容呢?这个在接下来会讲到。

其实,稍微细心的人看了ext_name.cpp就知道,去掉注释后,还有很多的宏命令,比如zend_module_entry、ZEND_GET_MODULE、PHP_MINIT_FUNCTION等等,读者不要着急,下面会一一道来。

关于ext_name.cpp文件中一些变量的命名,通常是PHP模块名(eg:ext_name)前面或者后面有一串字符,比如 le_ext_name、ext_name_functions、这是一种习惯,最好我们在书写的时候遵循这种习惯,这样写出来的代码不仅仅让你自己明 白,让其他的开发人员也能够很快熟悉你的代码。通常一些定义的常量会大写,比如要定义这个模块的名字和版本,可以在头文件中添加:
#define PHP_EXT_NAME_EXTNAME “ext_name”
#define PHP_EXT_NAME_VERSION “0.1″

然后修改ext_name_module_entry的内容,将”ext_name”和”0.1″分别用PHP_EXT_NAME_EXTNAME和PHP_EXT_NAME_VERSION来替换,这样具有方便且通用。

如果你可能在代码中可能需要用到stl之类的或者c++的一些库,那么你可以在ext_name.cpp文件中添加
#ifndef __APP_CPP__
#define __APP_CPP__
#include
#include
#include
/*
#include
#include #include
#include
#include
#include
#include
*/
#endif
PHP 与 Zend API

引用一句经典的原文来说明PHP和Zend API之间的关系
PHP的核心由两部分组成。最底层是Zend引擎(ZE)。ZE把人类易读的脚本解析成机器可读的符号,
然后在进程空间内执行这些符号。ZE也处理内存管理、变量作用域及调度程序调用。另一部分是PHP内核,
它绑定了SAPI层(Server Application Programming Interface,通常涉及主机环境,如Apache,IIS,CLI,CGI等),
并处理与它的通信。它同时对safe_mode和open_basedir的检测提供一致的控制层,就像流层将fopen()、fread()和
fwrite()等用户空间的函数与文件和网络I/O联系起来一样。
模块信息

模块信息主要体现在ext_name_module_entry结构上,它包含了

1, 标准模块的头

通常用 “STANDARD_MODULE_HEADER” 来填充,它指定了模块的四个成员:
标识整个模块结构大小的 size
值为 ZEND_MODULE_API_NO 常量的 zend_api
标识是否为调试版本(使用 ZEND_DEBUG 进行编译)的 zend_debug
还有一个用来标识是否启用了 ZTS (Zend 线程安全,使用 ZTS 或USING_ZTS 进行编译)的 zts。

2, 模块名称

模块名称这个名字就是使用 phpinfo() 函数后在“Additional Modules”部分所显示的名称。

3, PHP扩展可用到的函数或类

zend函数块的指针

4, 模块启动函数

5, 模块关闭函数

6, 请求启动函数

7, 请求关闭函数

8, 模块信息函数

9, 模块的版本号

10, 其它结构元素

原文:http://my.huhoo.net/archives/2008/02/php_ip.html#1
参考:
快速开发一个PHP扩展:http://blog.csdn.net/heiyeshuwu/archive/2008/12/05/3453854.aspx
如何编写PHP扩展:http://blog.csdn.net/taft/archive/2006/02/10/596291.aspx

posted @ 2009-08-31 17:57 小马歌 阅读(1135) | 评论 (0)编辑 收藏
仅列出标题
共95页: First 上一页 63 64 65 66 67 68 69 70 71 下一页 Last