Glib学习(22) 主事件循环 The Main Event Loop

glib源码下载:http://ftp.gnome.org/pub/gnome/sources/glib/
glib帮助文档:https://developer.gnome.org/glib/

主事件循环 - 管理所有可用的事件源

Includes
#include <glib.h>
描述
主事件循环管理GLib和GTK +应用程序的所有可用事件源。 这些事件可以来自任意数量的不同类型的源,例如文件描述符(普通文件,管道或套接字)和超时。 还可以使用g_source_attach()添加新类型的事件源。
为了允许在不同的线程中处理多个独立的源集,每个源与GMainContext相关联。 一个GMainContext只能在单个线程中运行,但可以将源添加到其中并在其他线程中删除。
为每个事件源分配优先级。 默认优先级G_PRIORITY_DEFAULT为0.小于0的值表示更高的优先级。 大于0的值表示较低的优先级。 来自高优先级源的事件始终在来自较低优先级源的事件之前处理。
还可以添加空闲函数,并为其分配优先级。 只要没有准备好处理具有更高优先级的事件,就会运行这些操作。
GMainLoop数据类型表示主事件循环。 使用g_main_loop_new()创建GMainLoop。 添加初始事件源后,将调用g_main_loop_run()。 这会不断检查每个事件源的新事件并发送它们。 最后,处理来自其中一个源的事件会导致调用g_main_loop_quit()以退出主循环,并返回g_main_loop_run()。
可以递归地创建GMainLoop的新实例。 在显示模态对话框时,这通常用于GTK +应用程序。 请注意,事件源与特定的GMainContext相关联,并将针对与该GMainContext关联的所有主循环进行检查和分派。
GTK +包含一些这些函数的包装器,例如 gtk_main(),gtk_main_quit()和gtk_events_pending()。
创建新的源类型
GMainLoop功能的一个不同寻常的功能是除了内置类型的事件源之外,还可以创建和使用新类型的事件源。 新事件源类型用于处理GDK事件。 通过从GSource结构“派生”创建新的源类型。 派生类型的源由一个结构表示,该结构具有GSource结构作为第一个元素,以及特定于新源类型的其他元素。 要创建新源类型的实例,请调用g_source_new()传递派生结构的大小和函数表。 这些GSourceFuncs确定新源类型的行为。
新的源类型基本上以两种方式与主上下文交互。 它们在GSourceFuncs中的准备函数可以设置超时,以确定主循环在再次检查源之前将休眠的最长时间。 此外,或者,源也可以将文件描述符添加到主上下文使用g_source_add_poll()检查的集合中。
自定义主循环迭代
可以使用g_main_context_iteration()运行GMainContext的单次迭代。 在某些情况下,需要更详细地控制主循环的细节如何工作,例如,将GMainLoop与外部主循环集成时。 在这种情况下,您可以直接调用g_main_context_iteration()的组件函数。 这些函数是g_main_context_prepare(),g_main_context_query(),g_main_context_check()和g_main_context_dispatch()。
Main Context的状态
这些功能的操作最好从状态图中看出,如该图所示。

在UNIX上,GLib主循环与fork()不兼容。 任何使用mainloop的程序都必须从子进程执行exec()或exit()而不返回mainloop。
内存管理源
传递给GSource的用户数据的内存管理有两种选择,可以在调用时传递给它的回调。 在调用g_timeout_add(),g_timeout_add_full(),g_idle_add()等时提供此数据,更一般地,使用g_source_set_callback()。 该数据通常是“拥有”超时或空闲回调的对象,例如小部件或网络协议实现。 在许多情况下,在此拥有对象被销毁之后调用回调是错误的,因为这会导致使用释放的内存。
第一个也是首选的选项是存储由g_timeout_add()或g_source_attach()等函数返回的源ID,并在最终确定拥有对象时使用g_source_remove()从主上下文中显式删除该源。 这确保了只有在对象仍然活动时才能调用回调。
第二个选项是在回调中保持对对象的强引用,并在回调的GDestroyNotify中释放它。 这可确保对象保持活动状态,直到源完成后,保证在最后一次调用之后。 GDestroyNotify是传递给GSource函数的“完整”变体的另一个回调(例如,g_timeout_add_full())。 它在源完成时调用,用于释放这样的引用。
第二种方法的一个重要警告是,如果在调用GSource之前停止主循环,它将使对象无限期地保持活动,这可能是不合需要的。
 

