Use of TerminateExecution in V8 - v8

I'm experimenting with V8 at the moment. I want to be able to run some (possibly long-running) javascript in one thread and then be able to terminate the execution "gracefully" at will from another thread.
I've written this simple snippet to test the concept of Lockers and the usage of TerminateExecution:
void breaker( Isolate* isolate, int tid ) {
getchar(); //wait for keyboard input on stdin
std::cout << "Breaking V8 execution" << std::endl;
v8::Locker locker( isolate ); //lock the isolate
v8::V8::TerminateExecution( tid ); //and terminate it
}
int main( int argc, char **argv ) {
if( argc != 2 ) {
std::cout << "No script name given" << std::endl;
return 1;
}
Isolate* isolate = Isolate::New(); //create a new isolate
Isolate::Scope isolateScope( isolate ); //enter it
v8::Locker locker( isolate ); //lock the isolate
v8::Locker::StartPreemption( 100 ); //and start preemption
v8::HandleScope handle_scope( isolate ); //open a new handle scope
/*** inject a console object into the global context ***/
v8::Handle<v8::ObjectTemplate> globalTemplate = v8::ObjectTemplate::New();
Handle<Context> context = Context::New( NULL, globalTemplate );
v8::Context::Scope context_scope( context );
Console* console = new Console;
Handle<Object> jsConsole = wrap_console( isolate, console );
expose_property( jsConsole, String::New( "log" ), InvocationCallback( Console::consoleLog ) );
context->Global()->Set( String::New( "console" ), jsConsole, v8::ReadOnly );
/*******************************************************/
//read a javascript file supplied via console
std::string contents = read_file( argv[1] );
v8::Handle<v8::String> js = v8::String::New( contents.c_str() );
v8::TryCatch try_catch;
v8::Handle<v8::Script> script = v8::Script::Compile( js );
if( script.IsEmpty() ) {
report_exception( try_catch );
}
//start the breaker thread
std::thread th( breaker, isolate, v8::V8::GetCurrentThreadId() );
log_info( "running script" );
script->Run();
log_info( "Script execution finished" );
th.join();
}
However, I always get a segfault on the terminateExecution() call. What am I doing wrong here?
thanks for any help

The v8::V8::GetCurrentThreadId() and v8::V8::TerminateExecution(int) methods have been removed from the V8 API. I recommend that you don't use them. The preemption feature is probably also not long for this world.
Instead, simply call v8::V8::TerminateExecution(v8::Isolate*). And don't lock the isolate in your breaker thread, as doing so will block until your runner thread releases the isolate, which it won't do until script execution is finished.

Related

async_write_some callback not called after delay

