Com_dotnet

March 16, 2004

Tutorials

Introduction
What’s Gone

Iterators
Exceptions
Variants
ByRef parameters
Event Handling
.Net Support
Summary
About the Author

Introduction

The new OO features in PHP 5 are not limited to private, protected and public variables in your scripts – some
significant changes have been made under the hood that allow extensions to really hook into the engine and allow
much greater flexibility for integrating support for external object models such as COM, Java, .Net and Corba.
It was an excellent opportunity to revitalize COM support, as it was suffering from the limitations of the PHP 4
object model – limitations that had resulted in some hacks, both in the extension code itself and in the scripts
of people using the extension. With that in mind, I decided to rewrite the COM support from scratch and drop backwards
compatibility for the uglier aspects of the PHP 4 COM extension.

What’s Gone

The following functions/features are not present in the PHP 5 COM support, as they are nasty hacks that really don’t
fit with the COM idea, and are really quite confusing things when you see them in the manual.

com_addref(), com_release() – your script should not worry about refcounts

com_get(), com_set(), com_invoke(), com_propget(),
com_propset(), com_propput() – just use regular PHP OO syntax to get or set properties or invoke methods.

com_isenum() and $com->Next() – use the foreach()
syntax described below instead.

com_load() – use the new operator and the COM class instead.

So, all the nasty stuff has been blasted away. Let’s now look at the goodies that have been added.

Iterators

If you’ve ever dealt with VBscript code, either from an ASP site or in .vbs admin scripts, you’re bound to have seen
code like this:


<%

set domainObject
= GetObject("WinNT://Domain")

for each obj in domainObject

  Response
.write obj.Name & "<br>"

next

%>


In PHP 4 COM, the equivalent code looks like this:


<?php

$domainObject
= new COM("WinNT://Domain");

while (
$obj = $domainObject->Next()) {

  echo $obj->Name . "<br>";

}

?>

If you’ve done a lot of VB/COM hacking, looking at that code snippet will leave you wondering what exactly the
Next() thing is doing; it is not a real method, and you certainly can’t do that same thing in other
languages that support COM. In PHP 5, this syntax has been dropped in favour of the much more natural
foreach() statement:


<?php

$domainObject
= new COM("WinNT://Domain");

foreach ($domainObject as $obj) {

  echo
$obj->Name . "<br>";

}

?>


Exceptions

In PHP 4, there was no sane way to handle errors triggered from within COM code – when an error occurred, PHP would
raise an E_WARNING so that you would know that there was an error, but you would have no good way to know
precisely where the error occurred, nor be able to handle it programmatically.

PHP 5 introduces structured exception handling (try, catch() and throw()) and this
allows us to expose the underlying COM exceptions to PHP using the built-in com_exception class. If you want
to catch errors in your scripts, you might write code like this:


<?php

$com
= new COM("...");

try {

  
$com->call_a_method();

}

catch (com_exception $e) {

  print
$e . "\n";

}

?>

Inside the catch block you have the opportunity to handle the error as is appropriate to your script.
The com_exception class extends the default exception class provided by PHP, and so it
has all of its methods. The COM exception/error code is made available via the getCode() method of the
class, making it drastically easier to handle specific errors.

Variants

One of the not very well documented features of COM in PHP 4 is its support for the VARIANT type. If you’re not familiar
with variants, you can think of them as the COM equivalent of the PHP variable – structures that can hold integer,
floating point, string, or object values. The COM variant has much greater diversity than the PHP variable, and not all
of its types can be directly expressed as native PHP types.

The variant support in PHP 4 relied on converting variants back and forth between COM and PHP which resulted in some
nasty looking code in the extension to handle the conversions. It was buggy and did not handle all of the possible
conversion cases. It was also possible that you would lose information/precision as conversions are made.

In PHP 5, the variant support has been greatly simplified by adopting the premise that we should only convert a variant
value to a PHP type when there is a direct 1:1 mapping. In all other cases, we represent the variant as an overloaded
object and defer evaluation of it until it is used in an expression. This results in much cleaner code and, if you are
working with variant arrays, faster scripts as we no longer need to copy the contents a variant array and put them into
a PHP array – the new OO model in PHP 5 allows us to access that variant object as though it were an array.

Another neat trick made possible by the new OO model is smart interpretation of the contents of a variant object using a
cast handler. As I mentioned above, we defer evaluation of the variant until it is used in an expression – when it
is used, the Zend Engine has some idea of the context in which it is being used (numeric, string etc.) and will ask the
variant object to convert itself into an appropriate format. This is particularly useful for us to decide exactly how
to convert the value.

If none of that was enough, a large number of the COM variant API functions have been added to the extension, allowing you
to add, subtract, multiply, etc. variants according to the same rules used by VB. While not overly useful for the common
basic types (integers and strings), it is useful for the more exotic variant types (dates, currency values and so on).

