클립보드와 드래그 앤 드롭의 구조 ── 업무 앱에서 OLE 데이터 전송을 올바르게 다루기

· 업데이트: · · Windows, 클립보드, 드래그 앤 드롭, OLE, COM, Windows 개발, WinForms, WPF

수정 이력(1건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

한국어 전면 재작성에 맞춰 본문 표현을 바로잡았습니다. 기술적인 주장은 일본어판과 같습니다.
최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.22176609)

아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.

Go Komura (2026). 「클립보드와 드래그 앤 드롭의 구조 ── 업무 앱에서 OLE 데이터 전송을 올바르게 다루기」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/windows-clipboard-drag-drop-ole-data-transfer/

DOI(등록된 아카이브)
10.5281/zenodo.22176609
DOI(마지막 등록 버전)
10.5281/zenodo.22176610

「Excel에서 복사한 표를 붙여 넣으면 서식이 무너진다. 표로 붙여 넣게 해 달라」「우리 앱에서 복사한 내용이 Word에 붙여 넣으면 이상한 것이 된다」「파일을 드래그 앤 드롭으로 가져오게 해 달라」── 업무 앱 개선 상담에서 복사·붙여 넣기와 드래그 앤 드롭(D&D) 관련 요청은 단골입니다.

「당연한 기능」인 만큼, 구조는 의외로 잘 알려지지 않습니다. 클립보드를 「데이터 하나를 넣는 상자」로 생각하면, 같은 복사인데도 붙여 넣는 쪽에 따라 결과가 달라지는 이유도, 원본 앱을 닫으면 붙여 넣을 수 없게 되는 이유도 설명할 수 없습니다. 실제 클립보드는 같은 내용을 여러 형식으로 동시에 두고, 붙여 넣는 쪽이 이해하는 형식을 고르는 구조입니다.

그리고 드래그 앤 드롭의 실체는, 클립보드와 완전히 같은 데이터 표현(IDataObject)을 COM 인터페이스로 주고받는 OLE 데이터 전송입니다. 즉 복사·붙여 넣기와 D&D는 형제이며, 한쪽을 올바르게 이해하면 다른 쪽은 바로 옆에 있습니다.

이 글에서는 중소기업 정보시스템 담당자와 Windows 앱 개발자를 대상으로, 클립보드 형식의 구조, 붙여 넣는 쪽·복사하는 쪽 각각의 실무, 올바른 감시 방법, 클립보드 기록·클라우드 동기·RDP의 관리 정책, OLE 드래그 앤 드롭의 구조와 함정까지를 하나로 연결합니다.

1. 먼저 결론

  • 클립보드는 같은 데스크톱(윈도우 스테이션) 안의 앱이 공유하는 하나의 영역이며, 그 안에는 「데이터 하나」가 아니라 같은 내용이 여러 형식으로 동시에 놓입니다. RDP처럼 다른 세션 사이는 원래 별도 클립보드이고, 리다이렉션 기능이 둘을 잇습니다. 붙여 넣는 쪽이 이해하는 형식을 고르므로, 같은 복사라도 붙여 넣는 앱에 따라 결과가 달라집니다.12
  • 텍스트는 CF_UNICODETEXT를 사용합니다. CF_TEXT는 ANSI라서 코드 페이지에 의존하며, 일본어 환경에서는 깨짐의 온상입니다. 시스템이 둘을 암시적으로 변환하지만, 정본은 Unicode 쪽입니다.3
  • 파일 전달은 CF_HDROP(이중 NUL 종료 경로 배열)이고, 서식 있는 텍스트는 등록 형식 「HTML Format」입니다. HTML Format은 바이트 오프셋 헤더를 가진 UTF-8 텍스트라는 독특한 구조입니다.45
  • 「원본 앱을 닫으니 붙여 넣을 수 없다」의 원인은 지연 렌더링입니다. 데이터 본체가 아니라 「요청되면 만들겠다」는 약속만 두는 메커니즘이며, 종료 때 확정(WM_RENDERALLFORMATS에 응답, OLE라면 OleFlushClipboard)을 빼먹으면 붙여 넣을 수 없게 됩니다.26
  • 붙여 넣는 데이터는 외부에서 온, 신뢰할 수 없는 입력으로 다룹니다. Microsoft 스스로 「클립보드 데이터는 신뢰하지 마십시오. 신중하게 파싱하십시오」라고 명시합니다.7
  • 감시는 AddClipboardFormatListener + WM_CLIPBOARDUPDATE가 유일한 선택입니다. 폴링이나 옛 SetClipboardViewer(뷰어 체인)는 쓰지 않습니다. 기밀을 기록·동기에서 빼는 등록 형식(ExcludeClipboardContentFromMonitorProcessing 등)도 준비되어 있습니다.81
  • 클립보드 기록(Win+V)과 클라우드 동기는 IT 담당자의 관리 대상입니다. GPO/Intune(Policy CSP)의 AllowClipboardHistory·AllowCrossDeviceClipboard로 제어할 수 있고, RDP 클립보드 리다이렉션에도 전용 정책이 있습니다.91011
  • 드래그 앤 드롭은 COM입니다. 클립보드와 같은 IDataObject를 IDropSource(드래그 원본)와 IDropTarget(드롭 대상)이 DoDragDrop 루프를 통해 주고받습니다. RegisterDragDrop에는 OleInitialize(STA)로 초기화하는 것이 필수입니다.1213
  • 승격된 앱에는 보통 권한의 탐색기에서 드롭할 수 없습니다. 원인은 UIPI(무결성 수준에 따른 메시지 차단)이며, 설계 단계에서 알아 두어야 할 제약입니다.14

이하에서는 클립보드의 기반부터 순서대로 설명합니다.

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 20건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. 클립보드의 실체 ── 「데이터 하나」가 아니라 「같은 내용의 여러 형식」

클립보드는 같은 데스크톱을 공유하는 모든 앱이 접근할 수 있는 공통 데이터 공유 구조입니다(정확히는 윈도우 스테이션 단위이며, 다른 사용자 세션이나 RDP 세션은 각각 별도 클립보드를 갖습니다. RDP에서 복사·붙여 넣기가 되는 것은 리다이렉션 기능이 둘을 잇기 때문입니다 ── 7장). 사용자가 주도해 쓰는 것이 대원칙이고, 사용자 모르게 데이터를 넣거나 빼지 않는다는 것이 공식 설계 방침입니다.1

중요한 점은, 복사 조작으로 놓이는 것이 「데이터 하나」가 아니라는 것입니다. 복사하는 쪽 창은 클립보드를 비운 뒤, 같은 내용을 표현력이 높은 형식부터 낮은 형식 순으로 여러 개 나란히 둡니다.2 예를 들어 스프레드시트에서 표를 복사하면, 개념적으로는 다음과 같은 것이 동시에 실립니다.