My callback for async_write_some is not called after a one second sleep. If I am starting an io_service worker thread for every write, why is the callback not being called?
header
boost::system::error_code error_1;
boost::shared_ptr <boost::asio::io_service> io_service_1;
boost::shared_ptr <boost::asio::ip::tcp::socket> socket_1;
connect
void eth_socket::open_eth_socket (void)
{
// 1. reset io services
io_service_1.reset();
io_service_1 = boost::make_shared <boost::asio::io_service> ();
// 2. create endpoint
boost::asio::ip::tcp::endpoint remote_endpoint(
boost::asio::ip::address::from_string("10.0.0.3"),
socket_1_port
);
// 3. reset socket
socket_1.reset(new boost::asio::ip::tcp::socket(*io_service_1));
// 4. connect socket
socket_1->async_connect(remote_endpoint,
boost::bind(
&eth_socket::socket_1_connect_callback,
this, boost::asio::placeholders::error
)
);
// 5. start io_service_1 run thread after giving it work
boost::thread t(boost::bind(&boost::asio::io_service::run, *&io_service_1));
return;
}
write
void eth_socket::write_data (std::string data)
{
// 1. check socket status
if (!socket_1->is_open())
{
WARNING << "socket_1 is not open";
throw -3;
}
// 2. start asynchronous write
socket_1->async_write_some(
boost::asio::buffer(data.c_str(), data.size()),
boost::bind(
&eth_socket::socket_1_write_data_callback,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
// 3. start io_service_1 run thread after giving it work
boost::thread t(boost::bind(&boost::asio::io_service::run, *&io_service_1));
return;
}
callback
void eth_socket::socket_1_write_data_callback (const boost::system::error_code& error, size_t bytes_transferred)
{
// 1. check for errors
if (error)
{
ERROR << "error.message() >> " << error.message().c_str();
return;
}
if (socket_1.get() == NULL || !socket_1->is_open())
{
WARNING << "serial_port_1 is not open";
return;
}
INFO << "data written to 10.0.0.3:1337 succeeded; bytes_transferred = " << bytes_transferred;
return;
}
test
open_eth_socket();
write_data("Hello"); // callback called
write_data("Hello"); // callback called
write_data("Hello"); // callback called
sleep(1);
write_data("Hello"); // callback not called after sleep
boost::thread t(boost::bind(&boost::asio::io_service::run, *&io_service_1));
That's weird for a number of reasons.
You should not "run" io_services for each operation. Instead, run them steadily while operations may be posted. Optionally use io_service::work to prevent run from returning.
You should not (have to) create threads for each operation. If anything, it's recipe for synchronization issues (Why do I need strand per connection when using boost::asio?)
When running io_service again after it returned (without error) you should call reset() first, as per documentation (Why must io_service::reset() be called?)
You destruct a non-detached thread - likely before it had completed. If you had used std::thread this would even have caused immediate abnormal program termination. It's bad practice to not-join non-detached threads (and I'd add it's iffy to use detached threads without explicit synchronization on thread termination). See Why is destructor of boost::thread detaching joinable thread instead of calling terminate() as standard suggests?
I'd add to these top-level concerns
the smell from using names like socket_1 (just call it socket_ and instantiate another object with a descriptive name to contain the other socket_). I'm not sure, but the question does raise suspicion these might even be global variables. (I hope that's not the case)
throw-ing raw integers, really?
You are risking full on data-races by destructing io_service while never checking that worker threads had completed.
More Undefined Behaviour here:
_sock.async_write_some(
ba::buffer(data.c_str(), data.size()),
You pass a reference to the parameter data which goes out of scope. When the async operation completes, it will be a dangling reference
There's some obvious copy/paste trouble going on here:
if (socket_1.get() == NULL || !socket_1->is_open())
{
WARNING << "serial_port_1 is not open";
return;
}
I'd actually say this stems from precisely the same source that lead to the variable names being serial_port_1 and socket_1
Some Cleanup
Simplify. There wasn't self-contained code, so nothing complete here, but at least see the many points of simplification:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <iostream>
namespace ba = boost::asio;
using ba::ip::tcp;
using boost::system::error_code;
#define ERROR std::cerr
#define WARNING std::cerr
#define INFO std::cerr
struct eth_socket {
~eth_socket() {
_work.reset();
if (_worker.joinable())
_worker.join(); // wait
}
void open(std::string address);
void write_data(std::string data);
private:
void connected(error_code error) {
if (error)
ERROR << "Connect failed: " << error << "\n";
else
INFO << "Connected to " << _sock.remote_endpoint() << "\n";
}
void written(error_code error, size_t bytes_transferred);
private:
ba::io_service _svc;
boost::optional<ba::io_service::work> _work{ _svc };
boost::thread _worker{ [this] { _svc.run(); } };
std::string _data;
unsigned short _port = 6767;
tcp::socket _sock{ _svc };
};
void eth_socket::open(std::string address) {
tcp::endpoint remote_endpoint(ba::ip::address::from_string(address), _port);
_sock.async_connect(remote_endpoint, boost::bind(&eth_socket::connected, this, _1));
}
void eth_socket::write_data(std::string data) {
_data = data;
_sock.async_write_some(ba::buffer(_data), boost::bind(&eth_socket::written, this, _1, _2));
}
void eth_socket::written(error_code error, size_t bytes_transferred) {
INFO << "data written to " << _sock.remote_endpoint() << " " << error.message() << ";"
<< "bytes_transferred = " << bytes_transferred << "\n";
}
int main() {
{
eth_socket s;
s.open("127.0.0.1");
s.write_data("Hello"); // callback called
s.write_data("Hello"); // callback called
s.write_data("Hello"); // callback called
boost::this_thread::sleep_for(boost::chrono::seconds(1));
s.write_data("Hello"); // callback not called after sleep
} // orderly worker thread join here
}
My problems are now fixed thanks to sehe's help and prayer.
This line in open_eth_socket:
boost::thread t(boost::bind(&boost::asio::io_service::run, *&io_service_1));
is now this:
boost::shared_ptr <boost::thread> io_service_1_thread; // in header
if (io_service_1_thread.get()) io_service_1_thread->interrupt();
io_service_1_thread.reset(new boost::thread (boost::bind(&eth_socket::run_io_service_1, this)));
I added this function:
void eth_socket::run_io_service_1 (void)
{
while (true) // work forever
{
boost::asio::io_service::work work(*io_service_1);
io_service_1->run();
io_service_1->reset(); // not sure if this will cause problems yet
INFO << "io_service_1 run complete";
boost::this_thread::sleep (boost::posix_time::milliseconds (100));
}
return;
}

USN NFTS change notification event interrupt

I'm trying to find a way to let the system tell me whenever there's a new entry in the USN Change Journal to track modifications made to files and directories on an NTFS volume (Server 2008/2012).
This way I don't have to constantly poll the journal and can just let my thread sleep until I get notified when there's a new change-event.
However, is there even such an interrupt?
The FSCTL_QUERY_USN_JOURNAL function doesn't specifically mention interrupts (events, notifications), nor have I been able to find another way to achieve this with less intensive poll-and-compare techniques.
I'm not a hard-core programmer so there may be simpler ways to tie these functions to interrupts that I'm not aware of.
Could I perhaps find out where the USN Change Journal is stored and watch that file with another process that can generate and interrupt on change?
https://msdn.microsoft.com/en-us/library/aa365729(v=vs.85).aspx
The code posted here blocks the executing thread till the new USN record is created in the Journal. When new records arrive, the thread awakens and you can process changes and/or notify listeners via a callback that filesystem has changed (in the example it just prints message to the console). Then the thread blocks again. This example uses one thread per volume (so for each volume, separate NTFSChangesWatcher class instance needed).
It is not specified which tools or language you use, so I will write as I did it. To run this code, create a Visual Studio C++ Win32 Console Application.
Create NTFSChangesWatcher class. Paste this code in NTFSChangesWatcher.h file (replacing auto-generated one):
#pragma once
#include <windows.h>
#include <memory>
class NTFSChangesWatcher
{
public:
NTFSChangesWatcher(char drive_letter);
~NTFSChangesWatcher() = default;
// Method which runs an infinite loop and waits for new update sequence number in a journal.
// The thread is blocked till the new USN record created in the journal.
void WatchChanges();
private:
HANDLE OpenVolume(char drive_letter);
bool CreateJournal(HANDLE volume);
bool LoadJournal(HANDLE volume, USN_JOURNAL_DATA* journal_data);
bool NTFSChangesWatcher::WaitForNextUsn(PREAD_USN_JOURNAL_DATA read_journal_data) const;
std::unique_ptr<READ_USN_JOURNAL_DATA> GetWaitForNextUsnQuery(USN start_usn);
bool NTFSChangesWatcher::ReadJournalRecords(PREAD_USN_JOURNAL_DATA journal_query, LPVOID buffer,
DWORD& byte_count) const;
std::unique_ptr<READ_USN_JOURNAL_DATA> NTFSChangesWatcher::GetReadJournalQuery(USN low_usn);
char drive_letter_;
HANDLE volume_;
std::unique_ptr<USN_JOURNAL_DATA> journal_;
DWORDLONG journal_id_;
USN last_usn_;
// Flags, which indicate which types of changes you want to listen.
static const int FILE_CHANGE_BITMASK;
static const int kBufferSize;
};
and this code in NTFSChangesWatcher.cpp file:
#include "NTFSChangesWatcher.h"
#include <iostream>
using namespace std;
const int NTFSChangesWatcher::kBufferSize = 1024 * 1024 / 2;
const int NTFSChangesWatcher::FILE_CHANGE_BITMASK =
USN_REASON_RENAME_NEW_NAME | USN_REASON_SECURITY_CHANGE | USN_REASON_BASIC_INFO_CHANGE | USN_REASON_DATA_OVERWRITE |
USN_REASON_DATA_TRUNCATION | USN_REASON_DATA_EXTEND | USN_REASON_CLOSE;
NTFSChangesWatcher::NTFSChangesWatcher(char drive_letter) :
drive_letter_(drive_letter)
{
volume_ = OpenVolume(drive_letter_);
journal_ = make_unique<USN_JOURNAL_DATA>();
bool res = LoadJournal(volume_, journal_.get());
if (!res) {
cout << "Failed to load journal" << endl;
return;
}
journal_id_ = journal_->UsnJournalID;
last_usn_ = journal_->NextUsn;
}
HANDLE NTFSChangesWatcher::OpenVolume(char drive_letter) {
wchar_t pattern[10] = L"\\\\?\\a:";
pattern[4] = static_cast<wchar_t>(drive_letter);
HANDLE volume = nullptr;
volume = CreateFile(
pattern, // lpFileName
// also could be | FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE
GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, // dwDesiredAccess
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // share mode
NULL, // default security attributes
OPEN_EXISTING, // disposition
// It is always set, no matter whether you explicitly specify it or not. This means, that access
// must be aligned with sector size so we can only read a number of bytes that is a multiple of the sector size.
FILE_FLAG_NO_BUFFERING, // file attributes
NULL // do not copy file attributes
);
if (volume == INVALID_HANDLE_VALUE) {
// An error occurred!
cout << "Failed to open volume" << endl;
return nullptr;
}
return volume;
}
bool NTFSChangesWatcher::CreateJournal(HANDLE volume) {
DWORD byte_count;
CREATE_USN_JOURNAL_DATA create_journal_data;
bool ok = DeviceIoControl(volume, // handle to volume
FSCTL_CREATE_USN_JOURNAL, // dwIoControlCode
&create_journal_data, // input buffer
sizeof(create_journal_data), // size of input buffer
NULL, // lpOutBuffer
0, // nOutBufferSize
&byte_count, // number of bytes returned
NULL) != 0; // OVERLAPPED structure
if (!ok) {
// An error occurred!
}
return ok;
}
bool NTFSChangesWatcher::LoadJournal(HANDLE volume, USN_JOURNAL_DATA* journal_data) {
DWORD byte_count;
// Try to open journal.
if (!DeviceIoControl(volume, FSCTL_QUERY_USN_JOURNAL, NULL, 0, journal_data, sizeof(*journal_data), &byte_count,
NULL)) {
// If failed (for example, in case journaling is disabled), create journal and retry.
if (CreateJournal(volume)) {
return LoadJournal(volume, journal_data);
}
return false;
}
return true;
}
void NTFSChangesWatcher::WatchChanges() {
auto u_buffer = make_unique<char[]>(kBufferSize);
auto read_journal_query = GetWaitForNextUsnQuery(last_usn_);
while (true) {
// This function does not return until new USN record created.
WaitForNextUsn(read_journal_query.get());
cout << "New entry created in the journal!" << endl;
auto journal_query = GetReadJournalQuery(read_journal_query->StartUsn);
DWORD byte_count;
if (!ReadJournalRecords(journal_query.get(), u_buffer.get(), byte_count)) {
// An error occurred.
cout << "Failed to read journal records" << endl;
}
last_usn_ = *(USN*)u_buffer.get();
read_journal_query->StartUsn = last_usn_;
// If you need here you can:
// Read and parse Journal records from the buffer.
// Notify an NTFSChangeObservers about journal changes.
}
}
bool NTFSChangesWatcher::WaitForNextUsn(PREAD_USN_JOURNAL_DATA read_journal_data) const {
DWORD bytes_read;
bool ok = true;
// This function does not return until new USN record created.
ok = DeviceIoControl(volume_, FSCTL_READ_USN_JOURNAL, read_journal_data, sizeof(*read_journal_data),
&read_journal_data->StartUsn, sizeof(read_journal_data->StartUsn), &bytes_read,
nullptr) != 0;
return ok;
}
unique_ptr<READ_USN_JOURNAL_DATA> NTFSChangesWatcher::GetWaitForNextUsnQuery(USN start_usn) {
auto query = make_unique<READ_USN_JOURNAL_DATA>();
query->StartUsn = start_usn;
query->ReasonMask = 0xFFFFFFFF; // All bits.
query->ReturnOnlyOnClose = FALSE; // All entries.
query->Timeout = 0; // No timeout.
query->BytesToWaitFor = 1; // Wait for this.
query->UsnJournalID = journal_id_; // The journal.
query->MinMajorVersion = 2;
query->MaxMajorVersion = 2;
return query;
}
bool NTFSChangesWatcher::ReadJournalRecords(PREAD_USN_JOURNAL_DATA journal_query, LPVOID buffer,
DWORD& byte_count) const {
return DeviceIoControl(volume_, FSCTL_READ_USN_JOURNAL, journal_query, sizeof(*journal_query), buffer, kBufferSize,
&byte_count, nullptr) != 0;
}
unique_ptr<READ_USN_JOURNAL_DATA> NTFSChangesWatcher::GetReadJournalQuery(USN low_usn) {
auto query = make_unique<READ_USN_JOURNAL_DATA>();
query->StartUsn = low_usn;
query->ReasonMask = 0xFFFFFFFF; // All bits.
query->ReturnOnlyOnClose = FALSE;
query->Timeout = 0; // No timeout.
query->BytesToWaitFor = 0;
query->UsnJournalID = journal_id_;
query->MinMajorVersion = 2;
query->MaxMajorVersion = 2;
return query;
}
Now you can use it (for example in the main function for testing):
#include "NTFSChangesWatcher.h"
int _tmain(int argc, _TCHAR* argv[])
{
auto watcher = new NTFSChangesWatcher('z');
watcher->WatchChanges();
return 0;
}
And console output should be like this on every change in the filesystem:
This code was slightly reworked to remove unrelated details and is a part of the Indexer++ project. So for more details, you can refer to the original code.
You can use Journal, but in this case I'd use easier method via registering a directory notification by calling the FindFirstChangeNotification or ReadDirectoryChangesW functions, see https://msdn.microsoft.com/en-us/library/aa364417.aspx
If you'd prefer to use Journal, this is - I think - the best introductory article with many examples. It is written for W2K, but those concepts are still valid: https://www.microsoft.com/msj/0999/journal/journal.aspx

boost::io_service run() never returns sometimes

I have the following code to read data from a RS232 port. For some (from beginning) runs, io.run() returns immediately and m_handler() is called which is expected. but sometimes (a fresh run from beginning), io.run() goes into a inside forever loop(stacked inside forever) even for the first time called inside the while loop. And this happens randomly. What's is/are the potential problem(s) at here?
#define BUF_SIZE 512
char data[BUF_SIZE];
void my_handler(const boost::system::error_code& err, std::size_t bytes_transferred)
{
printf("%d\n", bytes_transferred);
if (!err){
process_data();
}
}
void test_serial_port( int argc, char* argv[] )
{
// hard coded parameters
const char *PORT = "COM1";
serial_port_base::baud_rate BAUD(9600);
serial_port_base::character_size CSIZE( 8 );
serial_port_base::flow_control FLOW( serial_port_base::flow_control::software );
serial_port_base::parity PARITY( serial_port_base::parity::none );
serial_port_base::stop_bits STOP( serial_port_base::stop_bits::one );
io_service io;
serial_port port( io, PORT );
// go through and set all the options as we need them
// all of them are listed, but the default values work for most cases
port.set_option( BAUD );
port.set_option( CSIZE );
port.set_option( FLOW );
port.set_option( PARITY );
port.set_option( STOP );
while( !kbhit() )
{
io.reset();
port.async_read_some(
boost::asio::buffer(data, BUF_SIZE),
boost::bind(&my_handler, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred )
);
io.run();
Sleep(300);
}
}

Win32 C++ How to get current application Service Name

Assuming my code (DLL) already runs inside application that runs as service.
How to find the service name ?
I would like more elegant way than getting process path and enumerating services in registry.
I would suggest using GetCurrentProcessId to get the process ID of the service you're running in. Then use EnumServiceStatusEx to enumerate the services (without having to use the registry yourself).
You can match the process id from ENUM_SERVICE_STATUS_PROCESS.ServiceStatusProcess.dwProcessId to your current process ID and get the name. For example:
void enumerateServices( DWORD processId )
{
SC_HANDLE hSCM = OpenSCManager(NULL, NULL,
SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_CONNECT);
if (hSCM == NULL)
{
return;
}
DWORD bufferSize = 0;
DWORD requiredBufferSize = 0;
DWORD totalServicesCount = 0;
EnumServicesStatusEx( hSCM,
SC_ENUM_PROCESS_INFO,
SERVICE_WIN32,
SERVICE_STATE_ALL,
nullptr,
bufferSize,
&requiredBufferSize,
&totalServicesCount,
nullptr,
nullptr );
std::vector<BYTE> buffer( requiredBufferSize );
EnumServicesStatusEx( hSCM,
SC_ENUM_PROCESS_INFO,
SERVICE_WIN32,
SERVICE_STATE_ALL,
buffer.data(),
buffer.size(),
&requiredBufferSize,
&totalServicesCount,
nullptr,
nullptr );
LPENUM_SERVICE_STATUS_PROCESS services =
reinterpret_cast<LPENUM_SERVICE_STATUS_PROCESS>( buffer.data() );
for ( unsigned int i = 0; i < totalServicesCount; ++i )
{
ENUM_SERVICE_STATUS_PROCESS service = services[i];
if ( service.ServiceStatusProcess.dwProcessId == processId )
{
// This is your service.
std::wcout << service.lpServiceName << L"PID: " <<
service.ServiceStatusProcess.dwProcessId << std::endl;
}
}
( void )CloseServiceHandle( hSCM );
}
Usual disclaimers about error handling apply. I've also not properly checked buffer sizes, etc.

How to use SCardGetStatusChange correctly on Windows 8?

The smart card service behaves differently on Windows 8 and MSDN hasn't updated their documentation. Can anyone give a code snippet on how to call SCardGetStatusChange correctly to monitor smart card actions on Windows 8? Thanks in advance!
Here is a C++ template function that I wrote for a personal blog project. It uses a library I am developing that is up on github, but you can also just rework the logic into your own context.
template<typename SetContext, typename ClearContext, typename Wait, typename Report>
unique_winerror monitor_smartcard_readers(
SetContext&& setContext,
ClearContext&& clearContext,
Wait&& wait,
Report&& report
)
{
unique_winerror winerror;
std::vector<wchar_t> readernames;
std::vector<SCARD_READERSTATE> readers;
while (winerror)
{
//
// make sure that the scard service has started
// and that the loop has not been cancelled
//
if (!std::forward<Wait>(wait)())
{
return winerror_cast(SCARD_E_CANCELLED);
}
monitor_error_contract(
[&] ()
{
unique_close_scardcontext context;
ON_UNWIND_AUTO(
[&]
{
std::forward<ClearContext>(clearContext)();
}
);
//
// need a fresh context whenever we start over.
// lots of sytem changes could have caused this
// restart including the scard service stopping
// and then restarting.
//
winerror.reset(
SCardEstablishContext(
SCARD_SCOPE_USER,
NULL,
NULL,
context.replace()
)
);
if (!winerror || !context)
{
return;
}
std::forward<SetContext>(setContext)(context.get());
//
// make sure that loop has not been cancelled.
// without this there is a race where the new
// context is not cancelled because the caller
// cancelled at a time when there was no
// context yet.
//
if (!std::forward<Wait>(wait)())
{
winerror = winerror_cast(SCARD_E_CANCELLED);
return;
}
if (readers.empty())
{
//
// add PnP state query
// setting the state to unaware causes SCardGetStatusChange
// to return immediately with the actual pnp state.
//
readers.push_back(make(L"\\\\?PnP?\\Notification"));
}
for(;;)
{
auto readersstaterange = lib::rng::make_range_raw(readers);
winerror.reset(
SCardGetStatusChange(
context.get(),
INFINITE,
readersstaterange.begin(),
lib::rng::size_cast<DWORD>(readersstaterange.size())
)
);
if (!winerror)
{
// exit
return;
}
//
// report changes
//
auto readersrange = lib::rng::make_range_raw(readers, 0, -1);
if (!readersrange.empty())
{
std::forward<Report>(report)(readersrange);
}
//
// record the changes we have reported
//
for (auto& state : readers)
{
state.dwCurrentState = state.dwEventState;
}
if ((readers.back().dwEventState & SCARD_STATE_CHANGED) == SCARD_STATE_CHANGED)
{
// Pnp event - list readers.
break;
}
}
// keep the old allocations for use to build the new list.
std::vector<wchar_t> oldreadernames(std::move(readernames));
std::vector<SCARD_READERSTATE> oldreaders(std::move(readers));
// exclude the pnp reader
auto oldreaderssortedrange = lib::rng::make_range(oldreaders, 0, -1);
LPWSTR concatreaderstrings = nullptr;
ON_UNWIND_AUTO(
[&] { if (concatreaderstrings) {SCardFreeMemory(context.get(), concatreaderstrings);};}
);
DWORD totallength = SCARD_AUTOALLOCATE;
winerror.reset(
SCardListReaders(
context.get(),
nullptr,
reinterpret_cast<LPWSTR>(&concatreaderstrings),
&totallength
)
);
if (winerror == winerror_cast(SCARD_E_NO_READERS_AVAILABLE))
{
// no readers is not an error, loop around to wait
// for a reader to be connected
winerror.suppress().release();
return;
}
else if (!winerror)
{
return;
}
// keep the names around because the state array will have pointers into this
readernames.assign(concatreaderstrings, concatreaderstrings + totallength);
auto readerstateless = [](const SCARD_READERSTATE& lhs, const SCARD_READERSTATE& rhs) -> bool
{
return _wcsicmp(lhs.szReader, rhs.szReader) < 0;
};
//
// all the reader names are concatenated in this array with
// embedded nulls for each and two nulls to mark the end
//
auto cursorreadernames = lib::rng::make_range_raw(readernames);
while(!cursorreadernames.empty() && cursorreadernames.front() != L'\0')
{
// access the current name
auto namerange = lib::rng::make_range(
cursorreadernames,
0,
wcslen(cursorreadernames.begin()) - cursorreadernames.size()
);
// skip to the next name
cursorreadernames = lib::rng::make_range(namerange, namerange.size() + 1, 0);
auto oldreader = std::equal_range(
oldreaderssortedrange.begin(),
oldreaderssortedrange.end(),
make(namerange.begin()),
readerstateless
);
if (oldreader.first != oldreader.second)
{
// keep the old state for this reader
readers.push_back(*oldreader.first);
// must use the new string allocation,
// the old one will be gone soon
readers.back().szReader = namerange.begin();
}
else
{
readers.push_back(make(namerange.begin()));
}
}
// keeping them sorted makes the updates more stable and allows the
// equal_range above instead of a linear find.
std::sort(readers.begin(), readers.end(), readerstateless);
//
// add PnP state query
// keep the existing state, and keep it at the
// end, out of the sorted area.
//
readers.push_back(oldreaders.back());
}
);
}
return winerror;
}
usage looks like this:
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#define NOMINMAX
// Windows Header Files:
#include <windows.h>
#include <Unknwn.h>
#include <winscard.h>
#include <ncrypt.h>
#include <Wincrypt.h>
#include <credentialprovider.h>
// TODO: reference additional headers your program requires here
#include <type_traits>
#include <algorithm>
#include <new>
#include <memory>
#include <utility>
#include <limits>
#include <iterator>
#include <thread>
#include <future>
#include <mutex>
#include <vector>
#include <iostream>
#include <iomanip>
int wmain(int argc, WCHAR* argv[])
{
unique_winerror winerror;
for (;;)
{
SCARDCONTEXT context = NULL;
// if you monitor in a separate thread, then add a cancel or shutdown event
// into the waitfor array and handle it in the Wait lambda
HANDLE waitfor[] = {SCardAccessStartedEvent()};
ON_UNWIND_AUTO([] {SCardReleaseStartedEvent();});
winerror = smart_card::monitor_smartcard_readers(
[&](SCARDCONTEXT context)
{
context = context;
},
[&]()
{
context = NULL;
},
[&]() -> bool
{
if (WAIT_OBJECT_0 != WaitForMultipleObjects(lib::rng::size(waitfor), waitfor, FALSE, INFINITE))
{
// monitor_smardcard_readers will return SCARD_E_CANCELLED
return false;
}
return true;
},
[&](lib::rng::range<SCARD_READERSTATE*> readersrange)
{
for (auto& state : readersrange)
{
auto stateChanges = (state.dwCurrentState ^ state.dwEventState) & std::numeric_limits<unsigned short>::max();
std::wcout
<< L"nothread - "
<< state.szReader
<< L" changes: " << std::hex << std::showbase << stateChanges
<< L"["
;
printSCardState(std::wcout, stateChanges)
<< L"] state: " << std::hex << std::showbase << state.dwEventState
<< L"["
;
printSCardState(std::wcout, state.dwEventState)
<< L"]"
<< std::endl
;
if (state.dwCurrentState != SCARD_STATE_UNAWARE &&
((state.dwEventState & SCARD_STATE_PRESENT) != SCARD_STATE_PRESENT ||
stateChanges == SCARD_STATE_INUSE ||
stateChanges == SCARD_STATE_UNPOWERED ||
(state.dwEventState & (SCARD_STATE_UNPOWERED | SCARD_STATE_EMPTY | SCARD_STATE_IGNORE | SCARD_STATE_UNKNOWN | SCARD_STATE_UNAVAILABLE | SCARD_STATE_MUTE)) ||
state.cbAtr == 0))
{
// we have seen this reader before and one of:
// no card
// only flipped INUSE
// only flipped UNPOWERED
// UNPOWERED EMPTY UNKNOWN UNAVAILABLE MUTE
// no atr
//
// don't try to read the card
continue;
}
// read the card in the reader and list the certs on the card
}
}
);
winerror.suppress();
}
return 0;
}
I know I'm over 2 years late, but maybe my reply can help someone nonetheless.
I have some simple code as a starting base for further development. I first created it on Windows 7; AFAICS it works fine on Windows 8 too. This uses only a single reader, but a previous iteration used a list of readers and it worked just as well. The relevant parts are as follows.
Initialization of the reader state structure:
memset(&m_State, 0, sizeof(m_State));
m_State.szReader = _wcsdup(m_ReaderName.c_str());
m_State.dwCurrentState = SCARD_STATE_UNAWARE;
Waiting for events:
bool TSmartCardReader::WaitForEvent(DWORD Timeout, TCardEvent &CardEvent)
{
CardEvent = None;
// Reset reader structure, except the specific fields we need
// (because that's what the docs say: "Important: Each member of each structure
// in this array must be initialized to zero and then set to specific values as
// necessary. If this is not done, the function will fail in situations that
// involve remote card readers.")
const wchar_t *szReader = m_State.szReader;
DWORD dwCurrentState = m_State.dwCurrentState;
memset(&m_State, 0, sizeof(m_State));
m_State.szReader = szReader;
m_State.dwCurrentState = dwCurrentState;
LONG rv = SCardGetStatusChangeW(m_hContext, Timeout, &m_State, 1);
if (rv == SCARD_S_SUCCESS)
{
HandleStatusChange(CardEvent);
// I'm not sure we really need to reset the SCARD_STATE_CHANGED bit
m_State.dwCurrentState = m_State.dwEventState & ~SCARD_STATE_CHANGED;
}
else if (rv == SCARD_E_TIMEOUT)
return false; // No status changes
else if (rv == SCARD_E_NO_READERS_AVAILABLE)
throw ESCNoReaders("No readers available");
else
throw ESCWaitForEvent(GetErrorText(rv));
return CardEvent != None;
}
As far as I understand the documentation, the key thing is that you set set dwCurrentState to what you believe is the current state of the reader. SCardGetStatusChange() takes that current state into account to decide what constitutes a state change.

Resources