最近做一个网站,需要频繁使用远程数据,数据接口已经做好。在做转换的时候遇到了性能上的问题:开始打算用php来实现转换,苦苦查了数天,都没有找到直接操作字节的方法。虽然可以使用 pack() 方法将各个数据压入结构中,但是在解压的时候却不能通过 unpack() 简单的解出来,需要通过
//php code
for( $i = 0; $i < $length; $i+=2 ){
$tempstr = $tempstr.chr( hexdec(substr($array["data"], $i, 2)) );
}
$array["data"] = $tempstr;
for( $i = 0; $i < $length; $i+=2 ){
$tempstr = $tempstr.chr( hexdec(substr($array["data"], $i, 2)) );
}
$array["data"] = $tempstr;
这类方法进行解码。频繁的使用各种字符串操作,无疑将对性能造成很大的影响。经过研究,发现有以下方法可以实现对字节的操作:
- 使用stream进行读写
- 使用socket进行读写
- 使用COM dll,将数据在C++ dll中进行转换
由于网上找不到相关的文档(其实是没好好找),stream和socket先后被PASS掉了。为了编译COM的dll,还专门下载了VC++ 6.0(为啥不装2005?硬盘太小,装不下,没办法啊)。经过无穷无尽的Google(全是php调用VB写的dll的信息,没多大帮助)和编译/调试,终于成功的把结果传递到php中。
下面简单介绍一下步骤和注意事项:
- 在VC++ 6.0中,File -> New… 选择Projects中的"ATL COM AppWizard",填写工程名称等。本例中,工程名为"ATLtest"。
- 在"ATL COM AppWizard – Step 1 on 1"对话框中,"Server Type" 选择"Dynamic Link Library (DLL)",之后Finish。
- 在"ClassView"中,右击"ATLtest",选择"New ATL Object…",在"ATL Object Wizard"中,选择默认的"Simple Object",之后"Next"。
- 在"ATL Object Wizard 属性"中,在"Short Name"输入接口的名称。本例中,接口名称为"test"。之后,"Names"选项卡中的所有textBox都自动填好了默认的值。注意两个地方:一个"Prog ID"(本例中为"ATLtest.test"),一个"Interface"(本例中为"Itest")。
- 完成之后,在"ClassView"中,"ATLtest classes"下生成了"Ctest"类,并且实现了"Itest"接口。
- 右击"Itest"接口,选择"Add Method…"。
- 在"Add Method to Interface"中,填写方法的名称和参数。注意:返回值一定是HRESULT型,真正的返回值是最后一个参数。比如
//C++ code
BSTR Encode(unsigned int msgType, unsigned int msgLength, BSTR message)这个函数,要写成
//C++ code
STDMETHODIMP Ctest::Encode(
unsigned int msgType,
unsigned int msgLength,
BSTR message,
BSTR *result
)这样的形式。还有就是返回值只接受简单的类型(不知道为什么,char**不能用)和指针,BSTR没法直接使用。
- 完成这个函数。当然,为了简单起见,这里就是给结果随便赋了一个值,用来说明参数成功的传递出来了。没有考虑任何内存泄漏问题。
//C++ code
STDMETHODIMP Ctest::Encode(
unsigned int msgType,
unsigned int msgLength,
BSTR message,
BSTR *result
)
{
BSTR temp = ::SysAllocString(L"asdfasdf");
*result = temp;return S_OK;
} - 编译,将得到的ATLtest.dll使用regsvr32进行注册,之后才能使用COM进行调用。
- 之后书写这样的php代码:
//php code
$com = new COM("ATLtest.test") or die("无法建立COM组件");
$result = "未处理的字符串";
echo ‘$result = "’.$result.‘"<br />’;$result = $com->Encode(1,1,"11");
echo ‘$result = "’.$result.‘"<br />’;$com = null;
- 注意这里的"ATLtest.test"是刚才(4)中的"Prog ID",并且使用Encode() 的方法和声明的也不一样。没有关系!
当然,由于完全没有用到三个输入参数,这里的1,1,"11"只是为了满足输入参数的数量。 - 这个php的输出是什么样的呢?
//HTML 结果
$result = "未处理的字符串"
$result = "asdfasdf"可见,$result 成功的改变成了dll中赋的值,说明 Encode() 方法成功的返回了值。
几点疑问
- 为什么 Encode() 中返回的是 BSTR* ,但是到了php中,就变成了字符串(BSTR) 呢?这个自动的转换是ATL进行的,还是php进行的呢?
- C++代码中通过SysAllocString()为BSTR分配的空间在何时进行垃圾收集?收集工作由哪里负责?会不会导致内存泄漏?
- 完恶的C++ 6.0 编译器,为什么返回值不支持 char** 这种简单的类型呢(使用char**直接编译出无数错误)? BSTR本质上就是指针嘛,也不支持(提示说只支持简单类型和指针),只好用一个不伦不类的BSTR*来写。嗯,下一步尝试改用CCOMBSTR或者_bstr_t,试试哪个更好用。
- 对于传入的BSTR* result,需要使用 SysFreeString() 进行处理么?在C++中看来,无疑是需要释放的;但是php在背后做了哪些工作呢?有没有对未被引用的常量"未处理的字符串"进行垃圾收集呢?
参考资料
以下资料比较有参考价值:
- 杨老师的《COM组件设计与应用》http://www.vckbase.com/document/viewdoc/?id=1488
这里给出的地址是第三节,讨论了COM组件设计中涉及的数据结构,如HRESULT、UNICODE、BSTR和VARIANT。写的很幽默,也很通俗易懂,值得一看。 - Notbird的《用ATL制作DOM》 http://pcfun.cn/articles.asp?ID=163&item=c
这里有一些使用VC++ 6.0制作ATL dll的截图,文章写的没什么意思,不过截图给了我很大提示。 - CSDN,不多说了。论坛很强大。
- zhhlv 的《PHP调用自定义COM的经验总结》http://www.chinaunix.net/jh/27/365999.html
基本上就是本文的缩写,不过缩写的太简单了,所以看了好半天都没看懂…… - php手册,不多说了,都快被翻烂了……
Posted by 小雨 on 2007年12月8日 at 15:55
强烈BS Windows Live Spaces,居然不支持div标签
我好不容易编辑的代码加亮的文档啊……就这么被吃了
Posted by 恩召 on 2009年03月16日 at 04:12
请问,函数Encode(unsigned int msgType, unsigned int msgLength, BSTR message, BSTR *result)是4个个参数,调用时怎么变成3个$com->Encode(1,1,"11")了???我把你的例子照着做了一遍,就最后的调用这一部出错了,无法显示,就是调用函数的事情。请指教php中没有指针,但是com组件的一个参数必须为指针,所以怎么解决这个调用的参数问题,请指教啊,谢谢,比较急用,发enzhaohoo@gmail.com 吧,非常感谢