Что такое порт

1.Что такое порт

Известно что в компьютере очень много собрано различных устройств , возникает вопрос как операционная система общается с ними. Для этого и служит порт, то есть эта «дверь» через которую программа (операционная система) может управлять данным устройством (считывать данные, заносить их).Причем я разделяю порты на две категории (это чисто мое разделение) — порты общеизвестные (COM LPT) и порты внутренние ,служащие для связи с внутренними устройствами ЭВМ.

2.Некоторые правила для работы с портами

Следует иметь в виду что при разработке программ имеющих дело работы с портами следует учитывать следующие факторы :

Стараться использовать функции высокого уровня для доступа к портам (в частности WinAPI) и не прибегать к низкоуровневым операциям чтения/записи портов. Если вы все-таки решили писать низкоуровневое чтение то эти процедуры нужно выносить в отдельную DLL или VXD, по следующим причинам — известно, что операционная система Windows95/98 а особенно NT являются по своей сути многозадачными системами. То есть если ваша программа обращается конкретно к порту не через динамический вызов функции DLL или VXD ( использования механизма DLL) а напрямую то это может сказаться на корректной работе системы или даже завалить ее. И даже если в Windows95/98 такой подход вполне может работать то в Windows NT вследствие его архитектуры не разрешит непосредственное чтение/запись напрямую, а использование механизма DLL или VXD позволяет обойти эту проблему.

Если вы работаете с каким-то нестандартным портом ввода-вывода (например портом хранящим состояние кнопок пульта ДУ TVTunera то наверняка в комплекте поставки родного софта найдется DLL или VXD для управления этим устройством и отпадет нужда писать код, так я при работе с пультом ДУ TVTunerа использую стандартную DLL поставляемую в комплекте, это сразу решило вопросы связанные с управлением портами данного тюнера)

Итак, отступление — немного практики…

Маленький пример для работы с портами (первый пример был уже опубликован в королевстве Дельфи и представлял собой пример работы с весами ПетрВес)

function PortInit: boolean; //инициализация

var

f: THandle;

ct: TCommTimeouts;

dcb: TDCB;

begin

f := Windows.CreateFile(PChar('COM1'), GENERIC_READ or

GENERIC_WRITE,

FILE_SHARE_READ or FILE_SHARE_WRITE,

nil, OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL, 0);

if (f < 0) or not Windows.SetupComm(f, 2048, 2048) or not

Windows.GetCommState(f, dcb) then

exit;

//init error

dcb.BaudRate := скоpость;

dcb.StopBits := стоп - биты;

dcb.Parity := четность;

dcb.ByteSize := 8;

if not Windows.SetCommState(f, dcb)

or not Windows.GetCommTimeouts(f, ct) then

exit;

//error

ct.ReadTotalTimeoutConstant := 50;

ct.ReadIntervalTimeout := 50;

ct.ReadTotalTimeoutMultiplier := 1;

ct.WriteTotalTimeoutMultiplier := 0;

ct.WriteTotalTimeoutConstant := 10;

if not Windows.SetCommTimeouts(f, ct)

or not Windows.SetCommMask(f, EV_RING EV_RXCHAR EV_RXFLAG

EV_TXEMPTY) then

exit; //error

result := true;

end;

function DoneComm: boolean; //закpыть поpт

begin

result := Windows.CloseHandle(f);

end;

function PostComm(var Buf; size: word): integer;

//пеpедача в поpт

var

p: pointer;

i: integer;

begin

p := @Buf;

result := 0;

while size > 0 do

begin

if not WriteFile(f, p^, 1, i, nil) then

exit;

inc(result, i);

inc(integer(p));

dec(size);

Application.ProcessMessages;

end;

end;

function ReadComm(var Buf; size: word): integer;

//пpием из поpта

var

i: integer;

ovr: TOverlapped;

begin

fillChar(buf, size, 0);

fillChar(ovr, sizeOf(ovr), 0);

i := 0;

result := -1;

if not windows.ReadFile(f, buf, size, i, @ovr) then

exit;

result := i;

end;

Данный пример был взят мной из многочисленный FAQ посвященных в DELPHI в сети ФИДО Итак,для работы с портами COM и LPT нам понадобится знание функций Windows API.

