WIN7下VS2008下编译PHP扩展的6个细节

1、编译生成的dll文件无法加载的问题
此时apache启动时可能会报如下错误:
PHP Warning: PHP Startup: Invalid library (maybe not a PHP library) ‘php_martin.dll’ in Unknown on line 0
原因:get_module在动态链接库中不对外开放
修改:在vs2008的项目属性,选择【Configuration Properties】-> 【C/C++】-> 【Preprocessor】-> 【Preprocessor Definitions】增加(COMPILE_DL_MARTIN)宏定义
查看方式:进入vs2008的命令行模式,进入dll所在文件夹,输入命令:dumpbin /exports php_martin.dll
查看是否提供了get_module函数
以上的martin需换成你自己的扩展名

2、LNK2001: unresolved external symbol _ZVAL_ADDREF问题
在之前的文章PHP5.3版本编译扩展时出现:LNK2001: unresolved external symbol _ZVAL_ADDREF
有提到解决方案,只是这样是将新的接口转换成旧的接口,这对于无法修改的旧代码可以适用,但是对于新的代码,我们建议在旧版本的时候使用Z_ADDREF_P将ZVAL_ADDREF替换,如下所示代码:

1
2
3
#ifndef Z_ADDREF_P
#define Z_ADDREF_P(x) ZVAL_ADDREF(x)
#endif

感谢鸟哥的指导

3、对于在被其它c文件include的c文件,在进行编译操作时需要将其从项目中排除掉。

4、Runtime Library
在编译时如遇到显示如下错误时:

1
2
3
4
5
Error	229	error LNK2019: unresolved external symbol __imp___free_dbg referenced in function
 
Error	230	error LNK2019: unresolved external symbol __imp___malloc_dbg referenced in function 
 
Error	231	error LNK2019: unresolved external symbol __imp___strdup_dbg referenced in function

LNK2019: unresolved external symbol __imp___free_dbg referenced
在vs2008的项目属性,选择【Configuration Properties】-> 【C/C++】-> 【Code Generation】-> 【Runtime Library】,将其改为/MDd,而不是/MD

5、32位,64位问题
如有报错:Error 137 error C2466: cannot allocate an array of constant size 0
这可能是VS2008 默认使用 64 位的 time_t 结构
建议在命令中添加:/D “_USE_32BIT_TIME_T=1″

6、对于不同版本的dll编译,除了对应版本的源码外,所需要的php5ts.lib文件也要使用其相对应版本
否则会在链接时报LNK2019错误,如:
error LNK2019: unresolved external symbol __imp__zend_str_tolower_dup referenced in function
error LNK2019: unresolved external symbol __imp__gc_remove_zval_from_buffer referenced in function

–EOF–

PHP5.3版本编译扩展时出现:LNK2001: unresolved external symbol _ZVAL_ADDREF

PHP5.3版本编译扩展时出现的问题
近,因要编译PHP扩展,本着最新最好的出发点,将PHP5.3的源码包下载下来,并将扩展源码创建VS2008项目,在经历百般磨难后,终于不再出现语法错误,然而当满怀希望按下F7键后,发现出现了N多的LNK2001和LNK2019错误,其中error LNK2001巨多,在纠结了一个周末后,可能是感动上天,终于在GOOGLE中找到了答案。
报错如下:
error LNK2001: unresolved external symbol _ZVAL_ADDREF
GOOGLE给的答案是php5.3以上版本更改了一些Zend API,而ZVAL_ADDREF刚好是其中的一个。
如下为解决方案:
假设我们的扩展为martin,则在php_martin.h或martin文件中,在include了相关php本身的头文件后添加如下 代码:

1
2
3
4
5
6
7
8
9
 
#if (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 3) || (PHP_MAJOR_VERSION >= 6)
#undef ZVAL_REFCOUNT
#undef ZVAL_ADDREF
#undef ZVAL_DELREF
#define ZVAL_REFCOUNT Z_REFCOUNT_P
#define ZVAL_ADDREF Z_ADDREF_P
#define ZVAL_DELREF Z_DELREF_P
#endif

以上代码的作用是将新的宏以旧名重定义,以保持其可用性。

【参考资料】

http://www.hightman.cn/bbs/showthread.php?tid=656

http://d.hatena.ne.jp/rsky/20071016/1192524940

数据源架构模式之活动记录

数据源架构模式之活动记录

【活动记录的意图】
一个对象,它包装数据表或视图中某一行,封装数据库访问,并在这些数据上增加了领域逻辑。

【活动记录的适用场景】
适用于不太复杂的领域逻辑,如CRUD操作等。

【活动记录的运行机制】
对象既有数据又有行为。其使用最直接的方法,将数据访问逻辑置于领域对象中。
活动记录的本质是一个领域模型,这个领域模型中的类和基数据库中的记录结构应该完全匹配,类的每个域对应表的每一列。
一般来说,活动记录包括如下一些方法:
1、由数据行构造一个活动记录实例;
2、为将来对表的插入构造一个新的实例;
3、用静态查找方法来包装常用的SQL查询和返回活动记录;
4、更新数据库并将活动记录中的数据插入数据库;
5、获取或设置域;
6、实现部分业务逻辑。