우선순 형식 내용
1 앱 고유 형식 수식·서식까지 완전한 내부 표현(같은 앱에 붙여 넣기용)
2 HTML Format 표의 구조와 서식을 유지한 HTML 조각
3 CSV 셀 구분 텍스트
4 CF_UNICODETEXT 탭 구분 일반 텍스트
5 이미지 형식 표 모습의 비트맵

붙여 넣는 쪽은 이 목록에서 자신이 이해하는 형식을 골라 꺼냅니다. Word에 붙여 넣으면 서식 있는 표가 되고, 메모장에 붙여 넣으면 탭 구분 텍스트가 되는 것은, 둘이 고른 형식이 다르기 때문입니다. 「붙여 넣는 쪽에 따라 결과가 다르다」는 버그가 아니라, 이 구조의 정상적인 결과입니다.

같은 복사가 붙여 넣는 쪽에 따라 다른 결과가 되는 구조복사 측은 같은 내용을 여러 형식으로 클립보드에 두고, 붙여 넣는 쪽이 이해하는 형식을 고르므로 Word는 서식 있는 표가 되고 메모장은 탭 구분 텍스트가 된다Word가 고른다메모장이 고른다복사 측: 스프레드시트클립보드(같은 내용의 여러 형식)앱 고유 형식HTML FormatCSVCF_UNICODETEXT서식 있는 표탭 구분 텍스트

거꾸로 말하면, 글머리의 「서식이 무너진다」「이상한 것이 붙여 넣어진다」는 상담은 거의 모두 어느 한쪽의 형식 고르기·제공 방식 문제로 환원됩니다. 붙여 넣는 쪽은 4장, 복사하는 쪽은 5장에서 다룹니다.

3. 표준 형식과 등록 형식 ── CF_UNICODETEXT·CF_HDROP·HTML Format

3.1. 표준 형식 ── 텍스트는 Unicode 쪽을 사용한다

OS가 미리 정의한 형식을 표준 형식이라고 합니다. 업무 앱에서 자주 나오는 것은 다음과 같습니다.3

형식 값 내용
CF_TEXT 1 ANSI 텍스트(코드 페이지 의존)
CF_UNICODETEXT 13 Unicode 텍스트. 텍스트의 정본은 이쪽
CF_HDROP 15 파일 경로 목록(HDROP 핸들)
CF_DIB 8 장치 독립 비트맵
CF_LOCALE 16 텍스트에 연관된 로케일 식별자

CF_TEXT와 CF_UNICODETEXT는 시스템이 암시적으로 상호 변환합니다(합성 형식). 그때의 문자 코드 변환에는 CF_LOCALE에 연관된 코드 페이지가 쓰입니다.3 변환에 의존하면 ANSI 쪽에서 표현하지 못하는 문자(예를 들어 Unicode 고유 기호나 결합 문자)가 빠지므로, 앱이 읽고 쓰는 것은 CF_UNICODETEXT(.NET이라면 DataFormats.UnicodeText)로 통일하는 것이 원칙입니다.

CF_UNICODETEXT와 CF_TEXT의 암시적 변환앱은 CF_UNICODETEXT만 읽고 쓰고, CF_TEXT는 시스템이 CF_LOCALE의 코드 페이지로 암시 변환해 합성한다. ANSI 쪽에서 표현하지 못하는 문자는 이 변환에서 빠진다시스템이 암시 변환(CF_LOCALE의 코드 페이지)앱의 읽기·쓰기CF_UNICODETEXT(정본)CF_TEXT(ANSI·코드 페이지 의존)표현하지 못하는 문자는 변환에서 빠진다(깨짐의 온상)

3.2. CF_HDROP ── 파일은 「경로 목록」으로 전달된다

탐색기에서 파일을 복사했을 때나, 파일을 D&D했을 때 쓰이는 것이 CF_HDROP입니다. 내용은 파일 본체가 아니라, DROPFILES 구조체 헤더에 이어서 전체 경로 문자열을 NUL 문자로 구분하고, 마지막에 빈 문자열을 둔 「이중 NUL 종료」 배열을 늘어놓은 메모리 블록입니다. 헤더의 pFiles가 경로 목록의 시작 오프셋을, fWide가 문자열이 Unicode인지 여부를 나타냅니다.4

[DROPFILES 헤더: pFiles=경로 목록의 시작 오프셋, fWide=1(Unicode)]
C:\data\a.txt(NUL)C:\data\b.txt(NUL)(NUL)

네이티브 코드에서는 DragQueryFile로 한 건씩 꺼내고, .NET에서는 DataFormats.FileDrop으로 string[]을 받을 수 있습니다. 「전달되는 것은 경로뿐이고, 파일 본체는 아니다」라는 점은 8~9장의 D&D에서도 유효합니다.

CF_HDROP의 메모리 블록 구조전역 메모리 선두에 DROPFILES 구조체가 있고, pFiles가 경로 목록의 시작 오프셋을, fWide가 Unicode 여부를 나타낸다. 이어서 NUL로 구분된 전체 경로가 늘어서고, 마지막은 빈 문자열에 의한 이중 NUL 종료로 끝난다. 전달되는 것은 경로뿐이고 파일 본체는 아니다DROPFILES 구조체(pFiles = 목록 시작 오프셋 / fWide = 1)C:\\data\\a.txt + NULC:\\data\\b.txt + NUL종료의 빈 문자열(이중 NUL)전달되는 것은 경로뿐이고, 파일 본체는 아니다

3.3. 등록 형식 ── RegisterClipboardFormat과 「HTML Format」

표준 형식으로 표현하지 못하는 데이터를 위해, 앱은 이름을 정해 고유 형식을 등록할 수 있습니다. RegisterClipboardFormat에 이름을 넘기면 형식 ID가 돌아오고, 같은 이름으로 등록하면 다른 앱에서도 같은 ID가 돌아오므로, 이름만 합의하면 앱 간에 데이터를 공유할 수 있습니다.1 자사 앱 그룹 사이에서 구조화 데이터를 주고받을 때는 KomuraSoft.Report.RowData처럼 충돌하지 않는 이름을 붙입니다.

등록 형식의 대표가 서식 있는 텍스트용 「HTML Format」입니다(RTF와 나란히 리치 텍스트의 양대 형식입니다). 내용은 UTF-8 텍스트이지만, 선두에 바이트 오프셋을 나열한 헤더가 붙는 독특한 구조입니다.5

Version:0.9
StartHTML:<HTML 전체의 시작 바이트 위치>
EndHTML:<HTML 전체의 종료 바이트 위치>
StartFragment:<조각의 시작 바이트 위치>
EndFragment:<조각의 종료 바이트 위치>
<html><body>
<!--StartFragment--><b>굵은</b> 조각 텍스트<!--EndFragment-->
</body></html>

