Without Objective-C or Swift, how can a display name be retrieved from CGDirectDisplayID in C or C++?

I'm trying to get a display name from CGDirectDisplayID in pure C++ (or C.)

I can do it in Objective-C++, similar to this:

#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <IOKit/graphics/IOGraphicsLib.h>
#import <Cocoa/Cocoa.h>

#include <string>


bool getDisplayNameForDispID(CGDirectDisplayID dispID,
                             std::string& strOutName)
{
    bool bRes = false;
    
    strOutName.clear();
    
    NSArray *screens = [NSScreen screens];

    for (NSScreen *screen in screens)
    {
        NSDictionary* screenDictionary = [screen deviceDescription];
        if(screenDictionary)
        {
            NSNumber* screenID = [screenDictionary objectForKey:@"NSScreenNumber"];
            if(screenID)
            {
                CGDirectDisplayID aID = [screenID unsignedIntValue];
                
                if(aID == dispID)
                {
                    //Got it
                    NSString* pName = [screen localizedName];
                    
                    strOutName.assign([pName UTF8String], [pName lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);

                    bRes = true;
                    
                    break;
                }
            }
        }
    }    

    return bRes;
}

However, I don't want to add this sluggish Objective-C code to my project merely to accomplish this one task. In low-level C, there must be a method to do that.

I am aware that there is a whole topic devoted to it. As CGDisplayIOServicePort is deprecated and just returns 0 and any following calls do thing, kindly take notice that none of the workarounds provided work for macOS Ventura.

Hey Veerahkr, welcome to the site

I think your asking this question in the wrong place. You explicitly ask for solutions without Objective C or Swift, but ... these are the Swift forums :thinking:. StackOverflow would be a better fit for this.

Anyway, I'll point you in the right direction at least: If you insist on not having any Objective C code directly, you can still call Objective C APIs from C using the Objective C runtime APIs, e.g.:

Class nsScreen = objc_lookUpClass("NSScreen");
objc_object *screens = objc_msgSend(nsScreen, sel_getUid("screens"));

// Desugaring "fast enumeration" is particularly knarly.
// for (NSScreen *screen in screens)
objc_object *enumerator = objc_msgSend(screens, sel_getUid("objectEnumerator"));
objc_object *screen;
while ((screen = objc_msgSend(enumerator, sel_getUid("nextObject")) != nil) {
    // ...
}
// ...

You'll find that it's much more "sluggish" than Objective C code (which is really just syntactic sugar for these kinds of function calls), because you need to cast everything all the time.

I also totally forgot to add in manually memory management. You'll need to retain/release things a whole bunch. It's all really quite a mess. If you can manage it, I'd strongly recommend just compiling this as Objective C.

2 Likes

I agree with AlexanderM that questions specifically asking about not-Swift are not ideal for the Swift forums, but I also wanted to note that this is not inherently true. Some capabilities simply aren't exposed in C; C is not "more fundamental" in any way than Swift or ObjC.

6 Likes

I think you're interpreting too much into the foreign-looking Objective-C syntax. See Objective-C less as a language of its own, and more like a convention for exposing system API in a stable way that allows Apple to update the internals without it affecting already-compiled executables. Like COM on Windows. Even Swift uses Objective-C conventions for public objects. Given that's what it has to do, ObjC is actually very fast and optimized.

The example that uses objc_msgSend is actually likely slower than the "slow, sluggish ObjC". ObjC can get help from the linker and loader to look up symbols like classes, methods etc. once at load time, whereas the C code has to do it at runtime (using the objc_lookUpClass() and sel_getUid() calls). The ObjC compiler knows more about the structure of the program and can optimize it better than the C compiler, which doesn't know what's special about objc_msgSend() etc.

Also, it might be helpful to know that you can "hide" Objective-C(++) from the rest of your program:

  1. Create a header that only contains C/C++ symbols, no Objective-C.
  2. If you need a data structure that keeps a pointer to an ObjC-object, forward-declare the ObjC-type, so that it looks like a struct pointer to C++, but like a class to Objective-C, E.g.
#if __OBJC__
@class Foo;
#else
struct Foo;
#endif
  1. Include the actual ObjC headers only in the .m/.mm file.
  2. Write a function or C++ class in the .m/.mm file that does the actual work, by calling upon Objective-C.

This way, the rest of your program can stay simple, cross-platform C++.

1 Like