Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WinIPC

CI License

Send structured messages between Windows processes over WM_COPYDATA.

WM_COPYDATA is the Win32 mechanism built for exactly this: handing a block of bytes to another process's window, with the kernel marshalling it across the boundary. No sockets, no named pipes, no shared files — just two processes and a window. WinIPC wraps it in a small typed API and a self-describing frame.

// receiver
Server := TIpcServer.Create('my-channel', OnMessageHandler);
// ... in your idle loop:
Server.ProcessPending;

// sender, in another process
Msg.Kind := 1;
Msg.Payload := 'hello, other process';
TIpcClient.Send('my-channel', Msg);

Install

With Boss:

boss install github.com/ramonruanxc/delphi-ipc

Or add src to your search path. Requires Delphi 10.1 Berlin or later, or Free Pascal 3.2 with -Mdelphi. The transport is Windows-only; the framing builds anywhere.

How it works

The receiver is a message-only window. TIpcServer creates a window parented to HWND_MESSAGE: it never shows, is not enumerated as a top-level window, and exists only to receive messages — exactly what a background IPC endpoint wants. The channel name becomes the window class, so a sender finds it with FindWindowEx under HWND_MESSAGE.

The sender uses SendMessage, never PostMessage. The kernel only marshals the buffer across the process boundary for the duration of a synchronous SendMessage. A posted message would hand the receiver a pointer into the sender's address space — a bug that looks like it works until the sender frees the buffer.

Cross-integrity delivery is allowed explicitly. When one process runs elevated and the other does not, User Interface Privilege Isolation silently drops the message. TIpcServer calls ChangeWindowMessageFilterEx to allow WM_COPYDATA through, so an elevated app can receive from a normal one.

The payload is a self-describing frame. WinIPC.Message encodes a (kind, payload) pair as magic | version | kind | length | bytes, and decoding validates every field. A frame that claims more payload than it carries — the classic buffer-overrun trap — is rejected, not trusted. This part has no Windows dependency and is where most of the tests live.

API

TIpcMessage = record
  Kind: UInt32;      // caller-defined message kind
  Payload: string;   // carried as UTF-8 on the wire
end;

TIpcServer = class
  constructor Create(const AChannel: string; AOnMessage: TIpcMessageEvent);
  function ProcessPending: Integer;        // dispatch queued messages, no block
  function PumpFor(ATimeoutMs: Cardinal): Boolean;  // block until one arrives
  property Handled: Int64;                 // count of valid messages received
end;

TIpcClient = class
  class function Send(const AChannel: string;
    const AMessage: TIpcMessage): TIpcSendResult;   // srDelivered / srNoServer / ...
end;

ProcessPending is for an app with its own message loop — call it when idle. PumpFor is for a program whose only job is to wait for IPC.

Note on PumpFor: a WM_COPYDATA sent from another thread or process is dispatched as a side effect of PeekMessage, which still returns False. Delivery is therefore judged by the Handled counter, not by whether the pump loop saw a queued message — a distinction that is easy to get wrong and is pinned by a test.

Building from a clone

Every .dpr lists its units with explicit in '...' paths, so opening one in the Delphi IDE and pressing build works with nothing to configure — no search path, no library path.

Free Pascal resolves units from -Fu rather than from the in clause, so a manual FPC build needs the paths on the command line. Every example below includes them.

Demo

demo/Demo.dpr is two real processes. Build it, then:

Demo.exe server
Demo.exe client "hello from the other process"

The server prints each message it receives; every client run is a separate process crossing the boundary.


Running the tests

The framing suite is portable and is the Linux CI coverage:

fpc -Mdelphi -Fusrc -Futests -FUbuild -obuild/CoreTests tests/CoreTests.dpr
./build/CoreTests

The full suite adds the WM_COPYDATA transport and runs on Windows. It performs a real cross-thread round trip in one process — a server window on the main thread, a worker thread sending to it — which exercises the same kernel marshalling path as a true cross-process send, without a second executable:

fpc -Mdelphi -Fusrc -Futests -FUbuild -obuild/WinTests.exe tests/WinTests.dpr
./build/WinTests.exe

CI runs both: the framing job on Linux, the transport job on Windows.


Licence

MIT. See LICENSE.

About

Send structured messages between Windows processes over WM_COPYDATA, with a portable, tested frame.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages