Can't get print job notification with status JOB_STATUS_DELETED - c++

I want to get notification when print job has been completed or deleted. Now I see that notification mechanism provides the JOB_STATUS_DELETING, but no JOB_STATUS_DELETED status can be got.
I found something similar Here, but it doesn't solve my problem.
I'm doing next thing:
HANDLE hChange = FindFirstPrinterChangeNotification(hPrinter,
PRINTER_CHANGE_ALL,
0,
&NotificationOptions);
DWORD dwChange;
HANDLE aHandles[2];
aHandles[0] = hChange;
aHandles[1] = owner->GetStopRequestEvent();
while (hChange != INVALID_HANDLE_VALUE)
{
// sleep until a printer change notification wakes this thread or the
// event becomes set indicating it's time for the thread to end.
WaitForMultipleObjects(2, aHandles, FALSE, INFINITE);
if (WaitForSingleObject(hChange, 0U) == WAIT_OBJECT_0)
{
FindNextPrinterChangeNotification(hChange, &dwChange, &NotificationOptions, (LPVOID *) &pNotification);
if (pNotification != NULL)
{
// if a notification overflow occurred,
if (pNotification->Flags & PRINTER_NOTIFY_INFO_DISCARDED)
{
DWORD dwOldFlags = NotificationOptions.Flags;
// we must refresh to continue
NotificationOptions.Flags = PRINTER_NOTIFY_OPTIONS_REFRESH;
FreePrinterNotifyInfo(pNotification);
FindNextPrinterChangeNotification(hChange, &dwChange, &NotificationOptions, (LPVOID *) &pNotification);
NotificationOptions.Flags = dwOldFlags;
}
// iterate through each notification
for (DWORD x = 0; x < pNotification->Count; x++)
{
PRINTER_NOTIFY_INFO_DATA data = pNotification->aData[x];
if (data.Type == JOB_NOTIFY_TYPE)
{
if (data.Field == JOB_NOTIFY_FIELD_STATUS)
{
if (data.NotifyData.adwData[0] & ( JOB_STATUS_DELETED | JOB_STATUS_DELETING | JOB_STATUS_PRINTED))
{
owner->SendJobsData(data.NotifyData.adwData[0]);
}
......
when i delete job, JOB_NOTIFY_FIELD_STATUS passes only DELETING, and no any further status-notification, but I really need to get DELETED status. What am I doing wrong?
full code of poller method here:
void Poll(JobTracker* owner, CServiceBase* service)
{
HANDLE hPrinter = NULL;
HANDLE hNotification;
if (!OpenPrinter(owner -> GetPrinterName(), &hPrinter, NULL))
return;
PPRINTER_NOTIFY_INFO pNotification = NULL;
WORD JobFields[] =
{
JOB_NOTIFY_FIELD_PRINTER_NAME,
JOB_NOTIFY_FIELD_MACHINE_NAME,
JOB_NOTIFY_FIELD_PORT_NAME,
JOB_NOTIFY_FIELD_USER_NAME,
JOB_NOTIFY_FIELD_NOTIFY_NAME,
JOB_NOTIFY_FIELD_DATATYPE,
JOB_NOTIFY_FIELD_PRINT_PROCESSOR,
JOB_NOTIFY_FIELD_PARAMETERS,
JOB_NOTIFY_FIELD_DRIVER_NAME,
JOB_NOTIFY_FIELD_DEVMODE,
JOB_NOTIFY_FIELD_STATUS,
JOB_NOTIFY_FIELD_STATUS_STRING,
JOB_NOTIFY_FIELD_DOCUMENT,
JOB_NOTIFY_FIELD_PRIORITY,
JOB_NOTIFY_FIELD_POSITION,
JOB_NOTIFY_FIELD_SUBMITTED,
JOB_NOTIFY_FIELD_START_TIME,
JOB_NOTIFY_FIELD_UNTIL_TIME,
JOB_NOTIFY_FIELD_TIME,
JOB_NOTIFY_FIELD_TOTAL_PAGES,
JOB_NOTIFY_FIELD_PAGES_PRINTED,
JOB_NOTIFY_FIELD_TOTAL_BYTES,
JOB_NOTIFY_FIELD_BYTES_PRINTED
};
PRINTER_NOTIFY_OPTIONS_TYPE Notifications[1] =
{
{
JOB_NOTIFY_TYPE,
0,
0,
0,
sizeof(JobFields) / sizeof(JobFields[0]),
JobFields
},
};
PRINTER_NOTIFY_OPTIONS NotificationOptions =
{
2,
PRINTER_NOTIFY_OPTIONS_REFRESH,
sizeof(Notifications) / sizeof(Notifications[0]),
Notifications
};
// get a handle to a printer change notification object.
HANDLE hChange = FindFirstPrinterChangeNotification(hPrinter,
PRINTER_CHANGE_ALL,
0,
&NotificationOptions);
DWORD dwChange;
HANDLE aHandles[2];
aHandles[0] = hChange;
aHandles[1] = owner->GetStopRequestEvent();
while (hChange != INVALID_HANDLE_VALUE)
{
// sleep until a printer change notification wakes this thread or the
// event becomes set indicating it's time for the thread to end.
WaitForMultipleObjects(2, aHandles, FALSE, INFINITE);
if (WaitForSingleObject(hChange, 0U) == WAIT_OBJECT_0)
{
FindNextPrinterChangeNotification(hChange, &dwChange, &NotificationOptions, (LPVOID *) &pNotification);
if (pNotification != NULL)
{
// if a notification overflow occurred,
if (pNotification->Flags & PRINTER_NOTIFY_INFO_DISCARDED)
{
DWORD dwOldFlags = NotificationOptions.Flags;
// we must refresh to continue
NotificationOptions.Flags = PRINTER_NOTIFY_OPTIONS_REFRESH;
FreePrinterNotifyInfo(pNotification);
FindNextPrinterChangeNotification(hChange, &dwChange, &NotificationOptions, (LPVOID *) &pNotification);
NotificationOptions.Flags = dwOldFlags;
}
// iterate through each notification
for (DWORD x = 0; x < pNotification->Count; x++)
{
PRINTER_NOTIFY_INFO_DATA data = pNotification->aData[x];
if (data.Type == JOB_NOTIFY_TYPE)
{
if (data.Field == JOB_NOTIFY_FIELD_STATUS)
{
if (data.NotifyData.adwData[0] & ( JOB_STATUS_DELETED | JOB_STATUS_DELETING | JOB_STATUS_PRINTED))
{
owner->SendJobsData(data.NotifyData.adwData[0]);
}
}
if (data.Field == JOB_NOTIFY_FIELD_STATUS_STRING)
{
int a = 0;
}
}
else if (data.Type == PRINTER_NOTIFY_TYPE)
{
if (data.Field == PRINTER_NOTIFY_FIELD_STATUS)
{
int a = 0;
}
if (data.Field == PRINTER_NOTIFY_FIELD_STATUS_STRING)
{
int a = 0;
}
}
}
}
FreePrinterNotifyInfo(pNotification);
pNotification = NULL;
}
else if (WaitForSingleObject(owner->GetStopRequestEvent(), 0U) == WAIT_OBJECT_0)
{
FindClosePrinterChangeNotification(hChange);
hChange = INVALID_HANDLE_VALUE;
}
}
}

If anyone will face such task I will leave my way which I solve it.
I've noticed that every time when job leaves queue, the PRINTER_CHANGE_JOB_DELETE notification (i mean change of FindNextPrinterChangeNotification).
So, I just track task list in thread-owner class (JobTracker), and refresh it every time on any PRINTER_CHANGE_JOB. But before refresh it, I look at the difference and if I see that some job dissapeared (compare by JobId), I take my vector of jobs, and send to server missing job.

Related

How Do I Use NotifyServiceStatusChange to Get Notified When a Service Is Deleted?

How can I use NotifyServiceStatusChange properly so I can get notified when the service specified is deleted? My current code successfully stops the service and marks it for deletion. However, I want to be notified when the service is fully deleted.
Here are the main points of my code:
SC_HANDLE SCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE,
SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
HANDLE EventHandle = CreateEventW(NULL, TRUE, FALSE, NULL);
SERVICE_NOTIFY ServiceNotify;
ServiceNotify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
ServiceNotify.pszServiceNames = ServiceName;
ServiceNotify.pContext = &EventHandle;
ServiceNotify.pfnNotifyCallback = (PFN_SC_NOTIFY_CALLBACK)CallbackFunction;
DWORD status = NotifyServiceStatusChangeW(SCManager, SERVICE_NOTIFY_DELETED, &ServiceNotify);
WaitForSingleObject(EventHandle, INFINITE);
CloseServiceHandle(SCManager);
CloseHandle(EventHandle);
(ServiceName is WCHAR*)
CallbackFunction code:
VOID CALLBACK CallbackFunction(IN PVOID pParameter) {
SERVICE_NOTIFY* ServiceNotify = pParameter;
HANDLE EventHandle = *(HANDLE*)ServiceNotify->pContext;
SetEvent(EventHandle);
}
NotifyServiceStatusChange is returning ERROR_SUCCESS (0). However, my callback function is not being called at all. How can I fix this?
Edit:
Here is minimal reproducible code:
void ErrorExit(char* FunctionName, unsigned long ErrorCode) {
char* ErrorMessage;
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, ErrorCode, LANG_USER_DEFAULT, (LPTSTR)&ErrorMessage, 0, NULL);
int MessageSize = (strlen(ErrorMessage) + strlen(FunctionName) + 50) * sizeof(char);
char* FullMessage = malloc(MessageSize);
sprintf_s(FullMessage, MessageSize, "%s failed with error %d: %s", FunctionName, ErrorCode, ErrorMessage);
MessageBoxA(NULL, FullMessage, "Error", MB_OK);
ExitProcess(ErrorCode);
}
PFN_SC_NOTIFY_CALLBACK CallbackFunction(PVOID pParameter) {
printf("CallbackFunction has been called.\r\n");
SERVICE_NOTIFY* ServiceNotify = pParameter;
HANDLE EventHandle = ServiceNotify->pContext;
if (!SetEvent(EventHandle)) {
ErrorExit("SetEvent", GetLastError());
}
}
int main()
{
WCHAR* ServiceName = L"SERVICE NAME"; // Input service name here
SC_HANDLE SCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
if (!SCManager) {
ErrorExit("OpenSCManagerW", GetLastError());
}
SC_HANDLE ServiceHandle = OpenServiceW(SCManager, ServiceName,
SERVICE_ENUMERATE_DEPENDENTS | SERVICE_STOP | DELETE);
if (!ServiceHandle) {
ErrorExit("ServiceHandle", GetLastError());
}
if (!DeleteService(ServiceHandle)) {
ErrorExit("DeleteService", GetLastError());
}
if (!CloseServiceHandle(ServiceHandle)) {
ErrorExit("CloseServiceHandle", GetLastError());
}
HANDLE EventHandle = CreateEventW(NULL, TRUE, FALSE, NULL);
if (!EventHandle) {
ErrorExit("CreateEventW", GetLastError());
}
SERVICE_NOTIFY ServiceNotify;
ServiceNotify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
ServiceNotify.pszServiceNames = ServiceName;
ServiceNotify.pContext = EventHandle;
ServiceNotify.pfnNotifyCallback = CallbackFunction;
DWORD status = NotifyServiceStatusChangeW(SCManager, SERVICE_NOTIFY_DELETED, &ServiceNotify);
if (status != ERROR_SUCCESS) {
ErrorExit("NotifyServiceStatusChangeW", GetLastError());
}
status = WaitForSingleObjectEx(EventHandle, INFINITE, TRUE);
if (status == WAIT_FAILED) {
ErrorExit("WaitForSingleObjectEx", GetLastError());
}
printf("WaitForSingleObjectEx Result: %lu\r\n", status);
system("pause");
return 0;
}
When I run this, no other service depends on the service being deleted, and the service being deleted is already stopped. My error handling function "ErrorExit" is never called. Nothing is printed on the screen. My program simply pauses, which I assume is from WaitForSingleObjectEx.
I know the service is being deleted because I have ProcessHacker open, and it is giving me notifications that the service is being deleted.
NotifyServiceStatusChange is returning ERROR_SUCCESS (0). However, my callback function is not being called at all.
The NotifyServiceStatusChangeW documentation says:
When the service status changes, the system invokes the specified callback function as an asynchronous procedure call (APC) queued to the calling thread. The calling thread must enter an alertable wait (for example, by calling the SleepEx function) to receive notification. For more information, see Asynchronous Procedure Calls.
So, make sure you are actually processing APC notifications while you wait. WaitForSingleObject() will not do that for you.
Use WaitForSingleObjectEx() instead. It has a bAlertable parameter you can set to TRUE. You will have to call it in a loop, since it will return when any APC call is processed by the calling thread, which may not be the one you are expecting.
You also need to call NotifyServiceStatusChangeW() in a loop, too. The documentation does not mention this, but the callback will be called only 1 time per use. Once the callback is called, you need to call NotifyServiceStatusChangeW() again to receive another notification if the current one is not the event you are expecting.
With that said, try something more like this:
struct MyCallbackInfo {
HANDLE EventHandle;
LPCWSTR pszServiceName;
bool bDeleted;
};
...
VOID CALLBACK CallbackFunction(PVOID pParameter) {
SERVICE_NOTIFYW* ServiceNotify = (SERVICE_NOTIFYW*) pParameter;
MyCallbackInfo *ci = (MyCallbackInfo*) ServiceNotify->pContext;
if (ServiceNotify->dwNotificationStatus == ERROR_SUCCESS) {
LPWSTR pServiceName = ServiceNotify->pszServiceNames;
while (*pServiceName != L'\0') {
if (lstrcmpW(pServiceName, ci->pszServiceName) == 0) {
ci.bDeleted = true;
break;
}
pServiceName += (lstrlenW(pServiceName) + 1);
}
LocalFree(ServiceNotify->pszServiceNames);
}
SetEvent(ci->EventHandle);
}
...
MyCallbackInfo ci;
ci.EventHandle = CreateEventW(NULL, TRUE, FALSE, NULL);
ci.pszServiceName = ServiceName;
ci.bDeleted = false;
if (!ci.EventHandle) {
// error handling...
}
SC_HANDLE SCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE,
SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
if (!SCManager) {
// error handling...
}
SERVICE_NOTIFYW ServiceNotify = {};
ServiceNotify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
ServiceNotify.pContext = &ci;
ServiceNotify.pfnNotifyCallback = CallbackFunction;
DWORD status;
do {
status = NotifyServiceStatusChangeW(SCManager, SERVICE_NOTIFY_DELETED, &ServiceNotify);
if (status != ERROR_SUCCESS) {
// error handling...
}
while ((status = WaitForSingleObjectEx(ci.EventHandle, INFINITE, TRUE)) == WAIT_IO_COMPLETION);
if (status == WAIT_FAILED) {
// error handling...
}
if (ci.bDeleted) {
// service has been deleted ...
break;
}
ResetEvent(ci.EventHandle);
}
while (true);
CloseServiceHandle(SCManager);
CloseHandle(ci.EventHandle);

WaitForSingleObject and WaitForMultipleObjects equivalent in modern c++?

The windows functions, CreateEvent, WaitForSingleObject and WaitForMultipleObjects are handy but only used in win32. Can I replace them using Modern C++ threading in a generic way?
I find a post, https://vorbrodt.blog/2019/02/08/event-objects/, to manually set and reset event in modern c++. is there a better way to do that?
The below is the code snippet of old windows code:
DWORD TX_LAYER::__TX_THREAD(HANDLE *done)
{
CONNECTIONS *con = nullptr;
void *temp = nullptr;
const HANDLE h[] =
{
*doneEvent,
evtTX
};
const DWORD num_evts = _countof(h);
while ( true )
{
DWORD ret = -1;
ret = WaitForMultipleObjects(num_evts, h, FALSE, INFINITE);
// Stop signal or critical error.
if ( (ret == 0) || (ret == WAIT_FAILED) )
break;
// TX the packet.
EnterCriticalSection(&csTX);
if ( _tx.empty() == true )
{
ResetEvent(evtTX);
LeaveCriticalSection(&csTX);
continue;
}
temp = *_tx.begin();
_tx.erase(_tx.begin());
if ( _tx.empty() == true )
ResetEvent(evtTX);
else
SetEvent(evtTX);
LeaveCriticalSection(&csTX);
....
....
....
temp = nullptr;
}

Windows biometric service runs SensorAdapterStartCapture in a loop when calling WinBioCaptureSample

I am implementing a Windows Biometric Driver using the umdf sample from github.
When I call WinBioCaptureSample the next plugin's methods run in a loop.
SensorAdapterClearContext
EngineAdapterClearContext
SensorAdapterStartCapture
SensorAdapterFinishCapture
I used TraceView to debug my driver and it shows the next trace messages when is stuck in a loop.
00000001 driver 352840 439560 1 1 04\05\2018-16:46:13:12 CBiometricDevice::OnGetSensorStatus Called.
00000002 driver 352840 439560 1 2 04\05\2018-16:46:13:12 CBiometricDevice::OnGetAttributes Called.
00000003 driver 352840 439560 1 3 04\05\2018-16:46:13:12 CBiometricDevice::OnCaptureDataBuffer too small - must be at least 0x18.
00000004 driver 352840 439560 1 4 04\05\2018-16:46:13:12 CBiometricDevice::OnCaptureData Called.
00000005 driver 352840 439560 4 5 04\05\2018-16:46:13:28 CBiometricDevice::OnGetSensorStatus Called.
00000006 driver 352840 439560 1 6 04\05\2018-16:46:13:29 CBiometricDevice::OnCaptureDataBuffer too small - must be at least 0x18.
00000007 driver 352840 439560 1 7 04\05\2018-16:46:13:29 CBiometricDevice::OnCaptureData Called.
00000008 driver 352840 439560 1 8 04\05\2018-16:46:13:30 CBiometricDevice::OnGetSensorStatus Called.
00000009 driver 352840 439560 4 9 04\05\2018-16:46:13:30 CBiometricDevice::OnCaptureDataBuffer too small - must be at least 0x18.
00000010 driver 352840 439560 1 10 04\05\2018-16:46:13:31 CBiometricDevice::OnCaptureData Called.
...
The method CBiometricDevice::OnGetSensorStatus always returns WINBIO_SENSOR_READY
diagnostics->WinBioHresult = S_OK;
diagnostics->SensorStatus = WINBIO_SENSOR_READY;
MyRequest.SetInformation(diagnostics->PayloadSize);
MyRequest.SetCompletionHr(S_OK);
Next is the method CBiometricDevice::OnCaptureData
DWORD WINAPI
CaptureSleepThread(
LPVOID lpParam
)
{
CBiometricDevice *device = (CBiometricDevice *) lpParam;
PCAPTURE_SLEEP_PARAMS sleepParams = device->GetCaptureSleepParams();
if (sleepParams->SleepValue > 60)
{
sleepParams->SleepValue = 60;
}
Sleep(sleepParams->SleepValue * 1000);
UCHAR szBuffer[] = { 0x08, 0x01, 0x00, 0x02 };
ULONG cbRead = 4;
sleepParams->captureData->WinBioHresult = S_OK;
sleepParams->captureData->SensorStatus = WINBIO_SENSOR_ACCEPT;
sleepParams->captureData->RejectDetail = 0;
sleepParams->captureData->CaptureData.Size = cbRead;
RtlCopyMemory(sleepParams->captureData->CaptureData.Data, szBuffer, cbRead);
device->CompletePendingRequest(sleepParams->Hr, sleepParams->Information);
return 0;
}
CBiometricDevice::OnCaptureData(
_Inout_ IWDFIoRequest *FxRequest
)
{
ULONG controlCode = 0;
PWINBIO_CAPTURE_PARAMETERS captureParams = NULL;
SIZE_T inputBufferSize = 0;
PWINBIO_CAPTURE_DATA captureData = NULL;
SIZE_T outputBufferSize = 0;
bool requestPending = false;
EnterCriticalSection(&m_RequestLock);
if (m_PendingRequest == NULL)
{
if (m_SleepThread != INVALID_HANDLE_VALUE)
{
LeaveCriticalSection(&m_RequestLock);
// TODO: Add code to signal thread to exit.
WaitForSingleObject(m_SleepThread, INFINITE);
CloseHandle(m_SleepThread);
m_SleepThread = INVALID_HANDLE_VALUE;
EnterCriticalSection(&m_RequestLock);
}
if (m_PendingRequest == NULL)
{
m_PendingRequest = FxRequest;
m_PendingRequest->MarkCancelable(this);
}
else
{
requestPending = true;
}
}
else
{
requestPending = true;
}
LeaveCriticalSection(&m_RequestLock);
if (requestPending)
{
FxRequest->Complete(WINBIO_E_DATA_COLLECTION_IN_PROGRESS);
return;
}
GetIoRequestParams(FxRequest,
&controlCode,
(PUCHAR *)&captureParams,
&inputBufferSize,
(PUCHAR *)&captureData,
&outputBufferSize);
if (inputBufferSize < sizeof (WINBIO_CAPTURE_PARAMETERS))
{
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Invalid argument(s).");
CompletePendingRequest(E_INVALIDARG, 0);
return;
}
if (outputBufferSize < sizeof(DWORD))
{
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Output buffer NULL or too small to return size information.");
CompletePendingRequest(E_INVALIDARG, 0);
return;
}
if (outputBufferSize < sizeof (WINBIO_CAPTURE_DATA))
{
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Buffer too small - must be at least 0x%x.", sizeof (WINBIO_CAPTURE_DATA));
DWORD cbSize = 262144;//obtained from MAXIMUM_TRANSFER_SIZE policy of WinUsb
captureData->PayloadSize = (DWORD) sizeof(WINBIO_CAPTURE_DATA) + cbSize;
CompletePendingRequest(S_OK, sizeof(DWORD));
return;
}
RtlZeroMemory(captureData, outputBufferSize);
captureData->PayloadSize = (DWORD) outputBufferSize;// (DWORD) sizeof(WINBIO_CAPTURE_DATA);
captureData->WinBioHresult = WINBIO_E_NO_CAPTURE_DATA;
captureData->SensorStatus = WINBIO_SENSOR_FAILURE;
captureData->RejectDetail= 0;
captureData->CaptureData.Size = 0;
if (captureParams->Purpose == WINBIO_NO_PURPOSE_AVAILABLE)
{
captureData->WinBioHresult = WINBIO_E_UNSUPPORTED_PURPOSE;
}
else if ((captureParams->Format.Type != WINBIO_ANSI_381_FORMAT_TYPE) ||
(captureParams->Format.Owner != WINBIO_ANSI_381_FORMAT_OWNER))
{
captureData->WinBioHresult = WINBIO_E_UNSUPPORTED_DATA_FORMAT;
}
else if (captureParams->Flags != WINBIO_DATA_FLAG_RAW)
{
captureData->WinBioHresult = WINBIO_E_UNSUPPORTED_DATA_TYPE;
}
struct _WINUSB_PIPE_INFORMATION InputPipeInfo, OutputPipeInfo;
m_pIUsbInputPipe->GetInformation(&InputPipeInfo);
m_pIUsbOutputPipe->GetInformation(&OutputPipeInfo);
m_SleepParams.PipeInId = InputPipeInfo.PipeId;
m_SleepParams.PipeOutId = OutputPipeInfo.PipeId;
m_SleepParams.hDeviceHandle = m_pIUsbInterface->GetWinUsbHandle();
m_SleepParams.cbSize = (DWORD) outputBufferSize;
m_SleepParams.SleepValue = 5;
m_SleepParams.Hr = S_OK;
m_SleepParams.Information = captureData->PayloadSize;
m_SleepParams.captureData = captureData;
m_SleepThread = CreateThread(NULL, // default security attributes
0, // use default stack size
CaptureSleepThread, // thread function name
this, // argument to thread function
0, // use default creation flags
NULL); // returns the thread identifier
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC! Called.");
}
The methods SensorAdapterStartCapture and SensorAdapterFinishCapture returns S_OK
static HRESULT
WINAPI
SensorAdapterStartCapture(
_Inout_ PWINBIO_PIPELINE Pipeline,
_In_ WINBIO_BIR_PURPOSE Purpose,
_Out_ LPOVERLAPPED *Overlapped
)
{
HRESULT hr = S_OK;
WINBIO_SENSOR_STATUS sensorStatus = WINBIO_SENSOR_FAILURE;
WINBIO_CAPTURE_PARAMETERS captureParameters = { 0 };
BOOL result = TRUE;
DWORD bytesReturned = 0;
// Verify that pointer arguments are not NULL.
if (!ARGUMENT_PRESENT(Pipeline) ||
!ARGUMENT_PRESENT(Purpose) ||
!ARGUMENT_PRESENT(Overlapped))
{
return E_POINTER;
}
// Retrieve the context from the pipeline.
PWINIBIO_SENSOR_CONTEXT sensorContext =
(PWINIBIO_SENSOR_CONTEXT)Pipeline->SensorContext;
// Verify the state of the pipeline.
if (sensorContext == NULL ||
Pipeline->SensorHandle == INVALID_HANDLE_VALUE)
{
return WINBIO_E_INVALID_DEVICE_STATE;
}
*Overlapped = NULL;
// Synchronously retrieve the status.
hr = SensorAdapterQueryStatus(Pipeline, &sensorStatus);
if (FAILED(hr))
{
return hr;
}
// Determine whether the sensor requires calibration.
//if (sensorStatus == WINBIO_SENSOR_NOT_CALIBRATED)
//{
// Call a custom function that sends IOCTLs to
// the sensor to calibrate it. This operation is
// synchronous.
//hr = _SensorAdapterCalibrate(Pipeline);
// Retrieve the status again to determine whether the
// sensor is ready.
//if (SUCCEEDED(hr))
//{
// hr = SensorAdapterQueryStatus(Pipeline, &sensorStatus);
//}
//if (FAILED(hr))
//{
// return hr;
//}
//}
if (sensorStatus == WINBIO_SENSOR_BUSY)
{
return WINBIO_E_DEVICE_BUSY;
}
if (sensorStatus != WINBIO_SENSOR_READY)
{
return WINBIO_E_INVALID_DEVICE_STATE;
}
// Determine whether the data format has been previously determined.
// If it has not, find a format supported by both the engine and
// the sensor.
if ((sensorContext->Format.Owner == 0) &&
(sensorContext->Format.Type == 0))
{
// Retrieve the format preferred by the engine.
hr = Pipeline->EngineInterface->QueryPreferredFormat(
Pipeline,
&sensorContext->Format,
&sensorContext->VendorFormat
);
if (SUCCEEDED(hr))
{
// Call a private function that queries the sensor driver
// and attaches an attribute array to the sensor context.
// This operation is synchronous.
hr = _SensorAdapterGetAttributes(Pipeline);
}
if (SUCCEEDED(hr))
{
// Search the sensor attributes array for the format
// preferred by the engine adapter.
DWORD i = 0;
for (i = 0; i < sensorContext->AttributesBuffer->SupportedFormatEntries; i++)
{
if ((sensorContext->AttributesBuffer->SupportedFormat[i].Owner == sensorContext->Format.Owner) &&
(sensorContext->AttributesBuffer->SupportedFormat[i].Type == sensorContext->Format.Type))
{
break;
}
}
if (i == sensorContext->AttributesBuffer->SupportedFormatEntries)
{
// No match was found. Use the default.
sensorContext->Format.Owner = WINBIO_ANSI_381_FORMAT_OWNER;
sensorContext->Format.Type = WINBIO_ANSI_381_FORMAT_TYPE;
}
}
else
{
return hr;
}
}
// Set up the parameter-input block needed for the IOCTL.
captureParameters.PayloadSize = sizeof(WINBIO_CAPTURE_PARAMETERS);
captureParameters.Purpose = Purpose;
captureParameters.Format.Owner = sensorContext->Format.Owner;
captureParameters.Format.Type = sensorContext->Format.Type;
CopyMemory(&captureParameters.VendorFormat, &sensorContext->VendorFormat, sizeof(WINBIO_UUID));
captureParameters.Flags = WINBIO_DATA_FLAG_RAW;
// Determine whether a buffer has already been allocated for this sensor.
if (sensorContext->CaptureBuffer == NULL)
{
DWORD allocationSize = 0;
sensorContext->CaptureBufferSize = 0;
// This sample assumes that the sensor driver returns
// a fixed-size DWORD buffer containing the required
// size of the capture buffer if it receives a buffer
// that is smaller than sizeof(WINBIO_CAPTURE_DATA).
//
// Call the driver with a small buffer to get the
// allocation size required for this sensor.
//
// Because this operation is asynchronous, you must block
// and wait for it to complete.
result = DeviceIoControl(
Pipeline->SensorHandle,
IOCTL_BIOMETRIC_CAPTURE_DATA,
&captureParameters,
sizeof(WINBIO_CAPTURE_PARAMETERS),
&allocationSize,
sizeof(DWORD),
&bytesReturned,
&sensorContext->Overlapped
);
if (!result && GetLastError() == ERROR_IO_PENDING)
{
SetLastError(ERROR_SUCCESS);
result = GetOverlappedResult(
Pipeline->SensorHandle,
&sensorContext->Overlapped,
&bytesReturned,
TRUE
);
}
if (!result || bytesReturned != sizeof(DWORD))
{
// An error occurred.
hr = _AdapterGetHresultFromWin32(GetLastError());
return hr;
}
// Make sure that you allocate at least the minimum buffer
// size needed to get the payload structure.
if (allocationSize < sizeof(WINBIO_CAPTURE_DATA))
{
allocationSize = sizeof(WINBIO_CAPTURE_DATA);
}
// Allocate the buffer.
sensorContext->CaptureBuffer = (PWINBIO_CAPTURE_DATA)_AdapterAlloc(allocationSize);
if (!sensorContext->CaptureBuffer)
{
sensorContext->CaptureBufferSize = 0;
return E_OUTOFMEMORY;
}
sensorContext->CaptureBufferSize = allocationSize;
}
else
{
// The buffer has already been allocated. Clear the buffer contents.
SensorAdapterClearContext(Pipeline);
}
// Send the capture request. Because this is an asynchronous operation,
// the IOCTL call will return immediately regardless of
// whether the I/O has completed.
result = DeviceIoControl(
Pipeline->SensorHandle,
IOCTL_BIOMETRIC_CAPTURE_DATA,
&captureParameters,
sizeof(WINBIO_CAPTURE_PARAMETERS),
sensorContext->CaptureBuffer,
(DWORD)sensorContext->CaptureBufferSize,
&bytesReturned,
&sensorContext->Overlapped
);
if (result ||
(!result && GetLastError() == ERROR_IO_PENDING))
{
*Overlapped = &sensorContext->Overlapped;
return S_OK;
}
else
{
hr = _AdapterGetHresultFromWin32(GetLastError());
return hr;
}
}
static HRESULT
WINAPI
SensorAdapterFinishCapture(
_Inout_ PWINBIO_PIPELINE Pipeline,
_Out_ PWINBIO_REJECT_DETAIL RejectDetail
)
{
HRESULT hr = S_OK;
//WINBIO_SENSOR_STATUS sensorStatus = WINBIO_SENSOR_FAILURE;
WINBIO_CAPTURE_PARAMETERS captureParameters = { 0 };
BOOL result = TRUE;
DWORD bytesReturned = 0;
// Verify that pointer arguments are not NULL.
if (!ARGUMENT_PRESENT(Pipeline) ||
!ARGUMENT_PRESENT(RejectDetail))
{
return E_POINTER;
}
// Retrieve the context from the pipeline.
PWINIBIO_SENSOR_CONTEXT sensorContext =
(PWINIBIO_SENSOR_CONTEXT)Pipeline->SensorContext;
// Verify the state of the pipeline.
if (sensorContext == NULL ||
Pipeline->SensorHandle == INVALID_HANDLE_VALUE)
{
return WINBIO_E_INVALID_DEVICE_STATE;
}
// Initialize the RejectDetail argument.
*RejectDetail = 0;
// Wait for I/O completion. This sample assumes that the I/O operation was
// started using the code example shown in the SensorAdapterStartCapture
// documentation.
SetLastError(ERROR_SUCCESS);
result = GetOverlappedResult(
Pipeline->SensorHandle,
&sensorContext->Overlapped,
&bytesReturned,
TRUE
);
if (!result)
{
// There was an I/O error.
return _AdapterGetHresultFromWin32(GetLastError());
}
if (bytesReturned == sizeof(DWORD))
{
// The buffer is not large enough. This can happen if a device needs a
// bigger buffer depending on the purpose. Allocate a larger buffer and
// force the caller to reissue their I/O request.
DWORD allocationSize = sensorContext->CaptureBuffer->PayloadSize;
// Allocate at least the minimum buffer size needed to retrieve the
// payload structure.
if (allocationSize < sizeof(WINBIO_CAPTURE_DATA))
{
allocationSize = sizeof(WINBIO_CAPTURE_DATA);
}
// Free the old buffer and allocate a new one.
_AdapterRelease(sensorContext->CaptureBuffer);
sensorContext->CaptureBuffer = NULL;
sensorContext->CaptureBuffer =
(PWINBIO_CAPTURE_DATA)_AdapterAlloc(allocationSize);
if (sensorContext->CaptureBuffer == NULL)
{
sensorContext->CaptureBufferSize = 0;
return E_OUTOFMEMORY;
}
sensorContext->CaptureBufferSize = allocationSize;
return WINBIO_E_BAD_CAPTURE;
}
// Normalize the status value before sending it back to the biometric service.
if (sensorContext->CaptureBuffer != NULL &&
sensorContext->CaptureBufferSize >= sizeof(WINBIO_CAPTURE_DATA))
{
switch (sensorContext->CaptureBuffer->SensorStatus)
{
case WINBIO_SENSOR_ACCEPT:
{
// The capture was acceptable.
DWORD cbRead = sensorContext->CaptureBuffer->CaptureData.Size;
UCHAR * data = sensorContext->CaptureBuffer->CaptureData.Data;
//wprintf(L"%d ", cbRead);
break;
}
case WINBIO_SENSOR_REJECT:
// The capture was not acceptable. Overwrite the WinBioHresult value
// in case it has not been properly set.
sensorContext->CaptureBuffer->WinBioHresult = WINBIO_E_BAD_CAPTURE;
break;
case WINBIO_SENSOR_BUSY:
// The device is busy. Reset the WinBioHresult value in case it
// has not been properly set.
sensorContext->CaptureBuffer->WinBioHresult = WINBIO_E_DEVICE_BUSY;
break;
case WINBIO_SENSOR_READY:
case WINBIO_SENSOR_NOT_CALIBRATED:
case WINBIO_SENSOR_FAILURE:
default:
// There has been a device failure. Reset the WinBioHresult value
// in case it has not been properly set.
sensorContext->CaptureBuffer->WinBioHresult = WINBIO_E_INVALID_DEVICE_STATE;
break;
}
*RejectDetail = sensorContext->CaptureBuffer->RejectDetail;
hr = sensorContext->CaptureBuffer->WinBioHresult;
}
else
{
// The buffer is not large enough or the buffer pointer is NULL.
hr = WINBIO_E_INVALID_DEVICE_STATE;
}
return hr;
}
I used the next code from this github project
HRESULT CaptureSample()
{
HRESULT hr = S_OK;
WINBIO_SESSION_HANDLE sessionHandle = NULL;
WINBIO_UNIT_ID unitId = 0;
WINBIO_REJECT_DETAIL rejectDetail = 0;
PWINBIO_BIR sample = NULL;
SIZE_T sampleSize = 0;
// Connect to the system pool.
hr = WinBioOpenSession(
WINBIO_TYPE_FINGERPRINT, // Service provider
WINBIO_POOL_SYSTEM, // Pool type
WINBIO_FLAG_RAW, // Access: Capture raw data //To call WinBioCaptureSample function successfully, you must open the session handle by specifying WINBIO_FLAG_RAW
//WINBIO_FLAG_RAW: The client application captures raw biometric data using WinBioCaptureSample.
NULL, // Array of biometric unit IDs //NULL if the PoolType parameter is WINBIO_POOL_SYSTEM
0, // Count of biometric unit IDs//zero if the PoolType parameter is WINBIO_POOL_SYSTEM.
WINBIO_DB_DEFAULT, // Default database
&sessionHandle // [out] Session handle
);
if(FAILED(hr))
{
std::cout << "WinBioOpenSession failed. hr = 0x" << std::hex << hr << std::dec << "\n";
if(sample != NULL)
{
WinBioFree(sample);
sample = NULL;
}
if(sessionHandle != NULL)
{
WinBioCloseSession(sessionHandle);
sessionHandle = NULL;
}
return hr;
}
// Capture a biometric sample.
std::cout << "Calling WinBioCaptureSample - Swipe sensor...\n";
hr = WinBioCaptureSample(
sessionHandle,
WINBIO_NO_PURPOSE_AVAILABLE,
WINBIO_DATA_FLAG_RAW,//WINBIO_DATA_FLAG_RAW
&unitId,
&sample,
&sampleSize,
&rejectDetail
);
if(FAILED(hr))
{
if(hr == WINBIO_E_BAD_CAPTURE)
std:: cout << "Bad capture; reason: " << rejectDetail << "\n";
else
std::cout << "WinBioCaptureSample failed.hr = 0x" << std::hex << hr << std::dec << "\n";
if(sample != NULL)
{
WinBioFree(sample);
sample = NULL;
}
if(sessionHandle != NULL)
{
WinBioCloseSession(sessionHandle);
sessionHandle = NULL;
}
return hr;
}
std::cout << "Swipe processed - Unit ID: " << unitId << "\n";
std::cout << "Captured " << sampleSize << " bytes.\n";
if(sample != NULL)
{
WinBioFree(sample);
sample = NULL;
}
if(sessionHandle != NULL)
{
WinBioCloseSession(sessionHandle);
sessionHandle = NULL;
}
return hr;
}
int main()
{
EnumerateSensors();
CreateDirectoryA("data", NULL);
CaptureSample();
}
Sometimes my code is stuck in a loop and other times is not :(
Any hint is welcomed Thanks.
It seems I might have found a temporary solution to my problem.
First what I did is to change the sensor mode from basic to advanced.
[DriverPlugInAddReg]
HKR,WinBio\Configurations,DefaultConfiguration,,"0"
HKR,WinBio\Configurations\0,SensorMode,0x10001,2 ; Basic - 1, Advanced - 2
And the loop won't start
But I also noticed If I comment the call to the Sleep method inside CaptureSleepThread the loop starts again.
My guess is that Windows Biometric Service is expecting the method CaptureSleepThread to take some time to be considered successful but if the method ends very fast it is considered to be failed despite of the successful responses S_OK and WINBIO_SENSOR_ACCEPT and Windows Biometric Service will retry again calling to SensorAdapterStartCapture causing a loop.
DWORD WINAPI
CaptureSleepThread(
LPVOID lpParam
)
{
CBiometricDevice *device = (CBiometricDevice *) lpParam;
PCAPTURE_SLEEP_PARAMS sleepParams = device->GetCaptureSleepParams();
//
// 1 minute or half a minute delay is the trick.
//
if (sleepParams->SleepValue > 60)
{
sleepParams->SleepValue = 60;
}
Sleep(sleepParams->SleepValue * 1000);
UCHAR szBuffer[] = { 0x08, 0x01, 0x00, 0x02 };
ULONG cbRead = 4;
sleepParams->captureData->WinBioHresult = S_OK;
sleepParams->captureData->SensorStatus = WINBIO_SENSOR_ACCEPT;
sleepParams->captureData->RejectDetail = 0;
sleepParams->captureData->CaptureData.Size = cbRead;
RtlCopyMemory(sleepParams->captureData->CaptureData.Data, szBuffer, cbRead);
device->CompletePendingRequest(sleepParams->Hr, sleepParams->Information);
return 0;
}
Other things I observed can cause a loop is when the call fails and not proper response is set.
//No delay is going to cause a loop
//Sleep(sleepParams->SleepValue * 1000);
//zeroes buffer is considered failed
UCHAR szBuffer[] = { 0x00, 0x00, 0x00, 0x00 };
//zero size is a failed read
ULONG cbRead = 0;
//returning other than S_FALSE or S_OK will cause a loop
sleepParams->captureData->WinBioHresult = S_FALSE;
sleepParams->captureData->SensorStatus = WINBIO_SENSOR_ACCEPT;
sleepParams->captureData->RejectDetail = 0;
sleepParams->captureData->CaptureData.Size = cbRead;
//It is going to fail if CaptureData.Data is empty
//RtlCopyMemory(sleepParams->captureData->CaptureData.Data, szBuffer, cbRead);
Also attaching a debugger like WinDbg or Visual Studio will cause a loop so is better to debug using only Trace messages and TraceView tool and attach a debugger when necessary and a loop will be expected in this case, just ignore it.

Adding Response from TSA to CRYPT_SIGN_MESSAGE_PARA for CryptSignMessage (c++, Crypto Api)

I'm struggling how must I add the response from a TSA server to my CryptSignMessage?
Using PKCS#7. I currently have my message digest and I successfully sign it with CryptSignMessage from crypto api. Like so:
// Initialize the signature structure.
CRYPT_SIGN_MESSAGE_PARA SigParams;
SigParams.cbSize = sizeof(CRYPT_SIGN_MESSAGE_PARA);
SigParams.dwMsgEncodingType = MY_ENCODING_TYPE;
SigParams.pSigningCert = hContext;
SigParams.HashAlgorithm.pszObjId = szOID_RSA_SHA1RSA;
SigParams.HashAlgorithm.Parameters.cbData = NULL;
SigParams.cMsgCert = 1;
SigParams.rgpMsgCert = &hContext;
SigParams.dwInnerContentType = 0;
SigParams.cMsgCrl = 0;
SigParams.cUnauthAttr = 0;
SigParams.dwFlags = 0;
SigParams.pvHashAuxInfo = NULL;
SigParams.cAuthAttr = 0;
SigParams.rgAuthAttr = NULL;
// First, get the size of the signed BLOB.
if(CryptSignMessage(
&SigParams,
FALSE,
1,
MessageArray,
MessageSizeArray,
NULL,
&cbSignedMessageBlob))
{
printf("%d bytes needed for the encoded BLOB.", cbSignedMessageBlob);
}
else
{
MyHandleError();
fReturn = false;
exit_SignMessage();
}
// Allocate memory for the signed BLOB.
if(!(pbSignedMessageBlob =
(BYTE*)malloc(cbSignedMessageBlob)))
{
MyHandleError();
exit_SignMessage();
}
// Get the signed message BLOB.
if(CryptSignMessage(
&SigParams,
TRUE,
1,
MessageArray,
MessageSizeArray,
pbSignedMessageBlob,
&cbSignedMessageBlob))
{
printf("The message was signed successfully. \n");
// pbSignedMessageBlob now contains the signed BLOB.
fReturn = true;
}
else
{
MyHandleError();
fReturn = false;
exit_SignMessage();
}
Now I want to use a TSA server to timestamp my digest, but I'm not really sure how to include this. Say I have a rfc3161 TimeStamp request; I send this to my TSA and I receive a rfc3161 TimeStamp response (probably using libcurl). How should incorporate the response into my SigParams? Must I extract the TimeStampToken and then store that as an unauthenticated counter signature? Something like:
CRYPT_ATTR_BLOB cablob[1];
CRYPT_ATTRIBUTE ca[1];
cablob[0].cbData = tstResponseSize;
cablob[0].pbData = tstResponse; // the response from TSA
ca[0].pszObjId = "1.2.840.113549.9.6"; // object identifier for counter signature
ca[0].cValue = 1;
ca[0].rgValue = cablob;
And then set the SigParams:
SigParams.cUnauthAtt = 1;
SigParams.rgUnauthAttr = ca;
Any advice would be greatly appreciated.
Thanks,
Magda
I struggled with this for a couple of days. There are not that many examples out there, so here is my solution. Hope it helps :)
HCRYPTMSG hMsg = ::CryptMsgOpenToDecode(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, CMSG_DETACHED_FLAG, 0, NULL, NULL, NULL);
if (NULL == hMsg)
{
throw std::exception("Failed to open messsage to decode");
}
if (!::CryptMsgUpdate(hMsg, signedData.pbData, signedData.cbData, TRUE))
{
throw std::exception("Failed to add signature block to message");
}
//get the digest from the signature
PCRYPT_TIMESTAMP_CONTEXT pTsContext = NULL;
DWORD encDigestSize = 0;
if (::CryptMsgGetParam(hMsg, CMSG_ENCRYPTED_DIGEST, 0, NULL, &encDigestSize))
{
std::unique_ptr<BYTE> pEncDigest(new BYTE[encDigestSize]);
if (::CryptMsgGetParam(hMsg, CMSG_ENCRYPTED_DIGEST, 0, pEncDigest.get(), &encDigestSize))
{
//get timestamp
if (::CryptRetrieveTimeStamp(L"http://sha256timestamp.ws.symantec.com/sha256/timestamp",
TIMESTAMP_NO_AUTH_RETRIEVAL,
0, //timeout?????
szOID_NIST_sha256,
NULL,
pEncDigest.get(),
encDigestSize,
&pTsContext,
NULL,
NULL))
{
CRYPT_ATTR_BLOB cryptBlob = {};
cryptBlob.cbData = pTsContext->cbEncoded;
cryptBlob.pbData = pTsContext->pbEncoded;
CRYPT_ATTRIBUTE cryptAttribute = {};
cryptAttribute.pszObjId = "1.2.840.113549.1.9.16.2.14"; //id-smime-aa-timeStampToken
cryptAttribute.cValue = 1;
cryptAttribute.rgValue = &cryptBlob;
DWORD encodedAttributeSize = 0;
std::unique_ptr<BYTE> encodedAttribute;
if (::CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_ATTRIBUTE, &cryptAttribute, NULL, &encodedAttributeSize))
{
encodedAttribute.reset(new BYTE[encodedAttributeSize]);
if (::CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_ATTRIBUTE, &cryptAttribute, encodedAttribute.get(), &encodedAttributeSize))
{
CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR_PARA unauthenticatedParam = { 0 };
unauthenticatedParam.cbSize = sizeof(unauthenticatedParam);
unauthenticatedParam.dwSignerIndex = 0; //only have 1 cert
unauthenticatedParam.blob.cbData = encodedAttributeSize;
unauthenticatedParam.blob.pbData = encodedAttribute.get();
if (::CryptMsgControl(hMsg, 0, CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR, &unauthenticatedParam))
{
DWORD encodedMessageLength = 0;
if (::CryptMsgGetParam(hMsg, CMSG_ENCODED_MESSAGE, 0, NULL, &encodedMessageLength))
{
std::unique_ptr<BYTE> pData(new BYTE[encodedMessageLength]);
if (::CryptMsgGetParam(hMsg, CMSG_ENCODED_MESSAGE, 0, pData.get(), &encodedMessageLength))
{
//save pData/encodedMessageLength here to file
}
}
}
}
}
}
}
}
if (NULL != pTsContext)
{
::CryptMemFree(pTsContext);
}
if (NULL != hMsg)
{
::CryptMsgClose(hMsg);
}

COM + WaitForSingleObject

I've been trying to find a good architecture for one application for the last few days, and after some research I'm finally stuck, and the reason is COM.
The app in question will have multiple GUI threads, and they will schedule work items for worker thread. The worker thread will initialize COM via CoInitialize(NULL);, create few COM components and will go into a loop which will wait for WaitForMultipleObjects(2, ...) (ExitEvent - to indicate that app is shutting down and ManualResetEvent - to indicate that there are actually work items to process), and on successful wait, will process the items and PostMessage them back to GUI threads. The ManualResetEvent will be reset inside worker if the queue will be empty and will happen inside queue critical section.
The problem is that COM, as usual, makes EVERYTHING 1000x harder...
If I understand correctly, CoInitialize(NULL); creates a hidden window, and any message posted during WaitForSingle/MultipleObject/s can cause a deadlock.
So, I need to call the MsgWaitForMultiple objects. Which in turn can fail if messages are not pumped correctly. Unfortunately, I can't quite understand how to pump them in a correct way. Do I have to create my own message loop? Will the app crash if COM decides to create a messagebox?
So far it seems I have to proceed like this?
HANDLE hEvents[2] = {};
int ThreadProc(LPVOID lpParam) {
int nRetVal = 0;
CoInitialize(NULL);
CComPtr<ISomething> smthn;
smthn.CoCreateInstance(...);
MSG msg = {};
bool bRun = true;
while(bRun) {
while(PeekMessage(&msg, ??NULL/-1??, 0, 0, PM_REMOVE)) { /*Which one here?*/
if(msg.Message == WM_QUIT) {
bRun = false;
nRetVal = msg.wParam;
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if(MsgWaitForMultipleObjects(2, &hEvents, ...)) {
if(exitevent) { bRun = false; nRetVal = 0; }
else if(processevent) { [processdata] }
}
}
smthn.release();
CoUninitialize();
return nRetVal;
}
But what about hidden window, messageboxes, am I even on the right path?
Just use CoWaitForMultipleHandles and it will do the necessary message pumping on the hidden COM window for inter-thread syncing.
The hidden window is of class OleMainThreadWndClass with OleMainThreadWndName as caption but on win9x its class is WIN95 RPC Wmsg. It's hidden which means you can't use straight EnumThreadWindows to find it.
Seems like overkill, but this worked for me :
int waitAndDispatch( HANDLE* event_handle, unsigned int ev_count, DWORD timeout )
{
int rval = -1;
bool bLoop = true; // if the loop should terminate
HANDLE* pHList = new HANDLE[ev_count];
for( unsigned int i = 0; i < ev_count; ++i )
{
pHList[i] = event_handle[i];
}
while( bLoop )
{
DWORD res = ::MsgWaitForMultipleObjects( ev_count, pHList, false, timeout, QS_ALLPOSTMESSAGE | QS_SENDMESSAGE );
if( res == WAIT_OBJECT_0 + ev_count ) // messages arrived
{
MSG tmpMsg;
bool hasMsg = true;
while( bLoop && hasMsg )
{
::PeekMessage( &tmpMsg, 0, 0, 0, PM_NOREMOVE );
if( ::PeekMessage( &tmpMsg, 0, WM_USER, WM_USER, PM_REMOVE ) || // WM_USER for COM
::PeekMessage( &tmpMsg, 0, 0, WM_KEYFIRST - 1, PM_REMOVE ) // all destroy update, ...
)
{
DWORD val = ::WaitForMultipleObjects( ev_count, pHList, false, 0 );
if( val >= WAIT_OBJECT_0 && val <= (WAIT_OBJECT_0 + ev_count) )
{
rval = val - WAIT_OBJECT_0;
bLoop = false;
}
::DispatchMessage( &tmpMsg );
}
else
{
hasMsg = false;
}
}
}
else if( res >= WAIT_OBJECT_0 && res < (WAIT_OBJECT_0 + ev_count) )
{
rval = res - WAIT_OBJECT_0;
bLoop = false;
}
else if( res == WAIT_TIMEOUT )
{
rval = ev_count;
bLoop = false;
}
else
{
rval = -1;
bLoop = false;
}
}
delete[] pHList;
return rval;
}
I had to write this piece for VB6 and its thread interactions over com compartments ... .
If you initialize your thread apartment with CoInitializeEx( 0, COINIT_MULTITHREADED ), your COM calls will not be queued into an message queue. But then you have the problem of objects created in different COM apartments. These need to be marshaled ... .