How to use NetPhp

How to use NetPhp

Difficulty: 
Come Get Some

This article is outdated.

See the new NetPhp User Guide for a comprehensive guide on integrating Php with .Net

Why should I mix .Net and PHP?

The PHP ecosystem is half broken. Bad coded snippets, lack of enterprise level libraries. The big base of PHP developers are amateurs with no real software background. Yet sometimes users and companies are driven by market forces to use some PHP based software such as Drupal, Wordpress, etc. But PHP has been a long way since what it was at the start of the 2000's and has now almost any feature you would expect from a modern programming language (sort of). There are also productivity oriented development tools like Visual Studio integration (PHP Tools) and more and more tools are coming into the market that have boosted the developing on PHP productivity. Automated testing, continuous integration, package distribution, and many other features have been consolidating over the last few years.

Being able to consume .Net code allows you not to get stuck by all of the half-baked PHP ecosystem. Whenever you need an enterprise level library (PDF generation, Word and Excel manipulation, etc.) that is only available in .Net it can be used seamlesly. You can even enjoy faster development by using enterprise level development tools such as Visual Studio or more solid software by moving part of your code to the .Net framework.

Understanding how it works

The NetPhp libraries are split into two main components:

Historically, consuming .Net assemblies from PHP is done by means of the com_dotnet extension (included in PHP core). The problem of using the extension as-is is that it is there are a big number of issues and rigidity when consuming your assemblies. For example, you are limited to using .Net 3.5 or lower compiled assemblies, and any binary you want to consume need to be decorated as COM Visible (so you need to recompile and interface anything you plan to use).

With NetPhp all those restrictions have been overcome, allowing you to consume any type of compiled .Net library (no mater what framework version) giving flexibility as to how is the type loaded where you can even specify the exact location of the dynamic link library to get it loaded real time.

The NetPhp classes use the com_dotnet php extension to instantiate some classes inside the compiled NetPhp binary that allow us to interact with any .Net library. You can even mix .Net runtime versions in the same process.

How to install the NetPhp binary

The NetPhp binary is a COM Visible dynamic link library (.dll) that you must register (system wide) as a COM object. To do so, use the regasm tool. You can register a COM object with regasm and there is no need (but you could) to bind it to a specific file in your file system.

The regasm tool is bundled with the .Net framework and you will find a copy in the location:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\regasm.exe

I recommend the following steps to register the binary:

  • Copy the binary file to your PHP installation directory (where the php.exe and php-fastcgi.exe are located) such as c:\php\php5_6\netutilities.dll
  • Run a simple regasm command with no parameters "regasm 'c:\php\php5_6\netutilities.dll'" (remeber to use an elevated command prompt)

Important: If you see the error "Could not instantiate COM class XXXX" make sure the the netutilities binary is in the same directory as your PHP executable file.

To ensure that the type has been properly installed use OleView (comes with the Windows SDK included with Visual Studio) just type "oleview" in your Visual Studio Developer Console. You can download a copy of oleView (x86 and x64 at the end of this article).

Remember that Windows is split between x86 and x64. Because your are probably using the x86 version of PHP, you must register the assembly using the x86 version of regasm and you will only see it with the x86 version of OleView.

Once in OleView open the .Net category:

And look for the netutilities.* COM objects:

Configure your PHP application to use the NetPHP libraries

The next step is to bring the NetPhp php library into your PHP project. 

The library is available on Github and there is a composer package for it.

You can download the files directly from Github and drop them into project, but you will need to manually setup an autoloader.

The recommended way of including it into your project is to use composer.

Add this to your composer.json:

{
    "require": {
        "drupalonwindows/netphp": "dev-master"
    }
}

And run the composer install command.

Your application is now ready to consume any .Net binary be it a custom library of yours, or any of the types inside the .net framework.

Using NetPhp: Importing types and libraries

All the magic in NetPhp revolves around the MagicWrapper class. This class must aware of a .Net type (a type can be any kind of .Net type, be it a regular class, an enum, etc...) in order to be instanced.

In order to obtain an instance of the MagicWrapper you need to use the NetManager class. The NetManager class is used to register you assemblies and type aliases, and then retrieving a MagicWrapper instance of any type inside any of the registered assemblies.

