Skip to content
Start free evaluation

pdcomm::opcua::MfcClientWorker

Class

One-shot MFC worker thread that owns a native OPC UA client.

class MfcClientWorker

Its CWinThread runs connect, activation, begin, poll, take, close and release serially: it connects, activates the session (anonymously or with the user name), reports Ready, then sends submitted requests in order and reports Completed for each response. It ends after RequestStop() or a fatal error and always reports Stopped last.

Rules for the application:

  • Own the object until Wait() returns; never destroy it on its own thread.
  • Handle the notification message on the receiver window's thread and call Drain() there. Events that no window drains stay in the worker and are destroyed with it.
  • Submit() and RequestStop() are thread-safe. Start() and Wait() belong to the owner thread.

Failures follow the C ABI status of the native call:

  • Request failures end only their request, and the worker keeps serving the others: client::begin() rejects it with #PDCOMM_OPCUA_INVALID_INPUT or #PDCOMM_OPCUA_LIMIT (phase begin), or client::take() completes it with #PDCOMM_OPCUA_RUNTIME_ERROR because the server aborted the response or the response was invalid (phase response). They count as completed.
  • A full handle registry is not a failure: client::take() reports #PDCOMM_OPCUA_LIMIT and the native client keeps the response. The worker then retries only that take, because every other client call returns #PDCOMM_OPCUA_BUSY meanwhile, and reports the response once the application releases responses. If the registry stays full for timeoutMs, the worker ends with #PDCOMM_OPCUA_LIMIT.
  • Every other native error ends the worker: connect, activation, poll (a lost connection, a request timeout, a security failure), close, and any other begin or take status, such as #PDCOMM_OPCUA_CLOSED after an invalid response closed the connection. The worker reports a worker failure, fails the remaining requests with phase abort and stops with reason native-error.

From the MFC consumer (examples/opcua-mfc/main.cpp):

// The UI thread polls the in-process server and pumps the receiver window until the
// worker reports its terminal event. Worker events are handled only on this thread.
template <typename Handler>
static void Pump(server &host, MfcClientWorker &worker, UINT message, Handler &&handle, bool &stopped) {
  const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(30);
  while (!stopped) {
    Require(std::chrono::steady_clock::now() < deadline, "MFC worker deadline");
    host.poll();
    MSG msg;
    while (::PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
      if (msg.message == message) {
        for (auto &event : worker.Drain()) handle(event);
      } else {
        ::TranslateMessage(&msg);
        ::DispatchMessageW(&msg);
      }
    }
    ::Sleep(1);
  }
}
// ...
HWND receiver = ::CreateWindowExW(0, L"STATIC", L"", 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr);
const UINT message = ::RegisterWindowMessageW(L"PDComm.OpcUa.Mfc.Consumer");
MfcClientConfiguration configuration;
configuration.endpoint = endpoint;
configuration.anonymous = true;
configuration.policy = PDCOMM_OPCUA_POLICY_NONE;
configuration.mode = PDCOMM_OPCUA_MODE_NONE;
configuration.flags = PDCOMM_OPCUA_ALLOW_NONE;
auto worker = std::make_unique<MfcClientWorker>(configuration, receiver, message);
Require(worker->Submit(kRead, 1) == PDCOMM_OPCUA_OK, "Read admitted");
Require(worker->Start() && !worker->Start(), "One-shot worker start");
bool stopped = false;
Pump(host, *worker, message, [&](const MfcClientEvent &event) {
  switch (event.kind) {
  case MfcClientEventKind::Completed: kept = event.value; break;
  case MfcClientEventKind::Stopped: stopped = true; break;
  default: break;
  }
  if (kept) worker->RequestStop(MfcClientStop::Drain);
}, stopped);
Require(worker->Wait(10000), "Worker joined");

Members

Drain

Method

Takes every queued event in order; call it on the receiver thread.

std::vector<MfcClientEvent> Drain()

Call it when the notification message arrives. After Drain() the next event posts a new message. It is also safe after the worker stopped.

Returns