【活动记录的优点和缺点】
优点:
1、简单,容易创建并且容易理解。
2、在使用事务脚本时,减少代码复制。
3、可以在改变数据库结构时不改变领域逻辑。
4、基于单个活动记录的派生和测试验证会很有效。

缺点:
1、没有隐藏关系数据库的存在。
2、仅当活动记录对象和数据库中表直接对应时,活动记录才会有效。
3、要求对象的设计和数据库的设计紧耦合,这使得项目中的进一步重构很困难

【活动记录与其它模式】
数据源架构模式之行数据入口:活动记录与行数据入口十分类似。二者的主要差别是行数据入口 仅有数据库访问而活动记录既有数据源逻辑又有领域逻辑。

【活动记录的PHP示例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
<?php
 
/**
 * 企业应用架构 数据源架构模式之活动记录 2010-10-17 sz
 * @author phppan.p#gmail.com  http://www.phppan.com
 * 哥学社成员(http://www.blog-brother.com/)
 * @package architecture
 */
 
/**
 * 定单类
 */
class Order {
 
    /**
     *  定单ID
     * @var <type>
     */
    private $_order_id;
 
    /**
     * 客户ID
     * @var <type>
     */
    private $_customer_id;
 
    /**
     * 定单金额
     * @var <type>
     */
    private $_amount;
 
    public function __construct($order_id, $customer_id, $amount) {
        $this->_order_id = $order_id;
        $this->_customer_id = $customer_id;
        $this->_amount = $amount;
    }
 
    /**
     * 实例的删除操作
     */
    public function delete() {
        $sql = "DELETE FROM Order SET WHERE order_id = " . $this->_order_id . " AND customer_id = "  . $this->_customer_id;
        return DB::query($sql);
    }
 
    /**
     * 实例的更新操作
     */
    public function update() {
    }
 
    /**
     * 插入操作
     */
    public function insert() {
    }
 
    public static function load($rs) {
        return new Order($rs['order_id'] ? $rs['order_id'] : NULL, $rs['customer_id'], $rs['amount'] ? $rs['amount'] : 0);
    }
 
}
 
class Customer {
 
    private $_name;
    private $_customer_id;
 
    public function __construct($customer_id, $name) {
        $this->_customer_id = $customer_id;
        $this->_name = $name;
    }
 
    /**
     * 用户删除定单操作 此实例方法包含了业务逻辑
     * 通过调用定单实例实现
     * 假设此处是对应的删除操作(实际中可能是一种以某字段来标记的假删除操作)
     */
    public function deleteOrder($order_id) {
        $order = Order::load(array('order_id' => $order_id, 'customer_id' => $this->_customer_id));
        return $order->delete();
    }
 
    /**
     * 实例的更新操作
     */
    public function update() {
    }
 
    /**
     * 入口类自身拥有插入操作
     */
    public function insert() {
    }
 
    public static function load($rs) {
        /* 此处可加上缓存 */
        return new Customer($rs['customer_id'] ? $rs['customer_id'] : NULL, $rs['name']);
    }
 
    /**
     * 根据客户ID 查找
     * @param integer $id   客户ID
     * @return  Customer 客户对象
     */
    public static function find($id) {
        return CustomerFinder::find($id);
    }
 
}
 
/**
 * 人员查找类
 */
class CustomerFinder {
 
    public static function find($id) {
        $sql = "SELECT * FROM person WHERE customer_id = " . $id;
        $rs = DB::query($sql);
 
        return Customer::load($rs);
    }
}
 
class DB {
 
    /**
     * 这只是一个执行SQL的演示方法
     * @param string $sql   需要执行的SQL
     */
    public static function query($sql) {
        echo "执行SQL: ", $sql, " <br />";
 
         if (strpos($sql, 'SELECT') !== FALSE) { //  示例,对于select查询返回查询结果
            return array('customer_id' => 1, 'name' => 'Martin');
        }
    }
 
}
 
/**
 * 客户端调用
 */
class Client {
 
    /**
     * Main program.
     */
    public static function main() {
 
 
        header("Content-type:text/html; charset=utf-8");
 
        /* 加载客户ID为1的客户信息 */
        $customer = Customer::find(1);
 
        /* 假设用户拥有的定单id为 9527*/
        $customer->deleteOrder(9527);
    }
 
}
 
Client::main();
?>

同前面的文章一样,这仅仅是一个活动记录的示例,关于活动记录模式的应用,可以查看Yii框架中的DB类,在其源码中有一个CActiveRecord抽象类,从这里可以看到活动记录模式的应用

另外,如果从事务脚本中创建活动记录,一般是首先将表包装为入口,接着开始行为迁移,使表深化成为活动记录。
对于活动记录中的域的访问和设置可以如yii框架一样,使用魔术方法__set方法和__get方法。