각 오프셋은 헤더 자신을 포함한 데이터 선두부터의 바이트 위치이며, 고정 자릿수(예: 10자리)로 영역을 확보해 두고, 본문을 조립한 뒤에 실측값을 다시 쓰는 것이 정석입니다. StartFragment/EndFragment는 「사용자가 실제로 선택한 조각」의 시작·종료를 바이트 단위로 나타냅니다(문자 단위가 아닙니다). 일본어를 포함한 UTF-8에서는 문자 수와 바이트 수가 어긋나므로, 이 오프셋 계산을 잘못하면 다른 앱에 붙여 넣을 때 앞이나 뒤가 빠집니다. 직접 HTML Format을 생성할 때는 UTF-8로 인코딩한 뒤의 바이트 위치로 헤더를 채워야 합니다.5

HTML Format의 헤더와 오프셋의 관계헤더의 StartHTML과 EndHTML이 HTML 전체를, StartFragment와 EndFragment가 사용자가 선택한 조각을, 모두 데이터 선두부터의 바이트 위치로 가리킨다. UTF-8에서는 문자 수와 바이트 수가 어긋나므로, 인코딩 뒤에 실측한 바이트 위치로 채운다헤더(Version / StartHTML / EndHTML / StartFragment / EndFragment)HTML 전체(StartHTML〜EndHTML)선택된 조각(StartFragment〜EndFragment)각 오프셋 = 데이터 선두부터의 바이트 위치(UTF-8 인코딩 뒤에 실측해 다시 쓴다)

이 밖에 표 형식 데이터에는 CSV(.NET의 DataFormats.CommaSeparatedValue)도 자주 쓰입니다. Excel과의 상호 운용에서는 HTML Format(서식 포함)과 CSV(값만)와 CF_UNICODETEXT(탭 구분)를 함께 제공하면, 붙여 넣는 쪽을 가리지 않습니다.

4. 붙여 넣는 쪽의 실무 ── 형식의 우선순위와 검증

4.1. 풍부한 형식부터 차례로 찾는다

클립보드상의 형식은 복사 측이 둔 순서(=표현력이 높은 순서)로 늘어서 있습니다. 붙여 넣는 쪽의 기본은, 자신이 다룰 수 있는 형식 가운데 정보량이 가장 많은 것부터 차례로 찾는 것입니다. Win32에서는 EnumClipboardFormats로 열거해 처음 인식한 형식을 쓰거나, GetPriorityClipboardFormat에 자신의 우선순위 목록을 넘겨 고릅니다.2

.NET이라면 다음과 같은 분기가 됩니다.

// 표의 붙여 넣기: 리치→플레인 순으로 찾는다
var data = Clipboard.GetDataObject();
if (data is null) return;

// 형식을 내세워도 실체가 string이라고 단정할 수 없다. 형까지 확인됐을 때만
// 이 분기를 쓰고, 아니면 다음 후보로 떨어뜨린다
if (data.GetDataPresent(DataFormats.Html)
    && data.GetData(DataFormats.Html) is string html)
{
    // HTML Format 헤더를 검증한 뒤 표로 가져온다
}
else if (data.GetDataPresent(DataFormats.CommaSeparatedValue))
{
    // CSV로 가져온다
}
else if (data.GetDataPresent(DataFormats.UnicodeText))
{
    // 탭 구분 텍스트로 가져온다
}

「Excel 표를 붙여 넣으면 무너진다」는 글머리 상담에 대한 답이 이것입니다. 일반 텍스트만 읽는 앱에는 표 구조가 넘어가지 않습니다. 어느 형식까지 받을지는 붙여 넣는 쪽의 설계 판단입니다.

풍부한 형식부터 차례로 찾는 붙여 넣기 분기HTML Format이 있고 실체도 문자열이면 표로 가져오고, 없으면 CSV, 그것도 없으면 탭 구분 텍스트로, 표현력이 높은 형식부터 차례로 떨어뜨린다. 후보가 하나도 없으면 받지 않는다예아니오예아니오예아니오붙여 넣기 시작HTML Format이 있고 실체도 string?헤더를 검증한 뒤 표로 가져온다CSV가 있는가?CSV로 가져온다UnicodeText가 있는가?탭 구분 텍스트로 가져온다받지 않는다

4.2. 붙여 넣는 데이터는 외부 입력이다

놓치기 쉽지만, 클립보드 내용은 어느 앱이 두었는지 알 수 없는, 외부에서 온 데이터입니다. Microsoft도 OLE 클립보드 문서에서 「클립보드 데이터는 신뢰하지 마십시오. 앱에서 쓰기 전에 신중하게 파싱하십시오」라고 경고합니다.7

  • HTML Format의 헤더 오프셋이 범위를 벗어나지 않는지 검증합니다(깨진 헤더를 내보내는 앱은 실제로 있습니다).
  • 숫자·날짜·코드로 가져오는 값은 화면 입력과 같은 유효성 검사를 통과시킵니다.
  • 거대한 데이터에 대한 방어를 넣습니다. 수백 MB 이미지나 수백만 행 텍스트가 붙여 넣어져도 UI를 막지 않고, 한도를 넘으면 거절하는 설계로 합니다. 주의할 점은, .NET의 GetData는 호출한 시점에(지연 렌더링도 시작해) 데이터 전체를 매니지드 문자열까지 실체화하므로, 크기 확인을 GetData 뒤에 두어도 방어가 되지 않는다는 것입니다. Win32에서 GetClipboardData가 반환하는 HGLOBAL을 GlobalSize로 확인하면 「거대한 데이터를 매니지드 문자열 변환·해석으로 보내지 않는다」는 단계의 방어는 되지만, 지연 렌더링 형식에서는 GetClipboardData 자체가 렌더링을 시작하므로, 복사 원본에서의 실체화 자체까지는 막지 못합니다. UI가 멈추지 않게 하려면 가져오기 처리를 UI 스레드 밖으로 냅니다(그 경우에도 .NET의 Clipboard는 STA가 필수이므로, Task.Run의 스레드 풀(MTA)이 아니라 STA로 설정한 전용 스레드에서 합니다 ── 5.1절).

「외부에서 들어오는 값은 경로가 무엇이든 검증한 뒤에 쓴다」는 생각은 「QR코드 판독값을 그대로 사용해서는 안 된다」에서 정리한 것과 같습니다. 붙여 넣기는 사용자 조작이니 안전하다는 착각이 사고의 불씨가 됩니다.

붙여 넣는 데이터를 검증한 뒤에 쓰는 흐름클립보드에서 꺼낸 데이터는 형식의 존재 확인, 실체의 형 확인, 크기 한도, 내용 검증을 차례로 통과하고, 어디에서든 불합격이면 거절하거나 다음 후보 형식으로 떨어뜨린다형이 다르다너무 크다올바르지 않다형식의 존재를 확인(GetDataPresent)실체의 형을 확인(is string / string[])크기 한도를 확인내용을 검증(헤더·경로·값)가져온다거절 / 다음 후보 형식

