2

What should the syntax be to call the MacOS' os_log from C# in a .NET Core console app?

Based on
https://developer.apple.com/documentation/os/os_log
and
How to use iOS OSLog with Xamarin?
and
https://opensource.apple.com/source/xnu/xnu-4903.221.2/libkern/os/log.h.auto.html
I was expecting something like this:

using System.Runtime.InteropServices;

namespace Foo 
{
 class Program
 {
  [DllImport("__Internal", EntryPoint = "os_log_create")]
  private static extern IntPtr os_log_create(string subsystem, string category);

  [DllImport("__Internal", EntryPoint = "os_log")]
  private static extern void os_log(IntPtr log, string format, string message);

  static void Main(string[] args)
  {  
   IntPtr log = os_log_create("some.bundle.id", "SomeCategory");
   os_log(log, "%s", "Test!");
  }
 }
}

However, when I try to run this on my Mac I get a System.DllNotFoundException that says Unable to load shared library '__Internal' or one of its dependencies... .

Any help with this issue or P/Invoke between C# and MacOS would be very helpful, thanks!

  • 2
    Xamarin is Mono, which is quite a different runtime from .NET Core. Try importing `libSystem.dylib` instead. (Disclaimer: I have no experience developing on a Mac whatsoever. This may well still not work because these functions are really macros calling `_os_log_internal`, and it's icky because it involves varargs.) – Jeroen Mostert Dec 10 '18 at 18:59
  • Thanks! That's a step forward, it now loads and gets stuck on "os_log" with "System.EntryPointNotFoundException" saying "unable to find an entry point named 'os_log' in shared library 'libSystem.dylib'". By the way - how did you tell I should be calling libSystem.dylib ?? Maybe it's a dumb question but I couldn't find the actual dylib name anywhere -.- ... – Jacek Kołodziejek Dec 10 '18 at 19:04
  • 2
    Yes, there's no actual function called `os_log` in the system, just one called `_os_log_internal` (and some others). Xamarin probably uses an intermediate layer that conveniently generates C/Objective-C based wrappers for the macro definitions. `libSystem.dylib` can be found in the [docs](https://learn.microsoft.com/dotnet/standard/native-interop) when it gives a sample for MacOS, and with that name in hand you can find ample references to it on other pages. – Jeroen Mostert Dec 10 '18 at 19:11
  • Okay, thanks for the hint. However, I still don't know how to perform the logging. @JeroenMostert , any further hints on that please? Anyone? Maybe I should be logging with something else entirely? – Jacek Kołodziejek Dec 11 '18 at 14:16
  • I wish I could help, but it'd involve getting a Mac, or at least a VM of some sort, along with some copious spare time. I have plenty of experience P/Invoking on Windows, none with MacOS, and varargs functions are always "interesting". What could help you is a Xamarin/Mono dev who can explain how this works in Xamarin and how to port those bits to .NET Core, but I'm not friends with one of those. In another day or so this question will become eligible for a bounty; if there are still no eyeballs by then I can put one up. It may help. – Jeroen Mostert Dec 11 '18 at 14:52
  • Understood, thanks! Anyone else maybe? :p – Jacek Kołodziejek Dec 11 '18 at 14:53

1 Answers1

8

Macro os_log

In contrast to the os_log_create function, os_log is a macro, as already mentioned in the comments.

So if you would write in C:

os_log(log, "%{public}s", "Test!");

It would finally call a function named _os_log_impl, but the first parameter of that would be a pointer __dso_handle to which we don't have access from the managed side.

Possible Solution

But you don't have to do without the new logging system from Apple. One possibility is to create a dynamic library that provides a defined API that can easily be called from the managed C# code.

How to Create a Dynamic Library in Xcode

It is easy to create a dynamic library in Xcode:

  • choose in XCode <File/New Project>

  • choose Library template in the macOS section

  • use Type Dynamic

Minimal Example

A minimal .c example for our own Logging library might look like this:

 #include <os/log.h>

 extern void Log(os_log_t log, char *message) {
     os_log(log, "%{public}s", message);
 }

Calling from .Net

I took your source and only slightly modified it:

using System;
using System.Runtime.InteropServices;

namespace Foo 
{
    class Program
    {
        [DllImport("System", EntryPoint = "os_log_create")]
        private static extern IntPtr os_log_create(string subsystem, string category);

        [DllImport("Logging", EntryPoint = "Log")]
        private static extern void Log(IntPtr log, string msg);

        static void Main(string[] args)
        {  
            IntPtr log = os_log_create("some.bundle.id", "SomeCategory");
            Log(log, "Test!");
        }
    }
}

The dynamic library created with Xcode has the name Logging. Our in C created logging function is named Log here.

Of course you can design the API as comfortable as you want, this should be a minimal example that is as close to the question as possible.

Output in Console Utility

The output in the Console utility (if you filter for some.bundle.id) would look like this:

output in Console utility

Stephan Schlecht
  • 26,556
  • 1
  • 33
  • 47
  • This looks awesome! I will take a shot at it next week! Thanks! – Jacek Kołodziejek Dec 15 '18 at 17:29
  • Hey Chief, I tried to reproduce your steps but I got a little stuck on your "minimal example" (I've never used XCode and I haven't touched C for 15 years :p). It probably is simple and I'll be able to figure it out once I find 30 minutes, I just had to focus on sth else... in the meantime, if you know any good guides on C/XCode for dummies, I'd appreciate it ;) – Jacek Kołodziejek Dec 18 '18 at 19:09
  • 2
    probably easier if I show it in a short screencast: 3 minutes for creating the dylib and a test run: https://www.software7.biz/iertzhghbvldhzh/CreateLoggingLib.mp4 – Stephan Schlecht Dec 18 '18 at 21:11
  • unbelievable - I have never received such amazing support from anyone. You've helped me a TON, Sir. My faith in Stack Overflow was singlehandedly restored :) – Jacek Kołodziejek Dec 18 '18 at 21:21