@@ -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-
331293void __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
468582void CloseBaton::Execute () {
469583 g_closingHandles.push_back (fd);
0 commit comments