2007/12/08
php调用dll经验小结
最近做一个网站,需要频繁使用远程数据,数据接口已经做好。在做转换的时候遇到了性能上的问题:开始打算用php来实现转换,苦苦查了数天,都没有找到直接操作字节的方法。虽然可以使用 pack() 方法将各个数据压入结构中,但是在解压的时候却不能通过 unpack() 简单的解出来,需要通过
//php code
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在背后做了哪些工作呢?有没有对未被引用的常量"未处理的字符串"进行垃圾收集呢?
参考资料
以下资料比较有参考价值: