From e1c2146817da92d8c50b12d660befd9de0c809d8 Mon Sep 17 00:00:00 2001 From: Indigo744 Date: Thu, 3 Sep 2020 17:49:48 +0200 Subject: [PATCH 1/5] com_dotnet extension: basic implementation (barely working) --- Peachpie.sln | 2 + src/Peachpie.Library.ComDotNet/Com.cs | 119 ++++++++++++++++++ src/Peachpie.Library.ComDotNet/ComDotNet.cs | 21 ++++ .../Peachpie.Library.ComDotNet.csproj | 21 ++++ .../Properties/AssemblyInfo.cs | 11 ++ .../Peachpie.ScriptTests.csproj | 1 + src/Tests/Peachpie.ScriptTests/ScriptsTest.cs | 1 + tests/com_dotnet/com.php | 30 +++++ tests/com_dotnet/com_create_guid.php | 13 ++ 9 files changed, 219 insertions(+) create mode 100644 src/Peachpie.Library.ComDotNet/Com.cs create mode 100644 src/Peachpie.Library.ComDotNet/ComDotNet.cs create mode 100644 src/Peachpie.Library.ComDotNet/Peachpie.Library.ComDotNet.csproj create mode 100644 src/Peachpie.Library.ComDotNet/Properties/AssemblyInfo.cs create mode 100644 tests/com_dotnet/com.php create mode 100644 tests/com_dotnet/com_create_guid.php diff --git a/Peachpie.sln b/Peachpie.sln index 2ff24ff158..e11e787e2f 100644 --- a/Peachpie.sln +++ b/Peachpie.sln @@ -82,6 +82,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peachpie.NET.SdkTests", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peachpie.App.Tests", "src\Tests\Peachpie.App.Tests\Peachpie.App.Tests.csproj", "{C8A0A533-BB62-4CB5-8861-06522DCF5EC2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peachpie.Library.ComDotNet", "src\Peachpie.Library.ComDotNet\Peachpie.Library.ComDotNet.csproj", "{B9DE827D-79F5-4066-80C5-7535EB66B443}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/Peachpie.Library.ComDotNet/Com.cs b/src/Peachpie.Library.ComDotNet/Com.cs new file mode 100644 index 0000000000..332b43d4fe --- /dev/null +++ b/src/Peachpie.Library.ComDotNet/Com.cs @@ -0,0 +1,119 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using Pchp.Core; + +namespace Peachpie.Library.ComDotNet +{ + /// + /// COM object + /// TODO : implements VARIANT and inherit from it, which would allow returning VARIANT object from __call and __get + /// and working with it, see test case com_dotnet/com.php + /// + [PhpType(PhpTypeAttribute.InheritName), PhpExtension("com_dotnet")] + public class COM + { + #region Private members + + object comObject = null; + + const BindingFlags MemberAccessCom = + BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; + + #endregion + + #region Construction + + public COM(Context ctx, string module_name) + { + __construct(ctx, module_name); + } + + [PhpFieldsOnlyCtor] + protected COM() + { + } + + public virtual void __construct(Context ctx, string module_name) + { + var comType = Type.GetTypeFromProgID(module_name); + if (comType == null) + return; + + comObject = Activator.CreateInstance(comType); + } + + public virtual void __construct(Context ctx, string module_name, string server_name) + { + var comType = Type.GetTypeFromProgID(module_name, server_name); + if (comType == null) + return; + + comObject = Activator.CreateInstance(comType); + } + + public virtual void __construct(Context ctx, string module_name, string server_name, int code_page) + { + throw new NotSupportedException(); + } + + public virtual void __construct(Context ctx, string module_name, string server_name, int code_page, string typelib) + { + throw new NotSupportedException(); + } + + #endregion + + #region Magic methods + + /// + /// Special field containing runtime fields. + /// + /// + /// The field is handled by runtime and is not intended for direct use. + /// Magic methods for property access are ignored without runtime fields. + /// + [CompilerGenerated] + internal PhpArray __peach__runtimeFields = null; + + public PhpValue __call(string name, PhpArray arguments) + { + if (comObject == null) + return PhpValue.Null; + + var parameters = arguments.GetValues().Select(v => v.ToClr()).ToArray(); + + var result = comObject.GetType().InvokeMember( + name, MemberAccessCom | BindingFlags.InvokeMethod, null, comObject, parameters); + + return PhpValue.FromClr(result); + } + + public virtual PhpValue __get(string name) + { + if (comObject == null) + return PhpValue.Null; + + var prop = comObject.GetType().InvokeMember( + name, MemberAccessCom | BindingFlags.GetProperty, null, comObject, null); + + return prop != null + ? PhpValue.FromClr(prop) + : PhpValue.Null; + } + + public virtual bool __set(string name, PhpValue value) + { + if (comObject == null) + return false; + + comObject.GetType().InvokeMember( + name, MemberAccessCom | BindingFlags.SetProperty, null, comObject, new object[1] { value.ToClr() }); + + return true; + } + + #endregion + } +} diff --git a/src/Peachpie.Library.ComDotNet/ComDotNet.cs b/src/Peachpie.Library.ComDotNet/ComDotNet.cs new file mode 100644 index 0000000000..cbb95206e7 --- /dev/null +++ b/src/Peachpie.Library.ComDotNet/ComDotNet.cs @@ -0,0 +1,21 @@ +using System; +using Pchp.Core; + +namespace Peachpie.Library.ComDotNet +{ + /// + /// COM functions + /// + [PhpExtension("com_dotnet")] + public static class ComDotNet + { + /// + /// Generate a globally unique identifier (GUID) + /// + public static PhpString com_create_guid() + { + return Guid.NewGuid().ToString("B"); + } + } + +} diff --git a/src/Peachpie.Library.ComDotNet/Peachpie.Library.ComDotNet.csproj b/src/Peachpie.Library.ComDotNet/Peachpie.Library.ComDotNet.csproj new file mode 100644 index 0000000000..7004bbccba --- /dev/null +++ b/src/Peachpie.Library.ComDotNet/Peachpie.Library.ComDotNet.csproj @@ -0,0 +1,21 @@ + + + + netstandard2.0 + Peachpie.Library.ComDotNet + Peachpie.Library.ComDotNet + peachpie;library;com;dotnet;com_dotnet + True + Peachpie PHP language library functions for COM/.Net interoperability on Windows. + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Peachpie.Library.ComDotNet/Properties/AssemblyInfo.cs b/src/Peachpie.Library.ComDotNet/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2685374ebe --- /dev/null +++ b/src/Peachpie.Library.ComDotNet/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyProduct("Peachpie.Library.ComDotNet")] +[assembly: AssemblyTrademark("")] + +// annotates this library as a php extension, +// all its public static methods with compatible signatures will be seen as global functions to php scope +[assembly: Pchp.Core.PhpExtension("com_dotnet")] \ No newline at end of file diff --git a/src/Tests/Peachpie.ScriptTests/Peachpie.ScriptTests.csproj b/src/Tests/Peachpie.ScriptTests/Peachpie.ScriptTests.csproj index b36d9e06c2..1ce69c2127 100644 --- a/src/Tests/Peachpie.ScriptTests/Peachpie.ScriptTests.csproj +++ b/src/Tests/Peachpie.ScriptTests/Peachpie.ScriptTests.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Tests/Peachpie.ScriptTests/ScriptsTest.cs b/src/Tests/Peachpie.ScriptTests/ScriptsTest.cs index 46ff3d02f5..693c86a74d 100644 --- a/src/Tests/Peachpie.ScriptTests/ScriptsTest.cs +++ b/src/Tests/Peachpie.ScriptTests/ScriptsTest.cs @@ -29,6 +29,7 @@ public class ScriptsTest typeof(Peachpie.Library.Scripting.Standard).Assembly.Location, typeof(Peachpie.Library.XmlDom.XmlDom).Assembly.Location, typeof(Peachpie.Library.Network.CURLFunctions).Assembly.Location, + typeof(Peachpie.Library.ComDotNet.COM).Assembly.Location, }; static readonly Context.IScriptingProvider _provider = Context.DefaultScriptingProvider; // use IScriptingProvider singleton diff --git a/tests/com_dotnet/com.php b/tests/com_dotnet/com.php new file mode 100644 index 0000000000..882bb79b03 --- /dev/null +++ b/tests/com_dotnet/com.php @@ -0,0 +1,30 @@ +GetTempName()) . PHP_EOL; + // echo $fso->GetSpecialFolder(0) . PHP_EOL; // <- Fails (COM_Object) + $drives = $fso->Drives; + echo gettype($drives).PHP_EOL; + foreach($drives as $d) { + //echo $d.PHP_EOL; // <- Fails (COM_Object) + $dO = $fso->GetDrive($d); + //echo $dO->DriveLetter.PHP_EOL; // <- Fails (COM_Object) + break; + } + + echo "testing Shell" .PHP_EOL; + $shell = new \COM('WScript.Shell'); + echo $shell->CurrentDirectory . PHP_EOL; + $shell->CurrentDirectory = "C:\\"; + echo $shell->CurrentDirectory . PHP_EOL; +} + +test(); diff --git a/tests/com_dotnet/com_create_guid.php b/tests/com_dotnet/com_create_guid.php new file mode 100644 index 0000000000..2a80a691be --- /dev/null +++ b/tests/com_dotnet/com_create_guid.php @@ -0,0 +1,13 @@ + Date: Sun, 6 Sep 2020 18:55:08 +0200 Subject: [PATCH 2/5] Mark COM test as skipped if not on Windows --- tests/com_dotnet/com.php | 4 ++++ tests/com_dotnet/com_create_guid.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tests/com_dotnet/com.php b/tests/com_dotnet/com.php index 882bb79b03..0cc26ffa30 100644 --- a/tests/com_dotnet/com.php +++ b/tests/com_dotnet/com.php @@ -2,6 +2,10 @@ namespace com_dotnet\com; function test() { + if (PHP_OS != "WINNT") { + exit("***SKIP***"); + } + if (!extension_loaded("com_dotnet")) { echo "Extension com_dotnet not loaded"; return; diff --git a/tests/com_dotnet/com_create_guid.php b/tests/com_dotnet/com_create_guid.php index 2a80a691be..17b02cdbbb 100644 --- a/tests/com_dotnet/com_create_guid.php +++ b/tests/com_dotnet/com_create_guid.php @@ -2,6 +2,10 @@ namespace com_dotnet\com_create_guid; function test() { + if (PHP_OS != "WINNT") { + exit("***SKIP***"); + } + if (!extension_loaded("com_dotnet")) { echo "Extension com_dotnet not loaded"; return; From 77e9c3a0fade610a2879c80646a30972476fb38c Mon Sep 17 00:00:00 2001 From: Indigo744 Date: Mon, 7 Sep 2020 12:09:17 +0200 Subject: [PATCH 3/5] Fix solution file --- Peachpie.sln | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Peachpie.sln b/Peachpie.sln index e11e787e2f..63a40b78f7 100644 --- a/Peachpie.sln +++ b/Peachpie.sln @@ -656,6 +656,26 @@ Global {C8A0A533-BB62-4CB5-8861-06522DCF5EC2}.Release|x64.Build.0 = Release|Any CPU {C8A0A533-BB62-4CB5-8861-06522DCF5EC2}.Release|x86.ActiveCfg = Release|Any CPU {C8A0A533-BB62-4CB5-8861-06522DCF5EC2}.Release|x86.Build.0 = Release|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Debug|ARM.ActiveCfg = Debug|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Debug|ARM.Build.0 = Debug|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Debug|x64.ActiveCfg = Debug|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Debug|x64.Build.0 = Debug|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Debug|x86.ActiveCfg = Debug|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Debug|x86.Build.0 = Debug|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Release|Any CPU.Build.0 = Release|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Release|ARM.ActiveCfg = Release|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Release|ARM.Build.0 = Release|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Release|x64.ActiveCfg = Release|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Release|x64.Build.0 = Release|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Release|x86.ActiveCfg = Release|Any CPU + {B9DE827D-79F5-4066-80C5-7535EB66B443}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -686,6 +706,7 @@ Global {8BE90675-F686-4327-8E13-BE3F7B540CF5} = {82A72490-8B53-4A22-BB14-B0C4D6C83D67} {937F85EB-7F06-4A34-8C7D-BA3BF88E71AA} = {30104149-66C2-44B1-899F-F97E9ECA3860} {C8A0A533-BB62-4CB5-8861-06522DCF5EC2} = {30104149-66C2-44B1-899F-F97E9ECA3860} + {B9DE827D-79F5-4066-80C5-7535EB66B443} = {30D6D91F-A671-4E73-A571-5614500FDE0B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A0FC0FC5-DA92-4FD6-9841-D8ABF963E7AD} From 8101b3dab7bd05e07e06cd1de46567b2efb90505 Mon Sep 17 00:00:00 2001 From: Indigo744 Date: Mon, 7 Sep 2020 18:40:35 +0200 Subject: [PATCH 4/5] Add reference to package in build-cache script --- build/dummy/dummy.msbuildproj | 1 + build/update-cache.ps1 | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build/dummy/dummy.msbuildproj b/build/dummy/dummy.msbuildproj index 4f2a8cb56c..b89ff85095 100644 --- a/build/dummy/dummy.msbuildproj +++ b/build/dummy/dummy.msbuildproj @@ -12,6 +12,7 @@ + diff --git a/build/update-cache.ps1 b/build/update-cache.ps1 index b869fbecbe..ecc753f12c 100644 --- a/build/update-cache.ps1 +++ b/build/update-cache.ps1 @@ -13,7 +13,10 @@ $defaultArgs = "/p:VersionPrefix=$version,VersionSuffix=$suffix" ## Delete old nuget packages Write-Host -f green "Deleting '$version-$suffix' packages from '$packagesSource' ..." -@("Peachpie.Runtime", "Peachpie.Library", "Peachpie.Library.Scripting", "Peachpie.Library.MySql", "Peachpie.Library.MsSql", "Peachpie.Library.Graphics", "Peachpie.Library.Network", "Peachpie.Library.PDO", "Peachpie.Library.XmlDom", "Peachpie.App", "Peachpie.CodeAnalysis", "Peachpie.AspNetCore.Web", "Peachpie.AspNetCore.Mvc", "Peachpie.NET.Sdk", "Peachpie.Library.PDO.MySql", "Peachpie.Library.PDO.Sqlite", "Peachpie.Library.SqlSrv") | % { +@("Peachpie.Runtime", "Peachpie.Library", "Peachpie.Library.Scripting", "Peachpie.Library.MySql", "Peachpie.Library.MsSql", + "Peachpie.Library.Graphics", "Peachpie.Library.Network", "Peachpie.Library.PDO", "Peachpie.Library.XmlDom", "Peachpie.App", + "Peachpie.CodeAnalysis", "Peachpie.AspNetCore.Web", "Peachpie.AspNetCore.Mvc", "Peachpie.NET.Sdk", "Peachpie.Library.PDO.MySql", + "Peachpie.Library.PDO.Sqlite", "Peachpie.Library.SqlSrv", "Peachpie.Library.ComDotNet") | % { $installedFolder = "$packagesSource/$_/$version-$suffix" if (Test-Path $installedFolder) { Remove-Item -Recurse -Force $installedFolder From da4d3d6e7f4137bf4b7b5bc7e0bcced53c3f0e7a Mon Sep 17 00:00:00 2001 From: Indigo744 Date: Tue, 8 Sep 2020 15:01:01 +0200 Subject: [PATCH 5/5] Hide internal members from PHP --- src/Peachpie.Library.ComDotNet/Com.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Peachpie.Library.ComDotNet/Com.cs b/src/Peachpie.Library.ComDotNet/Com.cs index 332b43d4fe..c4640e59ab 100644 --- a/src/Peachpie.Library.ComDotNet/Com.cs +++ b/src/Peachpie.Library.ComDotNet/Com.cs @@ -16,9 +16,11 @@ public class COM { #region Private members - object comObject = null; + [PhpHidden] + internal protected object comObject = null; - const BindingFlags MemberAccessCom = + [PhpHidden] + internal protected const BindingFlags MemberAccessCom = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; #endregion