The NetManager class has 3 methods:

  • RegisterAssembly($assemblyPath, $alias): Registerr an assembly
  • RegisterClass($assemblyName, $class, $alias): Register an alias for a type inside an assembly
  • Create($assembly, $class): Retrieve a MagicWrapper that is aware of a specific .Net type in the specified assembly
RegisterAssembly($assemblyPath, $alias)

Use RegisterAssembly to make the manager aware of a specific assembly and asign it an alias for future reference. You can use a strongly typed name or the path to a dll file.

For example, to register one of the .Net frameworks assemblies to use any of the types defined inside the mscorlib assembly:

$manager = new \NetPhp\Core\NetManager();
$manager->RegisterAssembly('mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089', 'mscorlib');

You can use your custom compiled DLL (you can target any .Net framework version and there is no need to make it COM visible so you can use any of the already compiled libraries that you have):

$manager = new \NetPhp\Core\NetManager();
$manager->RegisterAssembly('d:\\mypath\\mybinary.dll', 'MyLibrary');

You can give aliases to any full qualified type name in an assembly with the RegisterClass method so that you can later use short names to acces those types:

$manager->RegisterClass('mscorlib', 'System.Collections.ArrayList', 'ArrayList');

Once an assembly is registered, you can retrieve a MagicWrapper with the Create() method;

$m = $manager->Create('mscorlib', 'ArrayList');

Or without using the class alias:

$m = $manager->Create('mscorlib', 'System.Collections.ArrayList');

You do not need to register an alias to create a MagicWrapper for a type, but you must register an assembly.

Using NetPhp: The MagicWrapper/NetProxy

After using the Create() method of a NetManager you will be served an instance of NetProxy. We will go into details as to what the MagicWrapper does, but you must know that internally the NetProxy is simply a convenience wrapper over the MagicWrapper and that usually you will not directly operate on the internal MagicWrapper isntance of the NetProxy.

From now on we will talk without difference between the NetProxy and MagicWrapper.

You can retrieve the internal MagicWrapper at any time from a NetProxy instance using the GetWrapper() method.

A MagicWrapper by itself does not represent the instance of an object, what it does represent is a .Net Type awarenes. It can hold .Net objects inside it, and perform operations on them such as calling methods, accessing properties or instantiating classes.

The NetProxy has the following public methods:

  • Get($host): Used to obtain an instance of NetProxy because it cannot be instantiated directly. Host must be always an instance of MagicWrapper.
  • GetType(): Returns a string representation of the .Net type that is being wrapped.
  • Val(): Gives you the object that is being wrapped by the internal MagicWrapper, if the type can be converted to a PHP native type it will do so, otherwise you will get the COM instance (quite useless).
  • UnPack(): Returns the MagicWrapper COM instance. If your assembly is COM Visible you can directly interact with the returned COM object for better performance.
  • Instantiate(...$args): Create an instance of the type that the MagicWrapper is aware of. The instance is stored internally by the magic wrapper and you should operate on the NetProxy as if it was the .Net instance itself. Because constructors can be overloaded, you cannot pass NULL arguments to a constructor because type inference cannot be done on NULL objects.
  • Enum($value): Similar to Instance() but used for enums. Stores internally a reference to an Enum value that can be used to call methods that receive an Enum parameter.
  • IsNull(): Check if the internal instance is null. This will always return true before you call Instantiate() or Enum().
  • AsIterator(): If the internal .Net type is a collection, this method will return an instance of NetProxyCollection that you can use from PHP as if it was a regular array to perform enumerations.

Let's see all this into action with an example:

$manager = new \NetPhp\Core\NetManager();
$manager->RegisterAssembly('mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089', 'mscorlib');
$manager->RegisterClass('mscorlib', 'System.Collections.ArrayList', 'ArrayList');
$list = $manager->Create('mscorlib', 'ArrayList')->Instantiate();
// Retrieve a NetProxyCollection instance wrapper
$list = $list->AsIterator();  
// Check the .Net type.
$net_type = $list->GetType();
$this->assertEquals('System.Collections.ArrayList', $net_type);
// Populate the ArrayList contents
for ($x = 0; $x < 200; $x++) {
  $list->Add("Object {$x}");
}
// Make sure that count() works.
$this->assertEquals(200, count($list));
// Iterate over the collection
foreach ($list as $item) {
  echo $item->Val();
}