5. 복사하는 쪽의 실무 ── 여러 형식의 동시 제공과 지연 렌더링

5.1. 여러 형식을 동시에 둔다

복사 측의 실무는 4.1의 반대이며, 풍부한 형식과 일반 형식을 동시에 제공하는 것입니다. WinForms/WPF의 DataObject를 쓰면 몇 줄로 쓸 수 있습니다.15

// WinForms(System.Windows.Forms). WPF도 System.Windows의 DataObject/Clipboard로 같은 형태
var data = new DataObject();
data.SetData(DataFormats.Html, htmlFormatText);       // 헤더가 붙은 HTML Format 문자열
data.SetData(DataFormats.CommaSeparatedValue, csv);   // CSV
data.SetData(DataFormats.UnicodeText, plainText);     // 일반 텍스트
Clipboard.SetDataObject(data, copy: true);            // copy:true=앱 종료 뒤에도 남긴다

두 가지를 보충합니다. 첫째, .NET의 Clipboard 클래스는 STA 스레드에서만 쓸 수 있습니다.15 WinForms/WPF의 UI 스레드는 [STAThread]로 STA가 되어 있으므로 보통은 문제가 없지만, 백그라운드 스레드에서 건드리면 실패합니다(STA/MTA의 기초는 「COM STA/MTA의 기초 지식」을 참조하십시오). 둘째, copy: true의 의미는 다음 절의 지연 렌더링과 관련됩니다.

5.2. 지연 렌더링 ── 「닫으면 붙여 넣을 수 없다」의 원인

큰 데이터를 여러 형식으로 매번 만드는 것은 낭비이므로, 클립보드에는 지연 렌더링(delayed rendering)이라는 메커니즘이 있습니다. SetClipboardData의 데이터 핸들에 NULL을 넘기면, 데이터 본체 대신 「요청되면 만들겠다」는 약속만 등록되고, 누군가 그 형식을 요청한 시점에 복사 원본에 WM_RENDERFORMAT이 도착해, 그때 처음으로 데이터를 생성합니다.2

이 구조의 결과가 글머리의 「원본 앱을 닫으니 붙여 넣을 수 없게 됐다」입니다. 복사 원본은 종료 전에 WM_RENDERALLFORMATS를 받고, 아직 렌더링되지 않은 모든 형식을 실체화할 책임이 있지만, 이것을 빼먹고 종료하면 그 형식은 사라집니다.2

지연 렌더링의 흐름과 「닫으면 붙여 넣을 수 없다」복사 원본은 NULL 핸들로 약속만 두고, 요청이 오면 WM_RENDERFORMAT으로 실체화한다. 종료 때는 WM_RENDERALLFORMATS로 모든 형식을 실체화할 책임이 있으며, 빼먹으면 그 형식은 사라진다WM_RENDERALLFORMATS로 실체화실체화를 빼먹음복사 원본: SetClipboardData(format, NULL)로 「약속」만 등록붙여 넣는 쪽이 그 형식을 요청WM_RENDERFORMAT → 그 자리에서 데이터 생성복사 원본이 종료하려고 한다종료 뒤에도 붙여 넣기 가능그 형식은 사라진다(「닫으면 붙여 넣을 수 없다」)

OLE 클립보드(IDataObject를 OleSetClipboard로 두는 방식)에서는 이 관계가 더 분명합니다. 클립보드가 유지하는 것은 데이터 객체에 대한 포인터뿐이며, 앱 종료 때 OleFlushClipboard를 호출하면 데이터가 클립보드상에 실체화되어, 종료 뒤에도 붙여 넣을 수 있게 됩니다.6 .NET의 Clipboard.SetDataObject(data, copy: true)는 이 「종료 뒤에도 남긴다」 동작을 지정하는 것입니다.

Excel에서 큰 범위를 복사하고 종료하려고 하면 「클립보드에 큰 정보가 있습니다. 유지하시겠습니까?」라고 묻는 것은, 바로 이 확정 처리(플러시)를 실행할지에 대한 확인입니다. 자체 앱에서 지연 렌더링을 쓴다면, 종료 때의 확정 처리까지가 한 세트라고 기억해 주십시오. 지연 렌더링은 성능 최적화이며, 렌더링 요청은 메시지 처리 중에 동기로 달리므로, 생성하는 데 시간이 걸리는 데이터에서는 UI가 멈춘다는 트레이드오프도 있습니다.2

6. 클립보드 감시의 실무 ── 리스너·재시도·기록 제외

6.1. AddClipboardFormatListener를 사용한다

「바코드 리더 값이나 기간 시스템에서의 복사를 감지해 자동으로 가져오고 싶다」와 같은 요구에서는 클립보드 변경 감시가 필요합니다. 방법은 역사적으로 세 가지이지만, 현재의 정답은 하나입니다.8

방법 평가
타이머로 주기적으로 읽기(폴링) 낭비가 크고 감지 누락도 있다. 쓰지 않는다
SetClipboardViewer(뷰어 체인) 체인 안 앱 하나의 문제가 전체를 깨뜨린다. 하위 호환을 위해 남아 있을 뿐
AddClipboardFormatListener 권장. 등록한 창에 WM_CLIPBOARDUPDATE가 도착한다
클립보드 감시의 흐름핸들 생성 때 AddClipboardFormatListener로 등록하면, 어느 앱이 복사해도 WM_CLIPBOARDUPDATE가 도착한다. 읽기는 재시도를 두고, 핸들 파기 때 RemoveClipboardFormatListener로 대칭 해제한다대칭으로 해제OnHandleCreated: AddClipboardFormatListener대기어느 앱인가가 복사WM_CLIPBOARDUPDATE가 도착재시도를 두고 읽기(6.2절)OnHandleDestroyed: RemoveClipboardFormatListener
// WinForms에서의 최소 구현
public partial class MainForm : Form
{
    [DllImport("user32.dll", SetLastError = true)]
    static extern bool AddClipboardFormatListener(IntPtr hwnd);
    [DllImport("user32.dll", SetLastError = true)]
    static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
    const int WM_CLIPBOARDUPDATE = 0x031D;

    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        AddClipboardFormatListener(Handle);
    }

    protected override void OnHandleDestroyed(EventArgs e)
    {
        // 핸들의 파기·재생성에 맞춰 대칭으로 해제한다
        RemoveClipboardFormatListener(Handle);
        base.OnHandleDestroyed(e);
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_CLIPBOARDUPDATE)
        {
            // 여기에서 Clipboard.GetDataObject()를 읽고, 필요한 형식이면 가져온다
        }
        base.WndProc(ref m);
    }
}

6.2. 열리지 않을 때는 재시도한다

