diff --git a/Avalonia.sln b/Avalonia.sln
index 34ad19b41d..74a2dbb94b 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -226,6 +226,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "samples\Sandbox\Sandbox.csproj", "{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroComGenerator", "src\tools\MicroComGenerator\MicroComGenerator.csproj", "{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@@ -2064,6 +2068,54 @@ Global
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhone.Build.0 = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.Build.0 = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.Build.0 = Release|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.AppStore|iPhone.Build.0 = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Release|iPhone.Build.0 = Release|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2123,6 +2175,7 @@ Global
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+ {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj
index cd3ce9adcd..90c1c67956 100644
--- a/packages/Avalonia/Avalonia.csproj
+++ b/packages/Avalonia/Avalonia.csproj
@@ -5,8 +5,9 @@
-
+
+
@@ -15,9 +16,7 @@
-
+
<_PackageFiles Include="$(DesignerHostAppPath)/Avalonia.Designer.HostApp/bin/$(Configuration)/netcoreapp2.0/Avalonia.Designer.HostApp.dll">
diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 612c368633..57b4cb04e3 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -3,6 +3,7 @@
<_AvaloniaUseExternalMSBuild>$(AvaloniaUseExternalMSBuild)
<_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false
low
+ <_AvaloniaPatchComInterop Condition="'$(_AvaloniaPatchComInterop)' == ''">false
@@ -90,6 +91,7 @@
AssemblyOriginatorKeyFile="$(AssemblyOriginatorKeyFile)"
SignAssembly="$(SignAssembly)"
DelaySign="$(DelaySign)"
+ EnableComInteropPatching="$(_AvaloniaPatchComInterop)"
/>
();
+ var initializers = new List();
+ foreach (var type in asm.MainModule.Types)
+ {
+ var i = type.Methods.FirstOrDefault(m => m.Name == "__MicroComModuleInit");
+ if (i != null)
+ initializers.Add(i);
+
+ PatchType(type, classToRemoveList);
+ }
+
+ // Remove All Interop classes
+ foreach (var type in classToRemoveList)
+ asm.MainModule.Types.Remove(type);
+
+
+ // Patch automatic registrations
+ if (initializers.Count != 0)
+ {
+ var moduleType = asm.MainModule.Types.First(x => x.Name == "");
+
+ // Needed for compatibility with upcoming .NET 5 feature, look for existing initializer first
+ var staticCtor = moduleType.Methods.FirstOrDefault(m => m.Name == ".cctor");
+ if (staticCtor == null)
+ {
+ // Create a new static ctor if none exists
+ staticCtor = new MethodDefinition(".cctor",
+ MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName |
+ MethodAttributes.Static | MethodAttributes.Private,
+ asm.MainModule.TypeSystem.Void);
+ staticCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
+ moduleType.Methods.Add(staticCtor);
+ }
+
+ foreach (var i in initializers)
+ staticCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Call, i));
+ }
+
+ }
+
+
+
+ static void PatchMethod(MethodDefinition method)
+ {
+ if (method.HasBody)
+ {
+ var ilProcessor = method.Body.GetILProcessor();
+
+ var instructions = method.Body.Instructions;
+ for (int i = 0; i < instructions.Count; i++)
+ {
+ Instruction instruction = instructions[i];
+
+ if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference)
+ {
+ var methodDescription = (MethodReference)instruction.Operand;
+
+ if (methodDescription.Name.StartsWith("Calli") && methodDescription.DeclaringType.Name == "LocalInterop")
+ {
+ var callSite = new CallSite(methodDescription.ReturnType) { CallingConvention = MethodCallingConvention.StdCall };
+
+ if (methodDescription.Name.StartsWith("CalliCdecl"))
+ {
+ callSite.CallingConvention = MethodCallingConvention.C;
+ }
+ else if(methodDescription.Name.StartsWith("CalliThisCall"))
+ {
+ callSite.CallingConvention = MethodCallingConvention.ThisCall;
+ }
+ else if(methodDescription.Name.StartsWith("CalliStdCall"))
+ {
+ callSite.CallingConvention = MethodCallingConvention.StdCall;
+ }
+ else if(methodDescription.Name.StartsWith("CalliFastCall"))
+ {
+ callSite.CallingConvention = MethodCallingConvention.FastCall;
+ }
+
+ // Last parameter is the function ptr, so we don't add it as a parameter for calli
+ // as it is already an implicit parameter for calli
+ for (int j = 0; j < methodDescription.Parameters.Count - 1; j++)
+ {
+ var parameterDefinition = methodDescription.Parameters[j];
+ callSite.Parameters.Add(parameterDefinition);
+ }
+
+ // Create calli Instruction
+ var callIInstruction = ilProcessor.Create(OpCodes.Calli, callSite);
+
+ // Replace instruction
+ ilProcessor.Replace(instruction, callIInstruction);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Patches the type.
+ ///
+ /// The type.
+ static void PatchType(TypeDefinition type, List classToRemoveList)
+ {
+ // Patch methods
+ foreach (var method in type.Methods)
+ PatchMethod(method);
+
+ if (type.Name == "LocalInterop")
+ classToRemoveList.Add(type);
+
+ // Patch nested types
+ foreach (var typeDefinition in type.NestedTypes)
+ PatchType(typeDefinition, classToRemoveList);
+ }
+ }
+}
diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
index 8e1f6c257d..f44e90b25e 100644
--- a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
+++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
@@ -40,8 +40,8 @@ namespace Avalonia.Build.Tasks
var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input,
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
ProjectDirectory, OutputPath, VerifyIl, outputImportance,
- (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null
- );
+ (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
+ EnableComInteropPatching);
if (!res.Success)
return false;
if (!res.WrittenFile)
@@ -76,6 +76,8 @@ namespace Avalonia.Build.Tasks
public bool VerifyIl { get; set; }
+ public bool EnableComInteropPatching { get; set; }
+
public string AssemblyOriginatorKeyFile { get; set; }
public bool SignAssembly { get; set; }
public bool DelaySign { get; set; }
diff --git a/src/Avalonia.Build.Tasks/Program.cs b/src/Avalonia.Build.Tasks/Program.cs
index 1909c4c6ec..b18c19cd63 100644
--- a/src/Avalonia.Build.Tasks/Program.cs
+++ b/src/Avalonia.Build.Tasks/Program.cs
@@ -29,7 +29,8 @@ namespace Avalonia.Build.Tasks
OutputPath = args[2],
BuildEngine = new ConsoleBuildEngine(),
ProjectDirectory = Directory.GetCurrentDirectory(),
- VerifyIl = true
+ VerifyIl = true,
+ EnableComInteropPatching = true
}.Execute() ?
0 :
2;
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
index 6b01af2ede..938732cfe1 100644
--- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
@@ -42,13 +42,13 @@ namespace Avalonia.Build.Tasks
}
public static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory,
- string output, bool verifyIl, MessageImportance logImportance, string strongNameKey)
+ string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom)
{
var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input);
var asm = typeSystem.TargetAssemblyDefinition;
var emres = new EmbeddedResources(asm);
var avares = new AvaloniaResources(asm, projectDirectory);
- if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0)
+ if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0 && !patchCom)
// Nothing to do
return new CompileResult(true);
@@ -374,7 +374,9 @@ namespace Avalonia.Build.Tasks
loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
-
+
+ if (patchCom)
+ ComInteropHelper.PatchAssembly(asm, typeSystem);
var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
if (!string.IsNullOrWhiteSpace(strongNameKey))
diff --git a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj
new file mode 100644
index 0000000000..aaf54ed80e
--- /dev/null
+++ b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj
@@ -0,0 +1,8 @@
+
+
+
+ netstandard2.0
+ true
+
+
+
diff --git a/src/Avalonia.MicroCom/IMicroComExceptionCallback.cs b/src/Avalonia.MicroCom/IMicroComExceptionCallback.cs
new file mode 100644
index 0000000000..08f20339ec
--- /dev/null
+++ b/src/Avalonia.MicroCom/IMicroComExceptionCallback.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Avalonia.MicroCom
+{
+ public interface IMicroComExceptionCallback
+ {
+ void RaiseException(Exception e);
+ }
+}
diff --git a/src/Avalonia.MicroCom/IMicroComShadowContainer.cs b/src/Avalonia.MicroCom/IMicroComShadowContainer.cs
new file mode 100644
index 0000000000..a33d3a9811
--- /dev/null
+++ b/src/Avalonia.MicroCom/IMicroComShadowContainer.cs
@@ -0,0 +1,9 @@
+namespace Avalonia.MicroCom
+{
+ public interface IMicroComShadowContainer
+ {
+ MicroComShadow Shadow { get; set; }
+ void OnReferencedFromNative();
+ void OnUnreferencedFromNative();
+ }
+}
diff --git a/src/Avalonia.MicroCom/IUnknown.cs b/src/Avalonia.MicroCom/IUnknown.cs
new file mode 100644
index 0000000000..a46953ced9
--- /dev/null
+++ b/src/Avalonia.MicroCom/IUnknown.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Avalonia.MicroCom
+{
+ public interface IUnknown : IDisposable
+ {
+ void AddRef();
+ void Release();
+ int QueryInterface(Guid guid, out IntPtr ppv);
+ T QueryInterface() where T : IUnknown;
+ }
+}
diff --git a/src/Avalonia.MicroCom/LocalInterop.cs b/src/Avalonia.MicroCom/LocalInterop.cs
new file mode 100644
index 0000000000..785f4e03a5
--- /dev/null
+++ b/src/Avalonia.MicroCom/LocalInterop.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Avalonia.MicroCom
+{
+ unsafe class LocalInterop
+ {
+ public static unsafe void CalliStdCallvoid(void* thisObject, void* methodPtr)
+ {
+ throw null;
+ }
+
+ public static unsafe int CalliStdCallint(void* thisObject, Guid* guid, IntPtr* ppv, void* methodPtr)
+ {
+ throw null;
+ }
+ }
+}
diff --git a/src/Avalonia.MicroCom/MicroComProxyBase.cs b/src/Avalonia.MicroCom/MicroComProxyBase.cs
new file mode 100644
index 0000000000..56b80c2632
--- /dev/null
+++ b/src/Avalonia.MicroCom/MicroComProxyBase.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Avalonia.MicroCom
+{
+ public unsafe class MicroComProxyBase : IUnknown
+ {
+ private IntPtr _nativePointer;
+ private bool _ownsHandle;
+
+ public IntPtr NativePointer
+ {
+ get
+ {
+ if (_nativePointer == IntPtr.Zero)
+ throw new ObjectDisposedException(this.GetType().FullName);
+ return _nativePointer;
+ }
+ }
+
+ public void*** PPV => (void***)NativePointer;
+
+ public MicroComProxyBase(IntPtr nativePointer, bool ownsHandle)
+ {
+ _nativePointer = nativePointer;
+ _ownsHandle = ownsHandle;
+ }
+
+ protected virtual int VTableSize => 3;
+
+ public void AddRef()
+ {
+ LocalInterop.CalliStdCallvoid(PPV, (*PPV)[1]);
+ }
+
+ public void Release()
+ {
+ LocalInterop.CalliStdCallvoid(PPV, (*PPV)[2]);
+ }
+
+ public int QueryInterface(Guid guid, out IntPtr ppv)
+ {
+ IntPtr r = default;
+ var rv = LocalInterop.CalliStdCallint(PPV, &guid, &r, (*PPV)[0]);
+ ppv = r;
+ return rv;
+ }
+
+ public T QueryInterface() where T : IUnknown
+ {
+ var guid = MicroComRuntime.GetGuidFor(typeof(T));
+ var rv = QueryInterface(guid, out var ppv);
+ if (rv != 0)
+ return (T)MicroComRuntime.CreateProxyFor(typeof(T), ppv, true);
+ throw new COMException("QueryInterface failed", rv);
+ }
+
+ public bool IsDisposed => _nativePointer == IntPtr.Zero;
+
+ public void Dispose()
+ {
+ if (_ownsHandle)
+ Release();
+ _nativePointer = IntPtr.Zero;
+ }
+
+ public bool OwnsHandle => _ownsHandle;
+
+ public void EnsureOwned()
+ {
+ if (!_ownsHandle)
+ {
+ AddRef();
+ _ownsHandle = true;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.MicroCom/MicroComRuntime.cs b/src/Avalonia.MicroCom/MicroComRuntime.cs
new file mode 100644
index 0000000000..eccf24b496
--- /dev/null
+++ b/src/Avalonia.MicroCom/MicroComRuntime.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Concurrent;
+using System.Runtime.InteropServices;
+
+namespace Avalonia.MicroCom
+{
+ public static unsafe class MicroComRuntime
+ {
+ private static ConcurrentDictionary _vtables = new ConcurrentDictionary();
+
+ private static ConcurrentDictionary> _factories =
+ new ConcurrentDictionary>();
+ private static ConcurrentDictionary _guids = new ConcurrentDictionary();
+ private static ConcurrentDictionary _guidsToTypes = new ConcurrentDictionary();
+
+ static MicroComRuntime()
+ {
+ Register(typeof(IUnknown), new Guid("00000000-0000-0000-C000-000000000046"),
+ (ppv, owns) => new MicroComProxyBase(ppv, owns));
+ RegisterVTable(typeof(IUnknown), MicroComVtblBase.Vtable);
+ }
+
+ public static void RegisterVTable(Type t, IntPtr vtable)
+ {
+ _vtables[t] = vtable;
+ }
+
+ public static void Register(Type t, Guid guid, Func proxyFactory)
+ {
+ _factories[t] = proxyFactory;
+ _guids[t] = guid;
+ _guidsToTypes[guid] = t;
+ }
+
+ public static Guid GetGuidFor(Type type) => _guids[type];
+
+ public static T CreateProxyFor(void* ppv, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(ppv), ownsHandle);
+
+ public static object CreateProxyFor(Type type, IntPtr ppv, bool ownsHandle) => _factories[type](ppv, ownsHandle);
+
+ public static void* GetNativePointer(T obj, bool owned = false) where T : IUnknown
+ {
+ if (obj is MicroComProxyBase proxy)
+ return (void*)proxy.NativePointer;
+ if (obj is IMicroComShadowContainer container)
+ {
+ container.Shadow ??= new MicroComShadow(container);
+ void* ptr = null;
+ var res = container.Shadow.GetOrCreateNativePointer(typeof(T), &ptr);
+ if (res != 0)
+ throw new COMException(
+ "Unable to create native callable wrapper for type " + typeof(T) + " for instance of type " +
+ obj.GetType(),
+ res);
+ if (owned)
+ container.Shadow.AddRef((Ccw*)ptr);
+ }
+ throw new ArgumentException("Unable to get a native pointer for " + obj);
+ }
+
+ public static object GetObjectFromCcw(IntPtr ccw)
+ {
+ var ptr = (Ccw*)ccw;
+ var shadow = (MicroComShadow)GCHandle.FromIntPtr(ptr->GcShadowHandle).Target;
+ return shadow.Target;
+ }
+
+ public static bool TryGetTypeForGuid(Guid guid, out Type t) => _guidsToTypes.TryGetValue(guid, out t);
+
+ public static bool GetVtableFor(Type type, out IntPtr ptr) => _vtables.TryGetValue(type, out ptr);
+
+ public static void UnhandledException(object target, Exception e)
+ {
+ if (target is IMicroComExceptionCallback cb)
+ {
+ try
+ {
+ cb.RaiseException(e);
+ }
+ catch
+ {
+ // We've tried
+ }
+ }
+
+ }
+ }
+}
diff --git a/src/Avalonia.MicroCom/MicroComShadow.cs b/src/Avalonia.MicroCom/MicroComShadow.cs
new file mode 100644
index 0000000000..c9411a089e
--- /dev/null
+++ b/src/Avalonia.MicroCom/MicroComShadow.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Avalonia.MicroCom
+{
+ public unsafe class MicroComShadow : IDisposable
+ {
+ private readonly object _lock = new object();
+ private readonly Dictionary _shadows = new Dictionary();
+ private readonly Dictionary _backShadows = new Dictionary();
+ private GCHandle? _handle;
+ private volatile int _refCount;
+ internal IMicroComShadowContainer Target { get; }
+ internal MicroComShadow(IMicroComShadowContainer target)
+ {
+ Target = target;
+ Target.Shadow = this;
+ }
+
+ internal int QueryInterface(Ccw* ccw, Guid* guid, void** ppv)
+ {
+ if (MicroComRuntime.TryGetTypeForGuid(*guid, out var type))
+ return QueryInterface(type, ppv);
+ else
+ return unchecked((int)0x80004002u);
+ }
+
+ internal int QueryInterface(Type type, void** ppv)
+ {
+ if (!type.IsInstanceOfType(Target))
+ return unchecked((int)0x80004002u);
+
+ var rv = GetOrCreateNativePointer(type, ppv);
+ if (rv == 0)
+ AddRef((Ccw*)*ppv);
+ return rv;
+ }
+
+ internal int GetOrCreateNativePointer(Type type, void** ppv)
+ {
+ if (!MicroComRuntime.GetVtableFor(type, out var vtable))
+ return unchecked((int)0x80004002u);
+ lock (_lock)
+ {
+
+ if (_shadows.TryGetValue(type, out var shadow))
+ {
+ var targetCcw = (Ccw*)shadow;
+ AddRef(targetCcw);
+ *ppv = targetCcw;
+ return 0;
+ }
+ else
+ {
+ var intPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
+ var targetCcw = (Ccw*)intPtr;
+ *targetCcw = default;
+ targetCcw->RefCount = 0;
+ targetCcw->VTable = vtable;
+ if (_handle == null)
+ _handle = GCHandle.Alloc(this);
+ targetCcw->GcShadowHandle = GCHandle.ToIntPtr(_handle.Value);
+ _shadows[type] = intPtr;
+ _backShadows[intPtr] = type;
+ *ppv = targetCcw;
+
+ return 0;
+ }
+ }
+ }
+
+ internal int AddRef(Ccw* ccw)
+ {
+ if (Interlocked.Increment(ref _refCount) == 1)
+ {
+ try
+ {
+ Target.OnUnreferencedFromNative();
+ }
+ catch (Exception e)
+ {
+ MicroComRuntime.UnhandledException(Target, e);
+ }
+ }
+
+ return Interlocked.Increment(ref ccw->RefCount);
+ }
+
+ internal int Release(Ccw* ccw)
+ {
+ Interlocked.Decrement(ref _refCount);
+ var cnt = Interlocked.Decrement(ref ccw->RefCount);
+ if (cnt == 0)
+ return FreeCcw(ccw);
+
+ return cnt;
+ }
+
+ int FreeCcw(Ccw* ccw)
+ {
+ lock (_lock)
+ {
+ // Shadow got resurrected by a call to QueryInterface from another thread
+ if (ccw->RefCount != 0)
+ return ccw->RefCount;
+
+ var intPtr = new IntPtr(ccw);
+ var type = _backShadows[intPtr];
+ _backShadows.Remove(intPtr);
+ _shadows.Remove(type);
+ Marshal.FreeHGlobal(intPtr);
+ if (_shadows.Count == 0)
+ {
+ _handle?.Free();
+ _handle = null;
+ try
+ {
+ Target.OnUnreferencedFromNative();
+ }
+ catch(Exception e)
+ {
+ MicroComRuntime.UnhandledException(Target, e);
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ /*
+ Needs to be called to support the following scenario:
+ 1) Object created
+ 2) Object passed to native code, shadow is created, CCW is created
+ 3) Native side has never called AddRef
+
+ In that case the GC handle to the shadow object is still alive
+ */
+
+ public void Dispose()
+ {
+ lock (_lock)
+ {
+ List toRemove = null;
+ foreach (var kv in _backShadows)
+ {
+ var ccw = (Ccw*)kv.Key;
+ if (ccw->RefCount == 0)
+ {
+ toRemove ??= new List();
+ toRemove.Add(kv.Key);
+ }
+ }
+
+ if(toRemove != null)
+ foreach (var intPtr in toRemove)
+ FreeCcw((Ccw*)intPtr);
+ }
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct Ccw
+ {
+ public IntPtr VTable;
+ public IntPtr GcShadowHandle;
+ public volatile int RefCount;
+ public MicroComShadow GetShadow() => (MicroComShadow)GCHandle.FromIntPtr(GcShadowHandle).Target;
+ }
+}
diff --git a/src/Avalonia.MicroCom/MicroComVtblBase.cs b/src/Avalonia.MicroCom/MicroComVtblBase.cs
new file mode 100644
index 0000000000..4cd419cdaf
--- /dev/null
+++ b/src/Avalonia.MicroCom/MicroComVtblBase.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Avalonia.MicroCom
+{
+ public unsafe class MicroComVtblBase
+ {
+ private List _methods = new List();
+ [UnmanagedFunctionPointerAttribute(CallingConvention.ThisCall)]
+ private delegate int AddRefDelegate(Ccw* ccw);
+
+ [UnmanagedFunctionPointerAttribute(CallingConvention.ThisCall)]
+ private delegate int QueryInterfaceDelegate(Ccw* ccw, Guid* guid, void** ppv);
+
+ public static IntPtr Vtable { get; } = new MicroComVtblBase().CreateVTable();
+ public MicroComVtblBase()
+ {
+ AddMethod((QueryInterfaceDelegate)QueryInterface);
+ AddMethod((AddRefDelegate)AddRef);
+ }
+
+ protected void AddMethod(Delegate d)
+ {
+ GCHandle.Alloc(d);
+ _methods.Add(Marshal.GetFunctionPointerForDelegate(d));
+ }
+
+ protected unsafe IntPtr CreateVTable()
+ {
+ var ptr = (IntPtr*)Marshal.AllocHGlobal((IntPtr.Size + 1) * _methods.Count);
+ for (var c = 0; c < _methods.Count; c++)
+ ptr[c] = _methods[c];
+ return new IntPtr(ptr);
+ }
+
+ static int QueryInterface(Ccw* ccw, Guid* guid, void** ppv) => ccw->GetShadow().QueryInterface(ccw, guid, ppv);
+ static int AddRef(Ccw* ccw) => ccw->GetShadow().AddRef(ccw);
+ static int Release(Ccw* ccw) => ccw->GetShadow().Release(ccw);
+ }
+}
diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl
new file mode 100644
index 0000000000..c763007826
--- /dev/null
+++ b/src/Avalonia.Native/avn.idl
@@ -0,0 +1,522 @@
+@clr-namespace Avalonia.Native.MicroCom
+@clr-access internal
+@cpp-preamble @@
+#include "com.h"
+#include "key.h"
+#include "stddef.h"
+@@
+
+enum SystemDecorations {
+ SystemDecorationsNone = 0,
+ SystemDecorationsBorderOnly = 1,
+ SystemDecorationsFull = 2,
+}
+
+struct AvnSize
+{
+ double Width, Height;
+}
+
+struct AvnPixelSize
+{
+ int Width, Height;
+}
+
+struct AvnRect
+{
+ double X, Y, Width, Height;
+}
+
+struct AvnVector
+{
+ double X, Y;
+}
+
+struct AvnPoint
+{
+ double X, Y;
+}
+
+struct AvnScreen
+{
+ AvnRect Bounds;
+ AvnRect WorkingArea;
+ float PixelDensity;
+ bool Primary;
+}
+
+enum AvnPixelFormat
+{
+ kAvnRgb565,
+ kAvnRgba8888,
+ kAvnBgra8888
+}
+
+struct AvnFramebuffer
+{
+ void* Data;
+ int Width;
+ int Height;
+ int Stride;
+ AvnVector Dpi;
+ AvnPixelFormat PixelFormat;
+}
+
+struct AvnColor
+{
+ byte Alpha;
+ byte Red;
+ byte Green;
+ byte Blue;
+}
+
+enum AvnRawMouseEventType
+{
+ LeaveWindow,
+ LeftButtonDown,
+ LeftButtonUp,
+ RightButtonDown,
+ RightButtonUp,
+ MiddleButtonDown,
+ MiddleButtonUp,
+ XButton1Down,
+ XButton1Up,
+ XButton2Down,
+ XButton2Up,
+ Move,
+ Wheel,
+ NonClientLeftButtonDown,
+ TouchBegin,
+ TouchUpdate,
+ TouchEnd,
+ TouchCancel
+}
+
+enum AvnRawKeyEventType
+{
+ KeyDown,
+ KeyUp
+}
+
+enum AvnInputModifiers
+{
+ AvnInputModifiersNone = 0,
+ Alt = 1,
+ Control = 2,
+ Shift = 4,
+ Windows = 8,
+ LeftMouseButton = 16,
+ RightMouseButton = 32,
+ MiddleMouseButton = 64,
+ XButton1MouseButton = 128,
+ XButton2MouseButton = 256
+}
+
+[class-enum]
+enum AvnDragDropEffects
+{
+ None = 0,
+ Copy = 1,
+ Move = 2,
+ Link = 4,
+}
+
+[class-enum]
+enum AvnDragEventType
+{
+ Enter,
+ Over,
+ Leave,
+ Drop
+}
+
+enum AvnWindowState
+{
+ Normal,
+ Minimized,
+ Maximized,
+ FullScreen,
+}
+
+enum AvnStandardCursorType
+{
+ CursorArrow,
+ CursorIbeam,
+ CursorWait,
+ CursorCross,
+ CursorUpArrow,
+ CursorSizeWestEast,
+ CursorSizeNorthSouth,
+ CursorSizeAll,
+ CursorNo,
+ CursorHand,
+ CursorAppStarting,
+ CursorHelp,
+ CursorTopSide,
+ CursorBottomSize,
+ CursorLeftSide,
+ CursorRightSide,
+ CursorTopLeftCorner,
+ CursorTopRightCorner,
+ CursorBottomLeftCorner,
+ CursorBottomRightCorner,
+ CursorDragMove,
+ CursorDragCopy,
+ CursorDragLink,
+ CursorNone
+}
+
+enum AvnWindowEdge
+{
+ WindowEdgeNorthWest,
+ WindowEdgeNorth,
+ WindowEdgeNorthEast,
+ WindowEdgeWest,
+ WindowEdgeEast,
+ WindowEdgeSouthWest,
+ WindowEdgeSouth,
+ WindowEdgeSouthEast
+}
+
+enum AvnMenuItemToggleType
+{
+ None,
+ CheckMark,
+ Radio
+}
+
+enum AvnExtendClientAreaChromeHints
+{
+ AvnNoChrome = 0,
+ AvnSystemChrome = 0x01,
+ AvnPreferSystemChrome = 0x02,
+ AvnOSXThickTitleBar = 0x08,
+ AvnDefaultChrome = AvnSystemChrome,
+}
+
+[uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)]
+interface IAvaloniaNativeFactory : IUnknown
+{
+ HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator);
+ IAvnMacOptions* GetMacOptions();
+ HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv);
+ HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv);
+ HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv);
+ HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv);
+ HRESULT CreateScreens(IAvnScreens** ppv);
+ HRESULT CreateClipboard(IAvnClipboard** ppv);
+ HRESULT CreateDndClipboard(IAvnClipboard** ppv);
+ HRESULT CreateCursorFactory(IAvnCursorFactory** ppv);
+ HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv);
+ HRESULT SetAppMenu(IAvnMenu* menu);
+ HRESULT CreateMenu(IAvnMenuEvents* cb, IAvnMenu** ppv);
+ HRESULT CreateMenuItem(IAvnMenuItem** ppv);
+ HRESULT CreateMenuItemSeperator(IAvnMenuItem** ppv);
+}
+
+[uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)]
+interface IAvnString : IUnknown
+{
+ HRESULT Pointer(void**retOut);
+ HRESULT Length(int*ret);
+}
+
+[uuid(e5aca675-02b7-4129-aa79-d6e417210bda)]
+interface IAvnWindowBase : IUnknown
+{
+ HRESULT Show();
+ HRESULT Hide();
+ HRESULT Close();
+ HRESULT Activate();
+ HRESULT GetClientSize(AvnSize*ret);
+ HRESULT GetScaling(double*ret);
+ HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize);
+ HRESULT Resize(double width, double height);
+ HRESULT Invalidate(AvnRect rect);
+ HRESULT BeginMoveDrag();
+ HRESULT BeginResizeDrag(AvnWindowEdge edge);
+ HRESULT GetPosition(AvnPoint*ret);
+ HRESULT SetPosition(AvnPoint point);
+ HRESULT PointToClient(AvnPoint point, AvnPoint*ret);
+ HRESULT PointToScreen(AvnPoint point, AvnPoint*ret);
+ HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose);
+ HRESULT SetTopMost(bool value);
+ HRESULT SetCursor(IAvnCursor* cursor);
+ HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret);
+ HRESULT SetMainMenu(IAvnMenu* menu);
+ HRESULT ObtainNSWindowHandle(void** retOut);
+ HRESULT ObtainNSWindowHandleRetained(void** retOut);
+ HRESULT ObtainNSViewHandle(void** retOut);
+ HRESULT ObtainNSViewHandleRetained(void** retOut);
+ HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut);
+ HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
+ IAvnClipboard* clipboard, IAvnDndResultCallback* cb, void* sourceHandle);
+ HRESULT SetBlurEnabled(bool enable);
+}
+
+[uuid(83e588f3-6981-4e48-9ea0-e1e569f79a91)]
+interface IAvnPopup : IAvnWindowBase
+{
+
+}
+
+[uuid(cab661de-49d6-4ead-b59c-eac9b2b6c28d)]
+interface IAvnWindow : IAvnWindowBase
+{
+ HRESULT SetEnabled(bool enable);
+ HRESULT SetParent(IAvnWindow* parent);
+ HRESULT SetCanResize(bool value);
+ HRESULT SetDecorations(SystemDecorations value);
+ HRESULT SetTitle(void* utf8Title);
+ HRESULT SetTitleBarColor(AvnColor color);
+ HRESULT SetWindowState(AvnWindowState state);
+ HRESULT GetWindowState(AvnWindowState*ret);
+ HRESULT TakeFocusFromChildren();
+ HRESULT SetExtendClientArea(bool enable);
+ HRESULT SetExtendClientAreaHints(AvnExtendClientAreaChromeHints hints);
+ HRESULT GetExtendTitleBarHeight(double*ret);
+ HRESULT SetExtendTitleBarHeight(double value);
+}
+
+[uuid(939b6599-40a8-4710-a4c8-5d72d8f174fb)]
+interface IAvnWindowBaseEvents : IUnknown
+{
+ HRESULT Paint();
+ void Closed();
+ void Activated();
+ void Deactivated();
+ void Resized([const] AvnSize* size);
+ void PositionChanged(AvnPoint position);
+ void RawMouseEvent(AvnRawMouseEventType type,
+ uint timeStamp,
+ AvnInputModifiers modifiers,
+ AvnPoint point,
+ AvnVector delta);
+ bool RawKeyEvent(AvnRawKeyEventType type, uint timeStamp, AvnInputModifiers modifiers, uint key);
+ bool RawTextInputEvent(uint timeStamp, [const] char* text);
+ void ScalingChanged(double scaling);
+ void RunRenderPriorityJobs();
+ void LostFocus();
+ AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position,
+ AvnInputModifiers modifiers, AvnDragDropEffects effects,
+ IAvnClipboard* clipboard, void* dataObjectHandle);
+}
+
+[uuid(1ae178ee-1fcc-447f-b6dd-b7bb727f934c)]
+interface IAvnWindowEvents : IAvnWindowBaseEvents
+{
+ /**
+ * Closing Event
+ * Called when the user presses the OS window close button.
+ * return true to allow the close, return false to prevent close.
+ */
+ bool Closing();
+
+ void WindowStateChanged(AvnWindowState state);
+
+ void GotInputWhenDisabled();
+}
+
+[uuid(e34ae0f8-18b4-48a3-b09d-2e6b19a3cf5e)]
+interface IAvnMacOptions : IUnknown
+{
+ HRESULT SetShowInDock(int show);
+ HRESULT SetApplicationTitle(void* utf8string);
+}
+
+[uuid(04c1b049-1f43-418a-9159-cae627ec1367)]
+interface IAvnActionCallback : IUnknown
+{
+ void Run();
+}
+
+[uuid(6df4d2db-0b80-4f59-ad88-0baa5e21eb14)]
+interface IAvnSignaledCallback : IUnknown
+{
+ void Signaled(int priority, bool priorityContainsMeaningfulValue);
+}
+
+[uuid(97330f88-c22b-4a8e-a130-201520091b01)]
+interface IAvnLoopCancellation : IUnknown
+{
+ void Cancel();
+}
+
+[uuid(fbc06f3d-7860-42df-83fd-53c4b02dd9c3)]
+interface IAvnPlatformThreadingInterface : IUnknown
+{
+ bool GetCurrentThreadIsLoopThread();
+ void SetSignaledCallback(IAvnSignaledCallback* cb);
+ IAvnLoopCancellation* CreateLoopCancellation();
+ HRESULT RunLoop(IAvnLoopCancellation* cancel);
+ // Can't pass int* to sharpgentools for some reason
+ void Signal(int priority);
+ IUnknown* StartTimer(int priority, int ms, IAvnActionCallback* callback);
+}
+
+[uuid(6c621a6e-e4c1-4ae3-9749-83eeeffa09b6)]
+interface IAvnSystemDialogEvents : IUnknown
+{
+ void OnCompleted(int numResults, void* ptrFirstResult);
+}
+
+[uuid(4d7a47db-a944-4061-abe7-62cb6aa0ffd5)]
+interface IAvnSystemDialogs : IUnknown
+{
+ void SelectFolderDialog(IAvnWindow* parentWindowHandle,
+ IAvnSystemDialogEvents* events,
+ [const] char* title,
+ [const] char* initialPath);
+
+ void OpenFileDialog(IAvnWindow* parentWindowHandle,
+ IAvnSystemDialogEvents* events,
+ bool allowMultiple,
+ [const] char* title,
+ [const] char* initialDirectory,
+ [const] char* initialFile,
+ [const] char* filters);
+
+ void SaveFileDialog(IAvnWindow* parentWindowHandle,
+ IAvnSystemDialogEvents* events,
+ [const] char* title,
+ [const] char* initialDirectory,
+ [const] char* initialFile,
+ [const] char* filters);
+}
+
+[uuid(9a52bc7a-d8c7-4230-8d34-704a0b70a933)]
+interface IAvnScreens : IUnknown
+{
+ HRESULT GetScreenCount(int* ret);
+ HRESULT GetScreen(int index, AvnScreen* ret);
+}
+
+[uuid(792b1bd4-76cc-46ea-bfd0-9d642154b1b3)]
+interface IAvnClipboard : IUnknown
+{
+ HRESULT GetText(char* type, IAvnString**ppv);
+ HRESULT SetText(char* type, void* utf8Text);
+ HRESULT ObtainFormats(IAvnStringArray**ppv);
+ HRESULT GetStrings(char* type, IAvnStringArray**ppv);
+ HRESULT SetBytes(char* type, void* utf8Text, int len);
+ HRESULT GetBytes(char* type, IAvnString**ppv);
+
+ HRESULT Clear();
+}
+
+[uuid(3f998545-f027-4d4d-bd2a-1a80926d984e)]
+interface IAvnCursor : IUnknown
+{
+}
+
+[uuid(51ecfb12-c427-4757-a2c9-1596bfce53ef)]
+interface IAvnCursorFactory : IUnknown
+{
+ HRESULT GetCursor(AvnStandardCursorType cursorType, IAvnCursor** retOut);
+}
+
+[uuid(60452465-8616-40af-bc00-042e69828ce7)]
+interface IAvnGlDisplay : IUnknown
+{
+ HRESULT CreateContext(IAvnGlContext* share, IAvnGlContext**ppv);
+ void LegacyClearCurrentContext();
+ HRESULT WrapContext(void* native, IAvnGlContext**ppv);
+ void* GetProcAddress(char* proc);
+}
+
+[uuid(78c5711e-2a98-40d2-bac4-0cc9a49dc4f3)]
+interface IAvnGlContext : IUnknown
+{
+ HRESULT MakeCurrent(IUnknown** ppv);
+ HRESULT LegacyMakeCurrent();
+ int GetSampleCount();
+ int GetStencilSize();
+ void* GetNativeHandle();
+}
+
+[uuid(931062d2-5bc8-4062-8588-83dd8deb99c2)]
+interface IAvnGlSurfaceRenderTarget : IUnknown
+{
+ HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret);
+}
+
+[uuid(e625b406-f04c-484e-946a-4abd2c6015ad)]
+interface IAvnGlSurfaceRenderingSession : IUnknown
+{
+ HRESULT GetPixelSize(AvnPixelSize* ret);
+ HRESULT GetScaling(double* ret);
+}
+
+[uuid(a7724dc1-cf6b-4fa8-9d23-228bf2593edc)]
+interface IAvnMenu : IUnknown
+{
+ HRESULT InsertItem(int index, IAvnMenuItem* item);
+ HRESULT RemoveItem(IAvnMenuItem* item);
+ HRESULT SetTitle(void* utf8String);
+ HRESULT Clear();
+}
+
+[uuid(59e0586d-bd1c-4b85-9882-80d448b0fed9)]
+interface IAvnPredicateCallback : IUnknown
+{
+ bool Evaluate();
+}
+
+[uuid(f890219a-1720-4cd5-9a26-cd95fccbf53c)]
+interface IAvnMenuItem : IUnknown
+{
+ HRESULT SetSubMenu(IAvnMenu* menu);
+ HRESULT SetTitle(void* utf8String);
+ HRESULT SetGesture(void* utf8String, AvnInputModifiers modifiers);
+ HRESULT SetAction(IAvnPredicateCallback* predicate, IAvnActionCallback* callback);
+ HRESULT SetIsChecked(bool isChecked);
+ HRESULT SetToggleType(AvnMenuItemToggleType toggleType);
+ HRESULT SetIcon(void* data, size_t length);
+}
+
+[uuid(0af7df53-7632-42f4-a650-0992c361b477)]
+interface IAvnMenuEvents : IUnknown
+{
+ /**
+ * NeedsUpdate
+ */
+ void NeedsUpdate();
+}
+
+[uuid(5142bb41-66ab-49e7-bb37-cd079c000f27)]
+interface IAvnStringArray : IUnknown
+{
+ uint GetCount();
+ HRESULT Get(uint index, IAvnString**ppv);
+}
+
+[uuid(a13d2382-3b3a-4d1c-9b27-8f34653d3f01)]
+interface IAvnDndResultCallback : IUnknown
+{
+ void OnDragAndDropComplete(AvnDragDropEffects effecct);
+}
+
+[uuid(f07c608e-52e9-422d-836e-c70f6e9b80f5)]
+interface IAvnGCHandleDeallocatorCallback : IUnknown
+{
+ void FreeGCHandle(void* handle);
+}
+
+[uuid(91c7f677-f26b-4ff3-93cc-cf15aa966ffa)]
+interface IAvnNativeControlHost : IUnknown
+{
+ HRESULT CreateDefaultChild(void* parent, void** retOut);
+ IAvnNativeControlHostTopLevelAttachment* CreateAttachment();
+ void DestroyDefaultChild(void* child);
+}
+
+[uuid(14a9e164-1aae-4271-bb78-7b5230999b52)]
+interface IAvnNativeControlHostTopLevelAttachment : IUnknown
+{
+ void* GetParentHandle();
+ HRESULT InitializeWithChildHandle(void* child);
+ HRESULT AttachTo(IAvnNativeControlHost* host);
+ void ShowInBounds(float x, float y, float width, float height);
+ void HideWithSize(float width, float height);
+ void ReleaseChild();
+}
diff --git a/src/tools/MicroComGenerator/Ast.cs b/src/tools/MicroComGenerator/Ast.cs
new file mode 100644
index 0000000000..8613cbba88
--- /dev/null
+++ b/src/tools/MicroComGenerator/Ast.cs
@@ -0,0 +1,102 @@
+using System.Collections.Generic;
+
+namespace MicroComGenerator.Ast
+{
+ public class AstAttributeNode
+ {
+ public string Name { get; set; }
+ public string Value { get; set; }
+
+ public AstAttributeNode(string name, string value)
+ {
+ Name = name;
+ Value = value;
+ }
+
+ public override string ToString() => $"{Name} = {Value}";
+ }
+
+ public class AstEnumNode : List
+ {
+ public List Attributes { get; set; } = new List();
+ public string Name { get; set; }
+ public override string ToString() => Name;
+ }
+
+ public class AstEnumMemberNode
+ {
+ public string Name { get; set; }
+ public string Value { get; set; }
+
+ public AstEnumMemberNode(string name, string value)
+ {
+ Name = name;
+ Value = value;
+ }
+
+ public override string ToString() => $"{Name} = {Value}";
+ }
+
+ public class AstStructNode : List
+ {
+ public List Attributes { get; set; } = new List();
+ public string Name { get; set; }
+ public override string ToString() => Name;
+ }
+
+ public class AstTypeNode
+ {
+ public string Name { get; set; }
+ public int PointerLevel { get; set; }
+
+ public override string ToString() => Name + new string('*', PointerLevel);
+ }
+
+ public class AstStructMemberNode
+ {
+ public string Name { get; set; }
+ public AstTypeNode Type { get; set; }
+
+ public override string ToString() => $"{Type} {Name}";
+ }
+
+ public class AstInterfaceNode : List
+ {
+ public List Attributes { get; set; } = new List();
+ public string Name { get; set; }
+ public string Inherits { get; set; }
+
+ public override string ToString()
+ {
+ if (Inherits == null)
+ return Name;
+ return $"{Name} : {Inherits}";
+ }
+ }
+
+ public class AstInterfaceMemberNode : List
+ {
+ public string Name { get; set; }
+ public AstTypeNode ReturnType { get; set; }
+ public List Attributes { get; set; } = new List();
+
+ public override string ToString() => $"{ReturnType} {Name} ({string.Join(", ", this)})";
+ }
+
+ public class AstInterfaceMemberArgumentNode
+ {
+ public string Name { get; set; }
+ public AstTypeNode Type { get; set; }
+ public List Attributes { get; set; } = new List();
+
+ public override string ToString() => $"{Type} {Name}";
+ }
+
+ public class AstIdlNode
+ {
+ public List Attributes { get; set; } = new List();
+ public List Enums { get; set; } = new List();
+ public List Structs { get; set; } = new List();
+ public List Interfaces { get; set; } = new List();
+ }
+}
diff --git a/src/tools/MicroComGenerator/AstParser.cs b/src/tools/MicroComGenerator/AstParser.cs
new file mode 100644
index 0000000000..46404da3d7
--- /dev/null
+++ b/src/tools/MicroComGenerator/AstParser.cs
@@ -0,0 +1,228 @@
+using System.Collections.Generic;
+using MicroComGenerator.Ast;
+
+namespace MicroComGenerator
+{
+ public class AstParser
+ {
+ public static AstIdlNode Parse(string source)
+ {
+ var parser = new TokenParser(source);
+ var idl = new AstIdlNode { Attributes = ParseGlobalAttributes(ref parser) };
+
+ while (!parser.Eof)
+ {
+ var attrs = ParseLocalAttributes(ref parser);
+
+ if (parser.TryParseKeyword("enum"))
+ idl.Enums.Add(ParseEnum(attrs, ref parser));
+ else if (parser.TryParseKeyword("struct"))
+ idl.Structs.Add(ParseStruct(attrs, ref parser));
+ else if (parser.TryParseKeyword("interface"))
+ idl.Interfaces.Add(ParseInterface(attrs, ref parser));
+ else
+ throw new ParseException("Unexpected character", ref parser);
+ }
+
+ return idl;
+ }
+
+ static List ParseGlobalAttributes(ref TokenParser parser)
+ {
+ var rv = new List();
+ while (!parser.Eof)
+ {
+ parser.SkipWhitespace();
+ if (parser.TryConsume('@'))
+ {
+ var ident = parser.ParseIdentifier("-");
+ var value = parser.ReadToEol().Trim();
+ if (value == "@@")
+ {
+ parser.Advance(1);
+ value = "";
+ while (true)
+ {
+ var l = parser.ReadToEol();
+ if (l == "@@")
+ break;
+ else
+ value = value.Length == 0 ? l : (value + "\n" + l);
+ parser.Advance(1);
+ }
+
+ }
+ rv.Add(new AstAttributeNode(ident, value));
+ }
+ else
+ return rv;
+ }
+
+ return rv;
+ }
+
+ static List ParseLocalAttributes(ref TokenParser parser)
+ {
+ var rv = new List();
+ if (parser.TryConsume("["))
+ {
+ while (!parser.TryConsume("]") && !parser.Eof)
+ {
+ if (parser.TryConsume(','))
+ continue;
+
+ // Get identifier
+ var ident = parser.ParseIdentifier("-");
+
+ // No value, end of attribute list
+ if (parser.TryConsume(']'))
+ {
+ rv.Add(new AstAttributeNode(ident, null));
+ return rv;
+ }
+ // No value, next attribute
+ else if (parser.TryConsume(','))
+ rv.Add(new AstAttributeNode(ident, null));
+ // Has value
+ else if (parser.TryConsume('('))
+ {
+ var value = parser.ReadTo(')');
+ parser.Consume(')');
+ rv.Add(new AstAttributeNode(ident, value));
+ }
+ else
+ throw new ParseException("Unexpected character", ref parser);
+ }
+
+ if (parser.Eof)
+ throw new ParseException("Unexpected EOF", ref parser);
+ }
+
+ return rv;
+ }
+
+ static void EnsureOpenBracket(ref TokenParser parser)
+ {
+ if (!parser.TryConsume('{'))
+ throw new ParseException("{ expected", ref parser);
+ }
+
+ static AstEnumNode ParseEnum(List attrs, ref TokenParser parser)
+ {
+ var name = parser.ParseIdentifier();
+ EnsureOpenBracket(ref parser);
+ var rv = new AstEnumNode { Name = name, Attributes = attrs };
+ while (!parser.TryConsume('}') && !parser.Eof)
+ {
+ if (parser.TryConsume(','))
+ continue;
+
+ var ident = parser.ParseIdentifier();
+
+ // Automatic value
+ if (parser.TryConsume(',') || parser.Peek == '}')
+ {
+ rv.Add(new AstEnumMemberNode(ident, null));
+ continue;
+ }
+
+ if (!parser.TryConsume('='))
+ throw new ParseException("Unexpected character", ref parser);
+
+ var value = parser.ReadToAny(",}").Trim();
+ rv.Add(new AstEnumMemberNode(ident, value));
+
+ if (parser.Eof)
+ throw new ParseException("Unexpected EOF", ref parser);
+ }
+
+
+ return rv;
+ }
+
+ static AstTypeNode ParseType(ref TokenParser parser)
+ {
+ var ident = parser.ParseIdentifier();
+ var t = new AstTypeNode { Name = ident };
+ while (parser.TryConsume('*'))
+ t.PointerLevel++;
+ return t;
+ }
+
+ static AstStructNode ParseStruct(List attrs, ref TokenParser parser)
+ {
+ var name = parser.ParseIdentifier();
+ EnsureOpenBracket(ref parser);
+ var rv = new AstStructNode { Name = name, Attributes = attrs };
+ while (!parser.TryConsume('}') && !parser.Eof)
+ {
+ var t = ParseType(ref parser);
+ bool parsedAtLeastOneMember = false;
+ while (!parser.TryConsume(';'))
+ {
+ // Skip any ,
+ while (parser.TryConsume(',')) { }
+
+ var ident = parser.ParseIdentifier();
+ parsedAtLeastOneMember = true;
+ rv.Add(new AstStructMemberNode { Name = ident, Type = t });
+ }
+
+ if (!parsedAtLeastOneMember)
+ throw new ParseException("Expected at least one enum member with declared type " + t, ref parser);
+ }
+
+ return rv;
+ }
+
+ static AstInterfaceNode ParseInterface(List interfaceAttrs, ref TokenParser parser)
+ {
+ var interfaceName = parser.ParseIdentifier();
+ string inheritsFrom = null;
+ if (parser.TryConsume(":"))
+ inheritsFrom = parser.ParseIdentifier();
+
+ EnsureOpenBracket(ref parser);
+ var rv = new AstInterfaceNode
+ {
+ Name = interfaceName, Attributes = interfaceAttrs, Inherits = inheritsFrom
+ };
+ while (!parser.TryConsume('}') && !parser.Eof)
+ {
+ var memberAttrs = ParseLocalAttributes(ref parser);
+ var returnType = ParseType(ref parser);
+ var name = parser.ParseIdentifier();
+ var member = new AstInterfaceMemberNode
+ {
+ Name = name, ReturnType = returnType, Attributes = memberAttrs
+ };
+ rv.Add(member);
+
+ parser.Consume('(');
+ while (true)
+ {
+ if (parser.TryConsume(')'))
+ break;
+
+ var argumentAttrs = ParseLocalAttributes(ref parser);
+ var type = ParseType(ref parser);
+ var argName = parser.ParseIdentifier();
+ member.Add(new AstInterfaceMemberArgumentNode
+ {
+ Name = argName, Type = type, Attributes = argumentAttrs
+ });
+
+ if (parser.TryConsume(')'))
+ break;
+ if (parser.TryConsume(','))
+ continue;
+ throw new ParseException("Unexpected character", ref parser);
+ }
+
+ parser.Consume(';');
+ }
+
+ return rv;
+ }
+ }
+}
diff --git a/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs b/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs
new file mode 100644
index 0000000000..2fcf1b404b
--- /dev/null
+++ b/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs
@@ -0,0 +1,453 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using MicroComGenerator.Ast;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+
+// ReSharper disable CoVariantArrayConversion
+
+// HERE BE DRAGONS
+
+namespace MicroComGenerator
+{
+ public partial class CSharpGen
+ {
+ abstract class Arg
+ {
+ public string Name;
+ public string NativeType;
+
+ public virtual StatementSyntax CreateFixed(StatementSyntax inner) => inner;
+
+ public virtual void PreMarshal(List body)
+ {
+ }
+
+ public virtual void PreMarshalForReturn(List body) =>
+ throw new InvalidOperationException("Don't know how to use " + NativeType + " as HRESULT-return");
+
+ public virtual ExpressionSyntax Value(bool isHresultReturn) => ParseExpression(Name);
+ public abstract string ManagedType { get; }
+ public virtual string ReturnManagedType => ManagedType;
+
+ public virtual StatementSyntax[] ReturnMarshalResult() => new[] { ParseStatement("return " + Name + ";") };
+
+
+ public virtual void BackPreMarshal(List body)
+ {
+ }
+
+ public virtual ExpressionSyntax BackMarshalValue() => ParseExpression(Name);
+ public virtual ExpressionSyntax BackMarshalReturn(string resultVar) => ParseExpression(resultVar);
+
+ }
+
+ class InterfaceReturnArg : Arg
+ {
+ public string InterfaceType;
+ public override ExpressionSyntax Value(bool isHresultReturn) => ParseExpression("&" + PName);
+ public override string ManagedType => InterfaceType;
+
+ private string PName => "__marshal_" + Name;
+
+ public override void PreMarshalForReturn(List body)
+ {
+ body.Add(ParseStatement("void* " + PName + " = null;"));
+ }
+
+ public override StatementSyntax[] ReturnMarshalResult() => new[]
+ {
+ ParseStatement("return Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" +
+ PName + ", true);")
+ };
+
+ public override ExpressionSyntax BackMarshalValue()
+ {
+ return ParseExpression("INVALID");
+ }
+
+ public override ExpressionSyntax BackMarshalReturn(string resultVar)
+ {
+ return ParseExpression($"Avalonia.MicroCom.MicroComRuntime.GetNativePointer({resultVar}, true)");
+ }
+ }
+
+ class InterfaceArg : Arg
+ {
+ public string InterfaceType;
+
+ public override ExpressionSyntax Value(bool isHresultReturn) =>
+ ParseExpression("Avalonia.MicroCom.MicroComRuntime.GetNativePointer(" + Name + ")");
+
+ public override string ManagedType => InterfaceType;
+
+ public override StatementSyntax[] ReturnMarshalResult() => new[]
+ {
+ ParseStatement("return Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" +
+ Name + ", true);")
+ };
+
+ public override ExpressionSyntax BackMarshalValue()
+ {
+ return ParseExpression("Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" +
+ Name + ", false)");
+ }
+
+ public override ExpressionSyntax BackMarshalReturn(string resultVar)
+ {
+ return ParseExpression($"Avalonia.MicroCom.MicroComRuntime.GetNativePointer({resultVar}, true)");
+ }
+ }
+
+ class BypassArg : Arg
+ {
+ public string Type { get; set; }
+ public int PointerLevel;
+ public override string ManagedType => Type + new string('*', PointerLevel);
+ public override string ReturnManagedType => Type + new string('*', PointerLevel - 1);
+
+ public override ExpressionSyntax Value(bool isHresultReturn)
+ {
+ if (isHresultReturn)
+ return ParseExpression("&" + Name);
+ return base.Value(false);
+ }
+
+ public override void PreMarshalForReturn(List body)
+ {
+ if (PointerLevel == 0)
+ base.PreMarshalForReturn(body);
+ else
+ body.Add(ParseStatement(Type + new string('*', PointerLevel - 1) + " " + Name + "=default;"));
+ }
+ }
+
+ class StringArg : Arg
+ {
+ private string BName => "__bytemarshal_" + Name;
+ private string FName => "__fixedmarshal_" + Name;
+
+ public override void PreMarshal(List body)
+ {
+ body.Add(ParseStatement($"var {BName} = new byte[System.Text.Encoding.UTF8.GetByteCount({Name})+1];"));
+ body.Add(ParseStatement($"System.Text.Encoding.UTF8.GetBytes({Name}, 0, {Name.Length}, {BName}, 0);"));
+ }
+
+ public override StatementSyntax CreateFixed(StatementSyntax inner)
+ {
+ return FixedStatement(DeclareVar("byte*", FName, ParseExpression(BName)), inner);
+ }
+
+ public override ExpressionSyntax Value(bool isHresultReturn) => ParseExpression(FName);
+ public override string ManagedType => "string";
+ public override ExpressionSyntax BackMarshalValue()
+ {
+ return ParseExpression(
+ $"({Name} == null ? null : System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new IntPtr(" + Name + ")))");
+ }
+ }
+
+ string ConvertNativeType(string type)
+ {
+ if (type == "size_t")
+ return "System.IntPtr";
+ if (type == "HRESULT")
+ return "int";
+ return type;
+ }
+
+ Arg ConvertArg(string name, AstTypeNode type)
+ {
+ type = new AstTypeNode { Name = ConvertNativeType(type.Name), PointerLevel = type.PointerLevel };
+
+ if (type.PointerLevel == 2)
+ {
+ if (type.Name.StartsWith("I"))
+ return new InterfaceReturnArg { Name = name, InterfaceType = type.Name, NativeType = "void**" };
+ }
+ else if (type.PointerLevel == 1)
+ {
+ if (type.Name.StartsWith("I"))
+ return new InterfaceArg { Name = name, InterfaceType = type.Name, NativeType = "void*" };
+ if (type.Name == "char")
+ return new StringArg { Name = name, NativeType = "byte*" };
+ }
+
+ return new BypassArg
+ {
+ Name = name, Type = type.Name, PointerLevel = type.PointerLevel, NativeType = type.ToString()
+ };
+ }
+
+
+ void GenerateInterfaceMember(AstInterfaceMemberNode member, ref InterfaceDeclarationSyntax iface,
+ ref ClassDeclarationSyntax proxy, ref ClassDeclarationSyntax vtbl,
+ List vtblCtor, int num)
+ {
+ // Prepare method information
+ var args = member.Select(a => ConvertArg(a.Name, a.Type)).ToList();
+ var returnArg = ConvertArg("__result", member.ReturnType);
+ bool isHresult = member.ReturnType.Name == "HRESULT";
+ bool isHresultLastArgumentReturn = isHresult
+ && args.Count > 0
+ && (args.Last().Name == "ppv" || args.Last().Name == "retOut" || args.Last().Name == "ret")
+ && ((member.Last().Type.PointerLevel > 0
+ && !member.Last().Type.Name
+ .StartsWith("I"))
+ || member.Last().Type.PointerLevel == 2);
+
+ bool isVoidReturn = member.ReturnType.Name == "void" && member.ReturnType.PointerLevel == 0;
+
+
+ // Generate method signature
+ MethodDeclarationSyntax GenerateManagedSig(string returnType, string name,
+ IEnumerable<(string n, string t)> args)
+ => MethodDeclaration(ParseTypeName(returnType), name).WithParameterList(
+ ParameterList(
+ SeparatedList(args.Select(x => Parameter(Identifier(x.n)).WithType(ParseTypeName(x.t))))));
+
+ var managedSig =
+ isHresult ?
+ GenerateManagedSig(isHresultLastArgumentReturn ? args.Last().ReturnManagedType : "void",
+ member.Name,
+ (isHresultLastArgumentReturn ? args.SkipLast(1) : args).Select(a => (a.Name, a.ManagedType))) :
+ GenerateManagedSig(returnArg.ManagedType, member.Name, args.Select(a => (a.Name, a.ManagedType)));
+
+ iface = iface.AddMembers(managedSig.WithSemicolonToken(Semicolon()));
+
+ // Prepare args for marshaling
+ var preMarshal = new List();
+ if (!isVoidReturn)
+ preMarshal.Add(ParseStatement(returnArg.NativeType + " __result;"));
+
+ for (var idx = 0; idx < args.Count; idx++)
+ {
+ if (isHresultLastArgumentReturn && idx == args.Count - 1)
+ args[idx].PreMarshalForReturn(preMarshal);
+ else
+ args[idx].PreMarshal(preMarshal);
+ }
+
+ // Generate call expression
+ ExpressionSyntax callExpr = InvocationExpression(_localInterop.GetCaller(returnArg.NativeType,
+ args.Select(x => x.NativeType).ToList()))
+ .AddArgumentListArguments(Argument(ParseExpression("PPV")))
+ .AddArgumentListArguments(args
+ .Select((a, i) => Argument(a.Value(isHresultLastArgumentReturn && i == args.Count - 1))).ToArray())
+ .AddArgumentListArguments(Argument(ParseExpression("PPV[base.VTableSize + " + num + "]")));
+
+ // Save call result if needed
+ if (!isVoidReturn)
+ callExpr = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, ParseExpression("__result"),
+ callExpr);
+
+
+ // Wrap call into fixed() blocks
+ StatementSyntax callStatement = ExpressionStatement(callExpr);
+ foreach (var arg in args)
+ callStatement = arg.CreateFixed(callStatement);
+
+ // Build proxy body
+ var proxyBody = Block()
+ .AddStatements(preMarshal.ToArray())
+ .AddStatements(callStatement);
+
+ // Process return value
+ if (!isVoidReturn)
+ {
+ if (isHresult)
+ {
+ proxyBody = proxyBody.AddStatements(
+ ParseStatement(
+ $"if(__result != 0) throw new System.Runtime.InteropServices.COMException(\"{member.Name} failed\", __result);"));
+
+ if (isHresultLastArgumentReturn)
+ proxyBody = proxyBody.AddStatements(args.Last().ReturnMarshalResult());
+ }
+ else
+ proxyBody = proxyBody.AddStatements(returnArg.ReturnMarshalResult());
+ }
+
+ // Add the proxy method
+ proxy = proxy.AddMembers(managedSig.AddModifiers(SyntaxKind.PublicKeyword)
+ .WithBody(proxyBody));
+
+
+ // Generate VTable method
+
+ var shadowDelegate = DelegateDeclaration(ParseTypeName(returnArg.NativeType), member.Name+"Delegate")
+ .AddParameterListParameters(Parameter(Identifier("@this")).WithType(ParseTypeName("IntPtr")))
+ .AddParameterListParameters(args.Select(x =>
+ Parameter(Identifier(x.Name)).WithType(ParseTypeName(x.NativeType))).ToArray());
+
+ var shadowMethod = MethodDeclaration(shadowDelegate.ReturnType, member.Name)
+ .WithParameterList(shadowDelegate.ParameterList)
+ .AddModifiers(Token(SyntaxKind.StaticKeyword));
+
+ var backPreMarshal = new List();
+ foreach (var arg in args)
+ arg.BackPreMarshal(backPreMarshal);
+
+ backPreMarshal.Add(
+ ParseStatement($"__target = ({iface.Identifier.Text})Avalonia.MicroCom.MicroComRuntime.GetObjectFromCcw(@this);"));
+
+ var isBackVoidReturn = isVoidReturn || (isHresult && !isHresultLastArgumentReturn);
+
+ StatementSyntax backCallStatement;
+
+ var backCallExpr =
+ IsPropertyRewriteCandidate(managedSig) ?
+ ParseExpression("__target." + member.Name.Substring(3)) :
+ InvocationExpression(ParseExpression("__target." + member.Name))
+ .WithArgumentList(ArgumentList(SeparatedList(
+ (isHresultLastArgumentReturn ? args.SkipLast(1) : args)
+ .Select(a =>
+ Argument(a.BackMarshalValue())))));
+
+ if (isBackVoidReturn)
+ backCallStatement = ExpressionStatement(backCallExpr);
+ else
+ {
+ backCallStatement = LocalDeclarationStatement(DeclareVar("var", "__result", backCallExpr));
+ if (isHresultLastArgumentReturn)
+ {
+ backCallStatement = Block(backCallStatement,
+ ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
+ ParseExpression("*" + args.Last().Name),
+ args.Last().BackMarshalReturn("__result")
+ )));
+
+ }
+ else
+ backCallStatement = Block(backCallStatement,
+ ReturnStatement(returnArg.BackMarshalReturn("__result")));
+ }
+
+ BlockSyntax backBodyBlock = Block().AddStatements(backPreMarshal.ToArray()).AddStatements(backCallStatement);
+
+
+ backBodyBlock = Block(
+ TryStatement(
+ SingletonList(CatchClause(
+ CatchDeclaration(ParseTypeName("System.Exception"), Identifier("__exception__")), null,
+ Block(
+ ParseStatement(
+ "Avalonia.MicroCom.MicroComRuntime.UnhandledException(__target, __exception__);"),
+ isHresult ? ParseStatement("return unchecked((int)0x80004005u);")
+ : isVoidReturn ? EmptyStatement() : ParseStatement("return default;")
+ ))))
+ .WithBlock(Block(backBodyBlock))
+ );
+ if (isHresult)
+ backBodyBlock = backBodyBlock.AddStatements(ParseStatement("return 0;"));
+
+
+ backBodyBlock = Block()
+ .AddStatements(ParseStatement($"{iface.Identifier.Text} __target = null;"))
+ .AddStatements(backBodyBlock.Statements.ToArray());
+
+ shadowMethod = shadowMethod.WithBody(backBodyBlock);
+
+ vtbl = vtbl.AddMembers(shadowDelegate).AddMembers(shadowMethod);
+ vtblCtor.Add(ParseStatement("base.AddMethod((" + shadowDelegate.Identifier.Text + ")" +
+ shadowMethod.Identifier.Text + ");"));
+
+
+
+
+ }
+
+ class LocalInteropHelper
+ {
+ public ClassDeclarationSyntax Class { get; private set; } = ClassDeclaration("LocalInterop");
+ private HashSet _existing = new HashSet();
+
+ public ExpressionSyntax GetCaller(string returnType, List args)
+ {
+ var name = "CalliStdCall" + returnType.Replace("*", "_ptr");
+ var signature = returnType + "::" + name + "::" + string.Join("::", args);
+ if (_existing.Add(signature))
+ {
+ Class = Class.AddMembers(MethodDeclaration(ParseTypeName(returnType), name)
+ .AddModifiers(SyntaxKind.StaticKeyword, SyntaxKind.UnsafeKeyword, SyntaxKind.PublicKeyword)
+ .AddParameterListParameters(Parameter(Identifier("thisObj")).WithType(ParseTypeName("void*")))
+ .AddParameterListParameters(args.Select((x, i) =>
+ Parameter(Identifier("arg" + i)).WithType(ParseTypeName(x))).ToArray())
+ .AddParameterListParameters(Parameter(Identifier("methodPtr")).WithType(ParseTypeName("void*")))
+ .WithBody(Block(ExpressionStatement(ThrowExpression(ParseExpression("null"))))));
+ }
+
+ return ParseExpression("LocalInterop." + name);
+ }
+ }
+
+
+ void GenerateInterface(ref NamespaceDeclarationSyntax ns, ref NamespaceDeclarationSyntax implNs,
+ AstInterfaceNode iface)
+ {
+ var guidString = iface.Attributes.FirstOrDefault(x => x.Name == "uuid")?.Value;
+ if (guidString == null)
+ throw new CodeGenException("Missing GUID for " + iface.Name);
+ var inheritsUnknown = iface.Inherits == null || iface.Inherits == "IUnknown";
+
+ var ifaceDec = InterfaceDeclaration(iface.Name)
+ .WithBaseType(inheritsUnknown ? "Avalonia.MicroCom.IUnknown" : iface.Inherits)
+ .AddModifiers(Token(_visibility), Token(SyntaxKind.UnsafeKeyword));
+
+ var proxyClassName = "__MicroCom" + iface.Name + "Proxy";
+ var proxy = ClassDeclaration(proxyClassName)
+ .AddModifiers(Token(SyntaxKind.UnsafeKeyword), Token(_visibility))
+ .WithBaseType(inheritsUnknown ?
+ "Avalonia.MicroCom.MicroComProxyBase" :
+ ("__MicroCom" + iface.Inherits + "Proxy"));
+
+
+ // Generate vtable
+ var vtbl = ClassDeclaration("__MicroCom" + iface.Name + "VTable")
+ .AddModifiers(Token(SyntaxKind.UnsafeKeyword));
+
+ vtbl = vtbl.WithBaseType(inheritsUnknown ?
+ "Avalonia.MicroCom.MicroComVtblBase" :
+ "__MicroCom" + iface.Inherits + "VTable");
+
+ var vtblCtor = new List();
+ for (var idx = 0; idx < iface.Count; idx++)
+ GenerateInterfaceMember(iface[idx], ref ifaceDec, ref proxy, ref vtbl, vtblCtor, idx);
+
+ vtbl = vtbl.AddMembers(
+ ConstructorDeclaration(vtbl.Identifier.Text)
+ .AddModifiers(Token(SyntaxKind.PublicKeyword))
+ .WithBody(Block(vtblCtor))
+ )
+ .AddMembers(MethodDeclaration(ParseTypeName("void"), "__MicroComModuleInit")
+ .AddModifiers(Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.InternalKeyword))
+ .WithExpressionBody(ArrowExpressionClause(
+ ParseExpression("Avalonia.MicroCom.MicroComRuntime.RegisterVTable(typeof(" +
+ proxyClassName + "), new " + vtbl.Identifier.Text + "().CreateVTable())")))
+ .WithSemicolonToken(Semicolon()));
+
+
+ // Finalize proxy code
+ proxy = proxy.AddMembers(
+ MethodDeclaration(ParseTypeName("void"), "__MicroComModuleInit")
+ .AddModifiers(Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.InternalKeyword))
+ .WithBody(Block(
+ ParseStatement("Avalonia.MicroCom.MicroComRuntime.Register(typeof(" +
+ proxyClassName + "), new Guid(\"" + guidString + "\"), (p, owns) => new " +
+ proxyClassName + "(p, owns));")
+ )))
+ .AddMembers(ParseMemberDeclaration("public " + proxyClassName +
+ "(IntPtr nativePointer, bool ownsHandle) : base(nativePointer, ownsHandle) {}"))
+ .AddMembers(ParseMemberDeclaration("protected override int VTableSize => base.VTableSize + " +
+ iface.Count + ";"));
+
+ ns = ns.AddMembers(RewriteMethodsToProperties(ifaceDec));
+ implNs = implNs.AddMembers(RewriteMethodsToProperties(proxy), RewriteMethodsToProperties(vtbl));
+ }
+ }
+}
diff --git a/src/tools/MicroComGenerator/CSharpGen.Utils.cs b/src/tools/MicroComGenerator/CSharpGen.Utils.cs
new file mode 100644
index 0000000000..ed2bcdd6d8
--- /dev/null
+++ b/src/tools/MicroComGenerator/CSharpGen.Utils.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Formatting;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+namespace MicroComGenerator
+{
+ public partial class CSharpGen
+ {
+
+ CompilationUnitSyntax Unit()
+ => CompilationUnit().WithUsings(List(new[]
+ {
+ "System", "System.Text", "System.Collections", "System.Collections.Generic", "Avalonia.MicroCom"
+ }
+ .Concat(_extraUsings).Select(u => UsingDirective(IdentifierName(u)))));
+
+ string Format(CompilationUnitSyntax unit)
+ {
+ var cw = new AdhocWorkspace();
+ return Microsoft.CodeAnalysis.Formatting.Formatter.Format(unit.NormalizeWhitespace(), cw, cw.Options
+ .WithChangedOption(CSharpFormattingOptions.NewLineForMembersInObjectInit, true)
+ .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInObjectCollectionArrayInitializers, true)
+ .WithChangedOption(CSharpFormattingOptions.NewLineForMembersInAnonymousTypes, true)
+ .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInMethods, true)
+
+ ).ToFullString();
+ }
+
+
+ SyntaxToken Semicolon() => Token(SyntaxKind.SemicolonToken);
+
+ static VariableDeclarationSyntax DeclareVar(string type, string name,
+ ExpressionSyntax? initializer = null)
+ => VariableDeclaration(ParseTypeName(type),
+ SingletonSeparatedList(VariableDeclarator(name)
+ .WithInitializer(initializer == null ? null : EqualsValueClause(initializer))));
+
+ FieldDeclarationSyntax DeclareConstant(string type, string name, LiteralExpressionSyntax value)
+ => FieldDeclaration(
+ VariableDeclaration(ParseTypeName(type),
+ SingletonSeparatedList(
+ VariableDeclarator(name).WithInitializer(EqualsValueClause(value))
+ ))
+ ).WithSemicolonToken(Semicolon())
+ .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.ConstKeyword)));
+
+ FieldDeclarationSyntax DeclareField(string type, string name, params SyntaxKind[] modifiers) =>
+ DeclareField(type, name, null, modifiers);
+
+ FieldDeclarationSyntax DeclareField(string type, string name, EqualsValueClauseSyntax initializer,
+ params SyntaxKind[] modifiers) =>
+ FieldDeclaration(
+ VariableDeclaration(ParseTypeName(type),
+ SingletonSeparatedList(
+ VariableDeclarator(name).WithInitializer(initializer))))
+ .WithSemicolonToken(Semicolon())
+ .WithModifiers(TokenList(modifiers.Select(x => Token(x))));
+
+ bool IsPropertyRewriteCandidate(MethodDeclarationSyntax method)
+ {
+ if(method.Identifier.Text.Contains("GetScaling"))
+ Console.WriteLine();
+ return (method.Identifier.Text.StartsWith("Get") &&
+ method.ParameterList.Parameters.Count == 0);
+ }
+
+ TypeDeclarationSyntax RewriteMethodsToProperties(T decl) where T : TypeDeclarationSyntax
+ {
+ var replace = new Dictionary();
+ foreach (var method in decl.Members.OfType().ToList())
+ {
+ if (IsPropertyRewriteCandidate(method))
+ {
+ var getter = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration);
+ if (method.Body != null)
+ getter = getter.WithBody(method.Body);
+ else
+ getter = getter.WithSemicolonToken(Semicolon());
+
+ replace[method] = PropertyDeclaration(method.ReturnType,
+ method.Identifier.Text.Substring(3))
+ .WithModifiers(method.Modifiers).AddAccessorListAccessors(getter);
+
+ }
+ }
+
+ return decl.ReplaceNodes(replace.Keys, (m, m2) => replace[m]);
+ }
+
+ }
+}
diff --git a/src/tools/MicroComGenerator/CSharpGen.cs b/src/tools/MicroComGenerator/CSharpGen.cs
new file mode 100644
index 0000000000..49e4f7a09e
--- /dev/null
+++ b/src/tools/MicroComGenerator/CSharpGen.cs
@@ -0,0 +1,89 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using MicroComGenerator.Ast;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+// ReSharper disable CoVariantArrayConversion
+
+namespace MicroComGenerator
+{
+ public partial class CSharpGen
+ {
+ private readonly AstIdlNode _idl;
+ private List _extraUsings;
+ private string _namespace;
+ private SyntaxKind _visibility;
+ private LocalInteropHelper _localInterop = new LocalInteropHelper();
+
+ public CSharpGen(AstIdlNode idl)
+ {
+ _idl = idl;
+ _extraUsings = _idl.Attributes.Where(u => u.Name == "clr-using").Select(u => u.Value).ToList();
+ _namespace = _idl.Attributes.FirstOrDefault(x => x.Name == "clr-namespace")?.Value;
+ if (_namespace == null)
+ throw new CodeGenException("Missing clr-namespace attribute");
+ var visibilityString = _idl.Attributes.FirstOrDefault(x => x.Name == "clr-access")?.Value;
+ if (visibilityString == null)
+ throw new CodeGenException("Missing clr-visibility attribute");
+ if (visibilityString == "internal")
+ _visibility = SyntaxKind.InternalKeyword;
+ else if (visibilityString == "public")
+ _visibility = SyntaxKind.PublicKeyword;
+ else
+ throw new CodeGenException("Invalid clr-access attribute");
+ }
+
+ public string Generate()
+ {
+ var ns = NamespaceDeclaration(ParseName(_namespace));
+ var implNs = NamespaceDeclaration(ParseName(_namespace + ".Impl"));
+ ns = GenerateEnums(ns);
+ ns = GenerateStructs(ns);
+ foreach (var i in _idl.Interfaces)
+ GenerateInterface(ref ns, ref implNs, i);
+
+ implNs = implNs.AddMembers(_localInterop.Class);
+ var unit = Unit().AddMembers(ns, implNs);
+
+ return Format(unit);
+ }
+
+ NamespaceDeclarationSyntax GenerateEnums(NamespaceDeclarationSyntax ns)
+ {
+ return ns.AddMembers(_idl.Enums.Select(e =>
+ EnumDeclaration(e.Name)
+ .WithModifiers(TokenList(Token(_visibility)))
+ .WithMembers(SeparatedList(e.Select(m =>
+ {
+ var member = EnumMemberDeclaration(m.Name);
+ if (m.Value != null)
+ return member.WithEqualsValue(EqualsValueClause(ParseExpression(m.Value)));
+ return member;
+ })))
+ ).ToArray());
+ }
+
+ NamespaceDeclarationSyntax GenerateStructs(NamespaceDeclarationSyntax ns)
+ {
+ return ns.AddMembers(_idl.Structs.Select(e =>
+ StructDeclaration(e.Name)
+ .WithModifiers(TokenList(Token(_visibility)))
+ .AddModifiers(Token(SyntaxKind.UnsafeKeyword))
+ .AddAttributeLists(AttributeList(SingletonSeparatedList(
+ Attribute(ParseName("System.Runtime.InteropServices.StructLayout"),
+ AttributeArgumentList(SingletonSeparatedList(
+ AttributeArgument(
+ ParseExpression("System.Runtime.InteropServices.LayoutKind.Sequential"))))
+ ))))
+ .WithMembers(new SyntaxList(SeparatedList(e.Select(m =>
+ DeclareField(m.Type.ToString(), m.Name, SyntaxKind.PublicKeyword)))))
+ ).ToArray());
+ }
+
+
+
+ }
+}
diff --git a/src/tools/MicroComGenerator/CppGen.cs b/src/tools/MicroComGenerator/CppGen.cs
new file mode 100644
index 0000000000..133902f764
--- /dev/null
+++ b/src/tools/MicroComGenerator/CppGen.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Linq;
+using System.Text;
+using MicroComGenerator.Ast;
+
+namespace MicroComGenerator
+{
+ public class CppGen
+ {
+ static string ConvertType(AstTypeNode type)
+ {
+ var name = type.Name;
+ if (name == "byte")
+ name = "unsigned char";
+ else if(name == "uint")
+ name = "unsigned int";
+ return name + new string('*', type.PointerLevel);
+ }
+
+ public static string GenerateCpp(AstIdlNode idl)
+ {
+ var sb = new StringBuilder();
+ var preamble = idl.Attributes.FirstOrDefault(x => x.Name == "cpp-preamble")?.Value;
+ if (preamble != null)
+ sb.AppendLine(preamble);
+
+ foreach (var s in idl.Structs)
+ sb.AppendLine("struct " + s.Name + ";");
+
+ foreach (var s in idl.Interfaces)
+ sb.AppendLine("struct " + s.Name + ";");
+
+ foreach (var en in idl.Enums)
+ {
+ sb.Append("enum ");
+ if (en.Attributes.Any(a => a.Name == "class-enum"))
+ sb.Append("class ");
+ sb.Append(en.Name).Append(" ");
+ sb.AppendLine(en.Name).AppendLine("{");
+
+ foreach (var m in en)
+ {
+ sb.Append(" ").Append(m.Name);
+ if (m.Value != null)
+ sb.Append(" = ").Append(m.Value);
+ sb.AppendLine(",");
+ }
+
+ sb.AppendLine("};");
+ }
+
+ foreach (var s in idl.Structs)
+ {
+ sb.Append("struct ").AppendLine(s.Name).AppendLine("{");
+ foreach (var m in s)
+ sb.Append(" ").Append(ConvertType(m.Type)).Append(" ").Append(m.Name).AppendLine(";");
+
+ sb.AppendLine("};");
+ }
+
+ foreach (var i in idl.Interfaces)
+ {
+ var guidString = i.Attributes.FirstOrDefault(x => x.Name == "uuid")?.Value;
+ if (guidString == null)
+ throw new CodeGenException("Missing uuid for " + i.Name);
+
+ var guid = Guid.Parse(guidString).ToString().Replace("-", "");
+
+
+ sb.Append("COMINTERFACE(").Append(i.Name).Append(", ")
+ .Append(guid.Substring(0, 8)).Append(", ")
+ .Append(guid.Substring(8, 4)).Append(", ")
+ .Append(guid.Substring(12, 4));
+ for (var c = 0; c < 8; c++)
+ {
+ sb.Append(", ").Append(guid.Substring(16 + c * 2, 2));
+ }
+
+ sb.Append(") : ")
+ .AppendLine(i.Inherits ?? "IUnknown")
+ .AppendLine("{");
+
+ foreach (var m in i)
+ {
+ sb.Append(" ").Append(ConvertType(m.ReturnType)).Append(" ").Append(m.Name).AppendLine(" (");
+ for (var c = 0; c < m.Count; c++)
+ {
+ var arg = m[c];
+ sb.Append(" ");
+ if (arg.Attributes.Any(a => a.Name == "const"))
+ sb.Append("const ");
+ sb.Append(ConvertType(arg.Type))
+ .Append(" ")
+ .Append(arg.Name);
+ if (c != m.Count - 1)
+ sb.Append(", ");
+ sb.AppendLine();
+ sb.AppendLine(" );");
+ }
+ }
+
+ sb.AppendLine("}");
+ }
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/tools/MicroComGenerator/Extensions.cs b/src/tools/MicroComGenerator/Extensions.cs
new file mode 100644
index 0000000000..4942442d1b
--- /dev/null
+++ b/src/tools/MicroComGenerator/Extensions.cs
@@ -0,0 +1,90 @@
+
+using System;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+namespace MicroComGenerator
+{
+ public static class Extensions
+ {
+ public static ClassDeclarationSyntax AddModifiers(this ClassDeclarationSyntax cl, params SyntaxKind[] modifiers)
+ {
+ if (modifiers == null)
+ return cl;
+ return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
+ }
+
+ public static MethodDeclarationSyntax AddModifiers(this MethodDeclarationSyntax cl, params SyntaxKind[] modifiers)
+ {
+ if (modifiers == null)
+ return cl;
+ return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
+ }
+
+ public static PropertyDeclarationSyntax AddModifiers(this PropertyDeclarationSyntax cl, params SyntaxKind[] modifiers)
+ {
+ if (modifiers == null)
+ return cl;
+ return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
+ }
+
+ public static ConstructorDeclarationSyntax AddModifiers(this ConstructorDeclarationSyntax cl, params SyntaxKind[] modifiers)
+ {
+ if (modifiers == null)
+ return cl;
+ return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
+ }
+
+ public static AccessorDeclarationSyntax AddModifiers(this AccessorDeclarationSyntax cl, params SyntaxKind[] modifiers)
+ {
+ if (modifiers == null)
+ return cl;
+ return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
+ }
+
+ public static string WithLowerFirst(this string s)
+ {
+ if (string.IsNullOrEmpty(s))
+ return s;
+ return char.ToLowerInvariant(s[0]) + s.Substring(1);
+ }
+
+ public static ExpressionSyntax MemberAccess(params string[] identifiers)
+ {
+ if (identifiers == null || identifiers.Length == 0)
+ throw new ArgumentException();
+ var expr = (ExpressionSyntax)IdentifierName(identifiers[0]);
+ for (var c = 1; c < identifiers.Length; c++)
+ expr = MemberAccess(expr, identifiers[c]);
+ return expr;
+ }
+
+ public static ExpressionSyntax MemberAccess(ExpressionSyntax expr, params string[] identifiers)
+ {
+ foreach (var i in identifiers)
+ expr = MemberAccess(expr, i);
+ return expr;
+ }
+
+ public static MemberAccessExpressionSyntax MemberAccess(ExpressionSyntax expr, string identifier) =>
+ MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expr, IdentifierName(identifier));
+
+ public static ClassDeclarationSyntax WithBaseType(this ClassDeclarationSyntax cl, string bt)
+ {
+ return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt)));
+ }
+
+ public static InterfaceDeclarationSyntax WithBaseType(this InterfaceDeclarationSyntax cl, string bt)
+ {
+ return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt)));
+ }
+
+ public static string StripPrefix(this string s, string prefix) => string.IsNullOrEmpty(s)
+ ? s
+ : s.StartsWith(prefix)
+ ? s.Substring(prefix.Length)
+ : s;
+ }
+}
diff --git a/src/tools/MicroComGenerator/MicroComGenerator.csproj b/src/tools/MicroComGenerator/MicroComGenerator.csproj
new file mode 100644
index 0000000000..193bb9a100
--- /dev/null
+++ b/src/tools/MicroComGenerator/MicroComGenerator.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
diff --git a/src/tools/MicroComGenerator/ParseException.cs b/src/tools/MicroComGenerator/ParseException.cs
new file mode 100644
index 0000000000..dfa8dcfe54
--- /dev/null
+++ b/src/tools/MicroComGenerator/ParseException.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace MicroComGenerator
+{
+ class ParseException : Exception
+ {
+ public int Line { get; }
+ public int Position { get; }
+
+ public ParseException(string message, int line, int position) : base(message)
+ {
+ Line = line;
+ Position = position;
+ }
+
+ public ParseException(string message, ref TokenParser parser) : this(message, parser.Line, parser.Position)
+ {
+ }
+ }
+
+ class CodeGenException : Exception
+ {
+ public CodeGenException(string message) : base(message)
+ {
+ }
+ }
+}
diff --git a/src/tools/MicroComGenerator/Program.cs b/src/tools/MicroComGenerator/Program.cs
new file mode 100644
index 0000000000..578ba1465d
--- /dev/null
+++ b/src/tools/MicroComGenerator/Program.cs
@@ -0,0 +1,44 @@
+using System;
+using System.IO;
+using System.Linq;
+using CommandLine;
+
+namespace MicroComGenerator
+{
+ class Program
+ {
+ public class Options
+ {
+ [Option('i', "input", Required = true, HelpText = "Input IDL file")]
+ public string Input { get; set; }
+
+ [Option("cpp", Required = false, HelpText = "C++ output file")]
+ public string CppOutput { get; set; }
+
+ [Option("cs", Required = false, HelpText = "C# output file")]
+ public string CSharpOutput { get; set; }
+
+ }
+
+ static int Main(string[] args)
+ {
+ var p = Parser.Default.ParseArguments(args);
+ if (p is NotParsed)
+ {
+ return 1;
+ }
+
+ var opts = ((Parsed)p).Value;
+
+ var text = File.ReadAllText(opts.Input);
+ var ast = AstParser.Parse(text);
+
+ if (opts.CppOutput != null)
+ File.WriteAllText(opts.CppOutput, CppGen.GenerateCpp(ast));
+ if (opts.CSharpOutput != null)
+ File.WriteAllText(opts.CSharpOutput, new CSharpGen(ast).Generate());
+
+ return 0;
+ }
+ }
+}
diff --git a/src/tools/MicroComGenerator/TokenParser.cs b/src/tools/MicroComGenerator/TokenParser.cs
new file mode 100644
index 0000000000..ea8850b8e4
--- /dev/null
+++ b/src/tools/MicroComGenerator/TokenParser.cs
@@ -0,0 +1,417 @@
+using System;
+using System.Globalization;
+using System.IO;
+
+namespace MicroComGenerator
+{
+ internal ref struct TokenParser
+ {
+ private ReadOnlySpan _s;
+ public int Position { get; private set; }
+ public int Line { get; private set; }
+ public TokenParser(ReadOnlySpan s)
+ {
+ _s = s;
+ Position = 0;
+ Line = 0;
+ }
+
+ public void SkipWhitespace()
+ {
+ while (true)
+ {
+ if(_s.Length == 0)
+ return;
+ if (char.IsWhiteSpace(_s[0]))
+ Advance(1);
+ else if (_s[0] == '/' && _s.Length>1)
+ {
+ if (_s[1] == '/')
+ SkipOneLineComment();
+ else if (_s[1] == '*')
+ SkipMultiLineComment();
+ else
+ return;
+ }
+ else
+ return;
+ }
+ }
+
+ void SkipOneLineComment()
+ {
+ while (true)
+ {
+ if (_s.Length > 0 && _s[0] != '\n' && _s[0] != '\r')
+ Advance(1);
+ else
+ return;
+ }
+ }
+
+ void SkipMultiLineComment()
+ {
+ var l = Line;
+ var p = Position;
+ while (true)
+ {
+ if (_s.Length == 0)
+ throw new ParseException("No matched */ found for /*", l, p);
+
+ if (_s[0] == '*' && _s.Length > 1 && _s[1] == '/')
+ {
+ Advance(2);
+ return;
+ }
+
+ Advance(1);
+ }
+ }
+
+ static bool IsAlphaNumeric(char ch) => (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') ||
+ (ch >= 'A' && ch <= 'Z');
+
+ public void Consume(char c)
+ {
+ if (!TryConsume(c))
+ throw new ParseException("Expected " + c, Line, Position);
+ }
+ public bool TryConsume(char c)
+ {
+ SkipWhitespace();
+ if (_s.Length == 0 || _s[0] != c)
+ return false;
+
+ Advance(1);
+ return true;
+ }
+
+ public bool TryConsume(string s)
+ {
+ SkipWhitespace();
+ if (_s.Length < s.Length)
+ return false;
+ for (var c = 0; c < s.Length; c++)
+ {
+ if (_s[c] != s[c])
+ return false;
+ }
+
+ Advance(s.Length);
+ return true;
+ }
+
+ public bool TryConsumeAny(ReadOnlySpan chars, out char token)
+ {
+ SkipWhitespace();
+ token = default;
+ if (_s.Length == 0)
+ return false;
+
+ foreach (var c in chars)
+ {
+ if (c == _s[0])
+ {
+ token = c;
+ Advance(1);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ public bool TryParseKeyword(string keyword)
+ {
+ SkipWhitespace();
+ if (keyword.Length > _s.Length)
+ return false;
+ for(var c=0; c keyword.Length && IsAlphaNumeric(_s[keyword.Length]))
+ return false;
+
+ Advance(keyword.Length);
+ return true;
+ }
+
+ public bool TryParseKeywordLowerCase(string keywordInLowerCase)
+ {
+ SkipWhitespace();
+ if (keywordInLowerCase.Length > _s.Length)
+ return false;
+ for(var c=0; c keywordInLowerCase.Length && IsAlphaNumeric(_s[keywordInLowerCase.Length]))
+ return false;
+
+ Advance(keywordInLowerCase.Length);
+ return true;
+ }
+
+ public void Advance(int c)
+ {
+ while (c > 0)
+ {
+ if (_s[0] == '\n')
+ {
+ Line++;
+ Position = 0;
+ }
+ else
+ Position++;
+
+ _s = _s.Slice(1);
+ c--;
+ }
+ }
+
+ public int Length => _s.Length;
+ public bool Eof
+ {
+ get
+ {
+ SkipWhitespace();
+ return Length == 0;
+ }
+ }
+
+ public char Peek
+ {
+ get
+ {
+ if (_s.Length == 0)
+ throw new ParseException("Unexpected EOF", Line, Position);
+ return _s[0];
+ }
+ }
+
+ public string ParseIdentifier(ReadOnlySpan extraValidChars)
+ {
+ if (!TryParseIdentifier(extraValidChars, out var ident))
+ throw new ParseException("Identifier expected", Line, Position);
+ return ident.ToString();
+ }
+
+ public string ParseIdentifier()
+ {
+ if (!TryParseIdentifier(out var ident))
+ throw new ParseException("Identifier expected", Line, Position);
+ return ident.ToString();
+ }
+
+ public bool TryParseIdentifier(ReadOnlySpan extraValidChars, out ReadOnlySpan res)
+ {
+ res = ReadOnlySpan.Empty;
+ SkipWhitespace();
+ if (_s.Length == 0)
+ return false;
+ var first = _s[0];
+ if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_'))
+ return false;
+ int len = 1;
+ for (var c = 1; c < _s.Length; c++)
+ {
+ var ch = _s[c];
+ if (IsAlphaNumeric(ch) || ch == '_')
+ len++;
+ else
+ {
+ var found = false;
+ foreach(var vc in extraValidChars)
+ if (vc == ch)
+ {
+ found = true;
+ break;
+ }
+
+ if (found)
+ len++;
+ else
+ break;
+ }
+ }
+
+ res = _s.Slice(0, len);
+ Advance(len);
+ return true;
+ }
+
+ public bool TryParseIdentifier(out ReadOnlySpan res)
+ {
+ res = ReadOnlySpan.Empty;
+ SkipWhitespace();
+ if (_s.Length == 0)
+ return false;
+ var first = _s[0];
+ if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_'))
+ return false;
+ int len = 1;
+ for (var c = 1; c < _s.Length; c++)
+ {
+ var ch = _s[c];
+ if (IsAlphaNumeric(ch) || ch == '_')
+ len++;
+ else
+ break;
+ }
+
+ res = _s.Slice(0, len);
+ Advance(len);
+ return true;
+ }
+
+ public string ReadToEol()
+ {
+ var initial = _s;
+ var len = 0;
+ while (true)
+ {
+ if (_s.Length > 0 && _s[0] != '\n' && _s[0] != '\r')
+ {
+ len++;
+ Advance(1);
+ }
+ else
+ return initial.Slice(0, len).ToString();
+ }
+ }
+
+ public string ReadTo(char c)
+ {
+ var initial = _s;
+ var len = 0;
+ var l = Line;
+ var p = Position;
+ while (true)
+ {
+ if (_s.Length == 0)
+ throw new ParseException("Expected " + c + " before EOF", l, p);
+
+ if (_s[0] != c)
+ {
+ len++;
+ Advance(1);
+ }
+ else
+ return initial.Slice(0, len).ToString();
+ }
+ }
+
+ public string ReadToAny(ReadOnlySpan chars)
+ {
+ var initial = _s;
+ var len = 0;
+ var l = Line;
+ var p = Position;
+ while (true)
+ {
+ if (_s.Length == 0)
+ throw new ParseException("Expected any of '" + chars.ToString() + "' before EOF", l, p);
+
+ var foundTerminator = false;
+ foreach (var term in chars)
+ {
+ if (_s[0] == term)
+ {
+ foundTerminator = true;
+ break;
+ }
+ }
+
+ if (!foundTerminator)
+ {
+ len++;
+ Advance(1);
+ }
+ else
+ return initial.Slice(0, len).ToString();
+ }
+ }
+
+ public bool TryParseCall(out ReadOnlySpan res)
+ {
+ res = ReadOnlySpan.Empty;
+ SkipWhitespace();
+ if (_s.Length == 0)
+ return false;
+ var first = _s[0];
+ if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z')))
+ return false;
+ int len = 1;
+ for (var c = 1; c < _s.Length; c++)
+ {
+ var ch = _s[c];
+ if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch<= 'Z') || ch == '.')
+ len++;
+ else
+ break;
+ }
+
+ res = _s.Slice(0, len);
+
+ // Find '('
+ for (var c = len; c < _s.Length; c++)
+ {
+ if(char.IsWhiteSpace(_s[c]))
+ continue;
+ if(_s[c]=='(')
+ {
+ Advance(c + 1);
+ return true;
+ }
+
+ return false;
+
+ }
+
+ return false;
+
+ }
+
+
+ public bool TryParseFloat(out float res)
+ {
+ res = 0;
+ SkipWhitespace();
+ if (_s.Length == 0)
+ return false;
+
+ var len = 0;
+ var dotCount = 0;
+ for (var c = 0; c < _s.Length; c++)
+ {
+ var ch = _s[c];
+ if (ch >= '0' && ch <= '9')
+ len = c + 1;
+ else if (ch == '.' && dotCount == 0)
+ {
+ len = c + 1;
+ dotCount++;
+ }
+ else
+ break;
+ }
+
+ var span = _s.Slice(0, len);
+
+#if NETSTANDARD2_0
+ if (!float.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, out res))
+ return false;
+#else
+ if (!float.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, out res))
+ return false;
+#endif
+ Advance(len);
+ return true;
+ }
+
+ public override string ToString() => _s.ToString();
+
+ }
+}