A few Oxygene.NET problems

I have a few problems with Oxygene.NET .971:

  1. Attribute’s “HandleImplementation” method does not inject code into the class to which aspect is applied. See attachment.
  2. Class completion (Ctrl-Shift-C) doesn’t work.
  3. Regions are not folded closed when a file is open. Checking “Regions” under “Fold closed on file open:” in “Tools/Options/TextEditor/Oxygene/Outlining” doesn’t help.
  4. Is there any way for the word “Region” to not appear when a region is closed? It’s quite irritating.
  5. If you try to REbuild the solution which contains class libraries a few times in a row, some libraries appear to stay locked after a few REbuilds. See attachment.

Best regards,
Niko Delic

Niko,
for case 1, you are applying the aspect to the class and are testing the name of the method. You really need to apply the aspect to the constructor in this case.
Alternatively, you can retrieve all the constructors of the class and modify all of them.
Patrick

Niko,
for case 2, it works on you source code on my PC, after removing the TestAttribute.HandleInterface method body.
Have you tried with the context menu?
Perhaps you have another function assigned to Ctrl+Shift+C.
Patrick

Case 1 worked with Prism XE. If you add a new method to main form (like ‘EmptyTestMethod’) and try to apply aspect to that method (replace ‘.ctor’ with the method’s name), the code in TestAttribute.HandleImplementation is still not injected. If you add the new method, rebuild the solution and then look at compiled code with dotPeek you will see that EmptyTestMehod body stays empty.

Case 2: I installed VS2010 and Oxygene in a brand new Win7 Ent 32 VM and both menu and shortcut sometimes work and sometimes don’t. They didn’t work when a was writing the attribute class for case 1, but they did work when I added EmptyTestMethod to main form.

Best regards,
Niko Delic

Niko,
for case 1, I have added

    method EmptyTestMethod; Empty;

to the MainForm and corrected the aspect to have

if String.Equals(aMethod.Name, 'EmptyTestMethod', StringComparison.OrdinalIgnoreCase) then ...

and the EmptyTestMethod method is still empty:

// AspectsTest.MainForm
protected void EmptyTestMethod()
{
}

Patrick

Niko,
I have corrected your aspect so that it should apply to a Method (and not a Class) and it works as intended.
It was surely a bug in the Prism XE compiler that make it work before…
Patrick

Can you show me the corrected version?

Niko,
the aspect definition is:

namespace AspectsTest;

interface

uses
  RemObjects.Oxygene.Cirrus,
  RemObjects.Oxygene.Cirrus.Statements,
  RemObjects.Oxygene.Cirrus.Values;

  type
    [AttributeUsage(AttributeTargets.Method)]
    TestAttribute = public class(Attribute, IMethodInterfaceDecorator, IMethodImplementationDecorator)
    public
      method HandleInterface(Services : IServices; aMethod : IMethodDefinition);
      method HandleImplementation(Services : IServices; aMethod : IMethodDefinition);
    end;

implementation

  method TestAttribute.HandleInterface(Services : IServices; aMethod : IMethodDefinition);
  begin
    var OwnerClass := aMethod.Owner;
    var ObjectType := Services.FindType('System.Object');
    var TestField : IFieldDefinition := OwnerClass.AddField('TestField', ObjectType, False);
    TestField.Visibility := Visibility.Private;
  end;
  
  method TestAttribute.HandleImplementation(Services : IServices; aMethod : IMethodDefinition);
  begin
    if String.Equals(aMethod.Name, '.ctor', StringComparison.OrdinalIgnoreCase) then begin
      var TestField_Value := new FieldValue(new SelfValue, aMethod.Owner.GetField('TestField'));
      aMethod.SetBody(Services,
        method begin
          Aspects.OriginalBody;
          unquote(TestField_Value) := new System.Object;
        end);
    end;
  end;

end.

The use in the class is:

  type
    MainForm = partial class(System.Windows.Forms.Form)
    private
    protected
      method Dispose(disposing: Boolean); override;
    public
      [aspect: Test]
      constructor;
    end;

And ILSpy decompiles the constructor as:

// AspectsTest.MainForm
private Container components = null;
public MainForm()
{
	this.InitializeComponent();
	this.TestField = new object();
}

I made some other changes in the project:

  • The reference to TestAttributes is made as a Cirrus reference, not a normal reference.
  • I’ve defined Project Dependencies to be sure that the aspect library is compiled before the main project.
    Patrick

Niko,
some last comments on this code:

  • the test on the name of the method is no more necessary, because the aspect is applied directly to a method and not to the class.
  • You should normally test on the HandleInterface method if the field already exists and take appropriate steps if it is: display an error or use the existing field.
    Patrick

Can you upload the entire project? I cannot make it work with the changes proposed.

Best regards,
Niko Delic

Niko,
see the attached file.
Patrick

Thank you for uploading the solution. Unfortunately, it does not compile. The compiler throws the same messages as I was getting when I tried to apply changes you proposed above:

Warning 1 (MSB3245) Could not resolve this reference. Could not locate the assembly “TestAttributes”. Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. c:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets 1360 9 AspectsTest
Error 2 (E28) Unknown type “Test” C:\Users\MOBR\Documents\Visual Studio 2010\Projects\AspectsTest\AspectsTest\Main.pas 18 16 AspectsTest

Best regards,
Niko Delic

Niko,
just remove the Cirrus reference to TestAttributes and add it again, choosing the folder where it is compiled: I don’t have the same directory structure as yours and the reference in the .oxygene file is an absolute one…
Patrick

Thank you for your help. Although it is hard for me to understand why injecting code into classes with class level attributes is not possible. It worked great in Prism XE and saved me a lot of coding. The problem is I’m injecting a lot of “one-liners” into different methods of my RO.NET services. So writing separate attributes for all these methods and then writing “aspect: Method1, aspect: Method2” etc. is total overkill. It’s easier to just put the “one-liners” directly into service’s methods. In Prism XE I just wrote a class level attribute and put “aspect: Service1” on top of service class’ definition and that was it. Simple and effective. I hope I can still do it somehow. Any help in that direction from you or other RO guys will be greatly appreciated.

Best regards,
Niko Delic

Niko,
you can do it again, but in your class Aspect, you need to enumerate the methods of the class to find the right ones and modify them.
Looking again at the documentation about aspects, on the IMethodImplementationDecorator interface, the wiki says:

Method decorators will only be called when the aspect is applied to a method, type or to the assembly.

But it is not clear at when the interface is used when the aspect is applied to a class or to the assembly: perhaps someone from RemObjects can clarify what it means exactly. Perhaps, when applied to a class, it should be called for each method of the class and, in this case, this is a bug if it is not called for a constructor.
Patrick