RPC_X_INVALID_ES_ACTION: Fix 0x00000723 on RPC handles
This error hits when your code tries to encode/decode an RPC handle that's already in use or closed. We'll fix it with a check-and-reset approach.
When this error hits
You'll see this error when you're deep in building a distributed system or a Windows service that uses RPC (Remote Procedure Call) for serialization. Specifically, this pops up in two scenarios:
- You call
MesEncodeIncrementalHandleCreateorMesDecodeIncrementalHandleCreatetwice without callingMesHandleFreein between. - You're reusing an encoding or decoding handle after it's been freed—or worse, after the underlying buffer has been released by another thread.
I ran into this on a Windows Server 2019 cluster where a background worker thread tried to encode a second message using the same handle before the first message's buffer was flushed. That's the classic trigger.
Root cause
RPC encoding/decoding handles are stateful objects. Once you create one via MesEncodeIncrementalHandleCreate, it's tied to a specific buffer and a set of internal pointers. If you try to start a new encoding operation without properly closing the old one—or if you pass a null or stale handle—the RPC runtime throws RPC_X_INVALID_ES_ACTION. The error code 0x00000723 literally means "invalid operation on the encoding/decoding handle."
The real fix is to treat these handles like file descriptors: always close them before reuse.
Fix it in 4 steps
Step 1: Identify the stale handle
Check every place you create a handle. Common culprits:
- Functions that call
MesEncodeIncrementalHandleCreateorMesDecodeIncrementalHandleCreateinside a loop. - Multithreaded code where one thread frees the handle while another still has a reference.
Add a debug breakpoint on the RPC_X_INVALID_ES_ACTION error return. In Visual Studio, use the Exception Settings window and add this error code as a break condition. Don't skip this step—it saves hours.
Step 2: Free the handle before re-creating
Your code probably looks like this (wrong):
RPC_STATUS status;
handle_t hEnc;
status = MesEncodeIncrementalHandleCreate(NULL, MyAlloc, MyWrite, &hEnc);
// ... encode first message
// BUG: forgot to free handle
status = MesEncodeIncrementalHandleCreate(NULL, MyAlloc, MyWrite, &hEnc); // crashes here
Fix it by adding MesHandleFree before the second create:
RPC_STATUS status;
handle_t hEnc;
status = MesEncodeIncrementalHandleCreate(NULL, MyAlloc, MyWrite, &hEnc);
// ... encode first message
status = MesHandleFree(hEnc); // close it
status = MesEncodeIncrementalHandleCreate(NULL, MyAlloc, MyWrite, &hEnc); // now safe
Step 3: Guard against double-free
If you're freeing the handle in cleanup code, set it to NULL immediately afterward:
if (hEnc != NULL) {
MesHandleFree(hEnc);
hEnc = NULL; // prevents use-after-free
}
Step 4: Check your alloc/write callbacks
Your midl_user_allocate and midl_user_write functions must return valid pointers and handle zero-length writes. If your write callback doesn't advance the buffer pointer correctly, the internal state gets corrupted, and the next encode attempt triggers 0x00000723. Add logging inside your allocator to confirm it's being called the expected number of times.
If it still fails
Try these three things:
- Switch to
MesEncodeFixedBufferHandleCreateorMesDecodeBufferHandleCreate. These simpler functions don't use callbacks—they work with a fixed buffer. Less moving parts means fewer places for this error to hide. - Enable RPC debug tracing. On Windows 10/11 and Server 2016+, run
wevtutil epl "Applications and Services Logs/Microsoft/Windows/RPC" rpc_trace.evtxto capture detailed RPC events. Look for events with ID 1 or 5 around the time of the error. - Check your multithreading. If you re-create a handle on a different thread than the one that freed it, the RPC runtime might not have cleaned up fully. Use a mutex or critical section around the handle lifecycle.
Most of the time, it's a missing MesHandleFree call. Add it, test it, and you're done.
Was this solution helpful?