COM management
This article applies to QuickOPC 2022.1 and later.
Concepts
This article describes the features present in QuickOPC applications that relate to management of Microsoft COM/DCOM, for the purpose of connecting to OPC Classic servers. The effective COM/DCOM behavior is given by a combination of settings made in the system (Windows - mainly by the DCOMCNFG tool), and the code or settings inside the QuickOPC-based application.
QuickOPC applications can perform tasks related to COM management using the ComManagement class. Although you can create your own instances of this class, you would normally be using an instance provided to you by a static read-only field, ComManagement.Instance. The ComManagement class is a semi-singleton, therefore even if you had multiple instances of it, they would all work on and share the same underlying data.
Configuring COM
In QuickOPC applications, COM is configured using the parameters in ComManagement.Instance.Configuration object. There are two main areas that can be configured:
The configuration parameters default to a reasonable set of values for many application. Upon program start, these defaults are optionally overridden by Intrinsic Component Configuration, which means that you can change them without needing to change the program code itself, which comes handy e.g. in scenarios where the software is already deployed in production environment, and its code cannot be easily changed. After the initial values are determined in this way, your code can further modify them, by setting the corresponding property values.
COM security initialization
In Windows, major part of parameters that influence the COM security can only be set globally for the whole process, and only once. As soon as the COM security is initialized in a process, its parameters cannot be changed.
Furthermore, COM security initialization can either be invoked either
- Explicitly: Some code in the process (your own code, QuickOPC, or execution infrastructure code) calls the CoInitializeSecurity Win32 API. In the explicit initialization case, the code specifies the COM security parameters to be used.
- Implicitly: Some code in the process (your own code, QuickOPC, or execution infrastructure code) marshalls or unmarshalls a COM interface. This can happen e.g. when the code uses some COM object and does not initialize its thread apartment state to MTA[1]. In the implicit initialization case, parameters from computer-wide COM default properties (in DCOMCNFG) are used.
As you can see, whoever initializes the COM security first in the process, either intentionally or intentionally, is the winner for the remainder of the existence of the process. This is unfortunate and unavoidable, although understandable aspect of how COM on Windows work. The danger here is that the initialization made "for you", earlier than you intended, would not have the parameters you want. Large parts of this article deal with approaches that aim at assuring that the COM security parameters can be set "your way".
QuickOPC concepts
In QuickOPC applications, the COM security is controlled by the properties in the ComManagement.Instance.Configuration.SecurityParameters object. Your code can set them to desired values, and QuickOPC will use them at the moment it initializes the COM security. Further changes to those parameters have no effect.
By default, the UseCustomSecurity property is set to true, which means that QuickOPC attempts to set the COM security according to the parameters specified. If you do not want QuickOPC to attempt to initialize the COM security, set the UseCustomSecurity property to false.
If the UseCustomSecurity property is set to true (the default), QuickOPC applications will attempt to initialize the COM security
- when ComManagement.Instance.AssureSecurityInitialization (or AssureSecurityInitializationAndRunOnStaThread) method is called, or
- when a first OPC Classic (i.e. OPC-DA or OPC A&E) operation is performed.
Your code can thus use any of following approaches, depending on its needs:
- Have no code related to COM security. In this case, QuickOPC will attempt to initialize the COM security with its default parameters when a first OPC Classic operation is performed.
- Put in code that modifies the parameters in ComManagement.Instance.Configuration.SecurityParameters before a first OPC Classic operation is performed. In this case, QuickOPC will attempt to initialize the COM security with the parameters your code has specified, when a first OPC Classic operation is performed. With this approach, you have good control over the COM security parameters, but the moment of COM security initialization is postponed and not quite apparent.
- Put in code that modifies the parameters in ComManagement.Instance.Configuration.SecurityParameters somewhere near the beginning of your program, and call the ComManagement.Instance.AssureSecurityInitialization method. In this case, QuickOPC will attempt to initialize the COM security with the parameters your code has specified, and will do so in the AssureSecurityInitialization method. With this approach, you have best control over when the COM security initialization happens, and its parameters.
Execution flow
Here is what generally happens (with regard to COM security initialization) in a program that uses QuickOPC.
- Your program's process starts. In some cases (e.g. standalone console apps, Windows Forms apps, WPF apps), this is a process dedicated to you app. In other cases (e.g. Web apps or Web services, or Windows services), this is some kind of hosting process over which you have less or no control.
- Before you own code gets a chance to execute, the process may (or may not) initialize COM security, either explicitly (by calling the CoInitializeSecurity function in Win32 API), or implicitly (usually by using some COM object from an STA thread). In case of explicit initialization, the hosting process determines the parameters used. In case of implicit initialization, the computer-wide COM default properties (from DCOMCNFG) are used.
- A code in you project runs. In some project types, there is a "main" method that gets executed. In other project types, there can be just objects that get instantiated and their constructors and other methods get executed.
- A code in your project may (usually unintentionally, by using some COM object from an STA thread) cause an implicit COM security initialization, if COM security has not been initialized yet.
- When your code makes a ComManagement.Instance.AssureSecurityInitialization() call, or it makes a first OPC "Classic" operation, QuickOPC attempts to initialize the COM security, using the parameters provided in ComManagement.Instance.Configuration.SecurityParameters. This attempt will succeed if the COM security has not been initialized earlier; otherwise, it will fail with RPC_E_TOO_LATE.
In short, QuickOPC allows you to initialize the COM security and specify the parameters that should be used for it, but it only works if some other code in the process has not already initialized the COM security earlier.
Determining what happens
QuickOPC applications write an entry to the Windows Application log whenever they attempt to explicitly initialize the COM security, indicating a success or failure, and additional relevant information. In order to view this log, start "Event Viewer" from the Windows search box, and in the "Event Viewer" window, select the Event Viewer (Local) -> Windows Logs -> Application node in the tree. The Source of the events is "OPCLabs-ComInterop".
A successful COM security initialization is indicated by an event with Level "Information" and ID 1. The message text will be something like
COM security initialization (process name "...", Id ...) for requestor 'ComSecurityInitializingEasyDAClient' succeeded; the initialization object was: (Default). Made system call: yes, current thread name: "", from thread pool: no, apartment state: MTA.
A failed COM security initialization is indicated by an event with Level "Error" and ID 2. The message text will be something like
COM security initialization (process name "…", Id …) for requestor 'ComSecurityInitializingEasyDAClient' failed; the initialization object was: (Default). CoInitializeSecurity failure (0x80010119): Security must be initialized before any interfaces are marshalled or unmarshalled. It cannot be changed once initialized. + This error (RPC_E_TOO_LATE) is not uncommon in hosted .NET applications. Depending on various factors, it might be possible to prevent it, or safely ignore it (if COM works as intended). Consult the product documentation. + Current thread name: "…", from thread pool: yes, managed thread Id: …, apartment state: MTA.
There may be different reasons for COM security initialization to fail (and the message text will differ); however, the failure with code 0x80010119 (RPC_E_TOO_LATE) is by far the most common, and it is the one we will focus on here. It means that the COM security has already been initialized, before QuickOPC code got a chance to do it.
Guidance for specific project types
Use the steps below to for proper COM security initialization in various project types.
- Console apps in C#
- The main method runs on an MTA thread, unless specified otherwise. This is generally good, because it prevents premature COM security initialization. If COM security initialization fails with RPC_E_TOO_LATE, and there is an [STAThread] attribute on the program's main method, remove it (and optionally replace with an [MTAThread] attribute). If it still fails with RPC_E_TOO_LATE, add a ComManagement.Instance.AssureSecurityInitialization() call to the very beginning of the program's main method.
- Console apps in VB.NET
- The main method runs on an STA thread, unless specified otherwise. This causes a premature COM security initialization. If there is an <STAThread> attribute on the program's main method, remove it. Then, add an <MTAThread> attribute to the program's main method. If the COM security initialization still fails with RPC_E_TOO_LATE, add a ComManagement.Instance.AssureSecurityInitialization() call to the very beginning of the program's main method.
- Web apps and Web services
- The COM security will most likely be initialized before your code gets a chance to do so, and further attempts will fail with RPC_E_TOO_LATE. In this case, the parameters used for the COM security initialization depend on the host (Web server), and you need to figure out whether the host allows them to be configured. If not, there is a chance that a DCOM configuration for the app (in DCOMCNFG) that represents the Web server, or the computer-wide COM default properties, will have effect.
- Windows Forms apps in C#
- The main method runs on an MTA thread, unless specified otherwise. This is generally good, because it prevents premature COM security initialization. If COM security initialization fails with RPC_E_TOO_LATE, and there is an [STAThread] attribute on the program's main method, remove it (and optionally replace with an [MTAThread] attribute). If it still fails with RPC_E_TOO_LATE, add a ComManagement.Instance.AssureSecurityInitialization() call to the very beginning of the program's main method.
- Windows Forms apps in VB.NET
- The main method runs on an STA thread, unless specified otherwise. This causes a premature COM security initialization. If there is an <STAThread> attribute on the program's main method, remove it. Then, add an <MTAThread> attribute to the program's main method. If the COM security initialization still fails with RPC_E_TOO_LATE, add a ComManagement.Instance.AssureSecurityInitialization() call to the very beginning of the program's main method. Note: Depending on how your project has been created, there may be no Program.vb file with the main method. In this case, create or locate Program.vb in some other Windows Forms project, copy it over to your project and edit accordingly (the namespace etc.). Then, go to project Properties -> Application, uncheck "Enable application framework", and set "Startup object" to "Sub Main".
- Windows services
- COM security is not initialized implicitly, therefore the initialization provided by QuickOPC works well. You can add ComManagement.Instance.AssureSecurityInitialization() call to the Main() method in Program.cs or Program.vb.
- WPF apps
- COM security initialization fails with RPC_E_TOO_LATE in the generated project. It should be possible to customize the program's main method (search the Web for "WPF main method" for hints on how to do that). Make the program's main method use the MTA thread and add a ComManagement.Instance.AssureSecurityInitialization() call to it. Alternatively, there is a chance computer-wide COM default properties (in DCOMCNFG) will have effect.
If the steps you took required you to switch from using an STA thread to an MTA thread, and you find that for some reason the remainder of your code has a problem running under an MTA thread, use the ComManagement.Instance.AssureSecurityInitializationAndRunOnStaThread() to run the remainder on your code on an STA thread.
Dealing with CVE-2021-26414
As a mitigation for Windows DCOM Server Security Feature Bypass vulnerability (CVE-2021-26414), newer Microsoft Windows systems enforce a minimum activation authentication level of RPC_C_AUTHN_LEVEL_PKT_INTEGRITY. This can cause OPC Classic interoperability problems.
If you are affected by this issue, you should configure the COM security in your QuickOPC-based client application accordingly. The corresponding setting is in the ComManagement.Instance.Configuration.SecurityParameters.EnsureDataIntegrity property (defaults to false). Set this property to true to deal with CVE-2021-26414 mitigation. This should be done before QuickOPC attempts to initialize the COM security, at the beginning of your program. It may then look similar to this:
[MTAThread] static void Main() { ComManagement.Instance.Configuration.SecurityParameters.EnsureDataIntegrity = true; ComManagement.Instance.AssureSecurityInitialization(); ... }
In cases when the execution framework initializes the COM security for you (as indicated by the RPC_E_TOO_LATE error in the Windows Application event log) and you cannot prevent that using the approaches described in this article (from the code), try setting the computer-wide COM default properties (in DCOMCNFG) - this will work unless the executing environment has explicitly initialized the COM security to some parameters of its own making. To do so:
- Type DCOMCNFG into Windows Search box, and start the DCOMCNFG application.
- In the "Component Services" window, right-click on Console Root -> Component Services -> Computers -> My Computer, and select Properties.
- In "My Computer Properties" dialog, switch to "Default Properties" tab, and under Default Distributed COM Communication Properties -> Default Authentication Level, select "Packet Integrity".
- Press OK to close the "My Computer Properties" dialog.
- Close the "Component Services" window.
- You may have to restart your computer for the setting to take effect (probably not necessary, but we are really not sure about this).
COM object instantiation
Default parameters that influence how COM objects are instantiated by QuickOPC are in ComManagement.Instance.Configuration.InstantiationParameters. These parameters can then be overridden for specific purposes, such as for OPCEnum instantiation, or instantiation of specific OPC servers, using additional properties elsewhere.
Related reading
COM settings in OPC Classic client components
KB5004442—Manage changes for Windows DCOM Server Security Feature Bypass (CVE-2021-26414)