0%

【Linux技术分享】wine下处理快速复制时偶现失败问题

某应用在在快速调用剪切板进行数据复制时,在第一次进行交互出现失败概率几乎为百分之百,后续点击交互失败概率大致为百分之20左右

wine中的剪贴板理解

图1

在wine中剪贴板主要集中在两个模块,一个是user32,一个是winex11drv。

  1. winex11drv主要实现剪贴板相关逻辑,在destop窗口创建的时候会开启一个窗口名称叫做“__wine_clipboard_manager”的剪贴板管理器线程(x11代理窗口),该线程创建一个剪贴板管理窗口,并开启消息循环。
1
2
TRACE( "clipboard thread %04x running\n", GetCurrentThreadId() );
while (GetMessageW( &msg, 0, 0, 0 )) DispatchMessageW( &msg );

在窗口消息处理回调函数处理剪贴板数据。

1
2
3
4
5
6
7
8
9
10
11
12
   case WM_CLIPBOARDUPDATE:
if (EVENT_x11_time_to_win32_time(0))
/* ICCCM says don't use CurrentTime, so try to use last message time if possibl*/
/* FIXME: this is not entirely correct*/
timestamp = GetMessageTime() - EVENT_x11_time_to_win32_time(0);
else
timestamp = CurrentTime;
if (is_clipboard_owner) break; /* ignore our own changes */
acquire_selection( thread_init_display() );
break;
case WM_RENDERFORMAT:
if (render_format( wp )) rendered_formats++;

当收到剪贴板数据变化的消息时,创建拥有剪贴板的窗口。当收到数据转换的消息时转换数据并调用opencliboard打开剪贴板,并设置对应数据到对应内存位置,关闭剪贴板。

而在收到窗口关闭消息时会创建剪贴板拥有窗口并得到剪贴板对应数据,然后调用X的事件循环,当有请求剪贴板数据时,发送对应数据到相应窗口。

  1. user32中主要实现的是windows API相关接口,然后发送消息到winex11drv模块中处理。

问题详细分析

  1. 通过与应用厂商沟通,他们应用f2应用流程大致:用户在数据元模块中点击f2->数据拷贝至系统剪切板中->通过管道通知电子病例需要获取剪切板数据->电子病例获取剪切板数据并进行拷贝至目标窗口

  2. 通过wine下的代码学习,确认wine中的剪切板数据更新是在wineserver中同步所有剪切板的

1
2
3
4
5
6
7
8
9
/* notify all listeners, and return the viewer window that should be notified if any */
static user_handle_t notify_listeners( struct clipboard *clipboard )
{
unsigned int i;

for (i = 0; i < clipboard->listen_count; i++)
post_message( clipboard->listeners[i], WM_CLIPBOARDUPDATE, 0, 0 );
return clipboard->viewer;
}
  1. 打开clipboard日志通道,发现剪切板的读取与写入在不同线程中执行,日志如下图,通过上一步的wine中剪切板数据同步代码学习,确认在同步时是需要进行wine中剪切板数据全部同步,包括需要同步至0x1003a(\__wine_clipboard_manager的剪贴板管理器),而同步时存在一定延迟性,该延迟就会导致该应用的剪切板读取偶尔出现无数据。
    图4

  2. 从日志发现在剪切板的确将内容写入到\__wine_clipboard_manager的剪贴板管理器中
    图5

  1. 通过代码学习“__wine_clipboard_manager”剪切板为x11与wine下交互的中间工具,在desktop进程创建时进行创建,通过clipboard_thread线程进行管理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void manage_desktop( WCHAR *arg )
    {
    UuidCreate( &guid );
    TRACE( "display guid %s\n", debugstr_guid(&guid) );
    graphics_driver = load_graphics_driver( driver, &guid );

    /* create the desktop window */
    hwnd = CreateWindowExW( 0, DESKTOP_CLASS_ATOM, NULL,
    WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0, 0, 0, 0, 0, 0, 0, &guid );
    ......
    thread = CreateThread( NULL, 0, clipboard_thread, NULL, 0, &id );
    ......
    }
  2. 在日志中定位与wine代码学习中确认wine中将剪切板内容写入到“__wine_clipboard_manager”窗口中时是跨进程操作,而window下的读取速度较快,在第一次写入时还需要注册剪切板部分内容,所以导致第一次失败概率较高,注册日志如下:
    图3

  3. 从日志发现,该应用调用NtUserGetClipboardData函数获取剪切板数据时,是通过获取0x1003a(\__wine_clipboard_manager的剪贴板管理器)窗口剪切板进行数据获取的,从而也印证了,若同步时存在一定延迟性,该延迟就会导致该应用的剪切板读取偶尔出现无数据。
    图7

问题总结

问题原因:在wine中使用剪切板时需要考虑内容是否拷贝至非wine下进程窗口中。在x11下,wine是在desktop进程创建时,
创建了“__wine_clipboard_manager”窗口用于管理wine与x11窗口剪切板的交互,具体流程是在数据拷贝时,把拷贝内容转发至“__wine_clipboard_manager”窗口
,再将内容写入x11剪切板中,并且在第一次内容写入时进行消息注册,由于消息转发是跨进程操作存在消息异步问题,会导致剪切板数据更新不及时,使电子病例的引用第一次大概率失败,后续偶现失败,出问题时交互如下图:
图11

修改方案

  1. 根据上述流程分析发现是与“__wine_clipboard_manager”窗口延迟导致该问题,目前在点击f2引用时只与wine中进程进行交互,修改方向就是点击f2时将与x11下剪切板管理工具交互逻辑进行屏蔽

  2. 增加WM_DISABLECLIPBOARD消息,当用户点击引用时,发送该消息至“__wine_clipboard_manager”窗口,将该窗口的x11下剪切板更新逻辑进行屏蔽,部分代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @@ -2257,6 +2257,10 @@ LRESULT X11DRV_ClipboardWindowProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM l
    case WM_NCCREATE:
    return clipboard_init( hwnd );
    case WM_CLIPBOARDUPDATE:
    + if(theEMRClickQuote){
    + theEMRClickQuote = FALSE;
    + break;
    + }
    if (is_clipboard_owner) break; /* ignore our own changes */
    acquire_selection( thread_init_display() );
    break;
    @@ -2267,9 +2271,13 @@ LRESULT X11DRV_ClipboardWindowProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM l
    if (!is_clipboard_owner) break;
    request_selection_contents( thread_display(), FALSE );
    break;
    + case WM_DISABLECLIPBOARD:
    + theEMRClickQuote = TRUE;
    + TRACE("WM_DISABLECLIPBOARD: disable x11 cliboard\n");
    + break;
    case WM_DESTROYCLIPBOARD:
    TRACE( "WM_DESTROYCLIPBOARD: lost ownership\n" );
    + is_clipboard_owner = FALSE;
    NtUserKillTimer( hwnd, 1 );
    break;
    }