Skip to content

Commit d0f9881

Browse files
committed
Fix Windows Read and Write
Signed-off-by: Gareth Hancock <64541249+GazHank@users.noreply.github.com>
1 parent a4cb0a3 commit d0f9881

File tree

2 files changed

+190
-176
lines changed

2 files changed

+190
-176
lines changed

packages/bindings/src/serialport_win.cpp

Lines changed: 184 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -290,44 +290,6 @@ bool IsClosingHandle(int fd) {
290290
return false;
291291
}
292292

293-
Napi::Value Write(const Napi::CallbackInfo& info) {
294-
Napi::Env env = info.Env();
295-
// file descriptor
296-
if (!info[0].IsNumber()) {
297-
Napi::TypeError::New(env, "First argument must be an int").ThrowAsJavaScriptException();
298-
return env.Null();
299-
}
300-
int fd = info[0].As<Napi::Number>().Int32Value();
301-
302-
// buffer
303-
if (!info[1].IsObject() || !info[1].IsBuffer()) {
304-
Napi::TypeError::New(env, "Second argument must be a buffer").ThrowAsJavaScriptException();
305-
return env.Null();
306-
}
307-
Napi::Buffer<char> buffer = info[1].As<Napi::Buffer<char>>();
308-
//getBufferFromObject(info[1].ToObject().ti);
309-
char* bufferData = buffer.Data(); //.As<Napi::Buffer<char>>().Data();
310-
size_t bufferLength = buffer.Length();//.As<Napi::Buffer<char>>().Length();
311-
312-
// callback
313-
if (!info[2].IsFunction()) {
314-
Napi::TypeError::New(env, "Third argument must be a function").ThrowAsJavaScriptException();
315-
return env.Null();
316-
}
317-
318-
Napi::Function callback = info[2].As<Napi::Function>();
319-
WriteBaton* baton = new WriteBaton(callback);
320-
baton->fd = fd;
321-
baton->buffer.Reset(buffer);
322-
baton->bufferData = bufferData;
323-
baton->bufferLength = bufferLength;
324-
baton->offset = 0;
325-
baton->complete = false;
326-
327-
baton->Queue();
328-
return env.Null();
329-
}
330-
331293
void __stdcall WriteIOCompletion(DWORD errorCode, DWORD bytesTransferred, OVERLAPPED* ov) {
332294
WriteBaton* baton = static_cast<WriteBaton*>(ov->hEvent);
333295
DWORD bytesWritten;
@@ -345,11 +307,59 @@ void __stdcall WriteIOCompletion(DWORD errorCode, DWORD bytesTransferred, OVERLA
345307
}
346308
}
347309

