Skip to content

Examples of language-neutral COM interfaces for IPC and language interop.

License

Notifications You must be signed in to change notification settings

forderud/ComSamples

Repository files navigation

Examples of language-neutral Component Object Model (COM) interfaces for IPC and programming languages interoperability.

Project listing:

Module Description
ComSupport .Net support functions for COM EXE registration (not needed for DLLs)
MyInterfaces COM interface definitions (MyInterfaces.idl)
MyClientCpp Sample C++ client
MyClientCs Sample C# client
MyClientPy Sample Python client
MyDllServerCpp C++ server DLL implementation
MyDllServerCs C# server DLL implementation
MyExeServerCpp C++ server EXE implementation
MyExeServerCs C# server EXE implementation
ServiceWrapper Support project for running "regular" COM servers as a Windows service

Both servers are implemented as on-demand loaded COM EXE servers. The processes can also be started manually in advance if desired. The .Net samples are based on OutOfProcCOM.

The server projects implement the IMyServer interface whereas the client projects implement the IMyClient callback interface:
Overview

How to test

  1. Ensure that you have a Python interpreter associated with .py files.
  2. Open the solution in Visual Studio.
  3. Build all projects.
  4. Register the server:
    • Either run MyExeServerCs.exe /regserver with admin privileges,
    • Or run MyExeServerCpp.exe /regserver with admin privileges,
    • Or run regsvr32.exe MyDlleServerCpp.dll with admin privileges,
    • Or run regsvr32.exe MyDlleServerCs.dll with admin privileges,
  5. Run the test clients:
    • C++: run MyClientCpp.exe
    • C#: run MyClientCs.exe
    • Python: run MyClientPy.py
  6. Unregister the server to clean up:
    • Either run MyExeServerCs.exe /unregserver with admin privileges,
    • Or run MyExeServerCpp.exe /unregserver with admin privileges,
    • Or run regsvr32.exe /u MyDlleServerCpp.dll with admin privileges,
    • Or run regsvr32.exe /u MyDlleServerCs.dll with admin privileges,
    • Or run UNREGISTER.bat with admin privileges,

Server registration is primarily needed for on-demand loaded COM servers.

The client programs should output something resembling this:

pi = 3.141592653589793
Received message:
  sev=Info
  time=7/4/2023 10:27:57 AM
  value=1.23
  desc=Hello there!
  color=(255, 0, 0)
  data=[0,1,2,3]
...

COM description

COM interfaces are first defined in IDL files. The IDL files are afterwards compiled into TLB type libraries, TLH/TLI headers and/or .Net DLL assemblies, depending on the target language(s):
Interface Integration

COM strengths:

  • Language neutral interface definitions in IDL files.
  • COM is in-built in Windows. There's no need to install any additional "runtime" or "brokers". COM is used for GPU programming with Direct3D, HW diagnostics with WMI and most new Windows features are exposed through COM-based WinRT interfaces.
  • Support for type-safe interfaces where type mismatches are detected at compile-time instead of run-time.
  • Support for bidirectional and object-oriented communication.
  • Support for interface versioning for controlled API evolution.
  • Transparent IPC marshaling of method calls. The client doesn't need to know that an object resides in a different process.
  • Languages like C++ and C# natively support COM, whereas Python support is achieved through the comtypes package. This avoid the need for manually maintaining language bindings.
  • Long-term stable binary API (ABI) that has been available since 1993. Allows interoperability with COM servers compiled 20 years ago without having to rebuild them.
  • Inherently secure with strict default settings and design protection against remote attacks. Does unlike sockets not depend on opening any ports that need to be secured through authentication and firewall lockdown. Security settings are furthermore highly configurable.

COM limitations:

  • Best practice is poorly documented.
  • Some boilerplate code needed with C++.
  • Better alternatives usually exist for network-based distributed communication APIs.
  • Not as easily available on non-Windows platforms. The MiniCOM project seek to partly mitigate this.

COM concepts

Some common concepts:

  • IDL: Interface Definition Language source file. Used for defining COM interfaces, classes and type libraries.
  • Interface: Collection of callable functions that a class can implement. Expressed in a language-neutral way. Identified by a GUID[1]. Cannot be changed after an external release[2].
  • Class: Concrete implementation of one or more COM interfaces. Can optionally be identified by a ClassID (CLSID) GUID[1] to make class instances directly creatable.
  • Type library (TLB): Binary file format used by the COM runtime to marshal calls between languages and processes. Identified by a GUID[1]. Can either be stored in a separate TLB file or embedded into the COM server EXE or DLL.
  • ProgID: Optional string-based programmatic identifier for a COM class. Used to map a human-readable string to a ClassID, so that developers don't need to work directly with the underlying GUID.

[1] Globally Unique Identifier (GUID) are 128bit values typically expressed as a {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} hexadecimal string. Must be globally unique, so they cannot be copied between projects. Typically generated with the uuidgen.exe tool from a Developer Command Prompt.

[2] Neither COM interfaces nor their associated data structures or enumerations can be changed after an external release, since that would break compatibility with existing code. Developers instead need to create a new interface with a new GUID when changes are required. It's then common to add a version number suffix to the interface name as versioning scheme.

COM server types