该部分有很多函数,但是能力有限只能使用皮毛,下面就简单的使用一下定时器的功能。其实,真正最有用的应该是如何自己去创建source类型,和自己去控制事件调度。

函数也就条最简单的一些进行翻译和举例。

Functions

g_main_loop_new ()

创建一个新的GMainLoop结构。

参数

GMainContext(如果为NULL,将使用默认上下文)。

is_running

设置为TRUE表示循环正在运行。 这不是很重要,因为无论如何调用g_main_loop_run()都会将其设置为TRUE。

g_main_loop_run ()

运行主循环,直到在循环上调用g_main_loop_quit()。 如果为循环的GMainContext的线程调用它,它将处理来自循环的事件,否则它将只是等待。

g_main_loop_quit ()

停止运行GMainLoop。 对循环的任何g_main_loop_run()调用都将返回。

请注意,g_main_loop_quit()被调用时,已调度的源将仍然执行。

g_main_loop_is_running ()

检查主循环当前是否通过g_main_loop_run()运行。

返回值

如果当前正在运行主循环,则为TRUE。

g_main_loop_get_context ()

返回循GMainContext的loop。

g_main_context_new ()

创建一个新的GMainContext结构。

g_main_context_default ()

返回全局默认主上下文。 当未明确指定主循环时,这是用于主循环函数的主要上下文,并且对应于“main”主循环。 另请参见g_main_context_get_thread_default()。

返回值

全局默认main context。

g_main_context_acquire ()

试图成为指定上下文的所有者。 如果某个其他线程是上下文的所有者,则立即返回FALSE。 所有权是正确的递归:所有者可以再次要求所有权,并且在g_main_context_release()被调用多次g_main_context_acquire()时将释放所有权。

Y您必须是上下文的所有者才能调用g_main_context_prepare(),g_main_context_query(),g_main_context_check(),g_main_context_dispatch()。

返回值

如果操作成功,则为TRUE,此线程现在是上下文的所有者。

g_main_context_release ()

使用g_main_context_acquire()释放此线程先前获取的上下文的所有权。 如果多次获取上下文,则只有在调用g_main_context_release()多次时才会释放所有权。

g_main_context_is_owner ()

确定此线程是否包含此GMainContext的(递归)所有权。 在等待可能阻止获取上下文所有权的另一个线程之前,这很有用。

返回值

如果当前线程是上下文的所有者,则为TRUE。

g_main_current_source ()

返回此线程的当前触发源。

返回值

当前触发源或NULL。

g_timeout_source_new ()

创建新的超时源。

源最初不会与任何GMainContext相关联,必须在执行之前将其添加到g_source_attach()中。

给定的间隔是monotonic时间,而不是clock时间。 请参阅g_get_monotonic_time()。

参数

interval

超时间隔(以毫秒为单位)。

返回值

新创建的超时源

g_timeout_source_new_seconds ()

创建新的超时源。

源最初不会与任何GMainContext相关联,必须在执行之前将其添加到g_source_attach()中。

此超时源的调度粒度/准确度将以秒为单位。

以单调时间给出的间隔,而不是挂钟时间。 请参阅g_get_monotonic_time()。

参数

interval

超时间隔(秒)

返回值

新创建的超时源

g_timeout_add ()

设置要定期调用的函数,默认优先级为G_PRIORITY_DEFAULT。 重复调用该函数,直到它返回FALSE,此时超时自动销毁,并且不会再次调用该函数。 对函数的第一次调用将在第一个间隔结束时进行。

请注意,由于处理其他事件源,超时功能可能会延迟。 因此,不应依赖它们来进行精确计时。在每次调用超时函数之后,将根据当前时间和给定间隔重新计算下一次超时的时间(它不会尝试“赶上”延迟时间丢失)。

有关如何处理数据的返回值和内存管理的详细信息,请参阅源的内存管理。

如果你想在“秒”范围内有一个计时器而不关心第一次调用计时器的确切时间,请使用g_timeout_add_seconds()函数; 此功能允许更多优化和更高效的系统功耗。

