Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I call Win32API SetCommTimeouts from C#?

I am having problems performing serial communications via the Win32 API from C#. No matter which values I use when calling SetCommTimeouts(), the call to ReadFile will not return unless one or more characters are received.

Using the .Net System.IO.Port.SerialPort class is not an option. It has serious bugs regarding USB-connected COM-ports which is the reason why I am trying to use the Win32 API directly instead.

Could the problem be with marshalling the CommTimeouts structure so that the API receives incorrect values?

Complete source code provided below:

namespace SerialTest
{
    using System;
    using System.Globalization;
    using System.Runtime.InteropServices;
    using Microsoft.Win32.SafeHandles;

    [Flags]
    internal enum AccessRights : uint
    {
        GenericRead = (0x80000000),
        GenericWrite = (0x40000000),
        GenericExecute = (0x20000000),
        GenericAll = (0x10000000)
    }

    [Flags]
    internal enum ShareModes : uint
    {
        FileShareRead = 0x00000001,
        FileShareWrite = 0x00000002,
        FileShareDelete = 0x00000004
    }

    internal enum CreationDispositions
    {
        CreateNew = 1,
        CreateAlways = 2,
        OpenExisting = 3,
        OpenAlways = 4,
        TruncateExisting = 5
    }

    internal class CommTimeouts
    {
        public UInt32 ReadIntervalTimeout;
        public UInt32 ReadTotalTimeoutMultiplier;
        public UInt32 ReadTotalTimeoutConstant;
        public UInt32 WriteTotalTimeoutMultiplier;
        public UInt32 WriteTotalTimeoutConstant;
    }

    internal class Kernel32
    {
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern SafeFileHandle CreateFile(
            string lpFileName,
            uint dwDesiredAccess,
            uint dwShareMode,
            IntPtr lpSecurityAttributes,
            uint dwCreationDisposition,
            uint dwFlagsAndAttributes,
            IntPtr hTemplateFile
        );

        [DllImport("kernel32.dll", EntryPoint = "SetCommTimeouts", SetLastError = true)]
        public static extern bool SetCommTimeouts(SafeHandle hFile, CommTimeouts timeouts);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool ReadFile(SafeHandle hFile, [Out] byte[] lpBuffer,
           uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
    }

    public class SerialTest
    {
        public void Test(string portName)
        {
            if (portName.Length > 5) portName = @"\\.\" + portName;
            var hPort = Kernel32.CreateFile(portName,
                                            (uint) (AccessRights.GenericRead | AccessRights.GenericWrite),
                                            0, // Not shared
                                            IntPtr.Zero, // Security attributes,
                                            (uint) CreationDispositions.OpenExisting,
                                            0,
                                            IntPtr.Zero // Template file
                );

            if (hPort.IsInvalid)
            {
                throw new Exception("Could not open port " + portName + ". Error: " + Marshal.GetLastWin32Error().ToString(CultureInfo.InvariantCulture));
            }

            try
            {
                // Set timeout so call returns immediately
                var timeouts = new CommTimeouts
                    {
                        ReadIntervalTimeout = 0xFFFFFFFF,
                        ReadTotalTimeoutMultiplier = 0,
                        ReadTotalTimeoutConstant = 0,
                        WriteTotalTimeoutMultiplier = 0,
                        WriteTotalTimeoutConstant = 0
                    };

                if (!Kernel32.SetCommTimeouts(hPort, timeouts))
                {
                    var error = Marshal.GetLastWin32Error();
                    throw new Exception("Could not set timeouts. Error: " + error.ToString(CultureInfo.InvariantCulture));                    
                }

                var buf = new byte[1];
                uint readBytes;
                if (!Kernel32.ReadFile(hPort,
                                       buf,
                                       1,
                                       out readBytes,
                                       IntPtr.Zero))
                {
                    var error = Marshal.GetLastWin32Error();
                    throw new Exception("Could not read. Error: " + error.ToString(CultureInfo.InvariantCulture));
                }
            }
            finally
            {
                hPort.Close();
            }
        }
    }
}
like image 668
Krister Renaud Avatar asked Mar 01 '26 00:03

Krister Renaud


1 Answers

The SetCommTimeouts definition which I found online was incorrect. Thanks to Richard, I now use the correct definition.

static extern bool SetCommTimeouts(IntPtr hFile, [In] ref COMMTIMEOUTS
lpCommTimeouts);
like image 188
Krister Renaud Avatar answered Mar 02 '26 13:03

Krister Renaud



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!