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中。
      下面简单介绍一下步骤和注意事项

  1. 在VC++ 6.0中,File -> New…  选择Projects中的"ATL COM AppWizard",填写工程名称等。本例中,工程名为"ATLtest"。
  2. 在"ATL COM AppWizard – Step 1 on 1"对话框中,"Server Type" 选择"Dynamic Link Library (DLL)",之后Finish。
  3. 在"ClassView"中,右击"ATLtest",选择"New ATL Object…",在"ATL Object Wizard"中,选择默认的"Simple Object",之后"Next"。
  4. 在"ATL Object Wizard 属性"中,在"Short Name"输入接口的名称。本例中,接口名称为"test"。之后,"Names"选项卡中的所有textBox都自动填好了默认的值。注意两个地方:一个"Prog ID"(本例中为"ATLtest.test"),一个"Interface"(本例中为"Itest")。
  5. 完成之后,在"ClassView"中,"ATLtest classes"下生成了"Ctest"类,并且实现了"Itest"接口。
  6. 右击"Itest"接口,选择"Add Method…"。
  7. 在"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没法直接使用。

  8. 完成这个函数。当然,为了简单起见,这里就是给结果随便赋了一个值,用来说明参数成功的传递出来了。没有考虑任何内存泄漏问题。
    //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;
    }

  9. 编译,将得到的ATLtest.dll使用regsvr32进行注册,之后才能使用COM进行调用。
  10. 之后书写这样的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;

  11. 注意这里的"ATLtest.test"是刚才(4)中的"Prog ID",并且使用Encode() 的方法和声明的也不一样。没有关系!
    当然,由于完全没有用到三个输入参数,这里的1,1,"11"只是为了满足输入参数的数量。
  12. 这个php的输出是什么样的呢?
    //HTML 结果
    $result = "未处理的字符串"
    $result = "asdfasdf"

    可见,$result 成功的改变成了dll中赋的值,说明 Encode() 方法成功的返回了值。

几点疑问

  1. 为什么 Encode() 中返回的是 BSTR* ,但是到了php中,就变成了字符串(BSTR) 呢?这个自动的转换是ATL进行的,还是php进行的呢?
  2. C++代码中通过SysAllocString()为BSTR分配的空间在何时进行垃圾收集?收集工作由哪里负责?会不会导致内存泄漏?
  3. 完恶的C++ 6.0 编译器,为什么返回值不支持 char** 这种简单的类型呢(使用char**直接编译出无数错误)? BSTR本质上就是指针嘛,也不支持(提示说只支持简单类型和指针),只好用一个不伦不类的BSTR*来写。嗯,下一步尝试改用CCOMBSTR或者_bstr_t,试试哪个更好用。
  4. 对于传入的BSTR* result,需要使用 SysFreeString() 进行处理么?在C++中看来,无疑是需要释放的;但是php在背后做了哪些工作呢?有没有对未被引用的常量"未处理的字符串"进行垃圾收集呢?

参考资料
     
以下资料比较有参考价值:

2 responses to this post.

  1. Posted by 小雨 on 2007年12月8日 at 15:55

    强烈BS Windows Live Spaces,居然不支持div标签
    我好不容易编辑的代码加亮的文档啊……就这么被吃了

    回复

  2. 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 吧,非常感谢

    回复

小雨 发表评论 取消回复