[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:
Sebastien Pouliot 2017-04-26 20:37:46 -04:00 коммит произвёл GitHub
Родитель c7d0820398
Коммит 4be5b4d0d7
9 изменённых файлов: 205 добавлений и 18 удалений

Просмотреть файл

@ -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;

56
objcgen/protocolhelper.cs Normal file
Просмотреть файл

@ -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