The events in the order the worker produced them; empty when none are waiting.

MaximumCommands

Field

Maximum number of admitted requests that are queued or in flight.

static constexpr size_t MaximumCommands = 64

MaximumEvents

Field

Upper bound of the events that can wait in the mailbox at once.

static constexpr size_t MaximumEvents = 4 * MaximumCommands + 2

The worker stops taking queued requests while MaximumCommands or more events wait undrained, so the mailbox stays below this bound.

MaximumQueuedBytes

Field

Maximum size of one request and of all queued, unsent requests together (4 MiB).

static constexpr size_t MaximumQueuedBytes = 4 * 1024 * 1024

MfcClientWorker

Constructor

Creates a worker; Start() begins the connection.

MfcClientWorker(MfcClientConfiguration configuration, HWND receiver, UINT message)

Parameters

NameDescription
configurationSettings, taken by value.
receiverWindow that receives the notification message; a message-only window (HWND_MESSAGE) is sufficient.
messageMessage posted to receiver with wParam and lParam 0 when the mailbox gets events; for example a RegisterWindowMessage value.

Notes

  • Throws error: with #PDCOMM_OPCUA_LIMIT if the endpoint, application URI or user name exceeds 4096 bytes, a certificate or key exceeds 64 KiB, or the user secret exceeds 16 KiB.

MfcClientWorker

Constructor

Copying is disabled.

MfcClientWorker(const MfcClientWorker &) = delete

RequestStop

Method

Asks the worker to stop; thread-safe.

void RequestStop(MfcClientStop mode = MfcClientStop::Drain)

Later Submit() calls return #PDCOMM_OPCUA_CLOSED. With MfcClientStop::Drain the worker completes every admitted request, then sends CloseSession and closes the channel. With MfcClientStop::Abort it releases the client in its own thread without a protocol close and fails the remaining requests with phase abort. An abort request is never downgraded to a drain.

The worker checks the request between native calls; a blocking connect, activation, close or DNS lookup is not interrupted.

Parameters

NameDescription
modeMfcClientStop::Drain (default) or MfcClientStop::Abort.

Start

Method

Starts the worker thread; one-shot.

BOOL Start()

Returns

TRUE when the thread started. FALSE when the worker was already started or the thread cannot be created; in the second case the worker is finished and Submit() returns #PDCOMM_OPCUA_CLOSED.

Submit

Method

Queues one UA Binary service request; thread-safe.

int32_t Submit(const std::vector<uint8_t> &request, ULONGLONG correlation)

Only copies the request: no native call runs in the calling thread. Requests submitted before Ready are sent after activation, in submission order. An admitted request gets exactly one Completed or Failed event once the worker runs. The correlation stays reserved until that event.

Parameters

NameDescription
requestComplete encoded request, as for client::begin(); copied.
correlationNon-zero application ID, unique among the requests that have not ended yet.

Returns

Admission status, not an OPC UA StatusCode: #PDCOMM_OPCUA_OK; #PDCOMM_OPCUA_INVALID_INPUT for an empty request or correlation 0; #PDCOMM_OPCUA_BUSY for a correlation in use; #PDCOMM_OPCUA_LIMIT when the request exceeds 4 MiB or would exceed MaximumCommands or MaximumQueuedBytes; #PDCOMM_OPCUA_CLOSED after RequestStop() or the end of the worker.

Wait

Method

Waits for the worker thread to exit; owner thread only.

BOOL Wait(DWORD milliseconds = INFINITE)

Parameters

NameDescription
millisecondsMaximum wait, or INFINITE (default).

Returns

TRUE once the worker has exited or was never started; FALSE on timeout or when called on the worker thread itself.

operator=

Method

Copy assignment is disabled.

MfcClientWorker &operator=(const MfcClientWorker &) = delete

~MfcClientWorker

Method

Requests an abort, waits for the thread and wipes the credentials.

~MfcClientWorker()

The wait is cooperative: a blocking connect, activation, close or DNS lookup finishes first. Destroying the worker on its own thread calls std::terminate().