php的扩展和嵌入--c++类的扩展开发
今天花了几乎一天的时间研究php的相关c++扩展,第一次接触的时候很多地方不太熟悉,也碰到了不少坑,这里把整个过程叙述如下,参考的文章主要是http://devzone.zend.com/1435/wrapping-c-classes-in-a-php-extension/:
现在定义了一个Car类,它有一些成员函数,整个扩展包括的文件如下:
- config.m4 扩展的配置文件
- php_vehicles.h 扩展的头文件
- vehicles.cc 扩展的源文件
- car.h 类的头文件
- car.cc 类的源文件
接下来就按照文件的顺序对这个扩展的每个部分分别进行叙述:
配置文件:config.m4
1 PHP_ARG_ENABLE(vehicles, 2 [Whether to enable the "vehicles" extension], 3 [ --enable-vehicles Enable "vehicles" extension support]) 4 5 if test $PHP_VEHICLES != "no"; then 6 PHP_REQUIRE_CXX() 7 PHP_SUBST(VEHICLES_SHARED_LIBADD) 8 PHP_ADD_LIBRARY(stdc++, 1, VEHICLES_SHARED_LIBADD) 9 PHP_NEW_EXTENSION(vehicles, vehicles.cc car.cc, $ext_shared) 10 fi
로그인 후 복사
第六行是要求使用c++的编译器 第七行表示扩展会以动态连接库的形式出现 第八行表是增加了c++的库,也就是类似与string和std这种都可以用了. 第九行里面注意要把所有的源文件都包括进来. 类的头文件car.h这个跟c++的头文件声明是完全一样的. 类的源文件car.cc 源文件也是,属于C++的类定义#ifndef VEHICLES_CAR_H 2 #define VEHICLES_CAR_H 3 4 // A very simple car class 5 class Car { 6 public: 7 Car(int maxGear); 8 void shift(int gear); 9 void accelerate(); 10 void brake(); 11 int getCurrentSpeed(); 12 int getCurrentGear(); 13 private: 14 int maxGear; 15 int currentGear; 16 int speed; 17 }; 18 19 #endif /* VEHICLES_CAR_H */
로그인 후 복사2 #include "car.h" 3 Car::Car(int maxGear) { 4 this->maxGear = maxGear; 5 this->currentGear = 1; 6 this->speed = 0; 7 } 9 void Car::shift(int gear) { 10 if (gear < 1 || gear > maxGear) { 11 return; 12 } 13 currentGear = gear; 14 } 16 void Car::accelerate() { 17 speed += (5 * this->getCurrentGear()); 18 } 20 void Car::brake() { 21 speed -= (5 * this->getCurrentGear()); 22 } 24 int Car::getCurrentSpeed() { 25 return speed; 26 }
로그인 후 복사
接下来才是重点: php扩展的头文件php_vehicles.h首先用宏判断这个头文件是不是已经包含了.然后在第四行给这个扩展一个别名.第五行给定版本号. 注意在11到13行用extern "C"包含了起来,这是因为php是用c写的,所以在开发c++扩展的时候一定要声明一下. 第15行声明了整个扩展模块的入口,在这个入口函数中会定义诸如MINIT\RINIT这种startup函数 和 MSHUTDOWN RSHUTDOWN这种shutdown函数. php扩展的源文件vehicles.cc: 这个文件里面的内容相当多,因为它承载了如何把我们想要的c++的类与php的内核联系起来的任务.同时在这个文件中还需要把类中的成员函数进行相应的mapping,以方便php可以直接调用.这些功能会在下面的源码中一一加以说明: 在第一阶段的代码里,先不涉及类相关的部分,而是循序渐进,这里的代码先给出常规php扩展源码中需要进行的一些操作:1 #ifndef PHP_VEHICLES_H 2 #define PHP_VEHICLES_H 4 #define PHP_VEHICLES_EXTNAME "vehicles" 5 #define PHP_VEHICLES_EXTVER "0.1" 7 #ifdef HAVE_CONFIG_H 8 #include "config.h" 9 #endif 10 11 extern "C" { 12 #include "php.h" 13 } 14 15 extern zend_module_entry vehicles_module_entry; 16 #define phpext_vehicles_ptr &vehicles_module_entry; 17 18 #endif /* PHP_VEHICLES_H */
로그인 후 복사1 #include "php_vehicles.h" 2 PHP_MINIT_FUNCTION(vehicles) 3 { 4 return SUCCESS; 5 } 6 zend_module_entry vehicles_module_entry = { 7 #if ZEND_MODULE_API_NO >= 20010901 8 STANDARD_MODULE_HEADER, 9 #endif 10 PHP_VEHICLES_EXTNAME, 11 NULL, /* Functions */ 12 PHP_MINIT(vehicles), 13 NULL, /* MSHUTDOWN */ 14 NULL, /* RINIT */ 15 NULL, /* RSHUTDOWN */ 16 NULL, /* MINFO */ 17 #if ZEND_MODULE_API_NO >= 20010901 18 PHP_VEHICLES_EXTVER, 19 #endif 20 STANDARD_MODULE_PROPERTIES 21 }; 22 #ifdef COMPILE_DL_VEHICLES 23 extern "C" { 24 ZEND_GET_MODULE(vehicles) 25} 26 #endif
로그인 후 복사
第一行引入头文件.2~5行定义了模块的入口函数,这里先不进行任何操作.这里一般可以初始化模块的全局变量. 第6~21行通过zend_module_entry给出了扩展和Zend引擎之间的联系. 在这里因为只有MINT函数被定义了,所以其他位置都是NULL. 第22~24行则是常规项目,在扩展中都要加上,注意这里为了C++做了extern "C"的特殊处理. 在完成了这一步之后,已经可以进行一次扩展编译了,可以验证一下自己之前的程序有没有错误.过程如下,之后就不重复了.- phpize
- ./configure --enable-vehicles
- make
- sudo make install
- 在php.ini中加入extension=vehicles.so(只需要一次)
- 重启apache,如果是服务的话 sudo /etc/init.d/httpd restart
- 然后在info.php中查看是否已经有了vehicles这一项扩展.
- 如果觉得每次打都很麻烦,也可以简单的写一个shell脚本来完成这些工作.
在完成了基本的初始化之后,就要开始考虑php用户空间与我们定义的C++类之间的联系了.这部分代码是为了把类中的函数都暴露给php的用户空间脚本,
- 首先需要定义一个名字同样是Car的php类,
- 然后还要定义一组zend_function_entry表,用来说明这个类中有哪些方法想要引入到php用户空间中.
- 需要注意的是,在php用户空间中的方法不一定要跟c++类中的方法同名,你同时还可以根据自己的需要增加或删减c++类中的方法.这点非常的自由.
按照如下的代码更改vehicles.h1 #include "php_vehicles.h" 2 zend_class_entry *car_ce; 3 PHP_METHOD(Car, __construct){} 5 PHP_METHOD(Car, shift) {} 7 PHP_METHOD(Car, accelerate) {} 9 PHP_METHOD(Car, brake) {} 11 PHP_METHOD(Car, getCurrentSpeed){} 13 PHP_METHOD(Car, getCurrentGear){} 15 zend_function_entry car_methods[] = { 16 PHP_ME(Car, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR) 17 PHP_ME(Car, shift, NULL, ZEND_ACC_PUBLIC) 18 PHP_ME(Car, accelerate, NULL, ZEND_ACC_PUBLIC) 19 PHP_ME(Car, brake, NULL, ZEND_ACC_PUBLIC) 20 PHP_ME(Car, getCurrentSpeed, NULL, ZEND_ACC_PUBLIC) 21 PHP_ME(Car, getCurrentGear, NULL, ZEND_ACC_PUBLIC) 22 {NULL, NULL, NULL} 23 }; 24 PHP_MINIT_FUNCTION(vehicles) 25 { 26 zend_class_entry ce; 27 INIT_CLASS_ENTRY(ce, "Car", car_methods); 28 car_ce = zend_register_internal_class(&ce TSRMLS_CC); 29 return SUCCESS; 30 } 31 zend_module_entry vehicles_module_entry = { 32 #if ZEND_MODULE_API_NO >= 20010901 33 STANDARD_MODULE_HEADER, 34 #endif 35 PHP_VEHICLES_EXTNAME, 36 NULL, /* Functions */ 37 PHP_MINIT(vehicles), /* MINIT */ 38 NULL, /* MSHUTDOWN */ 39 NULL, /* RINIT */ 40 NULL, /* RSHUTDOWN */ 41 NULL, /* MINFO */ 42 #if ZEND_MODULE_API_NO >= 20010901 43 PHP_VEHICLES_EXTVER, 44 #endif 45 STANDARD_MODULE_PROPERTIES 46 }; 47 #ifdef COMPILE_DL_VEHICLES 48 extern "C" { 49 ZEND_GET_MODULE(vehicles) 50 } 51 #endif
로그인 후 복사
首先是在第二行定义了一个zend_class_entry,这个入口会在MINIT的时候进行相应的初始化. 3~13行给出了C++类的成员函数所转换成的php方法的版本,之后会添加上相应的实现. 15~23行定义了函数入口zend_function_entry,php方法定义的地方.这里也可以声明一组自己定义的别名.(如何定义,怎么体现?) 24~30给出的是新的模块初始化MINIT函数:- INIT_CLASS_ENTRY函数把类的入口和之前在zend_function_entry中类的方法联系了起来,属于类的初始化
- 而car_ce = zend_register_internal_class(&ce TSRMLS_CC) ,注册类,把类加入到Class Table中,
31~51行跟之前的模块入口没什么差别。
现在已经声明了一组跟C++类成员函数同名的php函数,再接下来需要做的就是把两者联系起来: 每个C++的类实例都必须对应一个php的类实例,一种实现的方法是使用一个结构来追踪现有的C++和php的类实例。而为了做到这一点,就需要写出自己的对象处理器,在php5中,一个对象就是由一个句柄(使得Zend引擎能够定位你的类的id)、一个函数表、和一组处理器组成的。在对象的声明周期的不同阶段都可以重写处理器以实现不同的功能。所以在之前的代码基础上,先增加一个对象处理器:
1 #include "car.h" 2 zend_object_handlers car_object_handlers; 3 struct car_object { 4 zend_object std; 5 Car *car; 6 };
로그인 후 복사
这个car_object结构会被用来追踪C++的实例,然后与zend_object联系起来。在PHP_METHOD的声明之前,需要加上下面两个方法:
1 void car_free_storage(void *object TSRMLS_DC) 2 { 3 car_object *obj = (car_object *)object; 4 delete obj->car; 5 zend_hash_destroy(obj->std.properties); 6 FREE_HASHTABLE(obj->std.properties); 7 efree(obj); 8 } 9 zend_object_value car_create_handler(zend_class_entry *type TSRMLS_DC) 10 { 11 zval *tmp; 12 zend_object_value retval; 13 car_object *obj = (car_object *)emalloc(sizeof(car_object)); 14 memset(obj, 0, sizeof(car_object)); 15 obj->std.ce = type; 16 ALLOC_HASHTABLE(obj->std.properties); 17 zend_hash_init(obj->std.properties, 0, NULL, ZVAL_PTR_DTOR, 0); 18 zend_hash_copy(obj->std.properties, &type->default_properties, 19 (copy_ctor_func_t)zval_add_ref, (void *)&tmp, sizeof(zval *)); 20 retval.handle = zend_objects_store_put(obj, NULL, 21 car_free_storage, NULL TSRMLS_CC); 22 retval.handlers = &car_object_handlers; 23 return retval; 24 }
로그인 후 복사
而后需要对模块初始化函数MINIT进行如下修改:
25 PHP_MINIT_FUNCTION(vehicles) 26 { 27 zend_class_entry ce; 28 INIT_CLASS_ENTRY(ce, "Car", car_methods); 29 car_ce = zend_register_internal_class(&ce TSRMLS_CC); 30 car_ce->create_object = car_create_handler; 31 memcpy(&car_object_handlers, 32 zend_get_std_object_handlers(), sizeof(zend_object_handlers)); 33 car_object_handlers.clone_obj = NULL; 34 return SUCCESS; 35}
로그인 후 복사
这里我们看到第30行,首先调用car_create_handler创建一个create_object处理器。在调用car_create_handler的时候注意一个大坑那就是第18行这里$type->default_properties,php的新版本中这里是properties_info,这个编译错误在对比源码了之后才知道怎么改。- 13~15行给一个car_object申请了空间,并完成了初始化。同时把obj对应的zend_class_object和输入的type()进行了连接,也就是把MINIT中初始化的zend对象绑定在了car_object这个结构中。
- 在完成了绑定之后,16~19继续进行拷贝过程。
- 20~21行把obj加入到了zend的对象中,并用函数指针的方式定义了销毁时候的函数car_free_storage,同时产生了一个对象obj的句柄
- 第31行则给处理器handlers了相应的zend_object_handlers的值(这里还很不明晰)
现在在php的类构造函数中,就要读取用户的参数,并且把它们传递给C++的构造函数。一旦C++的类实例被创建了,那就可以从zend对象store中抓取car_object指针,然后设定结构体中的car值。这样的话,就把zend对象实例和C++的Car实例绑定了起来。
1 PHP_METHOD(Car, __construct) 2 { 3 long maxGear; 4 Car *car = NULL; 5 zval *object = getThis(); 6 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &maxGear) == FAILURE) { 7 RETURN_NULL(); 8 } 9 car = new Car(maxGear); 10 car_object *obj = (car_object *)zend_object_store_get_object(object TSRMLS_CC); 11 obj->car = car; 12 }
로그인 후 복사
通过调用zend_object_store_get_object函数就能够获得C++类的一个实例。而下面的两个函数也是同样的道理:
PHP_METHOD(accelerate) { Car *car; car_object *obj = (car_object *)zend_object_store_get_object( getThis() TSRMLS_CC); car = obj->car; if (car != NULL) { car->accelerate(); } } PHP_METHOD(getCurrentSpeed) { Car *car; car_object *obj = (car_object *)zend_object_store_get_object( getThis() TSRMLS_CC); car = obj->car; if (car != NULL) { RETURN_LONG(car->getCurrentSpeed()); } RETURN_NULL(); }
로그인 후 복사
好了,到现在为止基本上整个框架就搭好了。 不要忘了重新配置编译一下,然后用如下的php代码就可以进行测试了:<pre class="code">/ create a 5 gear car $car = new Car(5); print $car->getCurrentSpeed(); // prints '0' $car->accelerate(); print $car->getCurrentSpeed(); // prints '5' If you can run this script, congratulations, you’ve just created a PHP extension that wraps a C++ class.
로그인 후 복사
如果说输出跟标识的一致的话,那么整个过程就成功了,恭喜!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











