C#:从 libnotify c 结构中获取值

C#: Get values from libnotify c struct

我正在尝试从我的 c# 代码访问 c libnotify,以便在我的 linux 带有 dotnet 核心的笔记本电脑上使用 libnotify。
但是每次从库中获取值时都会出现问题。

这是有问题的 C 代码:

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);

现在我在我的 C# 代码中创建了两个结构和一个外部方法:

    [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);

使用这段代码,我将所有内容都转换为结构:

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

基础工作正常,我可以使用 notify_notification_new 方法中的指针从 libnotify 调用其他函数。 但在最后一行,调试器使用 WriteLine 表示:

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

没有异常,没有错误。怎么了?这是 dotnet 核心的问题?因为它仍处于测试阶段?

如何从属性app_name、摘要、正文中获取文本??

非常感谢您的帮助。

[StructLayout(LayoutKind.Explicit)] 表示 "dear compiler, I know what I'm doing, just... deal with it"。不幸的是,你不知道自己在做什么。 [FieldOffset] 采用以字节为单位的偏移量,而不是成员。

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

    NotifyNotificationPrivate *priv;
};

GObject,推测是指针类型。这意味着它占用 4 个字节 (x86) 或 8 个字节 (amd64)。由于 Linux 的 .NET Core 当前仅在 amd64 上受支持,因此它是 8 个字节。 (除非是其他一些原始结构)。

[FieldOffset(1)] 说 "start reading 1 byte after the pointer"。

你的下一个问题是在 C 中声明结构有两个成员,第二个是指针。你的 C# 结构说第二个成员本身就是一个结构。

所以如果notify_notification_new指向的地址的内存看起来像

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

您已阅读

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

使用顺序布局会更好:

[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
}

然后:

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

编辑: 虽然,最可预测的方法是制作一个 C 库(或带有 extern "C" 声明的 C++),它完全避免了 copying/interpreting 结构(它让 C 编译器来处理):

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

    return notification.priv->summary;
}

与之匹配的 C# 声明是将函数声明为返回 IntPtr 并将其传递给 Marshal.PtrToStringAnsi;因为如果你声明它返回一个 string GC 会认为它应该在字符串超出范围时清理内存。