348-
Napi::Value Read(const Napi::CallbackInfo& info) {
349-
Napi::Env env = info.Env();
310+
DWORD __stdcall WriteThread(LPVOID param) {
311+
uv_async_t* async = static_cast<uv_async_t*>(param);
312+
WriteBaton* baton = static_cast<WriteBaton*>(async->data);
313+
314+
OVERLAPPED* ov = new OVERLAPPED;
315+
memset(ov, 0, sizeof(OVERLAPPED));
316+
ov->hEvent = static_cast<void*>(baton);
317+
318+
while (!baton->complete) {
319+
char* offsetPtr = baton->bufferData + baton->offset;
320+
// WriteFileEx requires calling GetLastError even upon success. Clear the error beforehand.
321+
SetLastError(0);
322+
WriteFileEx(int2handle(baton->fd), offsetPtr,
323+
static_cast<DWORD>(baton->bufferLength - baton->offset), ov, WriteIOCompletion);
324+
// Error codes when call is successful, such as ERROR_MORE_DATA.
325+
DWORD lastError = GetLastError();
326+
if (lastError != ERROR_SUCCESS) {
327+
ErrorCodeToString("Writing to COM port (WriteFileEx)", lastError, baton->errorString);
328+
break;
329+
}
330+
// IOCompletion routine is only called once this thread is in an alertable wait state.
331+
SleepEx(INFINITE, TRUE);
332+
}
333+
delete ov;
334+
// Signal the main thread to run the callback.
335+
uv_async_send(async);
336+
ExitThread(0);
337+
}
338+
339+
void EIO_AfterWrite(uv_async_t* req) {
340+
WriteBaton* baton = static_cast<WriteBaton*>(req->data);
341+
Napi::Env env = baton->callback.Env();
342+
Napi::HandleScope scope(env);
343+
WaitForSingleObject(baton->hThread, INFINITE);
344+
CloseHandle(baton->hThread);
345+
uv_close(reinterpret_cast<uv_handle_t*>(req), AsyncCloseCallback);
346+
347+
v8::Local<v8::Value> argv[1];
348+
if (baton->errorString[0]) {
349+
baton->callback.Call({Napi::Error::New(env, baton->errorString).Value()});
350+
} else {
351+
baton->callback.Call({env.Null()});
352+
}
353+
baton->buffer.Reset();
354+
delete baton;
355+
}
356+
357+
358+
Napi::Value Write(const Napi::CallbackInfo& info) {
359+
Napi::Env env = info.Env();
350360
// file descriptor
351361
if (!info[0].IsNumber()) {
352-
Napi::TypeError::New(env, "First argument must be a fd").ThrowAsJavaScriptException();
362+
Napi::TypeError::New(env, "First argument must be an int").ThrowAsJavaScriptException();
353363
return env.Null();
354364
}
355365
int fd = info[0].As<Napi::Number>().Int32Value();
@@ -359,43 +369,32 @@ Napi::Value Read(const Napi::CallbackInfo& info) {
359369
Napi::TypeError::New(env, "Second argument must be a buffer").ThrowAsJavaScriptException();
360370
return env.Null();
361371
}
362-
Napi::Object buffer = info[1].ToObject();
363-
size_t bufferLength = buffer.As<Napi::Buffer<char>>().Length();
364-
365-
// offset
366-
if (!info[2].IsNumber()) {
367-
Napi::TypeError::New(env, "Third argument must be an int").ThrowAsJavaScriptException();
368-
return env.Null();
369-
}
370-
int offset = info[2].ToNumber().Int64Value();
371-
372-
// bytes to read
373-
if (!info[3].IsNumber()) {
374-
Napi::TypeError::New(env, "Fourth argument must be an int").ThrowAsJavaScriptException();
375-
return env.Null();
376-
}
377-
size_t bytesToRead = info[3].ToNumber().Int64Value();
378-
379-
if ((bytesToRead + offset) > bufferLength) {
380-
Napi::TypeError::New(env, "'bytesToRead' + 'offset' cannot be larger than the buffer's length").ThrowAsJavaScriptException();
381-
return env.Null();
382-
}
372+
Napi::Buffer<char> buffer = info[1].As<Napi::Buffer<char>>();
373+
//getBufferFromObject(info[1].ToObject().ti);
374+
char* bufferData = buffer.Data(); //.As<Napi::Buffer<char>>().Data();
375+
size_t bufferLength = buffer.Length();//.As<Napi::Buffer<char>>().Length();
383376

384377
// callback
385-
if (!info[4].IsFunction()) {
386-
Napi::TypeError::New(env, "Fifth argument must be a function").ThrowAsJavaScriptException();
378+
if (!info[2].IsFunction()) {
379+
Napi::TypeError::New(env, "Third argument must be a function").ThrowAsJavaScriptException();
387380
return env.Null();
388381
}
389-
Napi::Function callback = info[4].As<Napi::Function>();
390-
ReadBaton* baton = new ReadBaton(callback);
382+
383+
WriteBaton* baton = new WriteBaton();
384+
baton->callback = Napi::Persistent(info[2].As<Napi::Function>());
391385
baton->fd = fd;
392-
baton->offset = offset;
393-
baton->bytesToRead = bytesToRead;
386+
baton->buffer.Reset(buffer);
387+
baton->bufferData = bufferData;
394388
baton->bufferLength = bufferLength;
395-
baton->bufferData = buffer.As<Napi::Buffer<char>>().Data();
389+
baton->offset = 0;
396390
baton->complete = false;
397391

398-
baton->Queue();
392+
uv_async_t* async = new uv_async_t;
393+
uv_async_init(uv_default_loop(), async, EIO_AfterWrite);
394+
async->data = baton;
395+
// WriteFileEx requires a thread that can block. Create a new thread to
396+
// run the write operation, saving the handle so it can be deallocated later.
397+
baton->hThread = CreateThread(NULL, 0, WriteThread, async, 0, NULL);
399398
return env.Null();
400399
}
401400

@@ -464,6 +463,121 @@ void __stdcall ReadIOCompletion(DWORD errorCode, DWORD bytesTransferred, OVERLAP
464463
baton->complete = true;
465464
}
466465

466+
DWORD __stdcall ReadThread(LPVOID param) {
467+
uv_async_t* async = static_cast<uv_async_t*>(param);
468+
ReadBaton* baton = static_cast<ReadBaton*>(async->data);
469+
DWORD lastError;
470+
471+
OVERLAPPED* ov = new OVERLAPPED;
472+
memset(ov, 0, sizeof(OVERLAPPED));
473+
ov->hEvent = static_cast<void*>(baton);
474+
475+
while (!baton->complete) {
476+
// Reset the read timeout to 0, so that it will block until more data arrives.
477+
COMMTIMEOUTS commTimeouts = {};
478+
commTimeouts.ReadIntervalTimeout = 0;
479+
if (!SetCommTimeouts(int2handle(baton->fd), &commTimeouts)) {
480+
lastError = GetLastError();
481+
ErrorCodeToString("Setting COM timeout (SetCommTimeouts)", lastError, baton->errorString);
482+
break;
483+
}
484+
// ReadFileEx doesn't use overlapped's hEvent, so it is reserved for user data.
485+
ov->hEvent = static_cast<HANDLE>(baton);
486+
char* offsetPtr = baton->bufferData + baton->offset;
487+
// ReadFileEx requires calling GetLastError even upon success. Clear the error beforehand.
488+
SetLastError(0);
489+
// Only read 1 byte, so that the callback will be triggered once any data arrives.
490+
ReadFileEx(int2handle(baton->fd), offsetPtr, 1, ov, ReadIOCompletion);
491+
// Error codes when call is successful, such as ERROR_MORE_DATA.
492+
lastError = GetLastError();
493+
if (lastError != ERROR_SUCCESS) {
494+
ErrorCodeToString("Reading from COM port (ReadFileEx)", lastError, baton->errorString);
495+
break;
496+
}
497+
// IOCompletion routine is only called once this thread is in an alertable wait state.
498+
SleepEx(INFINITE, TRUE);
499+
}
500+
delete ov;
501+
// Signal the main thread to run the callback.
502+
uv_async_send(async);
503+
ExitThread(0);
504+
}
505+
506+
void EIO_AfterRead(uv_async_t* req) {
507+
ReadBaton* baton = static_cast<ReadBaton*>(req->data);
508+
Napi::Env env = baton->callback.Env();
509+
Napi::HandleScope scope(env);
510+
WaitForSingleObject(baton->hThread, INFINITE);
511+
CloseHandle(baton->hThread);
512+
uv_close(reinterpret_cast<uv_handle_t*>(req), AsyncCloseCallback);
513+
514+
if (baton->errorString[0]) {
515+
baton->callback.Call({Napi::Error::New(env, baton->errorString).Value(), env.Undefined()});
516+
} else {
517+
baton->callback.Call({env.Null(), Napi::Number::New(env, static_cast<int>(baton->bytesRead))});
518+
}
519+
delete baton;
520+
}
521+
522+
Napi::Value Read(const Napi::CallbackInfo& info) {
523+
Napi::Env env = info.Env();
524+
// file descriptor
525+
if (!info[0].IsNumber()) {
526+
Napi::TypeError::New(env, "First argument must be a fd").ThrowAsJavaScriptException();
527+
return env.Null();
528+
}
529+
int fd = info[0].As<Napi::Number>().Int32Value();
530+
531+
// buffer
532+
if (!info[1].IsObject() || !info[1].IsBuffer()) {
533+
Napi::TypeError::New(env, "Second argument must be a buffer").ThrowAsJavaScriptException();
534+
return env.Null();
535+
}
536+
Napi::Object buffer = info[1].ToObject();
537+
size_t bufferLength = buffer.As<Napi::Buffer<char>>().Length();
538+
539+
// offset
540+
if (!info[2].IsNumber()) {
541+
Napi::TypeError::New(env, "Third argument must be an int").ThrowAsJavaScriptException();
542+
return env.Null();
543+
}
544+
int offset = info[2].ToNumber().Int64Value();
545+
546+
// bytes to read
547+
if (!info[3].IsNumber()) {
548+
Napi::TypeError::New(env, "Fourth argument must be an int").ThrowAsJavaScriptException();
549+
return env.Null();
550+
}
551+
size_t bytesToRead = info[3].ToNumber().Int64Value();
552+
553+
if ((bytesToRead + offset) > bufferLength) {
554+
Napi::TypeError::New(env, "'bytesToRead' + 'offset' cannot be larger than the buffer's length").ThrowAsJavaScriptException();
555+
return env.Null();
556+
}
557+
558+
// callback
559+
if (!info[4].IsFunction()) {
560+
Napi::TypeError::New(env, "Fifth argument must be a function").ThrowAsJavaScriptException();
561+
return env.Null();
562+
}
563+
ReadBaton* baton = new ReadBaton();
564+
baton->callback = Napi::Persistent(info[4].As<Napi::Function>());
565+
baton->fd = fd;
566+
baton->offset = offset;
567+
baton->bytesToRead = bytesToRead;
568+
baton->bufferLength = bufferLength;
569+
570+
baton->bufferData = buffer.As<Napi::Buffer<char>>().Data();
571+
baton->complete = false;
572+
573+
uv_async_t* async = new uv_async_t;
574+
uv_async_init(uv_default_loop(), async, EIO_AfterRead);
575+
async->data = baton;
576+
baton->hThread = CreateThread(NULL, 0, ReadThread, async, 0, NULL);
577+
// ReadFileEx requires a thread that can block. Create a new thread to
578+
// run the read operation, saving the handle so it can be deallocated later.
579+
return env.Null();
580+
}
467581

468582
void CloseBaton::Execute() {
469583
g_closingHandles.push_back(fd);

0 commit comments

Comments
 (0)