ByRef parameters

It is quite common to encounter a COM object with methods that expect their parameters to be passed by reference. In PHP 4,
the only way to make these methods work as expected was to manually create an instance of a VARIANT and set its ByRef flag.
The OO model in PHP 5 allows the engine to query COM about the method that it is about to call so that it can determine
which parameters are to be passed by reference. This allows you to call these methods without jumping through
hoops – the values are set for you automagically.

Event Handling

Not strictly new for PHP 5 (I added it in PHP 4.3), it is worth mentioning here since it is not particularly well
documented. Quite often you need to bind to a COM object so that you can receive notification of events – using
VB you would use the WithEvents clause when you Dim the variable and then Visual Basic
would handle the magic. In PHP things are a little different, although much easier to follow.

To be able to handle events from a COM object (known as a "source") you need to have another object that
can "sink" those events. To do this, you simply declare a class to act as your sink and create an instance
of it, and then bind the events to it:


<?php

class IESink {

    public
$terminated = false;

    public function OnQuit() {

        
$this->terminated = true;

    }

}

$ie = new COM("InternetExplorer.Application");

$ie->Visible = true;

$ie->Navigate("http://www.php.net/");

$sink = new IESink;

com_event_sink($ie, $sink, "DWebBrowserEvents2");

while (!
$sink->terminated) {

    
com_message_pump(4000);

}

print "finished!\n";

?>

This script will launch IE and browse to the PHP home page and then wait for you to quit the browser before continuing.
You should note that the com_event_sink() function is responsible for sinking events from $ie
to $sink and that it uses an interface named "DWebBrowserEvents2". The interface name must match
the name of the so-called outgoing dispinterface for your COM object – you can find out the name and also generate a
template sink class using the com_print_typeinfo() function.

.Net Support

PHP 5 includes integrated .Net support. To be more specific, it supports the instantiation of objects defined in .Net
assemblies via the COM interoperability layer for .Net. In implementation terms, PHP sees .Net objects as though
they were COM objects, although instantiation is slightly different:


<?php

  $stack
= new DOTNET("mscorlib", "System.Collections.Stack");

  $stack->Push(".Net");

  
$stack->Push("Hello ");

  echo $stack->Pop() . $stack->Pop();

?>

This provides you with convenient access to the very extensive .Net class library (it has several thousand different
classes!). Obviously, to use this feature you need to have installed the .Net runtime onto your server.

In Summary

Although each feature I’ve mentioned above doesn’t sound like much on its own, the total effect will make a massive
difference if you have ever written a COM enabled PHP script of decent length – you will find that not only is your
script shorter and easier to read and understand, but that it is a little bit faster too.

You can play with these all these features right now by downloading a PHP 5 snapshot from
http://snaps.php.net.
PHP 5 is currently in feature-freeze, which means that we are focusing on stabilizing the code ready for release – it
is already fairly stable, so you are encouraged to try it out for yourself. If you run into problems, please report them
using http://bugs.php.net.

About the Author

Wez Furlong is a Core Developer of PHP and "King" of PECL (The PHP Extension Community Library), having
contributed extensions such as SQLite, COM/.Net, ActivePHP, mailparse, the Streams API and more.


About Cal Evans

