Accessing the bundle of the call-site


(Rick Aurbach) #1

Does anyone know if it is possible to do the following in Swift 3.x? (I’ll describe the issue abstractly first, then give the use-case.)

Consider two modules: A and B. A could be either the main module of an application or an embedded framework. B is a different embedded framework.

Now A contains an public extension of class X which contains a function f(). Inside B, there is a reference to X.f(). Now what I want to do in f() is to access information (a module name or bundle name or bundle ID or something) that allows me to construct a Bundle object referring to B, without f() having any external knowledge of the organization of the application.

The use-case I’m thinking about is a localization extension of String that works in a multi-framework application architecture without requiring the caller to specify the name of the framework and/or module.

I.e., I want to write

  extension String {
    func locate() -> String {…}
  }

and put this extension into framework “A”. Then, from framework “B”, I want to use this function from within a function f() and [somehow] figure out from the runtime what the bundle of “B” is, so that I can use it’s localized strings file.

I understand that from within the locate() method, I can use #function and from it, parse out the module name of “A” and then use the correspondence between module names and framework names to figure out the bundle of “A”. BUT what I want here is the bundle resource for “B”, not “A”.

Thanks,

Rick Aurbach


(Greg Parker) #2

You should be able to use a trick similar to the one that assert() uses to collect file and line numbers:

    func locate(caller: StaticString = #function) {
        // `caller` is the caller's #function
    }

···

On Dec 2, 2016, at 9:44 AM, Rick Aurbach via swift-users <swift-users@swift.org> wrote:

Does anyone know if it is possible to do the following in Swift 3.x? (I’ll describe the issue abstractly first, then give the use-case.)

Consider two modules: A and B. A could be either the main module of an application or an embedded framework. B is a different embedded framework.

Now A contains an public extension of class X which contains a function f(). Inside B, there is a reference to X.f(). Now what I want to do in f() is to access information (a module name or bundle name or bundle ID or something) that allows me to construct a Bundle object referring to B, without f() having any external knowledge of the organization of the application.

The use-case I’m thinking about is a localization extension of String that works in a multi-framework application architecture without requiring the caller to specify the name of the framework and/or module.

I.e., I want to write

  extension String {
    func locate() -> String {…}
  }

and put this extension into framework “A”. Then, from framework “B”, I want to use this function from within a function f() and [somehow] figure out from the runtime what the bundle of “B” is, so that I can use it’s localized strings file.

I understand that from within the locate() method, I can use #function and from it, parse out the module name of “A” and then use the correspondence between module names and framework names to figure out the bundle of “A”. BUT what I want here is the bundle resource for “B”, not “A”.

--
Greg Parker gparker@apple.com Runtime Wrangler


(Rick Aurbach) #3

That’s clever! Thank you; I’d probably never have thought of that.

Cheers,

Rick Aurbach

···

On Dec 2, 2016, at 12:25 PM, Greg Parker <gparker@apple.com> wrote:

On Dec 2, 2016, at 9:44 AM, Rick Aurbach via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Does anyone know if it is possible to do the following in Swift 3.x? (I’ll describe the issue abstractly first, then give the use-case.)

Consider two modules: A and B. A could be either the main module of an application or an embedded framework. B is a different embedded framework.

Now A contains an public extension of class X which contains a function f(). Inside B, there is a reference to X.f(). Now what I want to do in f() is to access information (a module name or bundle name or bundle ID or something) that allows me to construct a Bundle object referring to B, without f() having any external knowledge of the organization of the application.

The use-case I’m thinking about is a localization extension of String that works in a multi-framework application architecture without requiring the caller to specify the name of the framework and/or module.

I.e., I want to write

  extension String {
    func locate() -> String {…}
  }

and put this extension into framework “A”. Then, from framework “B”, I want to use this function from within a function f() and [somehow] figure out from the runtime what the bundle of “B” is, so that I can use it’s localized strings file.

I understand that from within the locate() method, I can use #function and from it, parse out the module name of “A” and then use the correspondence between module names and framework names to figure out the bundle of “A”. BUT what I want here is the bundle resource for “B”, not “A”.

You should be able to use a trick similar to the one that assert() uses to collect file and line numbers:

    func locate(caller: StaticString = #function) {
        // `caller` is the caller's #function
    }

--
Greg Parker gparker@apple.com <mailto:gparker@apple.com> Runtime Wrangler


(Jordan Rose) #4

