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

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

Clipboard.SetText(str);

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

Это довольно сложное решение - действительно ли это единственный способ?

Blorgbeard 16.09.2008 06:07

Это похоже на то, как MS реализовала это в Forms. Этот вопрос касался WPF (хотя я не понимал, что это имеет значение).

Robert Wagner 18.01.2010 11:22
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
55
2
20 062
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

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

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

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

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

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

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

for (int i = 0; i < 10; i++)
{
    try
    {
        Clipboard.SetText(str);
        return;
    }
    catch { }
    System.Threading.Thread.Sleep(10);
} 

Если вы посмотрите на внутреннее устройство Clipboard.SetText, по крайней мере, в .NET 2.0 SP1, вы увидите, что у него уже есть цикл повторных попыток / ожидания. Повторяется до 10 раз с задержкой 100 мс.

Mike Dimmick 26.09.2008 20:07

@Mike: System.Windows.Forms.Clipboard имеет повторную попытку, а System.Windows.Clipboard из WPF - нет.

Cameron MacFarland 18.09.2009 04:46

Это безумие, чистое безумие ...: D Я потратил 2 часа без какой-либо ссылки, пытаясь выяснить, как заставить эту чертову штуку работать, и вы говорите мне, что я должен был просто попробовать, ДО ТОГО, как это сработало в кровавом цикле? Безумие! :)

bor 17.01.2014 18:52

catch {} - плохая практика. Заменить на catch (COMException ex) { const uint CLIPBRD_E_CANT_OPEN = 0x800401D0; if ((uint)ex.ErrorCode != CLIPBRD_E_CANT_OPEN) throw; }

Maxence 26.10.2017 15:31

Действительно, в буфере обмена WinForms есть повторная попытка, видимый здесь

Borislav Ivanov 24.10.2018 13:45

На самом деле может возникнуть другая проблема. Вызов фреймворка (варианты 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 и менеджер загрузок), которые прослушивают событие буфера обмена. Как только сработает первый вызов, приложение откроет буфер обмена для просмотра данных, а второй вызов сброса завершится ошибкой.

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

Я решил эту проблему для своего собственного приложения, используя собственные функции 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;
    }
}

PS: Я также пробовал использовать управляемый вызов Clipboard.SetText() перед вызовом «родной» функции (т.е. использовал собственный способ, только если управляемый не работал). Но если управляемая версия терпит неудачу, она блокирует буфер обмена, а после этой собственной версии также не удается открыть буфер обмена.

Mar 11.05.2015 13:53

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

Решение:

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

Clipboard.SetText(str);

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

Clipboard.SetDataObject(str);

@K_Rol: Похоже, это объясняет ответ Ишая Галацера.

Cameron 10.05.2017 20:44

Я получаю сообщение «Значение типа String не может быть преобразовано в DataObject»?

AndruWitta 14.06.2018 04:42

К вашему сведению, мне пришлось использовать Clipboard.SetDataObject(str, true);, чтобы данные буфера обмена были доступны вне приложения.

deadlydog 26.11.2020 07:40

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

я использую

ApplicationCommands.Copy.Execute(null, myDataGrid);

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

Clipboard.Clear();
ApplicationCommands.Copy.Execute(null, myDataGrid);

Если Beyond Clipboard работает - Clipboard.Clear() вызовет точно такое же исключение. протестирован с Beyond Compare версии 4.2.3.22587

itsho 11.04.2018 09:52

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

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

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

Ref. https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.clipboard.setdataobject?view=netframework-4.7.2#System_Windows_Forms_Clipboard_SetDataObject_System_Object_System_Boolean_System_Int32_System_Int32_System_

Другие вопросы по теме