수정 이력(8건, 최종 수정 2026년 09월 03일)
이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.
- Codex P2에 따라 본문에 남은 일본어 잔여 표현을 한국어로 고쳤습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- 관련 기사 링크를 한국어 permalink에 맞추는 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- 기사 맨 앞에 「이 기사의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 모은 것입니다. 본문의 주장은 바꾸지 않았습니다.
- 외부 리뷰(1283건)에 대응해 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참고하세요.
- 판단표 기호 범례와 평가 기준을 추가했습니다(구현 비용과 throughput 행만 좋고 나쁨의 방향이 다르다는 점도 명시합니다). 동작 확인 절을 새로 두고, 시작 순서, 기대 응답, 서버가 기동되지 않았을 때의 동작, 이상 케이스 3종을 제시했습니다. 용어표, 정석 구성 그림, 판단표에서 정석 구성으로 돌아가는 참조도 추가했습니다.
- 본문의 관련 기사 링크 문구가 링크 대상의 현재 제목과 어긋나 있던 부분을 실제 제목에 맞췄습니다. 본문 내용은 바꾸지 않았습니다.
- 최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.21635348)
아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.
Go Komura (2026). 「Windows의 프로세스 간 통신을 어떻게 고를까 ── Named Pipe / TCP / gRPC / 공유 메모리 / COM 판단표」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/windows-ipc-decision-table/
- DOI(등록된 아카이브)
- 10.5281/zenodo.21635348
- DOI(마지막 등록 버전)
- 10.5281/zenodo.21635349
「UI와 서비스를 나누면, 그 사이 통신은 어떻게 하면 되는가」「32bit 앱에서 64bit DLL의 기능을 쓰고 싶다」「다른 프로세스의 측정 엔진에서 화면으로 데이터를 흘리고 싶다」. 앱을 여러 프로세스로 나누는 설계는 견고성·권한 분리·비트 수 문제에 대한 현실적인 해법으로 자주 나오지만, 나눈 순간 「사이 통신을 어떻게 할 것인가」라는 선택이 반드시 생깁니다.
이 블로그에서는 지금까지 공유 메모리의 함정, TCP의 framing, 자식 프로세스의 안전한 다루기, 파일 연동과 잠금처럼 개별 통신 수단은 다뤄 왔습니다. 그런데 「애초에 어떤 수단을 골라야 하는가」라는 총론은 아직 없었습니다. 이 기사에서는 Windows의 프로세스 간 통신(IPC)의 주요 선택지──파일 연동·Named Pipe·로컬 TCP·gRPC·공유 메모리·COM──의 강점과 함정을, 이 블로그에서 늘 쓰는 판단표 형식으로 정리합니다.
먼저 알아 둘 용어
본문에 약어 그대로 나오는 것을 먼저 모았습니다.
| 용어 | 의미 |
|---|---|
| IPC (Inter-Process Communication) | 프로세스 간 통신. 다른 프로세스와 데이터나 신호를 주고받는 메커니즘의 총칭입니다 |
| ACL (Access Control List) | 액세스 제어 목록. 「누구에게 어떤 조작을 허락할지」를 늘어놓은 목록으로, Windows에서는 파이프·파일·공유 메모리 등에 붙일 수 있습니다. .NET에서는 PipeSecurity 같은 클래스로 조립합니다 |
| UAC (User Account Control) | 사용자 계정 컨트롤. 관리자 계정이라도 기본은 표준 권한으로 움직이고, 필요할 때만 권한을 올리는 메커니즘입니다. 권한을 올린 쪽과 올리지 않은 쪽은 다른 권한으로 움직이므로, 그 사이 통신이 2.2절 이야기가 됩니다 |
| RPC (Remote Procedure Call) | 다른 프로세스·다른 머신의 절차를 로컬 함수 호출처럼 쓸 수 있는 메커니즘입니다. COM의 out-of-process 호출도 이 위에 올라갑니다 |
| data plane / control plane | 데이터 면과 제어 면. 실제 데이터의 흐름과, 시작·정지·설정 변경 같은 지시의 흐름을 나눠 생각할 때의 말입니다(2.5절·제4장) |
| framing | 바이트 나열의 어디부터 어디까지가 1메시지인지를 정하는 메커니즘입니다. TCP에서는 자체 설계가 필수입니다(2.3절·제6장) |
1. 먼저 결론
- 같은 머신 안의 요청-응답(명령을 보내고 결과를 받는 경우)이라면 Named Pipe가 첫 후보입니다. OS 표준, 포트 불필요, Windows의 액세스 제어와 통합되어 있고, .NET에서는
System.IO.Pipes로 바로 쓸 수 있습니다. 1 - 나중에 네트워크를 넘을 가능성이 있으면, 처음부터 로컬 TCP나 gRPC로 합니다. 파이프에서 소켓으로 옮기는 작업은 「나중에 조금 고치면 된다」로 끝나지 않는 차이가 되기 쉽습니다. 다만 순수 TCP는 바이트 스트림이므로 framing 설계가 필수입니다.
- 서비스나 호출 종류가 늘어 자체 프로토콜 유지가 부담이 되면 gRPC입니다. proto로 스키마를 정의하고 코드를 생성하며 양방향 스트리밍을 얻고, .NET 8 이후는 ASP.NET Core(Kestrel)가 Named Pipe를 transport로 쓸 수 있습니다. 2
- 대용량·고빈도 데이터(이미지 프레임, 파형)만 공유 메모리에 올립니다. 가장 빠르지만 동기화를 전부 직접 설계하게 되므로, 제어 메시지까지 공유 메모리에 올리지 않는 것이 철칙입니다. 3
- 느슨한 결합·비동기로 충분하고 감사 로그를 남기고 싶은 연동이라면 파일 연동이 지금도 유력합니다. 다만 성패는 배타 제어 설계로 갈립니다.
- COM(out-of-process)은 신규 설계의 첫 후보로 두지 않습니다만, VBA·다른 언어에서의 이용이나 32bit/64bit 브리지라는 맥락에서는 지금도 현역 도구입니다. 4
- 어떤 수단을 골라도 메시지의 구분·버전 번호·타임아웃·재연결이라는 공통 설계 과제에서는 벗어날 수 없습니다(제6장). 수단 고르기와 같은 만큼 여기에 시간을 쓰세요.
그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 21건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle
2. 선택지의 성격
2.1 파일 연동 ── 느슨한 결합·비동기·감사 가능
「A가 출력 폴더에 파일을 두고, B가 주워 처리한다」는 고전적인 연동입니다. 양쪽이 동시에 실행 중일 필요가 없고, 처리 기록이 파일로 남으며, 장애 때는 사람이 파일을 직접 보고·고치고 재투입할 수 있습니다. 실시간성을 요구하지 않는 배치형 연동에서는 지금도 강력합니다.
함정은 거의 한 점, 배타 제어로 모입니다. 「쓰기 도중 파일을 읽어 버린다」「두 처리가 같은 파일을 다툰다」는 전형적인 사고이며, 임시 파일 이름으로 쓴 뒤 이름을 바꾸는 등의 정석을 밟아야 합니다. 자세한 내용은 「파일 연동과 잠금의 베스트 프랙티스」에 모아 두었으므로, 이 방식을 고를 때는 반드시 참고하세요. 응답이 필요한 대화형 통신, 초당 수십 회 발생하는 주고받기에는 맞지 않습니다.
2.2 Named Pipe ── 같은 머신 IPC의 정석
Named Pipe는 Windows가 커널에서 제공하는 양방향 통신 경로로, 같은 머신 안의 클라이언트·서버형 IPC의 정석입니다. .NET에서는 NamedPipeServerStream / NamedPipeClientStream으로 다루며, 하나의 파이프 이름에 여러 클라이언트가 연결할 수 있습니다. 5 TCP와 달리 포트 번호 관리가 필요 없고, 방화벽에도 걸리지 않습니다.
실무에서 알아 둘 포인트를 정리합니다.
- 메시지 모드를 쓸 수 있습니다.
PipeTransmissionMode.Message를 지정하면, 쓰기 1회분이 1메시지로 구분되어 도착합니다. TCP에서 필수가 되는 length prefix 같은 framing을 OS가 대신해 주는, 실무상 큰 이점입니다(수신 측은IsMessageComplete로 1메시지를 다 읽어야 합니다. 제5장 참조). - 기본 액세스 권한은 생각보다 느슨합니다. 파이프의 기본 보안 설명자는 LocalSystem·관리자·작성자에게 전체 제어를 주는 한편, Everyone과 익명 계정에도 읽기를 허용합니다. 6 같은 사용자의 프로세스 사이라면
PipeOptions.CurrentUserOnly만 붙이면 「같은 사용자가 만든 상대하고만 연결한다」를 강제할 수 있고, 이를 기본 관행으로 두는 것을 권합니다. 7 다른 계정 사이(서비스와의 통신 등)는PipeSecurity로 ACL을 명시합니다. - 파이프 이름은 모두에게 보이는 네임스페이스에 있습니다. 파이프 이름은
\\.\pipe\아래의 단일 네임스페이스에 놓이며, 머신 위의 다른 사용자 프로세스에서도 보입니다. 여기서 주의할 것이 이름 점유(스쿼팅)입니다. 악의적인 프로세스가 먼저 같은 이름으로 서버를 세워 대기하면, 클라이언트는 그쪽에 연결해 버립니다. 서버 측은PipeOptions.FirstPipeInstance를 지정해 같은 이름의 파이프가 이미 있으면 실패시키고, 클라이언트 측은 연결 후 파이프의 소유자 계정을 검증하는 식의 대책을, 권한이 다른 사용자가 공존하는 머신에서는 넣어 두어야 합니다. 8 - 권한 경계를 넘는 통신이 가능합니다. UAC로 분리된 「표준 권한 UI + 관리자 권한 브로커」 사이 통신 경로로도 정석입니다. 다만 이 구성에서는
CurrentUserOnly를 쓸 수 없습니다(같은 사용자라도 권한 상승 수준까지 일치할 것을 요구하기 때문입니다7). ACL을 명시적으로 설계해야 하며, 구체적인 구현은 「관리자 권한의 분리(브로커)」에서 자세히 썼습니다.
약점은, 머신을 넘는 통신에는 사실상 맞지 않는다는 점(스펙상은 가능하지만 운영 면 제약이 많습니다), Windows 이외와의 상호 연결에 쓰기 어렵다는 점입니다. 그 요건이 보이면 TCP / gRPC를 고릅니다.
2.3 로컬 TCP ── 언어·OS를 넘는 범용성
localhost에 대한 TCP 연결은, 거의 어떤 언어·런타임·OS에서든 쓸 수 있는 가장 범용적인 IPC입니다. 「Linux 위의 분석 엔진과 Windows의 UI」 같은 혼재 구성에서는, 우선 TCP(또는 그 위의 HTTP/gRPC)가 후보가 됩니다.
주의점은 세 가지입니다.
- 바이트 스트림입니다. TCP에는 「보낸 단위로 도착한다」는 성질이 없습니다.
Send한 3개 메시지가 1회의Receive로 이어져 도착하는 것도, 하나의 메시지가 분할되어 도착하는 것도 정상입니다. length prefix 같은 framing을 앱 계층에서 설계해야 하며, 이를 생략한 코드는 「우연히 동작하고 있을 뿐」입니다. 자세한 내용은 「TCP에서 Send한 단위마다 Receive할 수 있다는 오해」를 참고하세요. - 대기 공개 범위를 좁힙니다. 같은 머신 안 통신이더라도,
0.0.0.0으로 대기하면 네트워크상의 다른 머신에서 연결할 수 있습니다. 로컬 IPC라면127.0.0.1(루프백)에 바인드하는 것이 원칙입니다. 그래도 같은 머신 안의 다른 사용자로부터도 연결할 수 있으므로, 상대 확인이 필요하면 앱 계층에서 인증을 더합니다. 파이프처럼 OS와 통합된 액세스 제어가 없다는 점은 분명한 차이입니다. - 포트 운용이 따라다닙니다. 고정 포트는 다른 소프트웨어와 충돌할 수 있고, 방화벽 제품이 「수상한 수신 대기」로 검출하기도 합니다. 포트 번호 변경 수단은 처음부터 설계에 넣습니다.
2.4 gRPC ── 스키마와 코드 생성을 산다
순수 소켓 + 자체 프로토콜과 비교할 때, gRPC가 제공하는 것은 통신 그 자체라기보다 개발의 틀입니다. proto 파일로 서비스와 메시지를 정의하면 직렬화·framing·클라이언트/서버 코드가 모두 생성되고, 양방향 스트리밍(서버에서의 푸시 알림)도 언어 기능처럼 쓸 수 있습니다. 「메시지가 늘 때마다 자체 프로토콜의 switch 문과 문서를 고친다」는 단계에 들어가면, 이 틀이 효과를 냅니다.
.NET 8 이후는 ASP.NET Core(Kestrel)가 TCP뿐 아니라 Unix domain socket과 Named Pipe를 transport로 직접 지원합니다. 8 서버 측은 ListenNamedPipe만 호출하면 되고, PipeSecurity에 의한 액세스 제어도 설정할 수 있습니다. 2 「통신 경로는 파이프 그대로 포트 없이·ACL 통합, 프로토콜 계층은 gRPC」라는 조합은, UI + 서비스 분리(제4장)에서 유력한 선택지입니다.
한편 소규모 도구 간 통신에는 오버킬이 되기 쉽습니다. gRPC 서버를 세운다는 것은 ASP.NET Core 호스팅 기반을 떠안는다는 뜻이고, 배포물·의존 관계·기동 비용이 늘어납니다. 명령이 2~3종류뿐인 도구끼리라면 Named Pipe + JSON(제5장) 쪽이 총비용은 싸다는 기준은 갖고 두세요. 「proto에 쓰고 싶은 메시지가 10을 넘었다」「스트리밍 알림이 본질적으로 필요하다」「상대가 .NET이 아니다」 중 하나에 해당한 뒤에 도입해도 늦지 않습니다.
2.5 공유 메모리(메모리 맵 파일) ── 가장 빠르지만, 동기화는 전부 직접
같은 머신 안에서 가장 빠른 IPC는 공유 메모리입니다. .NET에서는 MemoryMappedFile.CreateNew로 이름 있는 공유 메모리를 만들고, 여러 프로세스에서 같은 바이트 열을 직접 읽고 쓸 수 있습니다. 3 복사도 직렬화도 끼어들지 않으므로, 이미지 프레임이나 파형 데이터처럼 「크고 빠른」 데이터에는 이것뿐이라고 해도 될 성능이 나옵니다.
다만 공유 메모리는 「빠른 파이프」가 아닙니다. 같은 바이트 열이 보일 뿐이고, 동기화는 1바이트도 제공되지 않습니다. 쓰기 도중 데이터를 읽지 않는 구조, 상대의 생존 여부 검출, 한쪽이 비정상 종료한 뒤의 복구──전부 직접 설계해야 합니다. 링 버퍼 구성이나 레이아웃 버전 관리까지 포함한 설계론은 「공유 메모리의 함정과 실무 베스트 프랙티스」에 한 편으로 모아 두었습니다.
실무에서 쓸 자리는 분명하고, 데이터 면(data plane)만 공유 메모리에 올리고, 제어 면(control plane)은 Named Pipe 등 다른 채널로 빼는 구성입니다(제4장 구성 3). 시작·정지·설정 변경 같은 제어 메시지까지 공유 메모리로 버티기 시작했다면, 설계를 다시 볼 신호입니다.
2.6 COM(out-of-process) ── 신규의 첫 후보는 아니지만, 현역으로 쓰는 경우가 있다
COM의 out-of-process 서버(EXE 서버)는, Windows가 오래전부터 가진 「다른 프로세스의 객체를 로컬 함수처럼 호출하는」 메커니즘입니다. 4 신규 앱 간 통신에서 첫 후보로 두지는 않지만, 다음 맥락에서는 지금도 실용적입니다.
- 32bit/64bit 브리지: 32bit 앱에서 64bit DLL(또는 그 반대)은 같은 프로세스에 로드할 수 없지만, out-of-process COM이면 경계를 넘을 수 있습니다. COM 기반이 마샬링을 대신하므로, 호출 측 코드를 거의 바꾸지 않아도 됩니다. 실제 예는 「32bit→64bit COM 브리지 사례」에 썼습니다.
- VBA·다른 언어에서의 이용: Excel VBA 등 오래된 실행 환경에서 .NET 기능을 호출하게 하고 싶을 때, COM으로 공개하는 것이 지금도 가장 자연스러운 경로입니다.
- 기존 COM 자산과의 연동: 상대가 COM으로만 말하면, 이쪽도 COM으로 말하는 것이 최단입니다(COM 자체의 해설은 「COM·ActiveX·OCX란 무엇인가」).
신규 채택을 망설이는 이유는, 레지스트리 등록이 따르는 배포가 번거로운 점, 인터페이스 설계와 참조 카운트 학습 비용, 그리고 문제 시 조사의 어려움입니다. 「32/64 브리지가 목적이라면, 64bit 쪽을 그냥 helper 프로세스로 두고 Named Pipe로 통신한다」는 대안(제4장 구성 2)과 비교한 뒤에 결정하세요.
2.7 고전적인 수단 ── 신규에서는 고르지 않는다
Windows에는 이 밖에도 WM_COPYDATA(윈도우 메시지로 데이터를 보낸다), 클립보드, DDE, mailslot 같은 IPC 수단이 있고, 공식 IPC 개요 페이지에는 지금도 나열되어 있습니다. 4 다만 창(window)이 있어야 하거나, 사용 중단에 들어가 있거나(원격 mailslot은 사용 중단 과정에 있습니다)라서, 신규 설계에서 고를 이유는 거의 없습니다. 기존 앱 유지보수에서 만났을 때 알면 충분합니다.
3. 판단표
기호 의미를 먼저 정합니다. 「그 수단을 골랐을 때, 그 관점을 충족하기 위해 추가 설계·구현이 얼마나 필요한가」를 4단계로 나타냅니다. 속도나 기능의 절대 평가가 아닙니다.
| 기호 | 평가 기준 |
|---|---|
| ◎ | 그 용도를 위해 만들어져 있다. 표준 기능을 그대로 쓰면 되고, 추가 설계가 거의 필요 없다 |
| ○ | 문제없이 쓸 수 있다. 다만 정석대로의 구현이나 설정(ACL 작성, framing 등)은 필요하다 |
| △ | 가능하지만, 상응하는 자체 설계가 필요하거나, 무시할 수 없는 제약·부작용이 따른다 |
| ✕ | 실질적으로 할 수 없다. 다른 수단과 조합해야 한다 |
「구현 비용」 행만 기호가 아니라 저·중·고로 적혀 있고, 낮은 쪽이 바람직하다는 방향입니다. 「throughput/latency」 행은 반대로, 높은 쪽(◎이 가장 빠름)이 바람직한 방향이 됩니다.
| 관점 | 파일 | Named Pipe | 로컬 TCP | gRPC | 공유 메모리 | COM |
|---|---|---|---|---|---|---|
| 통신 범위 | 공유 폴더로 머신 경계를 넘을 수 있음 | 같은 머신이 실용 범위 | 머신 경계를 넘을 수 있음 | 머신 경계를 넘을 수 있음 | 같은 머신 한정 | 같은 머신이 실용 범위 |
| 통신 모델 | 파일 주고받기(비동기) | 스트림+메시지 모드 | 바이트 스트림 | RPC+스트리밍 | 공유 상태 | 메서드 호출 |
| 서버→클라이언트 알림 | ✕(폴링) | ○ | ○ | ◎(양방향 스트리밍) | △(이벤트 병용) | △(만들 수 있으나 복잡) |
| 권한 경계 넘기(UAC·서비스) | ○(폴더 ACL) | ◎(PipeSecurity) | △(인증은 자체) | △~○(파이프 transport라면 ◎) | ○(ACL 가능, 설계는 어려움) | ○ |
| 32/64·언어 혼재 | ◎ | ○ | ◎ | ◎(proto에서 각 언어로 생성) | △(ABI 설계가 필수) | ○(브리지가 특기) |
| 구현 비용 | 저 | 저~중 | 중(framing 자체) | 중(기반 도입) | 고 | 고(신규에는) |
| 디버깅 용이성 | ◎(내용이 파일로 남는다) | ○ | ○(캡처 가능) | △(HTTP/2+바이너리) | △(증상이 요란하다) | △ |
| throughput/latency | 저 | 중~고 | 중 | 중 | ◎ | 중 |
보충 하나. 「◎이 많은 수단」을 고르는 것이 아니라, 요건에 해당하는 행만 보고 소거법으로 좁히는 것이 올바른 사용법입니다. 예를 들어 「초당 30프레임의 이미지」가 정해진 시점에, 데이터 면은 공유 메모리뿐입니다.
그리고 좁힌 결과는 대개 다음 장의 세 가지 정석 구성 중 하나로 귀결됩니다. 표로 수단을 정한 뒤에는, 이 대응으로 제4장으로 돌아가세요.
| 판단표에서 남은 수단과 요건 | 목적지 |
|---|---|
| Named Pipe + 권한 경계(UAC·서비스)를 넘는다 | 구성 1: UI 앱 + Windows 서비스 분리 |
| Named Pipe 또는 COM + 32/64bit 혼재가 요건 | 구성 2: 32bit 앱에서 64bit 기능으로의 브리지 |
| 공유 메모리 + 시작·정지 등의 제어도 필요하다 | 구성 3: 제어와 데이터의 2채널 구성 |
| gRPC가 남았다 | 구성 1의 통신 층을 gRPC의 Named Pipe transport로 바꾼 형태가 됩니다2 |
| 파일 연동이 남았다 | 정석 구성이라기보다 느슨한 결합 배치. 설계의 본체는 배타 제어(2.1절)입니다 |
4. 정석 구성의 예
판단표를 개별 요건에 대입하면, 실무에서는 다음 세 구성으로 떨어지는 경우가 많습니다. 그림으로 하면 다음과 같습니다.
flowchart TB
subgraph K1["구성 1: UI 앱 + Windows 서비스 분리"]
U1["UI 앱<br/>로그온 사용자의 권한"]
P1["Named Pipe<br/>JSON의 요청·응답<br/>다른 계정 사이이므로 PipeSecurity로 ACL을 명시"]
S1["Windows 서비스<br/>LocalSystem 등 다른 계정<br/>상주 처리·특권 처리"]
U1 <--> P1
P1 <--> S1
end
subgraph K2["구성 2: 32bit 앱에서 64bit 기능으로의 브리지"]
A2["32bit 앱(기존 자산)"]
P2["Named Pipe<br/>또는 out-of-process COM"]
H2["64bit helper 프로세스<br/>Job Object로 부모-자식을 연동 종료시킨다"]
D2["64bit 전용 DLL·SDK"]
A2 <--> P2
P2 <--> H2
H2 --> D2
end
subgraph K3["구성 3: 측정 엔진에서 표시로의 고빈도 데이터"]
E3["측정·이미지 처리 엔진"]
C3["제어 채널: Named Pipe<br/>시작·정지·설정 변경. 초에 수 회"]
D3["데이터 채널: 공유 메모리 + named event<br/>프레임·파형. 초에 수십 회 이상"]
V3["UI 앱(표시)"]
V3 <--> C3
C3 <--> E3
E3 --> D3
D3 --> V3
end
구성 1: UI 앱 + Windows 서비스 분리. 상주 처리나 특권이 필요한 처리를 서비스에 두고, UI는 보통 사용자 프로세스로 두는 구성입니다(서비스 측 구현은 같은 날 공개한 「Windows 서비스 기사」를 참고). 통신은 Named Pipe가 정석이고, 프로토콜은 JSON 메시지+메시지 모드(또는 바이트 모드+length prefix)부터 시작하는 것이 무난하며, 호출 종류가 늘어나면 gRPC의 Named Pipe transport로 옮기는 길도 있습니다. 2 서비스는 다른 계정(LocalService 등)으로 움직이므로 CurrentUserOnly는 쓸 수 없고, PipeSecurity로 「Users에 읽기 쓰기를 허용, 원격은 거부」 같은 ACL을 명시하는 것이 포인트입니다.
구성 2: 32bit 앱 → 64bit 기능 브리지. 64bit에서만 동작하는 DLL·드라이버 SDK를 32bit 기존 앱에서 쓰고 싶을 때, 64bit 쪽을 별도 프로세스로 분리합니다. 구현은 두 가지입니다. (a) 64bit helper 프로세스를 세우고 Named Pipe로 통신한다, (b) 64bit out-of-process COM 서버로 만든다. 호출 측이 VBA나 오래된 언어라면 (b)가 자연스럽고, C#끼리라면 (a)가 배포와 조사 모두 쉽습니다. (a)의 경우 helper 프로세스의 기동·감시·부모-자식 연동 종료는 「자식 프로세스를 안전하게 다루기」에서 쓴 Job Object 패턴을 그대로 쓰세요.
구성 3: 측정 엔진 → 표시의 고빈도 데이터. 계측·이미지 처리 엔진을 별도 프로세스로 두고 UI로 데이터를 흘리는 구성에서는, 제어(시작·정지·설정)는 Named Pipe, 데이터(프레임·파형)는 공유 메모리+named event라는 2채널 구성이 정석입니다. 제어는 초에 수 회이므로 파이프로 충분하고, 데이터는 공유 메모리로 제로 카피에 가깝게 합니다. 채널을 나누면 데이터 측 설계(링 버퍼 등)를 제어 사정과 떼어 최적화할 수 있습니다. 엔진이나 외부 기기 상태 표시는 「외부 기기의 상태 표시」도 참고하세요.
5. 구현 예 ── Named Pipe의 비동기 서버와 클라이언트
Named Pipe 구현의 토대가 되는 .NET 8 최소 구성을 보입니다. 메시지 모드·복수 클라이언트 대응·연결 끊김 처리까지 넣은 형태입니다. 프로토콜은 「UTF-8 JSON을 메시지당 하나」로 하고, 반드시 version 필드를 갖춥니다. 아래 예는 같은 사용자 프로세스 간 통신을 전제로 CurrentUserOnly를 씁니다. 구성 1처럼 서비스(다른 계정)와 통신하는 경우는, 뒤에서 말하듯 CurrentUserOnly를 빼고 PipeSecurity로 ACL을 명시하세요.
먼저 서버 측입니다. 연결을 받을 때마다 새 서버 스트림을 다시 만들고, 클라이언트별 처리는 태스크로 분리합니다.
using System.IO.Pipes;
using System.Text.Json;
public sealed class PipeServer(string pipeName)
{
// camelCase·대소문자 비구별 Web 기본값. System.Text.Json 기본은
// 대소문자를 구별하므로, 이것을 공유하지 않으면 "version"이
// Request.Version에 바인딩되지 않고, 올바른 요청이 unknown_type으로 떨어진다
internal static readonly JsonSerializerOptions JsonOptions =
new(JsonSerializerDefaults.Web);
public async Task RunAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
var pipe = new NamedPipeServerStream(
pipeName,
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Message, // 1쓰기=1메시지
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
await pipe.WaitForConnectionAsync(ct);
_ = Task.Run(() => HandleClientAsync(pipe, ct), ct); // 연결마다 분리
}
}
// 1메시지 상한. 상대가 거대한 메시지를 보내 와도
// 서비스 측 메모리를 지킨다(IPC 상대도 외부 입력. 제6장)
private const int MaxMessageBytes = 1024 * 1024;
private static async Task HandleClientAsync(
NamedPipeServerStream pipe, CancellationToken ct)
{
await using (pipe)
{
var buffer = new byte[64 * 1024];
try
{
while (!ct.IsCancellationRequested)
{
// 메시지 모드라도, 1회 Read로 1메시지 전체가
// 읽힌다는 보장은 없다. IsMessageComplete까지 다 읽는다
using var ms = new MemoryStream();
do
{
int n = await pipe.ReadAsync(buffer, ct);
if (n == 0) return; // 클라이언트가 연결을 끊었다
ms.Write(buffer, 0, n);
if (ms.Length > MaxMessageBytes) return; // 상한 초과. 연결을 끊는다
} while (!pipe.IsMessageComplete);
byte[] response = Dispatch(ms.ToArray());
await pipe.WriteAsync(response, ct);
}
}
catch (IOException)
{
// 이 클라이언트와의 통신 경로가 깨진 것뿐이다.
// 서버 전체는 멈추지 않고, 다른 연결 처리를 계속한다
}
}
}
private static byte[] Dispatch(byte[] payload)
{
Request? req;
try { req = JsonSerializer.Deserialize<Request>(payload, JsonOptions); }
catch (JsonException) { req = null; }
// 잘못된 형식·알 수 없는 버전·알 수 없는 type은 여기서 검사해 거부한다
object result = req switch
{
null => new { version = 1, error = "bad_request" },
// version 미지정(0에 바인딩됨)이나 알 수 없는 버전은 여기서 거부한다
{ Version: not 1 } => new { version = 1, error = "version_unsupported" },
{ Type: "getStatus" } => new { version = 1, running = true },
{ Type: "startJob" } => new { version = 1, jobId = StartJob(req) },
_ => new { version = 1, error = "unknown_type" },
};
return JsonSerializer.SerializeToUtf8Bytes(result, JsonOptions);
}
}
public sealed record Request(int Version, string Type, JsonElement? Body);
클라이언트 측은 「연결 → 요청 → 응답」을 하나의 메서드에 닫고, 타임아웃과 재시도를 처음부터 넣습니다.
using System.IO.Pipes;
using System.Text.Json;
public sealed class PipeClient(string pipeName)
{
// idempotent: 이 요청은 2번 도착해도 안전하다고 호출 측이 선언했을 때만
// 재시도한다. 기본은 「재시도하지 않는다」
public async Task<TResponse?> RequestAsync<TResponse>(
object request, CancellationToken ct, bool idempotent = false)
{
for (int attempt = 1; ; attempt++)
{
// 연결뿐 아니라 요청→응답 교환 전체에 상한 시간을 둔다.
// 서버가 연결만 받고 응답을 쓰지 않은 채 굳은 경우의
// 무한 대기를 막는다
using var deadline =
CancellationTokenSource.CreateLinkedTokenSource(ct);
deadline.CancelAfter(TimeSpan.FromSeconds(10));
try
{
using var pipe = new NamedPipeClientStream(
".", pipeName, PipeDirection.InOut,
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
// 무한히 기다리지 않는다. 서버가 기동되어 있지 않으면 예외가 된다
await pipe.ConnectAsync(timeout: 3000, deadline.Token);
pipe.ReadMode = PipeTransmissionMode.Message;
await pipe.WriteAsync(
JsonSerializer.SerializeToUtf8Bytes(
request, PipeServer.JsonOptions),
deadline.Token);
using var ms = new MemoryStream();
var buffer = new byte[64 * 1024];
do
{
int n = await pipe.ReadAsync(buffer, deadline.Token);
if (n == 0) throw new IOException("서버가 연결을 끊었습니다.");
ms.Write(buffer, 0, n);
} while (!pipe.IsMessageComplete);
return JsonSerializer.Deserialize<TResponse>(
ms.ToArray(), PipeServer.JsonOptions);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
throw; // 호출 측 취소. 재시도하지 않는다
}
catch (Exception ex) when (
ex is IOException or TimeoutException or OperationCanceledException
&& idempotent && attempt < 3)
{
// 서버 재기동 중 같은 일시적 실패를 재시도한다.
// 「보냈으나 응답을 읽기 전에 끊긴」 케이스에서는 요청이 이미 실행된
// 가능성이 있으므로, startJob처럼 부작용 있는 요청은
// 기본대로 재시도하지 않고, 요청 ID로 중복을 걸러 내는 설계를 한 뒤에
// idempotent: true를 넘긴다(제6장)
await Task.Delay(500 * attempt, ct);
}
}
}
}
이 코드의 설계 판단을 세 가지만 보충합니다.
version을 첫 릴리스부터 넣습니다. UI와 서비스는 따로 갱신되는(한쪽만 새로워지는) 순간이 반드시 옵니다. 응답 측이 「모르는 버전은 명시적으로 거부한다」만으로, 말없이 이상한 동작을 하는 사고가 「알 수 있는 오류」로 바뀝니다.- 연결은 일회용으로 할지, 상시 연결로 할지를 정합니다. 위 예는 요청마다 연결하는 일회용형으로, 연결 끊김·재연결 상태 관리가 필요 없는 대신 고빈도 호출에는 맞지 않습니다. 상시 연결+서버에서의 푸시 알림이 필요해지면, 그 복잡함은 gRPC의 양방향 스트리밍으로 갈아타는 판단 재료가 됩니다.
- 서비스와 통신하는 경우는
CurrentUserOnly를 빼고,PipeSecurity를 설계합니다. 위 예는 같은 사용자 프로세스 사이를 전제합니다. 상대가 다른 계정 서비스라면,NamedPipeServerStreamAcl.Create로 ACL이 붙은 서버 스트림을 만들고, 연결을 허락할 그룹을 명시하세요. 6
5.1 동작 확인 ── 시작 순서와, 무엇을 보면 통과했다고 말할 수 있는가
위 코드는 콘솔 앱 두 개에 붙이면 동작합니다. 옮겨 적은 뒤에는 다음 순서로 확인하세요.
먼저 서버 측입니다. PipeServer를 붙인 프로젝트의 Program.cs에 기동과 정지만 적습니다.
// 서버 측 Program.cs (.NET 8 / top-level statements)
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };
Console.WriteLine(@"대기 시작: \\.\pipe\demo-ipc (Ctrl+C로 정지)");
try
{
await new PipeServer("demo-ipc").RunAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("정지했습니다.");
}
이어서 클라이언트 측입니다. 요청을 하나 던지고 응답만 표시합니다.
// 클라이언트 측 Program.cs (.NET 8 / top-level statements)
using System.Text.Json;
var client = new PipeClient("demo-ipc");
var res = await client.RequestAsync<JsonElement>(
new { version = 1, type = "getStatus" }, CancellationToken.None);
Console.WriteLine(res); // {"version":1,"running":true}
확인 절차는 다음과 같습니다.
- 서버를 먼저 기동합니다. 「대기 시작」이 나오면 준비 완료입니다.
- 클라이언트를 실행합니다. 서버 측
Dispatch가 반환한 JSON이 그대로 표시됩니다. 응답이{"version":1,"running":true}가 되는 것은,JsonSerializerDefaults.Web으로 camelCase 출력하고 있기 때문입니다. - 일부러 서버를 멈추고, 클라이언트만 실행합니다.
ConnectAsync(timeout: 3000, ...)가 동작해, 3초에 실패가 돌아오는 것을 확인합니다. 여기서 굳으면, 나중에 「서비스가 떨어지면 UI까지 굳는다」로 되돌아옵니다. - 이상 케이스를 세 가지 시험합니다. 여기까지 통과해야 비로소 「프로토콜이 동작하고 있다」고 말할 수 있습니다.
| 시험할 것 | 클라이언트에서 보내는 것 | 기대하는 결과 |
|---|---|---|
| 알 수 없는 버전 | new { version = 2, type = "getStatus" } |
{"version":1,"error":"version_unsupported"} |
| 알 수 없는 명령 | new { version = 1, type = "reboot" } |
{"version":1,"error":"unknown_type"} |
| 상한을 넘는 메시지 | 1MB를 넘는 본문(MaxMessageBytes) |
서버가 말없이 연결을 끊고, 클라이언트 측은 IOException이 된다 |
파이프가 실제로 열려 있는지를 OS 측에서 보고 싶을 때는, Sysinternals의 pipelist로 \\.\pipe\ 아래 이름을 나열할 수 있습니다. 「연결되지 않는다」의 원인이 파이프 이름 철자 차이인지, 서버가 내려가 있는지를 1분이면 가를 수 있으므로, 조사 절차서에 넣어 두면 도움이 됩니다.
6. 프로토콜 설계의 공통 주의 ── 수단을 고른 뒤의 일
어떤 IPC를 골라도, 통신 내용 설계에는 공통 과제가 있습니다. 수단 고르기보다, 실은 이쪽이 사고의 근원입니다.
- 메시지의 구분(framing): 파이프의 메시지 모드처럼 OS가 구분해 주는 경우를 제외하고, 「어디부터 어디까지가 1메시지인가」는 앱의 책임입니다. 공유 메모리의 레코드 경계도 같은 문제입니다. length prefix 방식을 기본으로 하세요.
- 버저닝: 메시지에 형식 버전을 넣고, 수신 측은 「모르는 버전을 명시적으로 거부」합니다. 두 프로세스가 항상 동시에 갱신된다는 보증은, 같은 설치 프로그램으로 배포해도 없습니다.
- 타임아웃: 「상대가 응답하지 않는다」는 반드시 일어나는 일로 설계합니다. 연결·요청·응답 각각에 상한 시간을 정하고, 무한 대기 코드를 남기지 말 것. UI 스레드에서 동기적으로 기다리는 것은 논외입니다.
- 재연결과 멱등성: 재시도한다는 것은, 같은 요청이 2번 도착할 가능성을 만든다는 뜻입니다. 「2번 실행해도 안전한 요청인가」를 요청 종류마다 분류하고, 안전하지 않은 것(잡 투입 등)에는 요청 ID를 붙여 중복을 걸러 냅니다.
- 상대도 외부 입력으로 검증합니다: 같은 머신 안 통신이면 「상대는 내 앱이니까」라며 검증을 빼기 쉽지만, 권한 경계가 있는 경우, IPC 상대는 네트워크에서의 입력과 같은 「신뢰할 수 없는 입력」입니다. 관리자 권한 브로커나 서비스는, 표준 권한 클라이언트에서 도착한 경로나 명령을 그대로 실행해서는 안 됩니다. 파이프 이름 점유(2.2절)를 포함해, 「누가」「무엇을」 양쪽을 의심하는 것이 권한 경계를 넘는 IPC의 관행입니다.
- 관측 수단을 남겨 둡니다: 통신 로그(적어도 메시지 종류와 상대·결과)를 낼 수 있게 해 두면, 「연결되지 않는다」「돌아오지 않는다」 종류의 조사 시간이 자릿수가 달라집니다.
7. 정리
프로세스 간 통신의 선택은, 다음 순서로 생각하면 거의 헤매지 않습니다. 먼저 같은 머신 한정으로 충분한가. 한정이면 요청-응답은 Named Pipe, 대용량 고빈도 데이터만 공유 메모리, 느슨한 결합 배치는 파일 연동. 머신 경계를 넘거나 언어 혼재가 보이면 로컬 TCP나 gRPC로, 자체 프로토콜 관리가 부담이 되는 규모라면 gRPC. COM은 신규의 첫 후보로 두지 않고, 32/64 브리지와 VBA 연동 도구로 남겨 둔다──이 기사의 판단표는 이 흐름을 표로 만든 것입니다.
그리고 수단이 정해지면, 제6장의 공통 설계(framing·버전·타임아웃·재연결·상대 검증)를 첫 릴리스에 넣을 것. IPC 트러블은 「수단 선택 실수」보다 「프로토콜 설계를 생략한」 것이 원인인 쪽이 압도적으로 많다는 것이 실무에서의 체감입니다. 프로세스 분할이나 통신 방식 재검토는, 먼저 「어느 프로세스가·누구의 권한으로·무엇을·어느 빈도로 주고받는가」를 점검하는 일부터 시작하는 것을 권합니다.
관련 기사
- 공유 메모리의 함정과 실무 베스트 프랙티스
- TCP에서 Send한 단위마다 Receive할 수 있다는 오해 ── 바이트 스트림으로 다루기 위한 수신 설계
- Windows 앱에서 「관리자 권한이 필요한 처리만」 분리하는 구체적으로 쓰는 법
- Windows 서비스 만드는 법과 운영 ── 작업 스케줄러와의 용도 구분부터 BackgroundService의 서비스화까지
관련 상담 영역
合同会社小村ソフト에서는 프로세스 분할·통신 방식의 설계 리뷰, 32bit/64bit 브리지를 포함한 기존 자산과의 연동 설계, 「연결되지 않는다·돌아오지 않는다」 종류의 통신 트러블의 원인 조사를 다룹니다.
참고 링크
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication. NamedPipeServerStream / NamedPipeClientStream에 의한 복수 클라이언트 대응 서버·클라이언트 구현 예에 대해. ↩
-
Microsoft Learn, Inter-process communication with gRPC and Named pipes. .NET 8 이후 Kestrel의 ListenNamedPipe로 gRPC를 Named Pipe 위에서 동작시키는 구성과, PipeSecurity에 의한 액세스 제어에 대해. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, MemoryMappedFile Class. 메모리 맵 파일의 .NET API. CreateNew로 만드는 파일에 매이지 않는 공유 메모리가 IPC 용도에 맞는 것에 대해. ↩ ↩2
-
Microsoft Learn, Interprocess communications. Windows가 제공하는 IPC 메커니즘(클립보드, COM, WM_COPYDATA, DDE, 파일 매핑, mailslot, 파이프, RPC, Windows 소켓)의 목록과 용도 구분 지침에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, NamedPipeServerStream Class. Named Pipe의 서버 측 스트림. PipeTransmissionMode나 PipeSecurity를 지정하는 생성자에 대해. ↩
-
Microsoft Learn, Named Pipe Security and Access Rights. Named Pipe의 기본 보안 설명자가 Everyone과 익명 계정에 읽기를 허용하는 것과, 액세스 권한 구성에 대해. ↩ ↩2
-
Microsoft Learn, PipeOptions Enum. CurrentUserOnly가 「같은 사용자가 만든 상대」와의 연결만 허용하고, Windows에서는 사용자 계정에 더해 권한 상승 수준까지 검증하는 것에 대해. ↩ ↩2
-
Microsoft Learn, Inter-process communication with gRPC. gRPC를 IPC에 쓸 때의 transport(Unix domain socket, Named Pipe)와, 서버 소유자 검증·가장(impersonation) 대책 등 보안 고려 사항에 대해. ↩ ↩2
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
Windows 앱 데이터 저장 위치 고르기 ── SQLite / JSON / 레지스트리 / Access 판단표
Windows 데스크톱 앱의 데이터를 어디에, 무엇으로 저장할지. AppData/ProgramData 구분, SQLite·JSON 파일·레지스트리·Access(.accdb) 각각의 강점과 함정을 판단표와 함께 정리하고, 손상 대책과 비트 수 문제...
Windows 세션 분리를 어떻게 이해할 것인가 ── Session 0·RDP·다중 사용자 동시 실행
Windows 앱 개발자가 혼동하기 쉬운 「세션」 개념을 정리합니다. 서비스가 UI를 띄울 수 없는 Session 0 분리의 이유, RDP 연결 시 세션의 동작, named object의 세션 분리, 공유 PC·RDS 환경에서 자주 나오는 설계 ...
Windows서비스 만드는 법과 운영 ── 작업 스케줄러와의 구분부터 BackgroundService의 서비스화까지
상주 처리를 Windows서비스로 둘지, 작업 스케줄러로 충분한지. 판단표와 .NET Worker Service로 서비스를 만드는 방법, 실행 계정·복구 옵션·안전한 중지 처리까지 실무 관점에서 정리합니다.
멀티스레드 실무 베스트 프랙티스 .NET 편 ── 스레드를 늘리기 전에 정해 둘 것
「스레드를 만들었더니 가끔 죽거나 멈춘다」를 막는 설계의 정석을 .NET/C# 대상으로 정리합니다. 스레드를 직접 만들지 않고 Task에 맡기기, 공유 가변 상태 줄이기, 락의 규율, CancellationToken으로 정지 설계하기, UI 스레...
업무 시스템의 코드 설계 ── 상품 코드·고객 코드를 정하는 법과 check digit
상품 코드·고객 코드 등 업무 시스템의 코드 체계를 정하는 실무 가이드. 유의미 코드와 무의미 일련번호 판단표, JAN·Luhn 등의 check digit 산식과 C# 구현, Excel의 선행 0 소실 대책, 자릿수 초과와 이행까지 정리합니다.
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
ActiveX 이관
COM / ActiveX / OCX 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
기술 상담 & 설계 리뷰
설계 방향, 아키텍처 경계, 수명 관리, 기존 Windows 자산 처리 방법을 정리하는 데 도움을 드립니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- Windows의 프로세스 간 통신은 무엇을 첫 후보로 삼아야 합니까?
- 같은 머신 안의 요청-응답(명령을 보내고 결과를 받는 경우)이라면 Named Pipe가 첫 후보입니다. OS 표준이라 포트 번호 관리가 필요 없고, 방화벽에도 걸리지 않으며, Windows의 액세스 제어(PipeSecurity)와 통합되어 있고, .NET에서는 System.IO.Pipes로 바로 쓸 수 있습니다. 나중에 네트워크를 넘을 가능성이 있으면 처음부터 로컬 TCP나 gRPC, 대용량·고빈도 데이터(이미지 프레임, 파형)만 공유 메모리, 느슨한 결합·비동기로 감사 로그를 남기고 싶은 연동이라면 파일 연동, 이런 식으로 소거법으로 좁히는 것이 올바른 선택법입니다.
- gRPC를 프로세스 간 통신에 써야 하는 때는 언제입니까?
- 서비스나 호출 종류가 늘어 자체 프로토콜의 switch 문과 문서 유지가 부담이 된 때입니다. proto 파일로 스키마를 정의하고 코드를 생성할 수 있으며 양방향 스트리밍을 얻고, .NET 8 이후는 ASP.NET Core(Kestrel)가 Named Pipe를 transport로 직접 지원합니다. 한편 명령이 2~3종류뿐인 도구끼리는 ASP.NET Core 호스팅 기반을 떠안는 부담이 커서 오버킬이고, Named Pipe+JSON 쪽이 총비용은 낮아집니다. proto에 쓰고 싶은 메시지가 10을 넘었다, 스트리밍 알림이 본질적으로 필요하다, 상대가 .NET이 아니다, 중 하나에 해당한 뒤에 도입해도 늦지 않습니다.
- 32bit 앱에서 64bit DLL을 쓰려면 어떻게 해야 합니까?
- 32bit 프로세스에 64bit DLL은 같은 프로세스에 로드할 수 없으므로, 64bit 쪽을 별도 프로세스로 분리합니다. 구현은 두 가지입니다. (a) 64bit helper 프로세스를 세우고 Named Pipe로 통신하는 방법과, (b) 64bit out-of-process COM 서버로 만드는 방법입니다. 호출 측이 VBA나 오래된 언어라면 (b)가 자연스럽고, COM 기반이 마샬링을 대신하므로 호출 측 코드를 거의 바꾸지 않아도 됩니다. C#끼리라면 (a)가 배포와 조사 모두 쉽습니다. (a)의 경우 Job Object로 helper 프로세스의 부모-자식 연동 종료를 설계합니다.
- 공유 메모리는 어떤 경우에 써야 합니까?
- 이미지 프레임이나 파형 데이터처럼 대용량·고빈도 데이터에 한정해 씁니다. 복사도 직렬화도 끼어들지 않는 같은 머신 안에서 가장 빠른 IPC이지만, 동기화는 1바이트도 제공되지 않으므로, 쓰기 도중 데이터를 읽지 않는 구조, 상대의 생존 여부 검출, 비정상 종료 후 복구를 모두 직접 설계해야 합니다. 실무에서는 데이터 면(data plane)만 공유 메모리에 올리고, 시작·정지·설정 변경 같은 제어 면(control plane)은 Named Pipe 등 다른 채널로 빼는 2채널 구성이 정석입니다. 제어 메시지까지 공유 메모리로 버티기 시작했다면 설계를 다시 볼 신호입니다.