클립보드를 열 수 있는 것은 한 번에 창 하나뿐이며, 다른 프로세스가 연 동안 OpenClipboard는 실패합니다.2 WM_CLIPBOARDUPDATE를 받은 직후는 바로 복사 원본이나 다른 감시 앱이 조작 중인 경우가 많아, 읽기가 일시적으로 실패하는 것은 정상적인 현상입니다. 짧은 대기(수십 밀리초)를 둔 몇 번의 재시도를 반드시 넣으십시오. 참고로 .NET의 Clipboard 클래스에서 재시도 횟수와 간격을 지정할 수 있는 오버로드는 쓰기 측 SetDataObject뿐입니다. 읽기 측(GetDataObject 등)에는 없으므로, WinForms라면 ExternalException, WPF라면 COMException을 catch해 대기·재시도하는 코드를 직접 씁니다.

클립보드 읽기 재시도의 흐름클립보드를 열 수 있는 것은 한 번에 창 하나뿐이므로, 변경 알림 직후의 읽기는 다른 프로세스와 경합해 실패할 수 있다. 예외를 받으면 수십 밀리초 기다렸다가 재시도하고, 한도에 도달하면 이번은 포기하고 다음 갱신에서 줍는다성공실패(다른 창이 사용 중)몇 번까지 재시도한도 도달WM_CLIPBOARDUPDATE읽기를 시도가져오기로(4장의 검증)수십 밀리초 대기이번은 포기한다(다음 갱신에서 줍는다)

6.3. 기록·동기에 올리지 않는다 ── 기밀을 다루는 복사 기능의 배려

Windows에는 클립보드 기록(Win+V)과 기기 간 동기(클라우드 클립보드)가 있으며, 앱이 둔 데이터는 기본으로 그 대상이 됩니다. 암호나 계좌 번호 같은 기밀을 복사 기능에 올리는 앱은, 기록·동기에서 제외하기 위한 등록 형식을 함께 둡니다.1

  • ExcludeClipboardContentFromMonitorProcessing: 이것을 두면 그 복사 내용 전체가 기록에도 동기에도 포함되지 않습니다.
  • CanIncludeInClipboardHistory(DWORD 0): 기록만 억제합니다.
  • CanUploadToCloudClipboard(DWORD 0): 기기 간 동기만 억제합니다.

암호 관리자가 복사한 암호를 Win+V에 남기지 않는 것은 이 메커니즘 때문입니다. 이름을 RegisterClipboardFormat에 넘겨 형식 ID를 얻고, 보통 데이터와 나란히 설정하면 되므로, 기밀을 다루는 업무 앱에서는 구현해 둘 가치가 있습니다.

7. 정보시스템 담당자 관점의 클립보드 ── 기록·클라우드 동기·RDP 제어

개발 이야기에서 조금 벗어나, 관리자 관점의 쟁점을 정리합니다. 클립보드 기록은 최근 복사 내용을 쌓고, 클라우드 클립보드는 같은 Microsoft 계정/Microsoft Entra 계정으로 로그인한 기기 간에 복사 내용을 동기합니다.10 편리한 반면, 기간 시스템에서 복사한 개인정보가 기록에 쌓이고, 업무 PC에서 복사한 내용이 개인 PC로 동기되는 식의 정보 잔류·경계 이탈이 일어납니다.

조직에서 제어하는 정책은 다음 두 가지입니다.

제어 대상 GPO(컴퓨터 구성 > 관리 템플릿 > 시스템 > OS 정책) Policy CSP(Intune) 기본값
클립보드 기록 클립보드 기록 허용 Experience/AllowClipboardHistory 허용
기기 간 동기 기기 간 클립보드 동기 허용 Privacy/AllowCrossDeviceClipboard 허용

둘 다 Windows 10 버전 1809 이후에서 사용할 수 있고, 비활성화하면 설정 앱의 해당 항목이 회색으로 비활성화되며, 정책은 즉시 적용됩니다.910

또 하나의 단골이 RDP(원격 데스크톱)의 클립보드 리다이렉션입니다. 기본값에서는 로컬 PC와 원격 세션 사이에 복사·붙여 넣기가 되므로, 서버상 기밀의 반출 경로가 될 수 있습니다. 「클립보드 리다이렉션을 허용하지 않음」 정책(레지스트리 값 fDisableClip)으로 양방향을 모두 차단할 수 있습니다.11 최근 Windows Server/Windows 11에는 서버에서 클라이언트로의 방향만 텍스트만으로 제한하는 식의, 더 세밀한 제어를 하는 정책도 추가되어 있습니다. 전면 금지인지 단계적 제한인지는 운용과 보안의 균형으로 정합니다.

클립보드 내용이 퍼지는 경로와 제어 지점복사한 내용은 기본으로 기록과 클라우드 동기의 대상이 되고, RDP에서는 리다이렉션으로 다른 세션에 넘어간다. 각각 정책으로 제어할 수 있고, 앱 측은 제외 형식으로 기록과 동기에서 뺄 수 있다클립보드기록(Win+V)클라우드 동기 → 다른 기기RDP 리다이렉션 → 다른 세션제어: AllowClipboardHistory제어: AllowCrossDeviceClipboard제어: fDisableClip앱 측: ExcludeClipboardContentFromMonitorProcessing 등으로 제외(6.3절)

8. 드래그 앤 드롭은 COM이다 ── IDataObject+IDropSource+IDropTarget

8.1. 클립보드와 같은 데이터, 다른 운반 방식

OLE 드래그 앤 드롭은 다음 셋으로 동작합니다.12

역할 구현하는 쪽 일
IDataObject 드래그 원본 운반되는 데이터 본체. 클립보드와 같은 여러 형식의 데이터 객체
IDropSource 드래그 원본 드래그 계속/중지 판단과 커서 피드백
IDropTarget 드롭 대상 DragEnter/DragOver/DragLeave/Drop에서 수락 여부 표명과 수신

드래그 원본이 DoDragDrop을 호출하면 드래그 루프가 시작되고, 마우스가 드롭 대상 창에 들어가면 그 IDropTarget에 알림이 도착하며, 드롭 때 IDataObject가 넘어갑니다. 공식 문서도 「D&D는 클립보드의 복사·붙여 넣기와 완전히 같은 기능을 제공한다. 복사·붙여 넣기를 이미 구현한 앱이라면 추가는 적다」고 말합니다.12 2~5장에서 만든 여러 형식의 DataObject가 그대로 D&D의 짐이 된다는 뜻입니다.

OLE 드래그 앤 드롭의 흐름드래그 원본이 IDataObject를 짐으로 DoDragDrop을 호출해 드래그 루프가 시작되고, 드롭 대상의 IDropTarget이 DragEnter와 DragOver에서 수락 여부를 표명하며, Drop에서 IDataObject로부터 형식을 골라 꺼낸다DoDragDrop마우스가 창에 들어간다버튼을 놓는다드래그 원본: IDataObject + IDropSource드래그의 루프IDropTarget.DragEnter/DragOver(Effect를 매번 표명)IDropTarget.DropIDataObject에서 형식을 골라 꺼낸다