PHP용 SNMP 확장은 PHP가 SNMP 프로토콜을 통해 네트워크 장치와 통신할 수 있도록 하는 확장입니다. 이 확장 기능을 사용하면 CPU, 메모리, 네트워크 인터페이스 등 네트워크 장치의 구성 정보와 라우터, 스위치 등의 기타 정보를 쉽게 얻고 수정할 수 있습니다. 장치 포트 전환과 같은 제어 작업도 수행할 수 있습니다. 이 기사에서는 SNMP 프로토콜에 대한 기본 지식, PHP의 SNMP 확장 설치 방법, PHP에서 SNMP 확장을 사용하여 네트워크 장치를 모니터링하고 제어하는 방법을 소개합니다. 1. SN

처음부터 끝까지: HTTP 요청에 PHP 확장 cURL을 사용하는 방법 소개: 웹 개발에서는 종종 타사 API 또는 기타 원격 서버와 통신해야 합니다. cURL을 사용하여 HTTP 요청을 하는 것은 일반적이고 강력한 방법입니다. 이 기사에서는 PHP를 사용하여 cURL을 확장하여 HTTP 요청을 수행하는 방법을 소개하고 몇 가지 실용적인 코드 예제를 제공합니다. 1. 준비 먼저 php에 cURL 확장이 설치되어 있는지 확인하세요. 명령줄에서 php-m|grepcurl을 실행하여 확인할 수 있습니다.