这在内部使用g_timeout_source_new()创建一个主循环源,并使用g_source_attach()将其附加到全局GMainContext,因此将在运行该主上下文的任何线程中调用回调。 如果需要更好的控制或使用自定义主上下文,则可以手动执行这些步骤。

给定的间隔是monotonic时间,而不是clock时间。 请参阅g_get_monotonic_time()。

参数

调用函数之间的时间,以毫秒为单位(1/1000秒)

返回值

事件源的ID(大于0)。

g_timeout_add_full ()

设置要以给定优先级定期调用的函数。 重复调用该函数,直到它返回FALSE,此时超时自动销毁,并且不会再次调用该函数。 超时被销毁时调用notify函数。 对函数的第一次调用将在第一个间隔结束时进行。

请注意,由于处理其他事件源,超时功能可能会延迟。 因此,不应依赖它们来进行精确计时。 在每次调用超时函数之后,将根据当前时间和给定间隔重新计算下一次超时的时间(它不会尝试“赶上”延迟时间丢失)。

有关如何处理数据的返回值和内存管理的详细信息,请参阅源的内存管理。

这在内部使用g_timeout_source_new()创建一个主循环源,并使用g_source_attach()将其附加到全局GMainContext,因此将在运行该主上下文的任何线程中调用回调。 如果需要更好的控制或使用自定义主上下文,则可以手动执行这些步骤。

以monotonic时间给出的间隔,而不是clock时间。 请参阅g_get_monotonic_time()。

参数

priority

超时源的优先级。 通常,这将在G_PRIORITY_DEFAULT和G_PRIORITY_HIGH之间的范围内。

interval

调用函数之间的时间,以毫秒为单位(1/1000秒)

notify

删除超时时调用的函数,或NULL。

返回值

事件源的ID(大于0)。

g_timeout_add_seconds ()

设置要定期调用的函数,使用默认优先级G_PRIORITY_DEFAULT。 重复调用该函数,直到它返回FALSE,此时超时自动销毁,并且不会再次调用该函数。

这在内部使用g_timeout_source_new_seconds()创建一个主循环源,并使用g_source_attach()将其附加到主循环上下文。 如果需要更好的控制,可以手动执行这些步骤。 另请参阅g_timeout_add_seconds_full()。

请注意,定时器的第一次调用对于一秒钟的超时可能不准确。 如果您需要更精确的并且具有这样的超时,则可能需要使用g_timeout_add()。

有关如何处理数据的返回值和内存管理的详细信息,请参阅源的内存管理。

The interval given is in terms of monotonic time, not wall clock time. See g_get_monotonic_time().

给定的间隔是monotonic时间,而不是clock时间。 请参阅g_get_monotonic_time()。

参数

interval

调用函数之间的时间,以秒为单位

返回值

事件源的ID(大于0)。

g_idle_source_new ()

创建一个新的空闲源。

源最初不会与任何GMainContext相关联,必须在执行之前将其添加到g_source_attach()中。 请注意,与其他具有默认优先级G_PRIORITY_DEFAULT的源相比,空闲源的默认优先级为G_PRIORITY_DEFAULT_IDLE。

返回值

新创建的空闲源

g_idle_add ()

每当没有更高优先级事件挂起到默认主循环时,添加要调用的函数。 该函数具有默认的空闲优先级G_PRIORITY_DEFAULT_IDLE。 如果函数返回FALSE,它将自动从事件源列表中删除,不会再次调用。

有关如何处理数据的返回值和内存管理的详细信息,请参阅源的内存管理。

这在内部使用g_idle_source_new()创建一个主循环源,并使用g_source_attach()将其附加到全局GMainContext,因此将在运行该主上下文的任何线程中调用回调。 如果需要更好的控制或使用自定义主上下文,则可以手动执行这些步骤。

返回值

事件源的ID(大于0)。

g_source_attach ()

将GSource添加到上下文,以便在该上下文中执行。 通过调用g_source_destroy()删除它。

参数

context

GMainContext(如果为NULL,将使用默认上下文)。

返回值

GMainContext中源的ID(大于0)。

g_source_destroy ()

从其GMainContext中删除源(如果有),并将其标记为已销毁。 源不能随后添加到另一个上下文。 在已经从其上下文中删除的源上调用它是安全的。

g_source_is_destroyed ()

返回源是否已被销毁。