Many moons ago, at the tender age of 14, Cal touched his first computer. (We're using the term "computer" loosely here, it was a TRS-80 Model 1) Since then his life has never been the same. He graduated from TRS-80s to Commodores and eventually to IBM PC's. For the past 10 years Cal has worked with PHP and MySQL on Linux OSX, and when necessary, Windows. He has built on a variety of projects ranging in size from simple web pages to multi-million dollar web applications. When not banging his head on his monitor, attempting a blood sacrifice to get a particular piece of code working, he enjoys building and managing development teams using his widely imitated but never patented management style of "management by wandering around". Cal is currently based in Nashville, TN and is gainfully unemployed as the Chief Marketing Officer of Blue Parabola, LLC. Cal is happily married to wife 1.28, the lovely and talented Kathy. Together they have 2 kids who were both bright enough not to pursue a career in IT. Cal blogs at http://blog.calevans.com and is the founder and host of Day Camp 4 Developers

View all posts by Cal Evans

6 Responses to “Com_dotnet”

  1. alex-party Says:

    Thank you for this article,

    May I ask a question about object cross-references in VB and how can I handle those with PHP ?

    I have a simple example:
    ————————————————–
    // VBA CODE
    Private Sub Search()
    Dim fwSystem As New LIB.FileListCache
    Dim fwFlelist As New LIB.FileList
    Set fwFlelist = fwSystem.GetFileList("path:to")

    So we have 2 objects fwSystem and fwFlelist. fwSystem returns an object pointer to fwFlelist.

    ————————————————–
    // PHP CODE
    $fwSystem = new COM("LIB.FileListCache"); //1 com object
    $path = new Variant("path:to",VT_BSTR);
    $fwFlelist= $fwSystem->GetFileList($path); //2 com object
    ————————————————–

    Here, I expect that $fwFlelist will be a com Object returned from $fwSystem->GetFileList() function, which is true. But, when I try to lookup $fwFlelist->NumberOfFiles variable, it always 0, which should be a number. It seems like it returns only the object structure or pointer, but not the object itself.

    Any ideas? I also tried to initialize both objects, like:

    $fwSystem = new COM("LIB.FileListCache"); //1 com object
    $fwFlelist= new COM("LIB.FileList"); //2 com object
    $fwFlelist= $fwSystem->GetFileList($path);

    It gave me a Memory Violation Error :(

    I’m stuck now, please help!
    I’ve looked everywhere, there is not much information on the internet.
    Thanks,
    Alex

  2. troelskn Says:

    So far, I have managed to get a Form, with the following code:
    <code>
    define(‘DOTNET_SYSTEM_WINDOWS_FORMS_FORM’, ‘System.Windows.Forms,Version=1.0.5000.0,Culture=neutral,PublicKeyToken=b77a5c561934e089′);
    $form = new DOTNET(DOTNET_SYSTEM_WINDOWS_FORMS_FORM, ‘System.Windows.Forms.Form’);
    $form->ShowDialog();
    </code>
    However, an empty form is kinda boring, so I tried to add a control to it:
    <code>
    define(‘DOTNET_SYSTEM_WINDOWS_FORMS_FORM’, ‘System.Windows.Forms,Version=1.0.5000.0,Culture=neutral,PublicKeyToken=b77a5c561934e089′);
    $form = new DOTNET(DOTNET_SYSTEM_WINDOWS_FORMS_FORM, ‘System.Windows.Forms.Form’);
    $button = new DOTNET(DOTNET_SYSTEM_WINDOWS_FORMS_FORM, ‘System.Windows.Forms.Button’);
    $button->Text = "Hello World";
    $form->Controls->Add($button);
    $form->ShowDialog();
    </code>
    Unfortunately this blows; Apparently because $form->Control returns null. Any idea on, what’s going wrong?

  3. jcrutchfield Says:

    The server has Microsoft .NET Framework SDK v1.1 and Microsoft Visual Studio .NET 2003
    It is currently running other .Net web applications.

  4. tcarnes Says:

    Have you installed the MS .NET framework on the web server?

  5. jcrutchfield Says:

    I have not been able to get the Dot Net example to work. I am running PHP 5.2.1 and have copied the example exactly. However I get the following error:
    Fatal error: Uncaught exception ‘com_exception’ with message ‘Failed to instantiate .Net object [CreateInstance] [0x80131522] ‘ in C:wwwsdpdstarrin est.php:2 Stack trace: #0 C:wwwsdpdstarrin est.php(2): dotnet->dotnet(‘mscorlib’, ‘System.Collecti…’) #1 {main} thrown in C:wwwsdpdstarrin est.php on line 2

    I have been unable to find any other documentation on what setup may need to be done in order to make this work. I would be very appreciative for any help you could supply.

  6. tcarnes Says:

    I can get your DOTNET example to work fine, but I have a .NET assembly that requires me to pass a constructor parameter when I instantiate the object. Can I do that with PHP?

    The sample code that came with the .NET assembly gives the following examples of how to do this is with VB and C#:

    VB: Dim transaction As New Paymentech.Transaction(RequestType.CC_AUTHORIZE)

    C#: Paymentech.Transaction(Paymentech.RequestType.CC_AUTHORIZE);

    In trying to do this in PHP, I have tried the following:
    $transaction = new DOTNET("ptDotNetSDK20","Paymentech.Transaction(CC_AUTHORIZE) ")
    and
    $transaction = new DOTNET("ptDotNetSDK20","Paymentech.Transaction(RequestType.CC_AUTHORIZE) ")
    and even
    $transaction = new DOTNET("ptDotNetSDK20","Paymentech.Transaction(Paymentech.RequestType.CC_AUTHORIZE) ")

    and each time get errors "Failed to instantiate .Net object". I’ve even tried enclosing the parameter in single quotes, with no improvement.

    If I leave out the parameter and just try:
    $transaction = new DOTNET("ptDotNetSDK20","Paymentech.Transaction")
    I am able to create the transaction, but am then told "Transaction Object is invalid" when I later try to use it, which makes sense since I didn’t create it with the necessary parameter in the first place.

    Hopefully this can be done. Can it?