Ошибка CLIPBRD_E_CANT_OPEN при установке буфера обмена из .NET

Почему следующий код иногда вызывает исключение с содержимым «CLIPBRD_E_CANT_OPEN»:

Clipboard.SetText(str);

Обычно это происходит при первом использовании буфера обмена в приложении, а не после этого.


person Community    schedule 16.09.2008    source источник
comment
Это довольно сложное решение - действительно ли это единственный способ?   -  person Blorgbeard    schedule 16.09.2008
comment
Это похоже на то, как MS реализовала это в Forms. Этот вопрос касался WPF (хотя я не понимал, что это имеет значение).   -  person Robert Wagner    schedule 18.01.2010


Ответы (7)


На самом деле, я думаю, это ошибка Win32 API.

Чтобы поместить данные в буфер обмена, вы должны открыть его во-первых. Буфер обмена может быть открыт только для одного процесса. Итак, когда вы проверяете, открыт ли буфер обмена у другого процесса по какой-либо причине, ваша попытка открыть его потерпит неудачу.

Так уж получилось, что службы терминалов отслеживают буфер обмена, и в более старых версиях Windows (до Vista) вам нужно открыть буфер обмена, чтобы увидеть, что внутри ... что в конечном итоге блокирует вас. Единственное решение - дождаться, пока службы терминалов закроют буфер обмена, и повторить попытку.

Однако важно понимать, что это не относится к службам терминалов: это может случиться с чем угодно. Работа с буфером обмена в Win32 - это состояние гигантской гонки. Но поскольку по задумке вы должны возиться с буфером обмена только в ответ на ввод данных пользователем, это обычно не представляет проблемы.

person Tadmas    schedule 16.09.2008

Это вызвано ошибкой / функцией в буфере обмена служб терминалов (и, возможно, другими вещами) и реализацией буфера обмена .NET. Задержка открытия буфера обмена вызывает ошибку, которая обычно проходит в течение нескольких миллисекунд.

Решение состоит в том, чтобы попробовать несколько раз в цикле и засыпать между ними.

for (int i = 0; i < 10; i++)
{
    try
    {
        Clipboard.SetText(str);
        return;
    }
    catch { }
    System.Threading.Thread.Sleep(10);
} 
person Robert Wagner    schedule 16.09.2008
comment
Если вы посмотрите на внутреннее устройство Clipboard.SetText, по крайней мере, в .NET 2.0 SP1, вы увидите, что у него уже есть цикл повтора / ожидания. Повторяется до 10 раз с задержкой 100 мс. - person Mike Dimmick; 26.09.2008
comment
@Mike: System.Windows.Forms.Clipboard имеет повторную попытку, а System.Windows.Clipboard из WPF - нет. - person Cameron MacFarland; 18.09.2009
comment
Это безумие, чистое безумие ...: D Я потратил 2 часа без каких-либо ссылок, пытаясь выяснить, как заставить эту чертову штуку работать, а вы говорите мне, что я должен был просто попробовать, ДО ТОГО, КАК это сработало в кровавой петле? Безумие! :) - person bor; 17.01.2014
comment
catch {} - плохая практика. Заменить на catch (COMException ex) { const uint CLIPBRD_E_CANT_OPEN = 0x800401D0; if ((uint)ex.ErrorCode != CLIPBRD_E_CANT_OPEN) throw; } - person Maxence; 26.10.2017
comment
Действительно, в буфере обмена WinForms есть повторная попытка, видимая здесь - person Borislav Ivanov; 24.10.2018

Я знаю, что это старый вопрос, но проблема все еще существует. Как упоминалось ранее, это исключение возникает, когда системный буфер обмена заблокирован другим процессом. К сожалению, существует множество инструментов для вырезания, программ для создания снимков экрана и инструментов для копирования файлов, которые могут блокировать буфер обмена Windows. Таким образом, вы будете получать исключение каждый раз, когда попытаетесь использовать Clipboard.SetText(str), когда такой инструмент установлен на вашем ПК.

Решение:

никогда не использовать

Clipboard.SetText(str);

использовать вместо

Clipboard.SetDataObject(str);
person Community    schedule 24.08.2016
comment
@K_Rol: Похоже, это объясняет ответ Ишая Галацера. - person Cameron; 10.05.2017
comment
Я получаю значение типа String, которое нельзя преобразовать в DataObject? - person AndruWitta; 14.06.2018
comment
К вашему сведению, мне пришлось использовать Clipboard.SetDataObject(str, true);, чтобы данные буфера обмена были доступны вне приложения. - person deadlydog; 26.11.2020

