Search code examples
c#.net.net-corelibnotify

C#: Get values from libnotify c struct


I am trying to access the c libnotify from my c# code to use libnotify on my linux laptop with dotnet core.
But everytime there is a problem to get the values from the lib.

This is the problematic c code:

typedef struct _NotifyNotification NotifyNotification;
typedef struct _NotifyNotificationPrivate NotifyNotificationPrivate;

struct _NotifyNotification
{
    /*< private >*/
    GObject                    parent_object;

    NotifyNotificationPrivate *priv;
};

struct _NotifyNotificationPrivate
{
    guint32         id;
    char           *app_name;
    char           *summary;
    char           *body;

    /* NULL to use icon data. Anything else to have server lookup icon */
    char           *icon_name;

    /*
     * -1   = use server default
     *  0   = never timeout
     *  > 0 = Number of milliseconds before we timeout
     */
    gint            timeout;

    GSList         *actions;
    GHashTable     *action_map;
    GHashTable     *hints;

    gboolean        has_nondefault_actions;
    gboolean        updates_pending;

    gulong          proxy_signal_handler;

    gint            closed_reason;
};

NotifyNotification *
notify_notification_new (const char *summary,
                     const char *body,
                     const char *icon);

Now I created two structs in my c# code and an extern method:

    [StructLayout(LayoutKind.Explicit)]
    internal struct NotifyNotification
    {
        [FieldOffset(1)]
        public NotifyNotificationPrivate priv;
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct NotifyNotificationPrivate
    {
        [FieldOffset(0)]
        public uint id;

        [FieldOffset(1)]
        public IntPtr app_name;

        [FieldOffset(2)]
        public IntPtr summary;

        [FieldOffset(5)]
        public int timeout;
    }

    [DllImport("libnotify.so.4", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    internal static extern IntPtr notify_notification_new([MarshalAs(UnmanagedType.LPStr)] string summary,
                                                          [MarshalAs(UnmanagedType.LPStr)] string body,
                                                          [MarshalAs(UnmanagedType.LPStr)] string icon);

With this code I cast everything to the structs:

NotifyNotification no = (NotifyNotification) Marshal.PtrToStructure(not, typeof(NotifyNotification));
Console.WriteLine(Marshal.PtrToStringAnsi(no.priv.summary));

The basics are working and I can call other functions from the libnotify with the pointer from the notify_notification_new-method. But in the last line, with the WriteLine, the debugger says:

The program '...dll' has exited with code 0 (0x00000000).

There is no exception and no error. Whats going wrong? It is a problem with dotnet core? Because it is still in beta?

How can I get the text from the properties app_name, summary, body??

Thank you very much in advance for your help.


Solution

  • [StructLayout(LayoutKind.Explicit)] says "dear compiler, I know what I'm doing, just... deal with it". Unfortunately, you didn't know what you were doing. [FieldOffset] takes the offset in bytes, not members.

    struct _NotifyNotification
    {
        /*< private >*/
        GObject                    parent_object;
    
        NotifyNotificationPrivate *priv;
    };
    

    GObject, presumably, is a pointer type. That means it takes up either 4 bytes (x86) or 8 bytes (amd64). Since .NET Core for Linux is currently only supported on amd64, it's 8 bytes. (Unless it's some other primitive structure).

    [FieldOffset(1)] says "start reading 1 byte after the pointer".

    Your next problem comes in that in C the struct is declared to have two members, the second one being a pointer. Your C# struct says the second member is itself a struct.

    So if the memory at the address pointed to by notify_notification_new looked like

    00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
    

    You read

    id = [01 02 03 04] => 0x04030201 (because Little-Endian)
    app_name = [02 03 04 05 06 07 08 09] => 0x0908070605040302
    summary = [03 04 05 06 07 08 09 0A] => 0x0A09080706050403
    timeout = [06 07 08 09] => 0x09080706
    

    Much better would be to use Sequential layout:

    [StructLayout(LayoutKind.Sequential)]
    internal struct NotifyNotification
    {
        private IntPtr parent_object;
        public IntPtr priv;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    internal struct NotifyNotificationPrivate
    {
        public uint id;
        public IntPtr app_name;
        public IntPtr summary;
        private IntPtr body;
        private IntPtr icon_name;
        public int timeout;
        // If you're only ever reading one of these structures
        // you can skip the rest.  If you ever allocate one, be sure to
        // include all of the fields so the right amount of memory is created
    }
    

    Then:

    NotifyNotification no = (NotifyNotification) Marshal.PtrToStructure(not, typeof(NotifyNotification));
    NotifyNotificationPrivate noPriv = (NotifyNotificationPrivate) Marshal.PtrToStructure(no.priv, typeof(NotifyNotificationPrivate));
    Console.WriteLine(Marshal.PtrToStringAnsi(noPriv.summary));
    

    Edit: Though, the most predictable approach is to make a C library (or C++ with extern "C" declarations) which avoids copying/interpreting the structures altogether (it lets the C compiler take care of that):

    extern "C" char* ReadNotificationSummary(NotifyNotification* notification)
    {
        if (notification == nullptr || notification.priv == nullptr)
            return nullptr;
    
        return notification.priv->summary;
    }
    

    The matching C# declaration on that would be to have the function declared as returning IntPtr and to pass that still to Marshal.PtrToStringAnsi; because if you declare it as returning a string the GC will think it's supposed to clean up the memory when the string goes out of scope.