forderud / ComSamples

Examples of language-neutral COM interfaces for IPC and language interop.
MIT License
3 stars 3 forks source link
com inter-process-communication

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:

COM limitations:

COM concepts

Some common concepts:

[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:

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:

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:

[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:

Additional registry folders:

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

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:

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.