当您从空闲处理程序中操作对象时,这很重要,但可能在分配空闲处理程序之前释放了对象。

static gboolean

idle_callback (gpointer data)

{

  SomeWidget *self = data;

  

  GDK_THREADS_ENTER ();

  // do stuff with self

  self->idle_id = 0;

  GDK_THREADS_LEAVE ();

  

  return G_SOURCE_REMOVE;

}



static void

some_widget_do_stuff_later (SomeWidget *self)

{

  self->idle_id = g_idle_add (idle_callback, self);

}



static void

some_widget_finalize (GObject *object)

{

  SomeWidget *self = SOME_WIDGET (object);

  

  if (self->idle_id)

    g_source_remove (self->idle_id);

  

  G_OBJECT_CLASS (parent_class)->finalize (object);

}

如果在空闲处理程序因回调中空闲后使用而触发之前销毁小部件,则在多线程应用程序中将失败。 对于这个特定问题,解决方案是检查源是否已在回调中销毁。

static gboolean

idle_callback (gpointer data)

{

  SomeWidget *self = data;

 

  GDK_THREADS_ENTER ();

  if (!g_source_is_destroyed (g_main_current_source ()))

    {

      // do stuff with self

    }

  GDK_THREADS_LEAVE ();

 

  return FALSE;

}

从GSource附加的GMainContext获取的线程以外的线程调用此函数通常是多余的,因为在此函数返回后可能会立即销毁源。 但是,一旦源被销毁,它就不能被销毁,所以这个函数可以用于任何线程的机会性检查。

返回值

如果源已被破坏,则为TRUE

g_source_set_priority ()

设置源的优先级。 在运行主循环时,如果准备好调度源,则将调度源,并且不准备分派更高(数值更小)优先级的源。

子源始终与其父源具有相同的优先级。 一旦将源添加为另一个源的子级,就不允许更改源的优先级。

g_source_get_priority ()

获取源的优先级。

返回值

来源的优先权

g_source_get_id ()

返回特定源的数字ID。 源的ID是正整数,其在特定主循环上下文中是唯一的。 从ID到源的反向映射由g_main_context_find_source_by_id()完成。

返回值

源的ID(大于0)

g_source_get_name ()

获取源的名称,用于调试和分析。 如果从未使用g_source_set_name()设置该名称,则该名称可能为NULL。

返回值

来源的名称

g_source_set_name ()

设置源的名称,用于调试和分析。 名称默认为NULL。

源名称应以人类可读的方式描述源的作用。 例如,“X11事件队列”或“GTK +重新绘制空闲处理程序”或其他任何内容。

允许多次调用此函数,但由于潜在的性能影响,不建议使用此函数。 例如,可以更改GSourceFuncs的“check”函数中的名称,以包含源名称中的事件类型等详细信息。

如果更改名称时要小心,而另一个线程可能正在使用g_source_get_name()访问它; 该函数不复制该值,并且更改该值将释放它,而另一个线程可能正在尝试使用它。

g_source_set_name_by_id ()

使用其ID设置源的名称。

这是一个便利实用程序,用于从g_idle_add(),g_timeout_add()等的返回值设置源名称。

尝试设置不存在的源的名称是程序员错误。

更具体地说:在源被销毁之后可以重新发布源ID,因此使用具有可能已被移除的源ID的此函数永远无效。 一个例子是当使用g_idle_add()调度空闲以在另一个线程中运行时:空闲可能已经运行并且在其函数(现在无效的)源ID上被调用时被删除。 此源ID可能已重新发布,导致针对错误的源执行操作。

g_source_get_context ()

获取与源关联的GMainContext。

你可以在已被销毁的源上调用它,只要它附加的GMainContext仍然存在(在这种情况下它将返回该GMainContext)。 特别是,您始终可以在从g_main_current_source()返回的源上调用此函数。 但是在GMainContext被销毁的源上调用此函数是一个错误。

返回值

与源关联的GMainContext,如果尚未将上下文添加到源,则为NULL。

g_source_set_callback ()

设置源的回调函数。 源的回调函数从源的调度函数调用。

func的确切类型取决于源的类型;即。 你不应指望用数据作为第一个参数调用func。

有关如何处理数据内存管理的详细信息,请参阅源的内存管理。

