0
votes

The CreateFile function is useful for opening files or devices for read/write access, providing a handle.

The third parameter, dwShareMode, specifies if the file/device can later be accessed by others. An example, with files:

void* pFileHandle1 = ::CreateFileA("C:\\test.txt", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
DWORD lastError = GetLastError(); // 0, ERROR_SUCCESS
void* pFileHandle2 = ::CreateFileA("C:\\test.txt", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
lastError = GetLastError(); // 0, ERROR_SUCCESS

All good here: we have 2 different handles that can read/write a single file.

But in my case, I want to use a COM port:

void* pComHandle1 = ::CreateFileA("\\\\.\\COM3", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
lastError = GetLastError(); // 0, ERROR_SUCCESS
void* pComHandle2 = ::CreateFileA("\\\\.\\COM3", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
lastError = GetLastError(); // 5, ERROR_ACCESS_DENIED Oops!

The first handle is valid and can be used, but the second one is INVALID_HANDLE_VALUE.

What's up with that? Can't you share COM ports that way?

1
A serial port such as "COM3" (or the native NT name such as "\Device\Serial2") is an exclusive Device object, i.e. when the device is defined and created by the driver function serial!SerialCreateDevObj, it calls IoCreateDevice with Exclusive as true. The reason for exclusive access is that each application expects exclusive state and settings (e.g. bps, parity). That said, opening the Device returns a handle for a File object, which can be duplicated via DuplicateHandle. - Eryk Sun
@eryksun so you need to pass the handle (or duplicates) around? That seems difficult when 2 separate process need their own access... - Alex Millette
A child process can inherit the handle, or you can duplicate the handle into another process. This requires communicating the handle value in some way, such as in the command line string, an inherited environment variable, or a named pipe. But why are multiple processes reading from and writing to the same port? You'd be better off abstracting this behind another interface, such as a named pipe or socket, with a single server process that manages the serial port. - Eryk Sun
here even task not in that \Device\SerialN is exclusive (have flag DO_EXCLUSIVE) device. usually this is FDO device, attached to some PDO. and we can (and really must) open it by PDO interface name, returned by CM_Get_Device_Interface_ListW(&GUID_DEVINTERFACE_COMPORT,..). this PDO not exclusive, but create handler for serial usually allow only single file object opened on device anyway. so you can have multiple handles, but only single file object for serial. - RbMm
"But why are multiple processes reading from and writing to the same port?" Hardware requirements. A single COM port could be used to communicate to 2 devices, with different pins. - Alex Millette

1 Answers

4
votes

Quoting the documentation for CreateFile:

The CreateFile function can create a handle to a communications resource, such as the serial port COM1. For communications resources, the dwCreationDisposition parameter must be OPEN_EXISTING, the dwShareMode parameter must be zero (exclusive access), and the hTemplateFile parameter must be NULL. Read, write, or read/write access can be specified, and the handle can be opened for overlapped I/O.

The implication from the documentation here is that communication objects cannot be shared like ordinary files. The Windows API leaves it to whoever opened the port to decide how/if they want to share access to that resource, and leaves them to manage the consequences of that decision.

To share the port, you can use DuplicateHandle and pass that to whoever you want to grant access to the port after you've opened it. For further reading, check out this ancient article from MSDN

That said, if you want to share a COM port across multiple processes, you're better off opening it in only one of them, and using some form of IPC to transfer data. Let one process handle servicing the port.