Вот подробное описание функций, которые нам нужны (в эквиваленте C) для работы с портами. (извините за возможный местами неточный перевод ,если что поправьте меня если что не так перевел)

CreateFile

HANDLE CreateFile(

LPCTSTR lpFileName,

// указатель на строку PCHAR с именем файла

DWORD dwDesiredAccess,

// режим доступа

DWORD dwShareMode, // share mode

LPSECURITY_ATTRIBUTES lpSecurityAttributes,

// указатель на атрибуты

DWORD dwCreationDistribution,

// how to create

DWORD dwFlagsAndAttributes,

// атрибуты файла

HANDLE hTemplateFile

// хендл на temp файл

);

Пример кода на Дельфи

CommPort := 'COM2';

hCommFile := CreateFile(Pchar(CommPort),

GENERIC_WRITE, 0, nil,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

Параметры

LpFileName

Указатель на строку с нулевым символом в конце (PCHAR) , которая определяет название создаваемого объекта (файл, канал, почтовый слот, ресурс связи (в данном случае порты), дисковое устройство, приставка, или каталог)

DwDesiredAccess

Указывает тип доступа к объекту ,принимает значение

GENERIC_READ — для чтения

GENERIC_WRITE — для записи (смешивание с GENERIC_READ операцией GENERIC_READ and GENERIC_WRITE предостовляет полный доступ )

dwShareMode

Набор разрядных флагов, которые определяют как объект может быть разделен по доступу к нему. Если dwShareMode — 0, объект не может быть разделен. Последовательные операции открытия объекта будут терпеть неудачу, пока маркер(дескриптор) открытого объекта не будет закрыт. Фактически предоставляется монопольный доступ.

Чтобы разделять объект(цель), используйте комбинацию одних или большее количество следующих значений:

FILE_SHARE_DELETE (Только для Windows NT)

FILE_SHARE_READ

FILE_SHARE_WRITE

LpSecurityAttributes

Указатель на структуру SECURITY_ATTRIBUTES, которая определяет может ли возвращенный дескриптор быть унаследован дочерними процессами. Если lpSecurityAttributes НУЛЕВОЙ, маркер не может быть унаследован. Используется только в windows NT.

dwCreationDistribution

Определяет поведение функции если объект уже существует и как он будет открыт в этом случае Принимает одно из следующих значений :

CREATE_NEW

Создает новый объект (файл) Выдает ошибку если указанный объект (файл) уже существует.

CREATE_ALWAYS

Создает новый объект (файл) Функция перезаписывает существующий объект (файл)

OPEN_EXISTING

Открывает объект (файл) Выдает ошибку если указанный объект (файл) не существует.(Для более детального смотрите SDK)

OPEN_ALWAYS

Открывает объект (файл), если он существует. Если объект (файл) не существует, функция создает его, как будто dwCreationDistribution были CREATE_NEW.

TRUNCATE_EXISTING

Открывает объект (файл). После этого объект (файл) будет усечен до нулевого размера.Выдает ошибку если указанный объект (файл) не существует.

DwFlagsAndAttributes

Атрибуты объекта (файла) , атрибуты могут комбинироваться

FILE_ATTRIBUTE_ARCHIVE

FILE_ATTRIBUTE_COMPRESSED

FILE_ATTRIBUTE_HIDDEN

FILE_ATTRIBUTE_NORMAL

FILE_ATTRIBUTE_OFFLINE

FILE_ATTRIBUTE_READONLY

FILE_ATTRIBUTE_SYSTEM

FILE_ATTRIBUTE_TEMPORARY

HTemplateFile

Определяет дескриптор с GENERIC_READ доступом к временному объекту(файлу). Временный объект(файл)поставляет атрибуты файла и расширенные атрибуты для создаваемого объекта (файла) ИСПОЛЬЗУЕТСЯ ТОЛЬКО В WINDOWS NT Windows 95: Это значение должно быть установлено в Nil.

Возвращаемые значения

Если функция преуспевает, возвращаемое значение — открытый дескриптор к указанному объекту(файлу). Если файл не существует — 0.

Если произошли функциональные сбои, возвращаемое значение — INVALID_HANDLE_VALUE. Чтобы получить расширенные данные об ошибках, вызовите GetLastError.

Обратите внимание !

Для портов, dwCreationDistribution параметр должен быть OPEN_EXISTING, и hTemplate должен быть Nil. Доступ для чтения-записи должен быть определен явно.

SECURITY_ATTRIBUTES

Структура содержит описание защиты для объекта и определяет,может ли дескриптор быть унаследован дочерними процессами.

typedef struct _SECURITY_ATTRIBUTES

{ DWORD nLength;

LPVOID lpSecurityDescriptor;

BOOL bInheritHandle;

} SECURITY_ATTRIBUTES;

Параметры

NLength

Определяет размер, в байтах, этой структуры. Набор это значение к размеру структуры SECURITY_ATTRIBUTES В Windows NT функции которые используют структуру SECURITY_ATTRIBUTES, не проверяют значение nLength.

LpSecurityDescriptor

Дескриптор указывающий на описатель защиты для объекта, Если дескриптор ПУСТОЙ объект может быть назначен в наследование дочерними процессами.

BInheritHandle

Определяет, унаследован ли возвращенный дескриптор, когда новый дескриптор, создан. Если это значение принимает ИСТИНУ новый дескриптор наследует от головного.

Замечания

Указатель на структуру SECURITY_ATTRIBUTES используется как параметр в большинстве функций работы с окнами в Win32 API.

Читать и писать в последовательный порт

function OpenCOMPort: Boolean;

var

DeviceName: array[0..80] of Char;

ComFile: THandle;

begin

{ First step is to open the communications device for read/write.

This is achieved using the Win32 'CreateFile' function.

If it fails, the function returns false.

Wir versuchen, COM1 zu offnen.

Sollte dies fehlschlagen, gibt die Funktion false zuruck.

}


StrPCopy(DeviceName, 'COM1:');

ComFile := CreateFile(DeviceName,

GENERIC_READ or GENERIC_WRITE,

0,

nil,

OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL,

0);

if ComFile = INVALID_HANDLE_VALUE then

Result := False

else

Result := True;

end;

function SetupCOMPort: Boolean;

const

RxBufferSize = 256;

TxBufferSize = 256;

var

DCB: TDCB;

Config: string;

CommTimeouts: TCommTimeouts;

begin

{ We assume that the setup to configure the setup works fine.

Otherwise the function returns false.

wir gehen davon aus das das Einstellen des COM Ports funktioniert.

sollte dies fehlschlagen wird der Ruckgabewert auf "FALSE" gesetzt.

}


Result := True;

if not SetupComm(ComFile, RxBufferSize, TxBufferSize) then

Result := False;

if not GetCommState(ComFile, DCB) then

Result := False;

// define the baudrate, parity,...

// hier die Baudrate, Paritat usw. konfigurieren

Config := 'baud=9600 parity=n data=8 stop=1';

if not BuildCommDCB(@Config[1], DCB) then

Result := False;

if not SetCommState(ComFile, DCB) then

Result := False;

with CommTimeouts do

begin

ReadIntervalTimeout := 0;

ReadTotalTimeoutMultiplier := 0;

ReadTotalTimeoutConstant := 1000;

WriteTotalTimeoutMultiplier := 0;

WriteTotalTimeoutConstant := 1000;

end;

if not SetCommTimeouts(ComFile, CommTimeouts) then

Result := False;

end;

{

The following is an example of using the 'WriteFile' function

to write data to the serial port.

Folgendes Beispiel verwendet die 'WriteFile' Funktion, um Daten

auf den seriellen Port zu schreiben.

}


procedure SendText(s: string);

var

BytesWritten: DWORD;

begin

{

Add a word-wrap (#13 #10) to the string

An den ubergebenen String einen Zeilenumbruch (#13 #10) hangen

}


s := s #13 #10;

WriteFile(ComFile, s[1], Length(s), BytesWritten, nil);

end;

{

The following is an example of using the 'ReadFile' function to read

data from the serial port.

Folgendes Beispiel verwendet die 'ReadFile' Funktion, um Daten

vom seriellen Port zu lesen.

}


procedure ReadText: string;

var

d: array[1..80] of Char;

s: string;

BytesRead, i: Integer;

begin

Result := '';

if not ReadFile(ComFile, d, SizeOf(d), BytesRead, nil) then

begin

{ Raise an exception }

end;

s := '';

for i := 1 to BytesRead do s := s d[I];

Result := s;

end;

procedure CloseCOMPort;

begin

// finally close the COM Port!

// nicht vergessen den COM Port wieder zu schliessen!

CloseHandle(ComFile);

end;

Удаление файла в корзину

Функция удаляет файл, имя которого указаывается в аргументе FileName

в корзину. Второй необязательный параметр указывает на Handle окна,

которое будет родительским для отображаемых системой диалоговых окон

Зависимости: Windows, ShellAPI, Forms

function Recycle(const FileName: string; Wnd: HWND = 0): Boolean;

var

FileOp: TSHFileOpStruct;

begin

FillChar(FileOp, SizeOf(FileOp), 0);

if Wnd = 0 then

Wnd := Application.Handle;

FileOp.Wnd := Wnd;

FileOp.wFunc := FO_DELETE;

FileOp.pFrom := PChar(FileName);

FileOp.fFlags := FOF_ALLOWUNDO or FOF_NOERRORUI or FOF_SILENT;

Result := (SHFileOperation(FileOp) = 0) and (not

FileOp.fAnyOperationsAborted);

end;

Пример использования:

procedure TForm1.Button1Click(Sender: TObject)

begin

Recycle('d:\folder\filename.ext', Handle);

end;

[cc lang="delphi"] 

Получить количество файлов в корзине и их общий размер

type

PSHQueryRBInfo = ^TSHQueryRBInfo;

TSHQueryRBInfo = packed record

cbSize: DWORD;

// Size of the structure, in bytes.

// This member must be filled in prior to calling the function.

i64Size: Int64;

// Total size of all the objects in the specified Recycle Bin, in bytes.

i64NumItems: Int64;

// Total number of items in the specified Recycle Bin.

end;

const

shell32 = 'shell32.dll';

function SHQueryRecycleBin(szRootPath: PChar; SHQueryRBInfo: PSHQueryRBInfo): HResult;

stdcall; external shell32 Name 'SHQueryRecycleBinA';

function GetDllVersion(FileName: string): Integer;

var

InfoSize, Wnd: DWORD;

VerBuf: Pointer;

FI: PVSFixedFileInfo;

VerSize: DWORD;

begin

Result := 0;

InfoSize := GetFileVersionInfoSize(PChar(FileName), Wnd);

if InfoSize 0 then

begin

GetMem(VerBuf, InfoSize);

try

if GetFileVersionInfo(PChar(FileName), Wnd, InfoSize, VerBuf) then

if VerQueryValue(VerBuf, '\', Pointer(FI), VerSize) then

Result := FI.dwFileVersionMS;

finally

FreeMem(VerBuf);

end;

end;

end;

procedure TForm1.Button1Click(Sender: TObject);

var

DllVersion: integer;

SHQueryRBInfo: TSHQueryRBInfo;

r: HResult;

begin

DllVersion := GetDllVersion(PChar(shell32));

if DllVersion >= $00040048 then

begin

FillChar(SHQueryRBInfo, SizeOf(TSHQueryRBInfo), #0);

SHQueryRBInfo.cbSize := SizeOf(TSHQueryRBInfo);

R := SHQueryRecycleBin(nil, @SHQueryRBInfo);

if r = s_OK then

begin

label1.Caption := Format('Size:%d Items:%d',

[SHQueryRBInfo.i64Size, SHQueryRBInfo.i64NumItems]);

end

else

label1.Caption := Format('Err:%x', [r]);

end;

end;

{

The SHQueryRecycleBin API used in this method is

only available on systems with the latest shell32.dll installed with IE4 /

Active Desktop.

}


[cc lang="delphi"] 

Показать корзину Windows

uses

ActiveX, ShlObj, ShellApi;

procedure FreePidl(pidl: PItemIDList);

var

allocator: IMalloc;

begin

if Succeeded(shlobj.SHGetMalloc(allocator)) then

begin

allocator.Free(pidl);

{$IFDEF VER90}

allocator.Release;

{$ENDIF}

end;

end;

procedure TForm1.Button1Click(Sender: TObject);

var

exInfo: TShellExecuteInfo;

begin

// initialize all fields to 0

FillChar(exInfo, SizeOf(exInfo), 0);

with exInfo do

begin

cbSize := SizeOf(exInfo); // required!

fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_IDLIST;

Wnd := Handle;

nShow := SW_SHOWNORMAL;

lpVerb := 'open';

ShGetSpecialFolderLocation(Handle, CSIDL_BITBUCKET, PItemIDLIst(lpIDList));

end;

ShellExecuteEx(@exInfo);

FreePIDL(exinfo.lpIDList);

end;

Переслать файл в Корзину

Сначала нужно в директиве uses подключить модуль ShellAPI, чтобы мы смогли воспользоваться API-функцией SHFileOperation(). А затем уже можно использовать такой вот процедурой:

procedure SendToPomoyka(FileName: string);

var

SHF: TSHFileOpStruct;

begin

with SHF do

begin

Wnd := Application.Handle;

wFunc := FO_DELETE;

pFrom := PChar(FileName);

fFlags := FOF_SILENT or FOF_ALLOWUNDO;

end;

SHFileOperation(SHF);

end;

Очистить корзину

procedure EmptyRecycleBin;

const

SHERB_NOCONFIRMATION = $00000001;

SHERB_NOPROGRESSUI = $00000002;

SHERB_NOSOUND = $00000004;

type

TSHEmptyRecycleBin = function(Wnd: HWND;

pszRootPath: PChar;

dwFlags: DWORD): HRESULT; stdcall;

var

SHEmptyRecycleBin: TSHEmptyRecycleBin;

LibHandle: THandle;

begin { EmptyRecycleBin }

LibHandle := LoadLibrary(PChar('Shell32.dll'));

if LibHandle 0 then @SHEmptyRecycleBin :=

GetProcAddress(LibHandle, 'SHEmptyRecycleBinA')

else

begin

MessageDlg('Failed to load Shell32.dll.', mtError, [mbOK], 0);

Exit;

end;

if @SHEmptyRecycleBin nil then

SHEmptyRecycleBin(Application.Handle,

nil,

SHERB_NOCONFIRMATION or SHERB_NOPROGRESSUI or SHERB_NOSOUND);

FreeLibrary(LibHandle); @SHEmptyRecycleBin := nil;

end;

procedure TForm1.Button1Click(Sender: TObject);

begin

EmptyRecycleBin;

end;

{

Minimum operating systems: Windows XP/2000, Windows NT 4.0 with Internet Explorer 4.0,

Windows 98, Windows 95 with Internet Explorer 4.0

(shell32.dll version 4.71 or later)

}

Копирование директорий и удаление директорий в корзину

unit MainFrm;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls, Buttons;

type

TMainForm = class(TForm)

GroupBox1: TGroupBox;

spbtnGetFromDir: TSpeedButton;

spbtnGetToDir: TSpeedButton;

edtFromDir: TEdit;

edtToDir: TEdit;

Button1: TButton;

GroupBox2: TGroupBox;

edtRecycleDir: TEdit;

spbtnRecycleBin: TSpeedButton;

btnRecycleDir: TButton;

btnClose: TButton;

procedure spbtnGetFromDirClick(Sender: TObject);

procedure spbtnGetToDirClick(Sender: TObject);

procedure btnCopyDirectoryClick(Sender: TObject);

procedure spbtnRecycleBinClick(Sender: TObject);

procedure btnRecycleDirClick(Sender: TObject);

procedure btnCloseClick(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

var

MainForm: TMainForm;

implementation

uses ShellAPI, FileCtrl;

{$R *.DFM}

function GetDirectory: string;

begin

if not SelectDirectory(Result, [sdAllowCreate, sdPerformCreate, sdPrompt], 0)

then

Result := EmptyStr;

end;

procedure CopyDirectoryTree(AHandle: THandle; const AFromDirectory,

AToDirectory: string);

var

SHFileOpStruct: TSHFileOpStruct;

FromDir: PChar;

ToDir: PChar;

begin

GetMem(FromDir, Length(AFromDirectory) 2);

try

GetMem(ToDir, Length(AToDirectory) 2);

try

FillChar(FromDir^, Length(AFromDirectory) 2, 0);

FillChar(ToDir^, Length(AToDirectory) 2, 0);

StrCopy(FromDir, PChar(AFromDirectory));

StrCopy(ToDir, PChar(AToDirectory));

with SHFileOpStruct do

begin

Wnd := AHandle; // Assign the window handle

wFunc := FO_COPY; // Specify a file copy

pFrom := FromDir;

pTo := ToDir;

fFlags := FOF_NOCONFIRMATION or FOF_RENAMEONCOLLISION;

fAnyOperationsAborted := False;

hNameMappings := nil;

lpszProgressTitle := nil;

if SHFileOperation(SHFileOpStruct) 0 then

RaiseLastWin32Error;

end;

finally

FreeMem(ToDir, Length(AToDirectory) 2);

end;

finally

FreeMem(FromDir, Length(AFromDirectory) 2);

end;

end;

procedure ToRecycle(AHandle: THandle; const ADirName: string);

var

SHFileOpStruct: TSHFileOpStruct;

DirName: PChar;

BufferSize: Cardinal;

begin

BufferSize := Length(ADirName) 1 1;

GetMem(DirName, BufferSize);

try

FillChar(DirName^, BufferSize, 0);

StrCopy(DirName, PChar(ADirName));

with SHFileOpStruct do

begin

Wnd := AHandle;

wFunc := FO_DELETE;

pFrom := DirName;

pTo := nil;

fFlags := FOF_ALLOWUNDO;

fAnyOperationsAborted := False;

hNameMappings := nil;

lpszProgressTitle := nil;

end;

if SHFileOperation(SHFileOpStruct) 0 then

RaiseLastWin32Error;

finally

FreeMem(DirName, BufferSize);

end;

end;

procedure TMainForm.spbtnGetFromDirClick(Sender: TObject);

begin

edtFromDir.Text := GetDirectory;

end;

procedure TMainForm.spbtnGetToDirClick(Sender: TObject);

begin

edtToDir.Text := GetDirectory;

end;

procedure TMainForm.btnCopyDirectoryClick(Sender: TObject);

begin

CopyDirectoryTree(Handle, edtFromDir.Text, edtToDir.Text);

end;

procedure TMainForm.spbtnRecycleBinClick(Sender: TObject);

begin

edtRecycleDir.Text := GetDirectory;

end;

procedure TMainForm.btnRecycleDirClick(Sender: TObject);

begin

ToRecycle(0, edtRecycleDir.Text);

end;

procedure TMainForm.btnCloseClick(Sender: TObject);

begin

Close;

end;

end.

Как удалить файлы из корзины

program del;

uses

ShellApi;

//function SHFileOperation(const lpFileOp: TSHFileOpStruct): Integer; stdcall;

var

T: TSHFileOpStruct;

P: string;

begin

P := 'C:\Windows\System\EL_CONTROL.CPL';

with T do

begin

Wnd := 0;

wFunc := FO_DELETE;

pFrom := Pchar(P);

fFlags := FOF_ALLOWUNDO

end;

SHFileOperation(T);

end.

Восстановление

Есть некоторые причуды, и Вы должны помнить о следующем:

Дайте полный путь для каждого файла. Не доверяйте текущей директории, даже если Вы ее изменили непосредственно перед вызовом функции. Функция WinAPI SHFileOperation не достаточно «умная» для использования текущей директории при отсутствии информации о предыдущей директории (для осуществления функции восстановления). Так, даже если используете флаг FOF_ALLOWUNDO, это не восстановит удаленные файлы из корзины, поскольку функция ничего не знает о предыдущем месторасположении файлов, и, таким образом, не сможет их восстановить файлы из корзины в их оригинальное месторасположение. Она просто удалит файлы из текущей директории.

Microsoft скорректировала документацию о члене pFrom. Новая редакция сообщает о подробностях работы в пакетном режиме: необходимо разделить имя каждого файла символом NULL (#0) и добавить к концу списка двойной символ NULL. Терминатор из двух символов NULL необходим в любом случае: работаете вы с одним файлом, или же используете пакетный режим. Иногда это работает и без терминатора, но чаще нет. Это связано с тем, что функции при работе с памятью считывает данные из памяти, располагающейся до терминатора, а поскольку длина строки может не совпадать с распределенной памятью, то данные, находящиеся после терминатора, просто не обрабатываются.

Пример правильного кодирования:

var

FileList: string;

FOS: TShFileOpStruct;

begin

FileList := 'c:\delete.me'#0'c:\windows\temp.$$$'#0#0;

{ если Вы используете имена файлов в строковых переменных: }

FileList := Filename1 #0 Filename2 #0#0;

FOS.pFrom := PChar(FileList);

end;