通常,您不会使用此功能。 而是使用特定于您正在使用的源类型的函数。

GSourceFunc ()

指定传递给g_timeout_add(),g_timeout_add_full(),g_idle_add()和g_idle_add_full()的函数类型。

参数

user_data

传递给函数的数据,在使用上述函数之一创建源时设置

返回值

如果应删除源,则为FALSE。 G_SOURCE_CONTINUE和G_SOURCE_REMOVE是返回值的更令人难忘的名称。

g_source_get_time ()

获取检查此源时使用的时间。 调用此函数直接调用g_get_monotonic_time()的优点是,在检查多个源时,GLib可以缓存单个值,而不必重复获取系统monotonic时间。

这里的时间是系统monotonic时间(如果可用),或者其他一些合理的替代方案。 请参阅g_get_monotonic_time()。

g_source_remove ()

从默认主上下文中删除具有给定ID的源。 必须将g_source_destroy()用于添加到非默认主上下文的源。

GSource的ID由g_source_get_id()给出,或者由函数g_source_attach(),g_idle_add(),g_idle_add_full(),g_timeout_add(),g_timeout_add_full(),g_child_watch_add(),g_child_watch_add_full(),g_io_add_watch()返回。 和g_io_add_watch_full()。

尝试删除不存在的源是程序员错误。

更具体地说:在源被销毁之后可以重新发布源ID,因此使用具有可能已被移除的源ID的此函数永远无效。 一个例子是当使用g_idle_add()调度空闲以在另一个线程中运行时:空闲可能已经运行并且在其函数(现在无效的)源ID上被调用时被删除。 此源ID可能已重新发布,导致针对错误的源执行操作。

返回值

由于历史原因,此函数始终返回TRUE

Types and Values

GMainLoop

GMainLoop结构是一种不透明的数据类型,表示GLib或GTK +应用程序的主事件循环。

G_PRIORITY_HIGH

#define G_PRIORITY_HIGH            -100

将其用于高优先级事件源。

它不在GLib或GTK +中使用。

G_PRIORITY_DEFAULT

#define G_PRIORITY_DEFAULT          0

将其用于默认优先级事件源。

在GLib中,当使用g_timeout_add()添加超时函数时,将使用此优先级。 在GDK中,此优先级用于来自X服务器的事件。

G_PRIORITY_HIGH_IDLE

#define G_PRIORITY_HIGH_IDLE        100

用于高优先级空闲功能。

GTK +使用G_PRIORITY_HIGH_IDLE + 10来调整操作,使用G_PRIORITY_HIGH_IDLE + 20来重绘操作。 (这样做是为了确保在任何挂起的重绘之前处理任何挂起的调整大小,这样小部件就不会被不必要地重绘两次。)

G_PRIORITY_DEFAULT_IDLE

#define G_PRIORITY_DEFAULT_IDLE     200

用于默认优先级空闲功能。

在GLib中,在使用g_idle_add()添加空闲函数时使用此优先级。

G_PRIORITY_LOW

#define G_PRIORITY_LOW              300

将此用于非常低优先级的后台任务。

它不在GLib或GTK +中使用。

G_SOURCE_CONTINUE

#define G_SOURCE_CONTINUE       TRUE

使用此宏作为GSourceFunc的返回值,以将GSource保留在主循环中。

G_SOURCE_REMOVE

#define G_SOURCE_REMOVE         FALSE

使用此宏作为GSourceFunc的返回值,以从主循环中删除GSource。

 

示例代码:

#include <glib.h>
#include <glib/gprintf.h>

gboolean gsource_func (gpointer user_data)
{
    g_printf ("%s() in\n", __func__);
    GTimeVal result;
    g_get_current_time (&result);
    g_printf ("gsource_func %ld.%lds\n", result.tv_sec, result.tv_usec);
    int *data = (int *)user_data;
    (*data)++;
    g_printf ("user_data = %d\n", (*data));
    g_printf ("%s() out\n", __func__);
    if ((*data) == 10)
        return FALSE;
    return TRUE;
}

gboolean gsource_func1 (gpointer user_data)
{
    g_printf ("%s() in\n", __func__);
    GTimeVal result;
    g_get_current_time (&result);
    g_printf ("gsource_func1 %ld.%lds\n", result.tv_sec, result.tv_usec);
    g_printf ("%s() out\n", __func__);
    return TRUE;
}