On Apple platforms, we'd probably prefer you use the little-known #dsoHandle, a magic pointer that's unique to the current dylib. Parsing out a module name seems incredibly brittle; the form of #function is not guaranteed to be stable or useful across Swift versions.

Jordan

···

On Dec 2, 2016, at 10:35, Rick Aurbach via swift-users <swift-users@swift.org> wrote:

That’s clever! Thank you; I’d probably never have thought of that.

Cheers,

Rick Aurbach

On Dec 2, 2016, at 12:25 PM, Greg Parker <gparker@apple.com <mailto:gparker@apple.com>> wrote:

On Dec 2, 2016, at 9:44 AM, Rick Aurbach via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Does anyone know if it is possible to do the following in Swift 3.x? (I’ll describe the issue abstractly first, then give the use-case.)

Consider two modules: A and B. A could be either the main module of an application or an embedded framework. B is a different embedded framework.

Now A contains an public extension of class X which contains a function f(). Inside B, there is a reference to X.f(). Now what I want to do in f() is to access information (a module name or bundle name or bundle ID or something) that allows me to construct a Bundle object referring to B, without f() having any external knowledge of the organization of the application.

The use-case I’m thinking about is a localization extension of String that works in a multi-framework application architecture without requiring the caller to specify the name of the framework and/or module.

I.e., I want to write

  extension String {
    func locate() -> String {…}
  }

and put this extension into framework “A”. Then, from framework “B”, I want to use this function from within a function f() and [somehow] figure out from the runtime what the bundle of “B” is, so that I can use it’s localized strings file.

I understand that from within the locate() method, I can use #function and from it, parse out the module name of “A” and then use the correspondence between module names and framework names to figure out the bundle of “A”. BUT what I want here is the bundle resource for “B”, not “A”.

You should be able to use a trick similar to the one that assert() uses to collect file and line numbers:

    func locate(caller: StaticString = #function) {
        // `caller` is the caller's #function
    }

--
Greg Parker gparker@apple.com <mailto:gparker@apple.com> Runtime Wrangler

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Rick Aurbach) #5

Jordan,

I agree — #dsohandle is, indeed, little known. In fact, I’m having a devil of a time figuring out what it is and what I can do with it. It is clearly an UnsafeRawPointer, but to what??

Can you offer either a reference or a few lines of code that can help me get the information I need from it? [recall that I want the framework’s bundle so I can find it’s localized.strings file].

Cheers,

Rick Aurbach

···

On Dec 2, 2016, at 12:56 PM, Jordan Rose <jordan_rose@apple.com> wrote:

On Apple platforms, we'd probably prefer you use the little-known #dsoHandle, a magic pointer that's unique to the current dylib. Parsing out a module name seems incredibly brittle; the form of #function is not guaranteed to be stable or useful across Swift versions.

Jordan

On Dec 2, 2016, at 10:35, Rick Aurbach via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

That’s clever! Thank you; I’d probably never have thought of that.

Cheers,

Rick Aurbach

On Dec 2, 2016, at 12:25 PM, Greg Parker <gparker@apple.com <mailto:gparker@apple.com>> wrote:

On Dec 2, 2016, at 9:44 AM, Rick Aurbach via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Does anyone know if it is possible to do the following in Swift 3.x? (I’ll describe the issue abstractly first, then give the use-case.)

Consider two modules: A and B. A could be either the main module of an application or an embedded framework. B is a different embedded framework.

Now A contains an public extension of class X which contains a function f(). Inside B, there is a reference to X.f(). Now what I want to do in f() is to access information (a module name or bundle name or bundle ID or something) that allows me to construct a Bundle object referring to B, without f() having any external knowledge of the organization of the application.

The use-case I’m thinking about is a localization extension of String that works in a multi-framework application architecture without requiring the caller to specify the name of the framework and/or module.

I.e., I want to write

  extension String {
    func locate() -> String {…}
  }

and put this extension into framework “A”. Then, from framework “B”, I want to use this function from within a function f() and [somehow] figure out from the runtime what the bundle of “B” is, so that I can use it’s localized strings file.

I understand that from within the locate() method, I can use #function and from it, parse out the module name of “A” and then use the correspondence between module names and framework names to figure out the bundle of “A”. BUT what I want here is the bundle resource for “B”, not “A”.

You should be able to use a trick similar to the one that assert() uses to collect file and line numbers:

    func locate(caller: StaticString = #function) {
        // `caller` is the caller's #function
    }