8.2. OleInitialize(STA)가 필수이다

드롭 대상이 되는 창은 RegisterDragDrop로 등록하지만, 여기에 고전적인 함정이 있습니다. COM 초기화를 CoInitialize/CoInitializeEx로 하면 RegisterDragDrop는 반드시 E_OUTOFMEMORY로 실패하며, OleInitialize로 초기화해야 합니다.13 OleInitialize는 STA로 COM을 초기화하는 것이며, D&D가 창과 메시지 펌프라는 STA의 세계에 뿌리내린 기능이기 때문입니다. 호출 스레드가 메시지 펌프를 돌리고 있는 것도 필수이며, 빼먹으면 드래그 중인 다른 앱이 멈춥니다.13 이 배경은 「COM STA/MTA의 기초 지식」에서 다룬 스레드 모델 이야기 그 자체입니다.

WinForms/WPF 앱에서는 프레임워크가 OLE 초기화와 인터페이스 구현을 대신해 주므로, 개발자는 이벤트만 쓰면 됩니다.

// WinForms: 파일 드롭을 받는다
listView1.AllowDrop = true;
listView1.DragEnter += (s, e) =>
{
    // 원본 측이 Copy를 허용하는지도 확인한다(Move/Link만 허용하는 원본도 있다)
    e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
            && (e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy
        ? DragDropEffects.Copy      // 수락 가능: 복사로 받는다
        : DragDropEffects.None;     // 수락 불가
};
listView1.DragDrop += (s, e) =>
{
    // 드래그 데이터도 외부 입력. FileDrop을 내세워도 실체가 null이거나
    // 다른 형인 경우가 있고, 가져오기 자체가 실패할 수도 있다
    object data;
    try { data = e.Data.GetData(DataFormats.FileDrop); }
    catch (COMException) { return; }
    if (data is not string[] paths) return;
    foreach (var path in paths)
    {
        // 경로를 검증한 뒤 가져온다(9.3절)
    }
};

WPF에서도 구조는 같고, 요소의 AllowDrop="True"와 DragOver/Drop 이벤트로 받아 e.Data.GetData(DataFormats.FileDrop)으로 경로 배열을 꺼냅니다. DragEnter/DragOver에서 수락 여부(Effect)를 매번 표명하는 것이 IDropTarget의 방식이며, 여기를 빼먹으면 커서가 「금지」인 채로 바뀌지 않는 문제가 됩니다.

9. D&D의 함정 ── 승격·Move·경로 검증

9.1. 관리자 승격한 앱에는 드롭할 수 없다

「관리자로 실행」한 앱에 탐색기에서 파일을 드롭해도 반응이 없다 ── 이것은 구현 실수가 아니라 OS 사양입니다. UIPI(User Interface Privilege Isolation)가 낮은 무결성 수준의 프로세스에서 높은 무결성 수준의 창으로의 메시지를 기본으로 차단하기 때문이며, 보통 권한(중간 무결성)의 탐색기에서 승격 앱으로는 드롭 알림이 도착하지 않습니다.14

승격 앱으로의 드롭이 UIPI에 차단되는 구조중간 무결성의 탐색기에서 높은 무결성의 승격 앱으로의 드롭 알림은 UIPI가 기본으로 차단하므로 도착하지 않는다. UI를 보통 권한으로 두고 특권 처리를 분리하면 드롭은 도착한다드롭 알림차단(기본)통과특권 처리만 의뢰탐색기(중간 무결성)UIPI승격 앱(높은 무결성): 무반응보통 권한의 UI: 드롭이 도착승격이 필요한 처리를 분리한 별도 프로세스

ChangeWindowMessageFilterEx로 WM_DROPFILES 등 특정 메시지를 개별 허용하는 우회가 알려져 있지만14, 이것으로 통과하는 것은 옛(WM_DROPFILES) 드롭 알림이며, OLE D&D 전체가 해결되는 것은 아닙니다. 실무 지침은 분명하며, 앱을 항상 승격해 돌리는 설계를 그만두는 것입니다. 승격이 필요한 처리만 별도 프로세스로 분리하면, UI 본체는 보통 권한인 채로 D&D를 받을 수 있습니다(분리 설계는 「Windows 앱의 관리자 권한과 브로커 프로세스」에서 자세히 설명합니다).

9.2. DragDropEffects의 의미 ── Move는 「원본이 사라진다」는 계약

DragDropEffects의 Copy/Move/Link는 장식이 아니라, 드래그 원본과 드롭 대상 사이의 계약입니다. 드래그 원본은 DoDragDrop에서 허용하는 효과의 집합을 선언하고, 드롭 대상이 실제 효과를 고르며, Move가 성립하면 드래그 원본이 데이터(파일)를 삭제하는 것이 규약입니다. 받는 쪽이 깊게 생각하지 않고 Move를 반환하면 「드롭했더니 원본 파일이 사라졌다」는 사고가 됩니다. 업무 앱의 가져오기 용도에서는 받는 쪽이 Copy를 명시하는 것이 안전한 쪽의 기본입니다.

DragDropEffects의 계약 ── Move에서는 원본이 사라진다드래그 원본이 DoDragDrop에서 허용하는 효과의 집합을 선언하고, 드롭 대상이 실제 효과를 고른다. Move가 성립하면 드래그 원본이 파일을 삭제하는 규약이므로, 가져오기 용도의 받는 쪽은 Copy를 명시하는 것이 안전하다CopyMove드래그 원본: 허용하는 효과를 선언(Copy | Move | Link)드롭 대상: 실제 효과를 고른다원본 파일은 남는다(가져오기 용도의 안전한 쪽)드래그 원본이 파일을 삭제한다 ──「원본이 사라졌다」사고의 원인

9.3. 드롭된 경로의 검증

CF_HDROP/FileDrop으로 전달되는 것은 경로뿐입니다(3.2절). 가져오기 전에, 붙여 넣기와 같이 외부 입력으로서의 검증을 통과시킵니다.

  • 파일인가 폴더인가: 폴더 통째로 드롭되는 경우를 사양으로 정해 둡니다(재귀로 가져올지, 거절할지).
  • OneDrive의 플레이스홀더: 경로는 존재해도 파일 본체가 로컬에 없는 「온디맨드 파일」인 경우, 연 순간에 다운로드가 시작되고, 오프라인이면 실패합니다. 동작과 대책은 「OneDrive 「파일 온디맨드」와 업무 앱」을 참조하십시오.
  • 긴 경로·특수한 경로: MAX_PATH를 넘는 경로, 네트워크(UNC) 경로, 이동식 미디어상의 경로는 후속 처리가 대응할 수 있는지를 확인한 뒤에 받습니다.
  • 건수와 합계 크기: 수천 파일의 드롭으로 UI가 멈추지 않도록, 가져오기는 비동기로 처리하고 한도와 진행 표시를 둡니다.

10. 정리

  • 클립보드는 같은 데스크톱(윈도우 스테이션) 안에서 공유되는 하나의 영역에, 같은 내용을 여러 형식으로 동시에 두는 구조입니다. 붙여 넣는 쪽이 형식을 고르므로, 같은 복사라도 결과가 달라집니다.
  • 텍스트는 CF_UNICODETEXT, 파일은 CF_HDROP, 서식 있는 텍스트는 등록 형식 HTML Format(바이트 오프셋 헤더+UTF-8)입니다.
  • 붙여 넣는 쪽은 리치→플레인 순으로 형식을 찾고, 내용은 외부 입력으로 검증합니다. 복사하는 쪽은 여러 형식을 동시에 제공하고, 지연 렌더링을 쓴다면 종료 때의 확정(WM_RENDERALLFORMATS/OleFlushClipboard)까지 구현합니다.
  • 감시는 AddClipboardFormatListener+WM_CLIPBOARDUPDATE입니다. OpenClipboard의 경합에는 재시도로 대비하고, 기밀은 ExcludeClipboardContentFromMonitorProcessing 등으로 기록·동기에서 제외합니다.
  • 정보시스템 담당자는 클립보드 기록·클라우드 동기·RDP 리다이렉션을 GPO/Intune으로 제어할 수 있습니다. 기본값은 모두 허용이므로, 기밀을 다루는 환경에서는 의식적으로 판단하십시오.
  • D&D는 COM이며, 클립보드와 같은 IDataObject를 IDropSource/IDropTarget이 주고받습니다. RegisterDragDrop에는 OleInitialize(STA)가 필수입니다.
  • 승격 앱으로의 드롭은 UIPI로 차단됩니다. DragDropEffects의 Move는 「원본이 사라진다」는 계약이며, 드롭된 경로는 검증한 뒤에 가져옵니다.

복사·붙여 넣기와 D&D는 사용자에게는 공기 같은 기능입니다. 그래서 「붙여 넣을 수 없다」「무너진다」「사라졌다」가 일어났을 때의 경험 악화는 크고, 반대로 여러 형식의 제공과 드롭 대응이 갖춰진 앱은 그 자체로 매일의 조작이 매끄러워집니다. 개선 우선순위를 생각할 때의 판단 재료가 되면 좋겠습니다.

관련 기사

관련 상담 영역

합동회사 코무라소프트에서는 업무 앱의 복사·붙여 넣기·드래그 앤 드롭 대응 설계와 구현(여러 형식 제공, Excel 연동, 파일 드롭 가져오기), 「붙여 넣으면 무너진다」「복사가 사라진다」와 같은 문제의 원인 조사, 클립보드 감시를 쓰는 입력 자동화, 기밀 정보의 기록·동기 대책 구현을 다루고 있습니다. COM이나 OLE의 낮은 계층이 얽히는 건도, 현상을 가려내는 단계부터 괜찮습니다.

참고 링크

  1. Microsoft Learn, Clipboard Formats. 창이 같은 정보를 여러 클립보드 형식으로 둘 수 있다는 점, RegisterClipboardFormat에 의한 등록 형식(같은 이름 등록은 같은 값을 반환해 앱 간에 공유할 수 있음), 합성 형식, ExcludeClipboardContentFromMonitorProcessing·CanIncludeInClipboardHistory·CanUploadToCloudClipboard에 의한 클립보드 기록/클라우드 동기에서의 제외에 대해. ↩ ↩2 ↩3 ↩4 ↩5

  2. Microsoft Learn, Clipboard Operations. 클립보드를 열 수 있는 것은 한 번에 창 하나뿐이라는 점, 복사 때 표현력이 높은 형식부터 차례로 둔다는 점, 붙여 넣기 때 EnumClipboardFormats/GetPriorityClipboardFormat에 의한 형식 선택, SetClipboardData에 NULL을 넘기는 지연 렌더링과 WM_RENDERFORMAT/WM_RENDERALLFORMATS의 책임, 지연 렌더링의 트레이드오프에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  3. Microsoft Learn, Standard Clipboard Formats. CF_TEXT(ANSI)·CF_UNICODETEXT·CF_HDROP·CF_DIB·CF_LOCALE 등 표준 형식의 정의와, CF_LOCALE에 연관된 코드 페이지를 써서 CF_TEXT와 CF_UNICODETEXT가 시스템에 의해 암시 변환된다는 점에 대해. ↩ ↩2 ↩3

  4. Microsoft Learn, Shell Clipboard Formats. CF_HDROP이 DROPFILES 구조체와 이중 NUL 종료의 전체 경로 문자열 배열로 구성된다는 점, DragQueryFile에 의한 개별 경로 꺼내기, CFSTR_ 계열 셸 형식이 RegisterClipboardFormat에 의한 등록을 필요로 한다는 점에 대해. ↩ ↩2

  5. Microsoft Learn, HTML Clipboard Format. 등록 이름이 「HTML Format」이라는 점, Version·StartHTML·EndHTML·StartFragment·EndFragment 등의 오프셋(바이트 단위)을 가진 헤더 구조, 인코딩이 항상 UTF-8이라는 점, StartFragment/EndFragment 주석의 규약에 대해. ↩ ↩2 ↩3

  6. Microsoft Learn, OleFlushClipboard function (ole2.h). OleSetClipboard에서는 클립보드가 데이터 객체에 대한 포인터만 유지한다는 점, OleFlushClipboard가 데이터를 클립보드상에 실체화해 앱 종료 뒤의 붙여 넣기를 가능하게 한다는 점, 종료 때 남길 필요가 없으면 OleSetClipboard(NULL)로 비워야 한다는 점에 대해. ↩ ↩2

  7. Microsoft Learn, OleGetClipboard function (ole2.h). 클립보드에서 IDataObject를 얻는 방법과, 「클립보드 데이터는 신뢰하지 않으므로, 앱에서 쓰기 전에 신중하게 파싱해야 한다」는 경고에 대해. ↩ ↩2

  8. Microsoft Learn, Using the clipboard. 클립보드 감시의 세 방식(뷰어 창·시퀀스 번호·형식 리스너)의 비교, 새 프로그램은 AddClipboardFormatListener에 의한 리스너를 써야 한다는 점, 뷰어 체인이 체인 유지의 미비에 취약하다는 점, 시퀀스 번호를 폴링에 쓰면 안 된다는 점에 대해. ↩ ↩2

  9. Microsoft Learn, Policy CSP - Experience. Experience/AllowClipboardHistory 정책에 의한 클립보드 기록의 허용/금지, Windows 10 버전 1809 이후에서 사용할 수 있다는 점, 기본값이 허용이라는 점, GPO에서는 「시스템 > OS 정책」 아래에 매핑되어 변경이 즉시 적용된다는 점에 대해. ↩ ↩2

  10. Microsoft Learn, Policy CSP - Privacy. Privacy/AllowCrossDeviceClipboard 정책에 의한 기기 간 클립보드 동기의 허용/금지, 동기가 동일한 Microsoft 계정/Microsoft Entra 계정으로 로그인한 기기 간에 이루어진다는 점, 기본값이 허용이라는 점에 대해. ↩ ↩2 ↩3

  11. Microsoft Learn, Policy CSP - ADMX_TerminalServer. TS_CLIENT_CLIPBOARD(「클립보드 리다이렉션을 허용하지 않음」, 레지스트리 값 fDisableClip)에 의해 원격 데스크톱 세션에서의 로컬/원격 간 클립보드 공유를 금지할 수 있다는 점, 기본값에서는 리다이렉션이 허용된다는 점에 대해. ↩ ↩2

  12. Microsoft Learn, Drag and Drop (COM). OLE 드래그 앤 드롭이 IDropSource(드래그 원본)·IDropTarget(드롭 대상)·DoDragDrop(OLE가 제공하는 루프)의 셋으로 동작한다는 점, 클립보드의 복사·붙여 넣기와 동일한 기능을 제공하며 복사·붙여 넣기를 이미 구현한 앱이라면 추가가 적다는 점, 피드백의 종류에 대해. ↩ ↩2 ↩3

  13. Microsoft Learn, RegisterDragDrop function (ole2.h). 드롭 대상 창과 IDropTarget의 등록 방법, COM을 CoInitialize/CoInitializeEx로 초기화한 경우에는 항상 E_OUTOFMEMORY로 실패하고 OleInitialize가 필요하다는 점, 호출 스레드가 메시지 펌프를 돌리지 않으면 드래그 원본 앱이 멈춘다는 점에 대해. ↩ ↩2 ↩3

  14. Microsoft Learn, ChangeWindowMessageFilterEx function (winuser.h). UIPI가 낮은 무결성 수준의 송신 원본으로부터의 메시지 수신을 기본으로 차단하는 보안 메커니즘이라는 점, 창 단위의 메시지 필터로 특정 메시지를 허용(MSGFLT_ALLOW)할 수 있다는 점에 대해. ↩ ↩2 ↩3

  15. Microsoft Learn, How to add data to the Clipboard (Windows Forms). DataObject와 Clipboard.SetDataObject로 여러 형식의 데이터를 동시에 두는 방법, 다른 앱이 인식하도록 여러 형식으로 추가해야 한다는 점, Clipboard 클래스가 STA 스레드에서만 사용할 수 있어 [STAThread]가 필요하다는 점에 대해. ↩ ↩2

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

ActiveX 이관

COM / ActiveX / OCX 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

Excel에서 복사한 표를 앱에 붙여 넣으면 서식이 무너지는 이유는 무엇인가요?
클립보드에는 「데이터 하나」가 아니라, 같은 내용이 여러 형식(앱 고유 형식, HTML Format, CSV, Unicode 텍스트 등)으로 동시에 놓이며, 붙여 넣는 앱이 이해하는 형식을 골라 꺼내기 때문입니다. 서식이 무너질 때의 전형은 붙여 넣는 쪽이 일반 텍스트(CF_UNICODETEXT)만 읽는 경우입니다. 표 구조까지 받으려면 붙여 넣기 측에서 HTML Format이나 CSV를 우선해 꺼냅니다. 반대로 자체 앱에서 복사한 내용을 다른 앱이 올바르게 붙여 넣게 하려면, 복사 때 풍부한 형식과 일반 형식을 함께 제공합니다.
복사 원본 앱을 닫으면 붙여 넣을 수 없게 되는 이유는 무엇인가요?
원본이 지연 렌더링(delayed rendering)을 쓰기 때문입니다. 큰 데이터를 다루는 앱은 복사 때 데이터 본체를 두지 않고 「요청되면 만들겠다」는 약속만 클립보드에 등록합니다. 이 상태에서 원본이 종료 시 WM_RENDERALLFORMATS에 응답해 데이터를 확정하지 않고 종료하면, 아직 렌더링되지 않은 형식은 사라집니다. OLE 클립보드(IDataObject)를 쓰는 앱이라면, 종료 때 OleFlushClipboard를 호출해 데이터를 실체화해 두면 종료 뒤에도 붙여 넣을 수 있습니다.
자체 앱에서 클립보드 변경을 감시하려면 어떻게 하면 되나요?
AddClipboardFormatListener로 자기 창을 리스너로 등록하고, 내용이 바뀔 때마다 오는 WM_CLIPBOARDUPDATE 메시지를 처리하는 것이 현재 권장 방법입니다. 타이머로 주기적으로 읽는 폴링은 낭비가 크고, SetClipboardViewer의 옛 뷰어 체인은 체인 안 앱 하나의 문제가 전체를 깨뜨리므로 하위 호환을 위해 남아 있을 뿐입니다. 읽을 때 OpenClipboard는 다른 프로세스와 경합해 실패할 수 있으므로, 짧은 대기를 둔 재시도를 구현해 두면 안정적입니다.
암호 같은 기밀을 클립보드 기록(Win+V)에 남기지 않는 방법이 있나요?
앱 측과 정책 측, 두 가지 수단이 있습니다. 앱 측에서는 복사 때 ExcludeClipboardContentFromMonitorProcessing이라는 등록 형식을 함께 두면, 그 내용은 기록에도 기기 간 동기에도 포함되지 않습니다. CanIncludeInClipboardHistory(기록만)나 CanUploadToCloudClipboard(동기만)로 개별 제어도 가능합니다. 암호 관리자가 쓰는 메커니즘입니다. 조직 전체에서 끄려면 그룹 정책이나 Intune(Policy CSP)의 AllowClipboardHistory·AllowCrossDeviceClipboard로 기록·클라우드 동기 자체를 비활성화할 수 있습니다.
관리자로 실행한 앱에 파일을 드래그 앤 드롭할 수 없는 이유는 무엇인가요?
UIPI(User Interface Privilege Isolation)라는 보안 메커니즘이, 낮은 무결성 수준의 프로세스에서 높은 무결성 수준의 창으로 메시지를 보내는 것을 차단하기 때문입니다. 탐색기는 보통 권한(중간 무결성)으로 동작하므로, 승격된 앱 창에는 드래그 앤 드롭 알림이 도착하지 않습니다. ChangeWindowMessageFilterEx로 WM_DROPFILES 등을 개별 허용하는 우회도 알려져 있지만, 옛 드롭 알림에 한정됩니다. 근본적으로는 앱을 항상 승격해 돌리는 설계를 그만두고, 승격이 필요한 처리만 별도 프로세스로 분리하는 것이 정석입니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기