How to work with NSRectArray?

XamMac doesn't contains binding for rectArrayForGlyphRange:withinSelectedGlyphRange:inTextContainer:rectCount: method of NSLayoutManager class. I was able to move this call into native dynamic library and make a call to this function from C# code.

This method returns NSRectArray which doesn't have representation in XamMac. And I stuck at this point. How can I pass NSRectArray to C#?
I was thinking of returning NSArray of NSRect as described here http://goo.gl/xMQbHU . I could get NSArray in C# but how will I get back NSRect from NSArray? There is no NSRect representation in C#. RectangleF is structure and cannot be initialized via NSObject or IntPtr.

Best Answer

Answers

  • NikolayIvanetsNikolayIvanets USMember ✭✭

    Rolf, thanks for your tip! Finally, I did it!

    I'll post full solution a bit later.

  • NikolayIvanetsNikolayIvanets USMember ✭✭

    Probably, someone find this useful until it is implemented in MonoMac. Here you are:

    Objective-C:

    + (NSArray*)rectArrayForGlyphRange:(NSRange)glyphRange withinSelectedGlyphRange:(NSRange)selGlyphRange inTextContainer:(NSTextContainer*)container rectCount:(NSUInteger*)rectCount forLayoutManager:(NSLayoutManager*)manager {
        NSRectArray aRectArray = [manager rectArrayForGlyphRange:glyphRange withinSelectedGlyphRange:selGlyphRange inTextContainer:container rectCount:rectCount];
    
        NSMutableArray* outArray = [[NSMutableArray alloc] initWithCapacity:*rectCount];
        for (unsigned i = 0; i < *rectCount; i++) {
            NSRect glyphRect = aRectArray[i];
            [outArray addObject:([NSValue valueWithRect:glyphRect])];
        }
    
        return outArray;
    }
    

    C#:

    const int RTLD_NOW = 2;
    const string LIBOBJC_DYLIB = "/usr/lib/libobjc.dylib";
    const string NATIVEOSX_DYLIB = @"@executable_path/../MonoBundle/NativeOSX.dylib";
    const string NATIVEOSX_CLASS = "NativeOSX";
    
    [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSend")]
    public extern static IntPtr IntPtr_objc_msgSend_NSRange_NSRange_IntPtr_out_uint_IntPtr(IntPtr receiver, IntPtr selector, NSRange arg1, NSRange arg2, IntPtr arg3, out uint arg4, IntPtr arg5);
    
    public static RectangleF[] GetRectArrayForGlyphRangeWithinSelectedGlyphRangeInTextContainerRectCountForLayoutManager(
        NSRange glyphRange, NSRange selGlyphRange, NSTextContainer container, out uint rectCount, NSLayoutManager manager)
    {
        IntPtr handle = Dlfcn.dlopen(NATIVEOSX_DYLIB, RTLD_NOW);
    
        var cls = new Class(NATIVEOSX_CLASS);
        var sel = new Selector("rectArrayForGlyphRange:withinSelectedGlyphRange:inTextContainer:rectCount:forLayoutManager:");
    
        IntPtr pointer = IntPtr_objc_msgSend_NSRange_NSRange_IntPtr_out_uint_IntPtr(cls.Handle, sel.Handle, glyphRange, selGlyphRange, container.Handle, out rectCount, manager.Handle);
        NSValue[] values = NSArray.ArrayFromHandle<NSValue>(pointer);
        RectangleF[] rects = values.ToList().ConvertAll((NSValue input) => input.RectangleFValue).ToArray();
    
        Dlfcn.dlclose(handle);
    
        return rects;
    }
    

    The only thing I'm not sure about is either I should release outArray, returned in Objective-C code or it will be automatically released by C# code?

  • dmetzlerdmetzler USMember

    Is this still valid? I do not see NativeOSX.dylib in the MonoBundle folder in the executable.

    Where is this dylib located so I can include it?

    Thanks,
    -Don

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    @dmetzler So I don't believe that is valid anymore. I don't see NativeOSX either. I'm going to assume it was Monomac specific (and before my time here).

    I hacked this up:

    https://gist.github.com/chamons/cc82b52b1bd5266252aa

    And it matches the obj-c in my example. However, I was unable to test the multiple rect return case, because I don't know the API well enough to get it to return more than one thing.

    That is also very Classic specific. The uint rectCount, float* are the two things that come to mind that will have to be changed if you are using Unified 64-bit.

    I've filed a bug for a "real" binding: https://bugzilla.xamarin.com/show_bug.cgi?id=27823

  • NikolayIvanetsNikolayIvanets USMember ✭✭
    edited March 2015

    @dmetzler, @ChrisHamons,

    NativeOSX.dylib is the native objective-c library which you have to build yourself, using example provided above and put it into MonoBundle folder inside your executable.

  • dmetzlerdmetzler USMember

    @NikolayIvanets ,

    In your NativeOSX.m code, you have a function to return an NSRange:

    • (NSRange) glyphRangeForCharacterRange:(NSRange)charRange actualCharacterRange:(NSRangePointer)actualCharRange forLayoutManager:(NSLayoutManager*)manager {
      return [manager glyphRangeForCharacterRange:charRange actualCharacterRange:actualCharRange];
      }

    What would the code for this look like in C#? How would I return an NSRange from Obj-C to C#?

    Thanks,
    Don

  • NikolayIvanetsNikolayIvanets USMember ✭✭
    edited March 2015

    @dmetzler, here you are:

    public class NativeHelper
    {
        const string NATIVEOSX_DYLIB = @"@executable_path/../MonoBundle/NativeOSX.dylib";
        const string NATIVEOSX_CLASS = "NativeOSX";
        const string LIBOBJC_DYLIB = "/usr/lib/libobjc.dylib";
        const int RTLD_NOW = 2;
    
        [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSend")]
        extern static NSRange NSRange_objc_msgSend_NSRange_IntPtr_IntPtr(IntPtr receiver, IntPtr selector, NSRange arg1, IntPtr arg2, IntPtr arg3);
    
        public static NSRange GetGlyphRangeForCharacterRangeActualCharacterRangeForLayoutManager(NSRange charRange, NSLayoutManager manager)
        {
            IntPtr handle = Dlfcn.dlopen(NATIVEOSX_DYLIB, RTLD_NOW);
    
            var cls = new Class(NATIVEOSX_CLASS);
            var sel = new Selector("glyphRangeForCharacterRange:actualCharacterRange:forLayoutManager:");
    
            NSRange result = NSRange_objc_msgSend_NSRange_IntPtr_IntPtr(cls.Handle, sel.Handle, charRange, IntPtr.Zero, manager.Handle);
    
            Dlfcn.dlclose(handle);
    
            return result;
        }
    }
    

    and actual call:

    // returnRange and _layoutManager are defined somewhere else in your code
    NSRange glyphRange = NativeHelper.GetGlyphRangeForCharacterRangeActualCharacterRangeForLayoutManager(returnRange, _layoutManager);
    
  • dmetzlerdmetzler USMember

    How would I return an NSRect from Objective-C to a RectangleF structure in Xamarin?

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    If you all the right objective-c msg send or setup the binding correctly in the file you pass to the bmac is should "just work"

Sign In or Register to comment.