We have seen what public methods the NetProxy exposes, but we have not said yet that you can call any method or property of the internal .Net type directly on the NetProxy.

In the previous example we called the Add() method on the NetProxyCollection instance, but this  method really belongs to the System.Collections.ArrayList class.

When calling methods and accessing properties the return types are PHP native if they are compatible they will be returned in their PHP representations, otherwise we will get an instance of NetProxy wrapped over the .Net instance.

The following .Net types can be converted directly to PHP types:

  • System.String
  • System.Int32
  • System.Double
  • System.Boolean
  • System.Byte
  • System.Decimal
  • System.Char
  • System.Single
  • System.Void

When using the NetProxyCollection as an iterator the elements will be also retrieved with their native types when available.

Warning: PHP makes no distinction between 0 (integer, double, string) and NULL. That means that you can receive a NULL result from a call from where you will not be expecting a nulable type such as integer. If you then pass this value back to NetPHP where it does expect something not Nullable, you will get a type mismatch.

Comments

Hello,

I have been trying to use your fantastic libraries (great work) but I have found two main problems during the integration with my libraries. I could solve the first one but I am stuck with the second so I need your help in order to know if I can continue to use netphp or I have to modify my solution.

First of all, I couldn´t access to member variables (I have to use a dll without properties but only public member variables). To solve this I modified the magic methods __set and __get of NetProxy.php class. I did the following:

function __set($name, $value)
{
try
{
NetProxyUtils::UnpackParameter($value);
$this->wrapper->PropertySet($name, $value);
}
catch (\Exception $e)
{
$this->wrapper->$name = $value;
}
}

function __get($name)
{
try
{
$result = $this->wrapper->PropertyGet($name);
return NetProxy::Get($result);
}
catch (\Exception $e)
{
return $this->wrapper->$name;
}
}

The second problem I found was when I try to invoke a method of a library with three parameters. I obtain the following exception:

Fatal error: Uncaught exception 'com_exception' with message '<b>Source:</b> netutilities<br/><b>Description:</b> This version of PhpNet does not support calling methods with more than 2 parameters.' in C:\Apache24\htdocs\netphp-master\src\Core\MagicWrapper.php:95 Stack trace: #0 C:\Apache24\htdocs\netphp-master\src\Core\MagicWrapper.php(95): com->CallMethod('Create', Array) #1 C:\Apache24\htdocs\netphp-master\src\Core\NetProxy.php(60): NetPhp\Core\MagicWrapper->CallMethod('Create', Array) #2 C:\Apache24\htdocs\com.php(49): NetPhp\Core\NetProxy->__call('Create', Array) #3 C:\Apache24\htdocs\com.php(49): NetPhp\Core\NetProxy->Create('C:\\www\\prueba.t...', 2048, Object(NetPhp\Core\NetProxy)) #4 {main} thrown in C:\Apache24\htdocs\netphp-master\src\Core\MagicWrapper.php on line 95

The code is the following:

$manager = new \NetPhp\Core\NetManager();
$manager->RegisterAssembly('mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089', 'mscorlib');
$manager->RegisterClass('mscorlib', 'System.IO.File', 'File');
$manager->RegisterClass('mscorlib', 'System.IO.FileOptions', 'FileOptions');
$file = $manager->Create('mscorlib', 'File');
$fileoptions = $manager->Create('mscorlib', 'System.IO.FileOptions')->Enum('Encrypted');

$file->Create("C:\\www\\prueba.tres", 2048, $fileoptions);
//$file->Create("C:\\www\\prueba.tres", 2048); // This works

The problem here is that the exception is launched when netutilities MagicWrapper class in invoked so I am not sure if I can do anything (as I did in the first problem).

Any idea? suggestions? Thanks anyway

1.0.0.5 was just released, with the added ability to manipulate Fields and Properties using the PropertyGet/PropertySet methods of the MagicWrapper. I guess you could workaround this because your library is COM Visible, otherwise this would not have been possible.

As for your second issue, the free version of the library has some limitations. The messages have been improved in 1.0.0.5 to make it clear this is a license issue.

See NetPHP for a complete changelog.

BTW: You should not be using Apache on Windows (I can see that from the stack traces in your comment). Bad bad idea...

Add new comment

By: root Saturday, April 18, 2015 - 08:19