--
Greg Parker gparker@apple.com <mailto:gparker@apple.com> Runtime Wrangler

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Jordan Rose) #6

Heh, we probably should document it somewhere. It is an opaque UnsafeRawPointer, and maybe you can't really get from there to a Bundle. The dladdr function can get you from an address to a particular dylib, and it's supposed to have the path, and maybe you could then turn that into a URL to get to the bundle…

As far as I know no one has done this yet in Swift, though Mike Ash has an example using dladdr for unrelated purposes. https://www.mikeash.com/pyblog/friday-qa-2014-08-29-swift-memory-dumping.html

Jordan

···

On Dec 2, 2016, at 12:50, Rick Aurbach <rlaurb@icloud.com> wrote:

Jordan,

I agree — #dsohandle is, indeed, little known. In fact, I’m having a devil of a time figuring out what it is and what I can do with it. It is clearly an UnsafeRawPointer, but to what??

Can you offer either a reference or a few lines of code that can help me get the information I need from it? [recall that I want the framework’s bundle so I can find it’s localized.strings file].

Cheers,

Rick Aurbach

On Dec 2, 2016, at 12:56 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Apple platforms, we'd probably prefer you use the little-known #dsoHandle, a magic pointer that's unique to the current dylib. Parsing out a module name seems incredibly brittle; the form of #function is not guaranteed to be stable or useful across Swift versions.

Jordan

On Dec 2, 2016, at 10:35, Rick Aurbach via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

That’s clever! Thank you; I’d probably never have thought of that.

Cheers,

Rick Aurbach

On Dec 2, 2016, at 12:25 PM, Greg Parker <gparker@apple.com <mailto:gparker@apple.com>> wrote:

On Dec 2, 2016, at 9:44 AM, Rick Aurbach via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Does anyone know if it is possible to do the following in Swift 3.x? (I’ll describe the issue abstractly first, then give the use-case.)

Consider two modules: A and B. A could be either the main module of an application or an embedded framework. B is a different embedded framework.

Now A contains an public extension of class X which contains a function f(). Inside B, there is a reference to X.f(). Now what I want to do in f() is to access information (a module name or bundle name or bundle ID or something) that allows me to construct a Bundle object referring to B, without f() having any external knowledge of the organization of the application.

The use-case I’m thinking about is a localization extension of String that works in a multi-framework application architecture without requiring the caller to specify the name of the framework and/or module.

I.e., I want to write

  extension String {
    func locate() -> String {…}
  }

and put this extension into framework “A”. Then, from framework “B”, I want to use this function from within a function f() and [somehow] figure out from the runtime what the bundle of “B” is, so that I can use it’s localized strings file.

I understand that from within the locate() method, I can use #function and from it, parse out the module name of “A” and then use the correspondence between module names and framework names to figure out the bundle of “A”. BUT what I want here is the bundle resource for “B”, not “A”.

You should be able to use a trick similar to the one that assert() uses to collect file and line numbers:

    func locate(caller: StaticString = #function) {
        // `caller` is the caller's #function
    }

--
Greg Parker gparker@apple.com <mailto:gparker@apple.com> Runtime Wrangler

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Greg Parker) #7

On Darwin #dsohandle points to a Mach-O file header. You can pass that address to dyld but I don't see an easy way to pass it to NSBundle.

This might work:
1. Pass #dsohandle to dladdr()
2. Pass the dli_fname returned by dladdr() to NSBundle(path:).

···

On Dec 2, 2016, at 12:50 PM, Rick Aurbach <rlaurb@icloud.com> wrote:

Jordan,

I agree — #dsohandle is, indeed, little known. In fact, I’m having a devil of a time figuring out what it is and what I can do with it. It is clearly an UnsafeRawPointer, but to what??

Can you offer either a reference or a few lines of code that can help me get the information I need from it? [recall that I want the framework’s bundle so I can find it’s localized.strings file].

Cheers,

Rick Aurbach

On Dec 2, 2016, at 12:56 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Apple platforms, we'd probably prefer you use the little-known #dsoHandle, a magic pointer that's unique to the current dylib. Parsing out a module name seems incredibly brittle; the form of #function is not guaranteed to be stable or useful across Swift versions.

Jordan

On Dec 2, 2016, at 10:35, Rick Aurbach via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

That’s clever! Thank you; I’d probably never have thought of that.

Cheers,

Rick Aurbach

On Dec 2, 2016, at 12:25 PM, Greg Parker <gparker@apple.com <mailto:gparker@apple.com>> wrote:

On Dec 2, 2016, at 9:44 AM, Rick Aurbach via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Does anyone know if it is possible to do the following in Swift 3.x? (I’ll describe the issue abstractly first, then give the use-case.)

Consider two modules: A and B. A could be either the main module of an application or an embedded framework. B is a different embedded framework.

Now A contains an public extension of class X which contains a function f(). Inside B, there is a reference to X.f(). Now what I want to do in f() is to access information (a module name or bundle name or bundle ID or something) that allows me to construct a Bundle object referring to B, without f() having any external knowledge of the organization of the application.

The use-case I’m thinking about is a localization extension of String that works in a multi-framework application architecture without requiring the caller to specify the name of the framework and/or module.

I.e., I want to write

  extension String {
    func locate() -> String {…}
  }

and put this extension into framework “A”. Then, from framework “B”, I want to use this function from within a function f() and [somehow] figure out from the runtime what the bundle of “B” is, so that I can use it’s localized strings file.

I understand that from within the locate() method, I can use #function and from it, parse out the module name of “A” and then use the correspondence between module names and framework names to figure out the bundle of “A”. BUT what I want here is the bundle resource for “B”, not “A”.

You should be able to use a trick similar to the one that assert() uses to collect file and line numbers:

    func locate(caller: StaticString = #function) {
        // `caller` is the caller's #function
    }

--
Greg Parker gparker@apple.com <mailto:gparker@apple.com> Runtime Wrangler

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Jordan Rose) #8

Greg,

This is looking quite strange, because I didn’t see anything like what I expected.

Here’s the code that I’ve been using to test #dsohandle:

public extension String {
  
  public func localized(dsoHandle : UnsafeRawPointer = #dsohandle) {
    var dlInformation : dl_info = dl_info()
    let _ = dladdr(dsoHandle, &dlInformation)
    let path = String(describing: dlInformation.dli_fname!)

^ You asked for a string describing a pointer and you got one. :slight_smile: Try String(cString:) instead.

···

On Dec 2, 2016, at 15:36, Rick Aurbach <rlaurb@icloud.com> wrote:

    let bundle = Bundle(path: path)
  }
}

which is consistent with the following excerpt from the header file:

/*
* Structure filled in by dladdr().
*/
public struct dl_info {

    public var dli_fname: UnsafePointer<Int8>! /* Pathname of shared object */

    public var dli_fbase: UnsafeMutableRawPointer! /* Base address of shared object */

    public var dli_sname: UnsafePointer<Int8>! /* Name of nearest symbol */

    public var dli_saddr: UnsafeMutableRawPointer! /* Address of nearest symbol */

    public init()

    public init(dli_fname: UnsafePointer<Int8>!, dli_fbase: UnsafeMutableRawPointer!, dli_sname: UnsafePointer<Int8>!, dli_saddr: UnsafeMutableRawPointer!)
}
public typealias Dl_info = dl_info

public func dladdr(_: UnsafeRawPointer!, _: UnsafeMutablePointer<Dl_info>!) -> Int32
/* not POSIX */