COM servers can be compiled as either:

  • DLL: Server that runs in the same process as the client (in-process)[1]. This allows for more efficient direct communication without IPC. However, it does not allow for failure isolation or security sandboxing.
  • EXE: Server that runs in a separate process (out-of-process). This leads to some per-call marshaling overhead due to IPC. However, you get failure isolation so that a crashing server doesn't bring the client process down. The server can also run with more or fewer security privileges compared to the client.

COM provides transparent marshaling. This means that the client code doesn't need to know if the COM server runs in the same or a different process. The object that exposes a COM interface looks the same, regardless of if it's running in the same or a separate process or is implemented in a different programming language.

Microsoft provides COMServerDemo sample code for DLL-based servers implemented in C#/.Net and Create a Classic COM Component Using WRL for servers implemented in C++.

[1] DLL-based COM servers can also be started in a separate process if configuring a DllSurrogate. This can sometimes be useful for testing, since it allows the same COM server to either be loaded in-proc or out-of-proc without having to rebuild it.

Exception mapping

Both C++, .Net and Python can automatically map COM HRESULT error codes to exceptions, so that developers doesn't need to explicitly check each call for failure.

Details:

  • C++ with generated TLH-wrappers: HRESULT error codes are automatically mapped to _com_error exceptions. It's still possible to call the "old" HRESULT versions by adding a raw_ prefix to the method names.
  • .Net: HRESULT error codes are automatically mapped to comparable .Net exceptions.
  • Python: HRESULT error codes are automatically mapped to COMError exceptions.

Threading

COM clients and servers can decide their threading model for incoming calls[1] by configuring the thread associated with the class(es) receiving callbacks to run in either:

  • Single-threaded apartment (STA)[2]: Incoming calls are automatically serialized. This means that the client doesn't need to worry about thread safety, since the COM runtime ensures that only one incoming call is received at a time.
  • Multi-threaded apartment (MTA): Incoming calls are not serialized and might arrive concurrently. This means that the client needs to use mutexes or similar to protect against race conditions.

[1] The threading model only affects incoming calls marshalled by the COM runtime. This typically means COM servers implemented in a different programming language or running in a different process. Direct C++ communication between COM objects in the same process are not affected by the threading model.

[2] STA threads need to pump messages to process incoming calls - just like all GUI applications does to process mouse & keyboard events. The implementation then needs to consider that reentrancy can occur as part of the message pumping if pumping messages while processing an incoming call.

In the STA case COM pointers can only be called by the associated thread. The COM pointer need to be explicitly marshaled before they can be accessed by a different thread. This is handled automatically in .Net but needs to be performed explicitly with RoGetAgileReference and IAgileReference::Resolve in C++ as done by the ComMarshal sample. Marshaling between threads is not required in the MTA case.

Intermediate topics

Registry entries

Registry folders associated with a COM server:

  • HKCR\Interface\{GUID}: Registry entries for a COM interface.
  • HKCR\CLSID\{GUID}: Primary registry entries for a creatable COM class. Not needed if the class is not directly creatable.
  • HKCR\AppID\{GUID}: Optional additional registry entries for a COM class. Can use same GUID as the COM ClassID (CLSID)
  • HKCR\TypeLib\{GUID}: Registry entries for a type library. Used for marshaling calls between languages and processes.
  • HKCR\{ProgID}: Optional string-based programmatic identifier for a COM class.

Additional registry folders:

  • Registry entries for 32bit COM servers are stored beneath HKCR\WOW6432Node.
  • Per-user registrations are stored beneath HKCU\SOFTWARE\Classes.

COM servers are responsible for registering and unregistering themselves. This is typically done through the following commands from an admin command prompt:

  • DLL-based server: regsvr32.exe <server-name>.dll and regsvr32.exe /u <server-name>.dll
  • EXE-based server: <server-name>.exe /regserver and <server-name>.exe /unregserver

Memory management rules (for C++)

Rules:

Advanced topics

These topics are primary relevant when authoring COM servers. Consumers of a COM server can usually disregard these topics.

Background service deployment

The ServiceWrapper project can be used to start a COM server as a Windows background service.

If doing so, then one can also consider using the Running Object Table (ROT) to connect to the already running COM server instead of CoCreateInstance. This can be achieved through the following functions:

GetActiveObject only succeed if the COM server is already running, wheras CoCreateInstance always succeed and will start the COM server if not already running.

Security

Some noteworthy details:

  • The RunAs registry value can be used to configure the COM server to start in a different user account. The RunInSandbox ComRunAs tool can be used to configure this.
  • Connecting to an already running COM server in a higher privileges process is blocked by default. You'll need to configure CoInitializeSecurity in the COM server to enable this.
  • Connection attempts over the network are blocked by default. You'll need to enable DCOM to enable this.

COM security defaults are quite strict. Most security settings for a COM server can be configured through AppID registry entries. See the RunInSandbox project for examples of security sandboxing and elevation with COM.

Custom marshaling

Windows includes an in-built "automation" marshaler (oleaut32.dll) for propagating IPC calls between processes. This is sufficient for most projects, but it's also possible to customize IPC marshaling by implementing the IMarshal interface. The SharedMemMarshal project demonstrates how a custom marshaler can utilize shared memory to avoid copying overhead when passing large amounts of data between processes.

About

Examples of language-neutral COM interfaces for IPC and language interop.

Topics

Resources

License

Stars

Watchers

Forks