Cómo escribir en el puerto COM1 o COM2

Los puertos COM en Windows se tratan de manera similar a otros dispositivos (archivos en disco, pipes, etc.).

Como cualquier otro dispositivo, un puerto necesita un manejador o ‘Handle’ para ser utilizado. Sería como un identificador del dispositivo. Este manejador te lo proporciona la función del API de Windows ‘CreateFile()’ que, aunque parezca lo contrario, no se limita a « crear » y no trabaja sólo con « files », sino que permite hacer otras operaciones y con otros dispositivos. A esta función se le debe especificar el nombre del dispositivo a abrir (el archivo de disco, el puerto serie o paralelo, etc.).

Una vez obtenido el manejador, podemos leer y escribir en el dispositivo con las funciones del API de Windows ‘ReadFile()’ y ‘WriteFile()’, respectivamente.
Finalmente, se cierra el puerto con la función del API ‘CloseHandle()’ (como se ve, demasiado ‘file’ para acabar con un handle’: ¿porqué no un ‘CloseFile()’? Preguntadle a Microsoft :).

En fin, ahí va un ejemplo de código:

uses

   Windows;    // Sobretodo

procedure TForm1.AbrirCom_Escribir_y_Leer;
const
   NombrePuerto = 'COM2';   // Por decir algo
var
   HandlePuerto: THandle;   // Manejador para el puerto
   Cadena: String;          // Para leer del puerto
   dwValor: DWORD;          // Tamaño de la lectura
   Sta: COMSTAT;            // Estado o tamaño del buffer de lectura
   bResult: Boolean;        // Parámetro de resultado
begin
   // ~~~~~~~~~~~~~~~~~~~
   // Apertura del puerto
   // ~~~~~~~~~~~~~~~~~~~
   //
   // El puerto ha de abrirse con acceso exclusivo. Es una de las razones de que haya
   // un sólo "Acceso telefónico a redes" que actúa de servidor del puerto. Generalmente
   // también se abre con acceso de lectura y escritura ya que es extraño tener que leer
   // o escribir sólo en él. Pero, claro, esto depende de lo que vayas a hacer con el
   // mismo. El "solapamiento" se produce sólo si hay varios procesos que pueden acceder
   // al puerto a la vez y se utiliza cuando la aplicación tiene, por ejemplo, un hilo
   // para leer y otro para escribir. Presupongo que no es el caso y que sólo hay un hilo.
   // El último parámetro ('hTemplate') no tiene efecto para los puertos de comunicaciones.
   //
   HandlePuerto := Windows.CreateFile(
                              PChar(NombrePuerto), { Nombre del puerto }
                              GENERIC_READ or
                              GENERIC_WRITE,       { Modo de apertura: Lectura/Escritura }
                              0,                   { Acceso exclusivo }
                              nil,                 { Sin atributos de seguridad }
                              OPEN_EXISTING,       { El puerto debe existir }
                              0,                   { Sin solapamiento de Lectura/Escritura }
                              0);                  { No hay "hTemplate" }

   // Comprobar que la apertura ha sido correcta
   if (INVALID_HANDLE_VALUE <> HandlePuerto) then
   begin

      // Vamos a ver si hay algo que leer...
      if (Windows.ClearCommError(HandlePuerto, dwValor, @Sta)) then
      begin
         if (0 < Sta.cbInQue) then    //... pues sí
         begin
            // ~~~~~~~~~~~~~~~
            // Leer del puerto
            // ~~~~~~~~~~~~~~~
            // Redimensionar la cadena de lectura
            SetLength(Cadena, (Sta.cbInQue + 1));
            bResult := Windows.ReadFile(
                              HandlePuerto,   { Handle del puerto }
                              PChar(Cadena)^, { Puntero a los datos a leer }
                              Sta.cbInQue,    { Tamaño del buffer de lectura }
                              dwValor,        { Número de caracteres leídos }
                              nil);           { No hay lecturas/escrituras solapadas }

            if (bResult) then  // Lectura correcta
            begin
               // Dimensionar la cadena al número de bytes leídos
               SetLength(Cadena, dwValor);
               // Aquí se puede hacer lo que se quiera con 'Cadena'
            end;
         end;
      end;

      // ~~~~~~~~~~~~~~~~~~~~~
      // Escribir en el puerto
      // ~~~~~~~~~~~~~~~~~~~~~
      // En general, los puertos de comunicaciones esperan un retorno de carro 
      // tras lo que se envíe. Tiene que ver con los búferes del puerto: cuando
      // se llena el buffer hasta un nivel o se recibe el retorno de carro se
      // envía el contenido del buffer al puerto. Así que, para asegurarse, se
      // termina la cadena a escribir por esos caracteres:

      Cadena := 'Probando la escritura' + #13#10;

      if (Windows.WriteFile(
                              HandlePuerto,   { Handle del puerto }
                              PChar(Cadena)^, { Datos a escribir }
                              Length(Cadena), { Longitud de los datos }
                              dwValor,        { Número de caracteres escritos }
                              nil)            { Sin solapamiento de lecturas/escrituras }
      )
      then
      begin
         // Aquí nos damos con un canto en los dientes:
         // la 'Cadena' se ha escrito en el puerto. Podemos comprobarlo si
         // miramos 'dwValor' ya que debe ser igual a 'Length(Cadena)'.
         // Si no lo es, algo ha ido mal.
      end;

      // ~~~~~~~~~~~~~~~~
      // Cerrar el puerto
      // ~~~~~~~~~~~~~~~~
      Windows.CloseHandle(HandlePuerto);

   end;

end;