На самом деле может возникнуть другая проблема. Вызов фреймворка (варианты WPF и winform) примерно так (код взят из отражателя):

private static void SetDataInternal(string format, object data)
{
    bool flag;
    if (IsDataFormatAutoConvert(format))
    {
        flag = true;
    }
    else
    {
        flag = false;
    }
    IDataObject obj2 = new DataObject();
    obj2.SetData(format, data, flag);
    SetDataObject(obj2, true);
}

Обратите внимание, что в этом случае SetDataObject всегда вызывается со значением true.

Внутренне это вызывает два вызова win32 api: один для установки данных, а другой для их удаления из вашего приложения, чтобы он был доступен после закрытия приложения.

Я видел несколько приложений (несколько плагинов для Chrome и диспетчер загрузок), которые прослушивают событие буфера обмена. Как только сработает первый вызов, приложение откроет буфер обмена для просмотра данных, а второй вызов сброса завершится ошибкой.

Не нашел хорошего решения, кроме как написать свой собственный класс буфера обмена, который использует прямой Win32 API или вызвать setDataObject напрямую с false для хранения данных после закрытия приложения.

person Community    schedule 30.07.2012

Я решил эту проблему для своего собственного приложения, используя собственные функции Win32: OpenClipboard (), CloseClipboard () и SetClipboardData ().

Ниже созданного мной класса-оболочки. Может ли кто-нибудь пожалуйста просмотреть его и сказать, правильное оно или нет. Особенно, когда управляемый код работает как приложение x64 (я использую Any CPU в параметрах проекта). Что происходит, когда я подключаюсь к библиотекам x86 из приложения x64?

Спасибо!

Вот код:

public static class ClipboardNative
{
    [DllImport("user32.dll")]
    private static extern bool OpenClipboard(IntPtr hWndNewOwner);

    [DllImport("user32.dll")]
    private static extern bool CloseClipboard();

    [DllImport("user32.dll")]
    private static extern bool SetClipboardData(uint uFormat, IntPtr data);

    private const uint CF_UNICODETEXT = 13;

    public static bool CopyTextToClipboard(string text)
    {
        if (!OpenClipboard(IntPtr.Zero)){
            return false;
        }

        var global = Marshal.StringToHGlobalUni(text);

        SetClipboardData(CF_UNICODETEXT, global);
        CloseClipboard();

        //-------------------------------------------
        // Not sure, but it looks like we do not need 
        // to free HGLOBAL because Clipboard is now 
        // responsible for the copied data. (?)
        //
        // Otherwise the second call will crash
        // the app with a Win32 exception 
        // inside OpenClipboard() function
        //-------------------------------------------
        // Marshal.FreeHGlobal(global);

        return true;
    }
}
person Community    schedule 11.05.2015
comment
PS: Я также пытался использовать управляемый вызов Clipboard.SetText() перед вызовом собственной функции (т.е. использовал собственный способ, только если управляемый не работал). Но если управляемая версия терпит неудачу, она блокирует буфер обмена, а после этой собственной версии также не удается открыть буфер обмена. - person Mar; 11.05.2015
comment
Этот ответ работает как шарм ‹3, спасибо. Я использую .net framework 4.8, и больше ничего не помогло. - person hakamairi; 06.07.2021

Это случилось со мной в моем приложении WPF. У меня OpenClipboard Failed (исключение из HRESULT: 0x800401D0 (CLIPBRD_E_CANT_OPEN)).

я использую

ApplicationCommands.Copy.Execute(null, myDataGrid);

решение - сначала очистить буфер обмена

Clipboard.Clear();
ApplicationCommands.Copy.Execute(null, myDataGrid);
person Community    schedule 11.05.2017
comment
Если Beyond Clipboard запущен - Clipboard.Clear() вызовет точно такое же исключение. протестировано с Beyond Compare версией 4.2.3.22587 - person itsho; 11.04.2018

Используйте версию WinForms (да, использование WinForms в приложениях WPF не вредно), она обрабатывает все, что вам нужно:

System.Windows.Forms.SetDataObject(yourText, true, 10, 100);

Это попытается скопировать ваш текст в буфер обмена, он останется после того, как ваше приложение существует, будет пытаться до 10 раз и будет ждать 100 мс между каждой попыткой.

Ref.