[objc] Add basic interface/protocol support (#191)
This allow an .NET API to return an interface. That interface is defined as a protocol in ObjC. We also supply an internal `__*Wrapper` class so ObjC can call the interface members. Also fix virtual calls - the current code was calling the interface itself, not the implementation for it. Note: it's basic in the sense that it does not allow .NET to call back into ObjC code (that conforms to the protocol).
This commit is contained in:
Родитель
c7d0820398
Коммит
4be5b4d0d7
|
@ -26,7 +26,7 @@ namespace ObjC {
|
|||
|
||||
public int MetadataToken { get; set; }
|
||||
|
||||
public void BeginHeaders ()
|
||||
public virtual void BeginHeaders ()
|
||||
{
|
||||
headers.WriteLine ();
|
||||
headers.WriteLine ($"/** Class {Name}");
|
||||
|
@ -62,9 +62,10 @@ namespace ObjC {
|
|||
headers.WriteLine (" * It exists solely to allow the correct subclassing of managed (.net) types");
|
||||
headers.WriteLine (" */");
|
||||
headers.WriteLine ("- (nullable instancetype)initForSuper;");
|
||||
headers.WriteLine ();
|
||||
}
|
||||
|
||||
public void EndHeaders ()
|
||||
public virtual void EndHeaders ()
|
||||
{
|
||||
if (!IsStatic)
|
||||
DefineInitForSuper ();
|
||||
|
@ -72,22 +73,23 @@ namespace ObjC {
|
|||
headers.WriteLine ();
|
||||
}
|
||||
|
||||
public void BeginImplementation ()
|
||||
public virtual void BeginImplementation (string implementationName = null)
|
||||
{
|
||||
var name = implementationName ?? Name;
|
||||
implementation.WriteLine ();
|
||||
implementation.WriteLine ($"@implementation {Name} {{");
|
||||
implementation.WriteLine ($"@implementation {name} {{");
|
||||
implementation.WriteLine ("}");
|
||||
implementation.WriteLine ();
|
||||
WriteInitialize ();
|
||||
WriteInitialize (name);
|
||||
WriteDealloc ();
|
||||
}
|
||||
|
||||
void WriteInitialize ()
|
||||
void WriteInitialize (string name)
|
||||
{
|
||||
implementation.WriteLine ("+ (void) initialize");
|
||||
implementation.WriteLine ("{");
|
||||
implementation.Indent++;
|
||||
implementation.WriteLine ($"if (self != [{Name} class])");
|
||||
implementation.WriteLine ($"if (self != [{name} class])");
|
||||
implementation.Indent++;
|
||||
implementation.WriteLine ("return;");
|
||||
implementation.Indent--;
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace ObjC {
|
|||
public bool IsExtension { get; set; }
|
||||
public bool IsStatic { get; set; }
|
||||
public bool IsValueType { get; set; }
|
||||
public bool IsVirtual { get; set; }
|
||||
|
||||
public string ReturnType { get; set; }
|
||||
|
||||
|
@ -86,9 +87,14 @@ namespace ObjC {
|
|||
}
|
||||
}
|
||||
|
||||
var method = "__method";
|
||||
if (IsVirtual) {
|
||||
implementation.WriteLine ($"MonoMethod* __virtual_method = mono_object_get_virtual_method ({instance}, __method);");
|
||||
method = "__virtual_method";
|
||||
}
|
||||
if (!IsConstructor && (ReturnType != "void"))
|
||||
implementation.Write ("MonoObject* __result = ");
|
||||
implementation.WriteLine ($"mono_runtime_invoke (__method, {instance}, {args}, &__exception);");
|
||||
implementation.WriteLine ($"mono_runtime_invoke ({method}, {instance}, {args}, &__exception);");
|
||||
|
||||
implementation.WriteLine ("if (__exception)");
|
||||
implementation.Indent++;
|
||||
|
|
|
@ -319,6 +319,7 @@
|
|||
<Compile Include="HashHelper.cs" />
|
||||
<Compile Include="sourcewriter.cs" />
|
||||
<Compile Include="classhelper.cs" />
|
||||
<Compile Include="protocolhelper.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="support\" />
|
||||
|
|
|
@ -22,13 +22,6 @@ namespace ObjC {
|
|||
if (unsupported.Contains (t))
|
||||
return false;
|
||||
|
||||
// FIXME protocols
|
||||
if (t.IsInterface) {
|
||||
delayed.Add (ErrorHelper.CreateWarning (1010, $"Type `{t}` is not generated because `interfaces` are not supported."));
|
||||
unsupported.Add (t);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (t.IsGenericParameter || t.IsGenericType) {
|
||||
delayed.Add (ErrorHelper.CreateWarning (1010, $"Type `{t}` is not generated because `generics` are not supported."));
|
||||
unsupported.Add (t);
|
||||
|
@ -203,6 +196,7 @@ namespace ObjC {
|
|||
|
||||
List<Type> enums = new List<Type> ();
|
||||
List<Type> types = new List<Type> ();
|
||||
List<Type> protocols = new List<Type> ();
|
||||
|
||||
Dictionary<Type, List<ProcessedConstructor>> ctors = new Dictionary<Type, List<ProcessedConstructor>> ();
|
||||
Dictionary<Type, List<ProcessedMethod>> methods = new Dictionary<Type, List<ProcessedMethod>> ();
|
||||
|
@ -229,7 +223,11 @@ namespace ObjC {
|
|||
continue;
|
||||
}
|
||||
|
||||
types.Add (t);
|
||||
if (t.IsInterface) {
|
||||
protocols.Add (t);
|
||||
} else {
|
||||
types.Add (t);
|
||||
}
|
||||
|
||||
extension_type = t.HasCustomAttribute ("System.Runtime.CompilerServices", "ExtensionAttribute");
|
||||
|
||||
|
|
|
@ -50,6 +50,11 @@ namespace ObjC {
|
|||
|
||||
foreach (var t in types)
|
||||
implementation.WriteLine ($"static MonoClass* {GetObjCName (t)}_class = nil;");
|
||||
foreach (var t in protocols) {
|
||||
var pname = GetTypeName (t);
|
||||
headers.WriteLine ($"@protocol {pname};");
|
||||
implementation.WriteLine ($"@class __{pname}Wrapper;");
|
||||
}
|
||||
implementation.WriteLine ();
|
||||
|
||||
implementation.WriteLine ("static void __initialize_mono ()");
|
||||
|
@ -105,6 +110,10 @@ namespace ObjC {
|
|||
GenerateEnum (t);
|
||||
}
|
||||
|
||||
foreach (var t in protocols) {
|
||||
GenerateProtocol (t);
|
||||
}
|
||||
|
||||
foreach (var t in types) {
|
||||
Generate (t);
|
||||
}
|
||||
|
@ -179,6 +188,60 @@ namespace ObjC {
|
|||
headers.WriteLine ();
|
||||
}
|
||||
|
||||
void GenerateProtocol (Type t)
|
||||
{
|
||||
var pbuilder = new ProtocolHelper (headers, implementation) {
|
||||
AssemblyQualifiedName = t.AssemblyQualifiedName,
|
||||
AssemblyName = t.Assembly.GetName ().Name.Sanitize (),
|
||||
ProtocolName = GetTypeName (t),
|
||||
Namespace = t.Namespace,
|
||||
ManagedName = t.Name,
|
||||
MetadataToken = t.MetadataToken,
|
||||
};
|
||||
pbuilder.BeginHeaders ();
|
||||
|
||||
// no need to iterate constructors or fields as they cannot be part of net interfaces
|
||||
// do not generate implementations for protocols
|
||||
implementation.Enabled = false;
|
||||
|
||||
List<ProcessedProperty> props;
|
||||
if (properties.TryGetValue (t, out props)) {
|
||||
headers.WriteLine ();
|
||||
foreach (var pi in props)
|
||||
Generate (pi);
|
||||
}
|
||||
|
||||
List<ProcessedMethod> meths;
|
||||
if (methods.TryGetValue (t, out meths)) {
|
||||
headers.WriteLine ();
|
||||
foreach (var mi in meths)
|
||||
Generate (mi);
|
||||
}
|
||||
|
||||
pbuilder.EndHeaders ();
|
||||
|
||||
// wrappers are internal so not part of the headers
|
||||
headers.Enabled = false;
|
||||
implementation.Enabled = true;
|
||||
|
||||
pbuilder.BeginImplementation ();
|
||||
|
||||
if (properties.TryGetValue (t, out props)) {
|
||||
implementation.WriteLine ();
|
||||
foreach (var pi in props)
|
||||
Generate (pi);
|
||||
}
|
||||
|
||||
if (methods.TryGetValue (t, out meths)) {
|
||||
implementation.WriteLine ();
|
||||
foreach (var mi in meths)
|
||||
Generate (mi);
|
||||
}
|
||||
|
||||
pbuilder.EndImplementation ();
|
||||
headers.Enabled = true;
|
||||
}
|
||||
|
||||
protected override void Generate (Type t)
|
||||
{
|
||||
var aname = t.Assembly.GetName ().Name.Sanitize ();
|
||||
|
@ -536,6 +599,8 @@ namespace ObjC {
|
|||
|
||||
public string GetReturnType (Type declaringType, Type returnType)
|
||||
{
|
||||
if (protocols.Contains (returnType))
|
||||
return "id<" + GetTypeName (returnType) + ">";
|
||||
if (declaringType == returnType)
|
||||
return "instancetype";
|
||||
|
||||
|
@ -569,6 +634,7 @@ namespace ObjC {
|
|||
ObjCSignature = objcsig,
|
||||
ObjCTypeName = managed_type_name,
|
||||
IsValueType = type.IsValueType,
|
||||
IsVirtual = info.IsVirtual,
|
||||
};
|
||||
|
||||
if (pi == null)
|
||||
|
@ -706,7 +772,7 @@ namespace ObjC {
|
|||
case TypeCode.Object:
|
||||
if (t.Namespace == "System" && t.Name == "Void")
|
||||
return;
|
||||
if (!types.Contains (t))
|
||||
if (!types.Contains (t) && !protocols.Contains (t))
|
||||
goto default;
|
||||
|
||||
implementation.WriteLine ("if (!__result)");
|
||||
|
@ -714,7 +780,10 @@ namespace ObjC {
|
|||
implementation.WriteLine ("return nil;");
|
||||
implementation.Indent--;
|
||||
// TODO: cheating by reusing `initForSuper` - maybe a better name is needed
|
||||
implementation.WriteLine ($"{GetTypeName (t)}* __peer = [[{GetTypeName (t)} alloc] initForSuper];");
|
||||
var tname = GetTypeName (t);
|
||||
if (protocols.Contains (t))
|
||||
tname = "__" + tname + "Wrapper";
|
||||
implementation.WriteLine ($"\t{tname}* __peer = [[{tname} alloc] initForSuper];");
|
||||
implementation.WriteLine ("__peer->_object = mono_embeddinator_create_object (__result);");
|
||||
implementation.WriteLine ("return __peer;");
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using Embeddinator;
|
||||
|
||||
namespace ObjC {
|
||||
|
||||
public class ProtocolHelper : ClassHelper {
|
||||
|
||||
public ProtocolHelper (SourceWriter headers, SourceWriter implementation) :
|
||||
base (headers, implementation)
|
||||
{
|
||||
}
|
||||
|
||||
string protocolName;
|
||||
public string ProtocolName {
|
||||
get { return protocolName; }
|
||||
set {
|
||||
protocolName = value;
|
||||
Name = value;
|
||||
WrapperName = $"__{value}Wrapper";
|
||||
}
|
||||
}
|
||||
|
||||
public string WrapperName { get; set; }
|
||||
|
||||
public override void BeginHeaders ()
|
||||
{
|
||||
headers.WriteLine ();
|
||||
headers.WriteLine ($"/** Protocol {ProtocolName}");
|
||||
headers.WriteLine ($" * Corresponding .NET Qualified Name: `{AssemblyQualifiedName}`");
|
||||
headers.WriteLine (" */");
|
||||
headers.WriteLine ($"@protocol {ProtocolName} <NSObject>");
|
||||
headers.WriteLine ();
|
||||
}
|
||||
|
||||
public override void EndHeaders ()
|
||||
{
|
||||
headers.WriteLine ("@end");
|
||||
headers.WriteLine ();
|
||||
}
|
||||
|
||||
public override void BeginImplementation (string implementationName = null)
|
||||
{
|
||||
implementation.WriteLine ($"@interface {WrapperName} : NSObject <{ProtocolName}> {{");
|
||||
implementation.Indent++;
|
||||
implementation.WriteLine ("@public MonoEmbedObject* _object;");
|
||||
implementation.Indent--;
|
||||
implementation.WriteLine ("}");
|
||||
implementation.WriteLine ("- (nullable instancetype)initForSuper;");
|
||||
implementation.WriteLine ("@end");
|
||||
implementation.WriteLine ();
|
||||
implementation.WriteLine ($"static MonoClass* {ProtocolName}_class = nil;");
|
||||
implementation.WriteLine ();
|
||||
base.BeginImplementation (WrapperName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
|
||||
namespace Interfaces {
|
||||
|
||||
public interface IMakeItUp {
|
||||
|
||||
bool Boolean { get; }
|
||||
|
||||
string Convert (int integer);
|
||||
|
||||
string Convert (long longint);
|
||||
}
|
||||
|
||||
// not public - only the contract is exposed thru a static type
|
||||
class MakeItUp : IMakeItUp {
|
||||
|
||||
bool result;
|
||||
|
||||
public bool Boolean {
|
||||
get {
|
||||
result = !result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public string Convert (int integer)
|
||||
{
|
||||
return integer.ToString ();
|
||||
}
|
||||
|
||||
// overload
|
||||
public string Convert (long longint)
|
||||
{
|
||||
return longint.ToString ();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Supplier {
|
||||
|
||||
static public IMakeItUp Create ()
|
||||
{
|
||||
return new MakeItUp ();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,6 +44,7 @@
|
|||
<DependentUpon>subscripts.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="equalsHashOverrides.cs" />
|
||||
<Compile Include="interfaces.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="subscripts.tt">
|
||||
|
|
|
@ -670,6 +670,15 @@
|
|||
XCTAssertFalse ([c1 hash] == [c3 hash], "Non-equal objects have different hashes");
|
||||
}
|
||||
|
||||
- (void)testProtocols {
|
||||
id<Interfaces_IMakeItUp> m = [Interfaces_Supplier create];
|
||||
XCTAssertTrue ([m boolean], "true");
|
||||
XCTAssertFalse ([m boolean], "false");
|
||||
|
||||
XCTAssertEqualObjects (@"0", [m convertInt32:0], "0");
|
||||
XCTAssertEqualObjects (@"1", [m convertInt64:1ll], "1");
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@end
|
||||
|
|
Загрузка…
Ссылка в новой задаче