0

I have the following method section

- (NSString*) GetPathForFolder:(int)folder inDomains:(int) domains {
    id cls = objc_getClass("NSFileManager");
    SEL defaultManagerSelector = @selector(defaultManager);
    SEL urlsForDirectoryInDomainsSelector = @selector(URLsForDirectory:inDomains:);

    id fileMangerHandle = objc_msgSend(cls, defaultManagerSelector);

    //NSArray<NSURL *>* notUsedArray = [fileMangerHandle URLsForDirectory:folder inDomains:domains];
    NSArray<NSURL *>* resultArray = (NSArray<NSURL *>*) objc_msgSend(fileMangerHandle, urlsForDirectoryInDomainsSelector, folder, domains);
    return [resultArray lastObject].absoluteString;
}

Calling this method with [self GetPathForFolder:5 inDomains:1] returns file:///Applications/ which is wrong

The moment I uncomment the NSArray<NSURL *>* notUsedArray.. line I get a different and correct value which just happens to be the same as the one from

What does the objective-c version of the call do that I'm not doing in my C version?

Update:

  1. I'm using objc_msgSend because this method will eventually be called from C# but it's just easier to first try it in objective-c and than start worrying about the interop part.
  2. I was using sel_registerName because when running this inside of C#, I'm going to have to do my own registration.

More about Interop between C# and objective-c here. And also a java version of what I'm trying to understand is here.

Bobby Tables
  • 2,953
  • 7
  • 29
  • 53
  • 2
    *Why* are you using objc_msgSend? Compare https://stackoverflow.com/questions/17263354/why-shouldnt-you-use-objc-msgsend-in-objective-c. And why do you register *new* selectors? Shouldn't that be @selector(defaultManager) etc. ? – Martin R Nov 20 '18 at 12:57
  • @MartinR, I've updated my question. I'm trying to understand how interop between C#/Java and objective-c works. That's the reason I'm interested in objc_msgSend. And I've have already read why I should not use this method, but the goal here is to write C# which in turn makes use of the ios native api. I've also updated the code to use `SEL` – Bobby Tables Nov 20 '18 at 16:06
  • 2
    Do you get the same problem if you pass the values through instead of using `NSLibraryDirectory` or `NSUserDomainMask`? It seems to me that your parameters as `int` don't match the contents of the `enum` as `NSUInteger`. – Ian MacDonald Nov 20 '18 at 16:13

1 Answers1

1

You can’t use objc_msgSend like this, it is not a variadic function, even though it’s prototype suggests it. The compiler needs to use the same calling convention that the method is expecting. For this it needs to know the exact types of all parameters. You can tell it by casting objc_msgSend to a function pointer type with the correct parameters.

So for this case you would use something like this:

typedef NSArray<NSURL *> *(*UrlsForDirectoryType)(id, SEL, NSUInteger, NSUIteger);
NSArray<NSURL *>* resultArray = ((UrlsForDirectoryType)objc_msgSend)(fileMangerHandle, urlsForDirectoryInDomainsSelector, folder, domains);

The typedef is optional, of course, but then that makes the whole thing even harder to read.

To expose this to a different programming language you could also write regular C functions in Objective-C and call those. This is much easier to do than dealing with the runtime directly.

Sven
  • 22,475
  • 4
  • 52
  • 71
  • Thanks for the answer really got me headed in the correct direction. Do you think you could elaborate a bit on your last point "To expose this to a different programming language you could also write regular C functions in Objective-C and call those" – Bobby Tables Nov 21 '18 at 15:23
  • I've finally figured how to make it work. The problem actually ended up being the prototype of the method it actually needs to be `- (NSString*) GetPathForFolder:(long)folder inDomains:(long) domains`. And I didn't need to cast `objc_msgSend` to anything. The cast per my understanding is because of [this](https://stackoverflow.com/questions/24922913/too-many-arguments-to-function-call-expected-0-have-3). Would still appreciate a bit of insight though. – Bobby Tables Nov 21 '18 at 17:05
  • The cast is definitely needed. There might be situations where it works without, but in general it won't. For variadic functions *default argument promotion* happens which will first convert `char` and `short` to `int` and `float` to `double`. Also structs might be handled differently. – Sven Nov 21 '18 at 17:28