I would have expected path to look something like a URL. However, here is what the debugger says (with a breakpoint on the “let bundle…” line:

<Capto_Annotation.png>

As you can see, dli_fname doesn’t look anything like the “pathname of the shared object”. Instead it looks more like the address where it was loaded. Which, unfortunately, doesn’t get me any further along.

Thoughts?

Has this gotten hairy (and time consuming) enough that I should handle this as a Technical Incident??

Cheers,

Rick Aurbach

On Dec 2, 2016, at 3:08 PM, Greg Parker <gparker@apple.com <mailto:gparker@apple.com>> wrote:

On Darwin #dsohandle points to a Mach-O file header. You can pass that address to dyld but I don't see an easy way to pass it to NSBundle.

This might work:
1. Pass #dsohandle to dladdr()
2. Pass the dli_fname returned by dladdr() to NSBundle(path:).

On Dec 2, 2016, at 12:50 PM, Rick Aurbach <rlaurb@icloud.com <mailto:rlaurb@icloud.com>> wrote:

Jordan,

I agree — #dsohandle is, indeed, little known. In fact, I’m having a devil of a time figuring out what it is and what I can do with it. It is clearly an UnsafeRawPointer, but to what??

Can you offer either a reference or a few lines of code that can help me get the information I need from it? [recall that I want the framework’s bundle so I can find it’s localized.strings file].

Cheers,

Rick Aurbach

On Dec 2, 2016, at 12:56 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Apple platforms, we'd probably prefer you use the little-known #dsoHandle, a magic pointer that's unique to the current dylib. Parsing out a module name seems incredibly brittle; the form of #function is not guaranteed to be stable or useful across Swift versions.

Jordan


(Rick Aurbach) #9

Jordan,

You are oh, so right! Thank you for helping me resolve a particularly major piece of stupidity on my part.

The following code seems (subject to testing, of course), achieve what I’m looking for:

public extension String {
  
  public func localized(dsoHandle : UnsafeRawPointer = #dsohandle) -> String {
    var dlInformation : dl_info = dl_info()
    let _ = dladdr(dsoHandle, &dlInformation)
    let path = String(cString: dlInformation.dli_fname)
    let url = URL(fileURLWithPath: path).deletingLastPathComponent()
    let bundle = Bundle(url: url)
    let str = bundle?.localizedString(forKey: self, value: self, table: nil)
    return str!
  }
}

Cheers,

Rick

···

On Dec 2, 2016, at 5:37 PM, Jordan Rose <jordan_rose@apple.com> wrote:

On Dec 2, 2016, at 15:36, Rick Aurbach <rlaurb@icloud.com <mailto:rlaurb@icloud.com>> wrote:

Greg,

This is looking quite strange, because I didn’t see anything like what I expected.

Here’s the code that I’ve been using to test #dsohandle:

public extension String {
  
  public func localized(dsoHandle : UnsafeRawPointer = #dsohandle) {
    var dlInformation : dl_info = dl_info()
    let _ = dladdr(dsoHandle, &dlInformation)
    let path = String(describing: dlInformation.dli_fname!)

^ You asked for a string describing a pointer and you got one. :slight_smile: Try String(cString:) instead.

    let bundle = Bundle(path: path)
  }
}

which is consistent with the following excerpt from the header file:

/*
* Structure filled in by dladdr().
*/
public struct dl_info {

    public var dli_fname: UnsafePointer<Int8>! /* Pathname of shared object */

    public var dli_fbase: UnsafeMutableRawPointer! /* Base address of shared object */

    public var dli_sname: UnsafePointer<Int8>! /* Name of nearest symbol */

    public var dli_saddr: UnsafeMutableRawPointer! /* Address of nearest symbol */

    public init()

    public init(dli_fname: UnsafePointer<Int8>!, dli_fbase: UnsafeMutableRawPointer!, dli_sname: UnsafePointer<Int8>!, dli_saddr: UnsafeMutableRawPointer!)
}
public typealias Dl_info = dl_info

public func dladdr(_: UnsafeRawPointer!, _: UnsafeMutablePointer<Dl_info>!) -> Int32
/* not POSIX */

I would have expected path to look something like a URL. However, here is what the debugger says (with a breakpoint on the “let bundle…” line:

<Capto_Annotation.png>

As you can see, dli_fname doesn’t look anything like the “pathname of the shared object”. Instead it looks more like the address where it was loaded. Which, unfortunately, doesn’t get me any further along.

Thoughts?

Has this gotten hairy (and time consuming) enough that I should handle this as a Technical Incident??

Cheers,

Rick Aurbach

On Dec 2, 2016, at 3:08 PM, Greg Parker <gparker@apple.com <mailto:gparker@apple.com>> wrote:

On Darwin #dsohandle points to a Mach-O file header. You can pass that address to dyld but I don't see an easy way to pass it to NSBundle.

This might work:
1. Pass #dsohandle to dladdr()
2. Pass the dli_fname returned by dladdr() to NSBundle(path:).

On Dec 2, 2016, at 12:50 PM, Rick Aurbach <rlaurb@icloud.com <mailto:rlaurb@icloud.com>> wrote:

Jordan,

I agree — #dsohandle is, indeed, little known. In fact, I’m having a devil of a time figuring out what it is and what I can do with it. It is clearly an UnsafeRawPointer, but to what??

Can you offer either a reference or a few lines of code that can help me get the information I need from it? [recall that I want the framework’s bundle so I can find it’s localized.strings file].

Cheers,

Rick Aurbach

On Dec 2, 2016, at 12:56 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Apple platforms, we'd probably prefer you use the little-known #dsoHandle, a magic pointer that's unique to the current dylib. Parsing out a module name seems incredibly brittle; the form of #function is not guaranteed to be stable or useful across Swift versions.

Jordan