PHP와 HTML의 조합은 웹 개발의 일반적인 기술입니다. PHP는 HTML 파일에 동적 콘텐츠를 포함하고 보조 기능을 구현할 수 있으므로 웹 사이트의 상호 작용성과 사용자 정의 가능성이 크게 향상됩니다. 이 기사에서는 코드 삽입을 위한 세 가지 기술을 소개하고 참조용 특정 코드 예제를 제공합니다. 1. PHP 태그를 사용하여 코드 삽입 가장 일반적인 방법은 PHP 태그()를 사용하여 PHP 코드를 HTML 파일에 삽입하여 동적 콘텐츠를 표시하는 것입니다. 예를 들어 PHP를 사용할 수 있습니다.

PHP 함수 기능을 확장하려면 확장 기능과 타사 모듈을 사용할 수 있습니다. 확장은 pecl 패키지 관리자를 통해 설치하고 활성화할 수 있는 추가 기능과 클래스를 제공합니다. 타사 모듈은 특정 기능을 제공하며 Composer 패키지 관리자를 통해 설치할 수 있습니다. 실제 예로는 확장 기능을 사용하여 복잡한 JSON 데이터를 구문 분석하고 모듈을 사용하여 데이터 유효성을 검사하는 것이 포함됩니다.

1.UncaughtError:Calltoundefinitivefunctionmb_strlen(); 위의 오류가 발생하면 mbstring 확장이 설치되지 않았음을 의미합니다. 2. PHP 설치 디렉터리 cd/temp001/php-7.1.0/ext/mbstring을 입력합니다. 3. phpize( /usr/local/bin /phpize 또는 /usr/local/php7-abel001/bin/phpize) PHP 확장 4를 설치하는 명령../configure--with-php-config=/usr/local/php7-abel

Aurora 푸시 확장을 사용하여 PHP 애플리케이션에서 일괄 메시지 푸시 기능을 구현하는 방법 모바일 애플리케이션 개발에서 메시지 푸시는 매우 중요한 기능입니다. Jiguang Push는 풍부한 기능과 인터페이스를 제공하는 일반적으로 사용되는 메시지 푸시 서비스입니다. 이 기사에서는 Aurora 푸시 확장을 사용하여 PHP 애플리케이션에서 일괄 메시지 푸시 기능을 구현하는 방법을 소개합니다. 1단계: Jiguang Push 계정 등록 및 API 키 획득 먼저 Jiguang Push 공식 웹사이트(https://www.jiguang.cn/push)에 등록해야 합니다.

PHP는 웹 애플리케이션을 개발하고 파일을 처리하는 데 사용할 수 있는 인기 있는 서버측 언어입니다. PHP용 ZipArchive 확장은 PHP에서 zip 파일을 조작하기 위한 강력한 도구입니다. 이 기사에서는 PHP의 ZipArchive 확장을 사용하여 zip 파일을 생성하고 읽고 수정하는 방법을 다룹니다. 1. ZipArchive 확장을 설치합니다. ZipArchive 확장을 사용하기 전에 확장이 설치되었는지 확인해야 합니다. 설치 방법은 다음과 같습니다. 1. 설치

PHP용 POSIX 확장은 PHP가 POSIX 호환 운영 체제와 상호 작용할 수 있게 해주는 함수 및 상수 세트입니다. POSIX(PortableOperatingSystemInterface)는 소프트웨어 개발자가 다양한 UNIX 또는 UNIX 유사 운영 체제에서 실행될 수 있는 응용 프로그램을 작성할 수 있도록 설계된 운영 체제 인터페이스 표준 세트입니다. 이 기사에서는 설치 및 사용을 포함하여 PHP용 POSIX 확장을 사용하는 방법을 소개합니다. 1. PHP의 POSIX 확장을 설치합니다.