gboolean gsource_idle (gpointer user_data)
{
    g_printf ("%s() in\n", __func__);

    g_printf ("%s() out\n", __func__);
    return FALSE;
}

void gdestroy_notify (gpointer data)
{
   g_printf ("%s() in\n", __func__);
   g_free (data);
   g_printf ("%s() out\n", __func__);
}

gpointer thread_func (gpointer data)
{
	g_printf ("%s() in\n", __func__);
    GMainContext *context = g_main_context_new ();
    GMainLoop *loop = g_main_loop_new (context, FALSE);
    
    g_printf ("context is %sowner\n", g_main_context_is_owner (context)?"":"not ");
    g_main_context_acquire (context);
    g_printf ("context is %sowner\n", g_main_context_is_owner (context)?"":"not ");
    
    GSource *source = g_timeout_source_new_seconds (2);
    guint sourceID = g_source_attach (source, context);
    g_source_set_name (source, "thread_timeout");
    g_printf ("source_get_name:%s ID:%d\n", g_source_get_name (source), sourceID);
    int *data1 = g_new0(int, 1);
    g_source_set_callback (source, gsource_func, data1, gdestroy_notify);
    
    g_main_loop_run (loop);
    
    g_printf ("%s() out\n", __func__);
}

gpointer thread_func1 (gpointer data)
{
	g_printf ("%s() in\n", __func__);
    GMainContext *context = g_main_context_new ();
    GMainLoop *loop = g_main_loop_new (context, FALSE);
    
    g_printf ("context is %sowner\n", g_main_context_is_owner (context)?"":"not ");
    g_main_context_acquire (context);
    g_printf ("context is %sowner\n", g_main_context_is_owner (context)?"":"not ");
    
    GSource *source = g_timeout_source_new_seconds (2);
    guint sourceID = g_timeout_add (2500, gsource_func1, NULL);
    g_source_set_name (source, "thread_timeout1");
    g_printf ("source_get_name:%s ID:%d\n", g_source_get_name (source), sourceID);
    
    g_main_loop_run (loop);
    
    g_printf ("%s() out\n", __func__);
}

int main(int argc, char **argv)
{
    g_printf ("%s() in\n", __func__);
    GThread *gthread = g_thread_new ("func", thread_func, NULL);
    GThread *gthread1 = g_thread_new ("func1", thread_func1, NULL);
    
    GMainLoop *loop = g_main_loop_new (NULL, FALSE);
    
    GSource *source = g_timeout_source_new (1500);//1.5s
    guint sourceID = g_source_attach (source, NULL);
    g_printf ("source_get_ID:%d\n", sourceID);
    g_source_set_callback (source, gsource_func1, NULL, NULL);
    
    g_idle_add (gsource_idle, NULL);
    g_main_loop_run (loop);
    g_printf ("%s() out\n", __func__);
}

执行输出:

renz@media-Lenovo-Product:~/glibtest$ ./loop
main() in
source_get_ID:1
gsource_idle() in
thread_func() in
gsource_idle() out
thread_func1() in
context is not owner
context is owner
context is not owner
context is owner
source_get_name:thread_timeout1 ID:3
source_get_name:thread_timeout ID:1
gsource_func1() in
gsource_func1 1559725211.221778s
gsource_func1() out
gsource_func() in
gsource_func 1559725211.816416s
user_data = 1
gsource_func() out
gsource_func1() in
gsource_func1 1559725212.222865s
gsource_func1() out
gsource_func1() in
gsource_func1 1559725212.722452s
gsource_func1() out
gsource_func() in
gsource_func 1559725213.816440s
user_data = 2
gsource_func() out
gsource_func1() in
gsource_func1 1559725214.224010s
gsource_func1() out
gsource_func1() in
gsource_func1 1559725214.723595s
gsource_func1() out
gsource_func1() in
gsource_func1 1559725215.725656s
gsource_func1() out
gsource_func() in
gsource_func 1559725215.816543s
user_data = 3
gsource_func() out
gsource_func1() in
gsource_func1 1559725217.225192s
gsource_func1() out
gsource_func1() in
gsource_func1 1559725217.226268s
gsource_func1() out
…………………………

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值