pdcomm::opcua::MfcClientWorker
Class
One-shot MFC worker thread that owns a native OPC UA client.
class MfcClientWorkerIts 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 (phaseresponse). They count ascompleted. - 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
workerfailure, fails the remaining requests with phaseabortand stops with reasonnative-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 = 64MaximumEvents
Field
Upper bound of the events that can wait in the mailbox at once.
static constexpr size_t MaximumEvents = 4 * MaximumCommands + 2The 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 * 1024MfcClientWorker
Constructor
Creates a worker; Start() begins the connection.
MfcClientWorker(MfcClientConfiguration configuration, HWND receiver, UINT message)Parameters
| Name | Description |
|---|---|
configuration | Settings, taken by value. |
receiver | Window that receives the notification message; a message-only window (HWND_MESSAGE) is sufficient. |
message | Message 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 &) = deleteRequestStop
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
| Name | Description |
|---|---|
mode | MfcClientStop::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
| Name | Description |
|---|---|
request | Complete encoded request, as for client::begin(); copied. |
correlation | Non-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
| Name | Description |
|---|---|
milliseconds | Maximum 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().