How Windows 3.11 Processed OLE Automation Commands

In Microsoft Windows 3.11, Object Linking and Embedding (OLE) 2.0 introduced OLE Automation, enabling applications to expose programmatic functionality to external scripts and other desktop programs. Because Windows 3.11 operated as a 16-bit cooperative multitasking environment, cross-application communication relied on a combination of the system registration database, Lightweight Remote Procedure Calls (LRPC), shared global memory, and standard COM interfaces like IDispatch. This architecture allowed a client application, such as Visual Basic or Microsoft Excel, to locate, instantiate, and invoke methods on an external automation server seamlessly.

1. Server Registration and Resolution

The automation lifecycle began with the Windows Registration Database (REG.DAT). Developers registered their automation servers by defining a Programmatic Identifier (ProgID), such as Word.Basic, mapped to a unique 128-bit Class Identifier (CLSID). The registry mapped the CLSID to the exact file path of the server's executable (.EXE) or dynamic-link library (.DLL), along with its exposed interfaces and type libraries (.TLB).

When a client application requested a connection via an automation call like CreateObject("Word.Basic"), the OLE subsystem parsed the registry to resolve the ProgID to its CLSID.

2. Process Instantiation and Interface Acquisition

Once the target application was identified, the OLE runtime evaluated whether an instance of the server was already active:

  • Out-of-Process Servers (.EXE): If the server was not currently executing, the OLE runtime used the standard Windows 3.11 kernel routine WinExec or LoadModule to launch the server executable with specific automation switches (such as /Automation). Upon initialization, the server registered its class factory with the system using CoRegisterClassObject.
  • In-Process Servers (.DLL): The OLE libraries loaded the DLL directly into the calling application's task context using LoadLibrary.

The client then invoked CoCreateInstance to request the base interface for automation: IDispatch. The server's class factory instantiated the requested object and returned a pointer to this interface.

3. Inter-Process Communication via 16-Bit LRPC

Because 16-bit Windows ran applications within segmented memory, separate .EXE processes could not simply pass raw, native pointers to one another. To overcome this limitation, Windows 3.11 utilized Lightweight Remote Procedure Calls (LRPC) built atop the Windows message queue.

When crossing process boundaries:

  1. The OLE runtime generated a 16-bit proxy object on the client side and a stub object on the server side.
  2. The proxy serialized (marshalled) the method arguments into a standard data structure called a VARIANT, which packaged data types such as integers, strings, and dates into a uniform binary format.
  3. For large data payloads or string transfers, the system utilized Windows global memory blocks allocated with the GMEM_DDESHARE flag, making the memory address accessible across separate task selectors.
  4. The proxy posted specialized Windows messages containing pointers to these shared memory buffers directly to the server window’s message loop.

4. Dynamic Execution via IDispatch

OLE Automation in Windows 3.11 primarily used late binding, requiring a two-step dispatch mechanism handled by the IDispatch interface:

  • Name Resolution (GetIDsOfNames): The client sent the textual name of the property or method (for example, "Insert") to the server via IDispatch::GetIDsOfNames. The server matched the string against its internal dispatch tables and returned a 32-bit Dispatch Identifier (DISPID).
  • Method Invocation (Invoke): The client called IDispatch::Invoke, passing the DISPID, execution flags indicating whether the operation was a method call, property read, or property write, and an array of VARIANT parameters.

5. Cooperative Return and Memory Cleanup

The server task processed the incoming message, unpacked the parameters from the shared global memory segment, executed the requested method internally, and populated an output VARIANT with the return value.

The server then posted an acknowledgment message back to the client’s message queue containing the result. Because Windows 3.11 utilized cooperative multitasking, the client application entered a message dispatch loop while waiting for the server to process the request, yielding control back to the operating system until the server finished the operation. Once received, the proxy unpacked the return value, freed any allocated global memory segments, and returned execution flow to the client's local script or code.