NFC ACR122U-A9 connecting error with delphi - delphi

I just bought a NFC ACR122U.
It comes with samples to delphi 7.
I am using delphi XE8 and compiling the sample to 32 bits/win 8.1.
I did the correct changes(I believe) to adapt the api and sample project functions to delphi Xe8, replacing Pchar to PAnsiChar and Char to AnsiChar where needed.
I am using native win 8 drivers, no manufacturer drive.
I can initialize the device and get the device name correctly with:
procedure TfrmDevProg.btnInitClick(Sender: TObject);
var index: integer;
begin
//Establish context
retCode := SCardEstablishContext(SCARD_SCOPE_USER,
nil,
nil,
#hContext);
if retCode <> SCARD_S_SUCCESS then begin
displayout(GetScardErrMsg(retcode),2);
Exit;
end ;
//List PC/SC readers installed in the system
BufferLen := MAX_BUFFER_LEN;
retCode := SCardListReadersA(hContext,
nil,
#Buffer,
#BufferLen);
if retCode <> SCARD_S_SUCCESS then begin
DisplayOut(getscarderrmsg(retCode),2);
Exit;
end;
btnInit.Enabled := false;
btnConnect.Enabled := true;
LoadListToControl(cbReader,#buffer,bufferLen);
// Look for ACR128 PICC and make it the default reader in the combobox
for index := 0 to cbReader.Items.Count-1 do begin
cbReader.ItemIndex := index;
if AnsiPos('ACR122U PICC', cbReader.Text) > 0 then
Exit;
end;
cbReader.ItemIndex := 0;
end;
The procedure above works very well. Next, I use the next code to connect to device:
procedure TfrmDevProg.btnConnectClick(Sender: TObject);
begin
//Connect to reader using a shared connection
retCode := SCardConnectA(hContext,
PAnsiChar(cbReader.Text),
SCARD_SHARE_SHARED,
SCARD_PROTOCOL_T0 or SCARD_PROTOCOL_T1,
#hCard,
#dwActProtocol);
if retcode <> SCARD_S_SUCCESS then begin
displayout(GetScardErrMsg(retcode),2)
end
else begin
displayout('Successful connection to ' + cbReader.Text, 1)
end;
end;
Here, I am getting an error from SCardConnectA: "The specified reader name is not recognized." and the retcode var is: -2146435063.
Here is a snippet code of the api copied from DVD sent with the device, when I bought it:
///////////////////////////////////////////////////////////////////////////////
// Imported functions from Winscard.dll (WIN32 API)
///////////////////////////////////////////////////////////////////////////////
Function SCardEstablishContext(dwscope :DWORD;
pvReserved1: LPCVOID;
pvReserved2: LPCVOID;
phContext :LPSCARDCONTEXT):LONG; stdcall; external 'Winscard.dll';
Function SCardReleaseContext(hContext:SCARDCONTEXT):LONG; stdcall; external 'Winscard.dll';
Function SCardListReadersA(hContext : SCARDCONTEXT;
mszGroups:LPCSTR;
szReaders:LPSTR;
pcchReaders:LPDWORD):LONG; stdcall; external 'Winscard.dll';
//Note : ScardConnectA is for non-UNICODE characters which is only one byte.
// For UNICODE characters it is SCardConnectW. Special processing is
// required for UNICODE. Be careful!
Function SCardConnectA(hContext : SCARDCONTEXT;
szReaders:LPSTR;
dwShareMode : DWORD;
dwPreferredProtocols : DWORD;
phCard : LPSCARDHANDLE;
pdwActiveProtocols:LPDWORD):LONG; stdcall; external 'Winscard.dll';
I downloaded an binary app from mannufacturer site to test the device and all works well. But I need do it work with my Delphi app.
Any help, please.

the problem is the cast from cbReader.Text do PAnsiChar.
fix it to
retCode := SCardConnectA(hContext, PAnsiChar(AnsiString(cbReader.Text)), SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 or SCARD_PROTOCOL_T1, #hCard, #dwActProtocol);

Related

IcmpSendEcho (ping) fails on Windows 10

We have a legacy Delphi application which uses IcmpSendEcho (from iphlpapi.dll) to perform echo requests. As I understand it, this performs the same function as "ping" from the command prompt.
On Windows XP, the code below works fine. When the IPv4 address is correct the response is quick and if not error code 11010 (IP_REQ_TIMED_OUT) is returned as expected.
However, on my 32-bit Windows 10 machine, the error code is 87 (ERROR_INVALID_PARAMETER). I've carefully reviewed the Microsoft documentation for IcmpSendEcho and cannot see anything obvious that is wrong.
"ping 200.1.2.121" (the example IPv4 address I use in the code sample) works as expected from the command prompt in both XP and 10.
type
PIpAddress = ^TIpAddress;
TIpAddress = record
case Integer of
0: (S_un_b: TSunB);
1: (S_un_w: TSunW);
2: (S_addr: LongWord);
end;
IpAddress = TIpAddress;
// Functions imported from external DLLs
function IcmpCreateFile() : THandle; stdcall; external 'iphlpapi.dll';
function IcmpCloseHandle(icmpHandle: THandle) : Boolean; stdcall; external 'iphlpapi.dll';
function IcmpSendEcho(IcmpHandle: THandle; ipDest: IpAddress;
pRequestData: Pointer; nRequestSize: SmallInt; RequestOptions: Pointer;
pReplyBuffer: Pointer; dwReplySize: DWORD; dwTimeout: DWORD) : DWORD; stdcall; external 'iphlpapi.dll';
procedure TranslateStringToIpAddress(strIP: String; var ipAddress);
var
phe: PHostEnt;
pac: PChar;
begin
try
phe := GetHostByName(PChar(strIP));
if (Assigned(phe)) then
begin
pac := phe^.h_addr_list^;
if (Assigned(pac)) then
begin
with TIpAddress(ipAddress).S_un_b do
begin
by1 := Byte(pac[0]);
by2 := Byte(pac[1]);
by3 := Byte(pac[2]);
by4 := Byte(pac[3]);
end;
end
else
begin
raise Exception.Create('Error getting IP from HostName');
end;
end
else
begin
raise Exception.Create('Error getting HostName');
end;
except
FillChar(ipAddress, SizeOf(ipAddress), #0);
end;
end;
function Ping(strIpAddress : String) : Boolean;
const
ICMP_ECHO_BUFFER = 128; // Works as low as 28 on Windows XP (nothing works on Windows 10)
var
address: IpAddress;
dwReplies: DWORD;
{$IFDEF DBG} dwErrorCode: DWORD; {$ENDIF}
abyReplyBuffer: array[1..ICMP_ECHO_BUFFER] of BYTE;
begin
// Use this function to determine if an IPv4 address can be reached
Result := False;
// "m_cache.hPingHandle" is generated earlier with a call to "IcmpCreateFile"
if (m_cache.hPingHandle = INVALID_HANDLE_VALUE) then
Exit;
TranslateStringToIpAddress(strIpAddress, address);
dwReplies := IcmpSendEcho(
m_cache.hPingHandle, address, nil, 0, nil, #abyReplyBuffer, ICMP_ECHO_BUFFER, 0);
{$IFDEF DBG}
if (dwReplies = 0) then
begin
dwErrorCode := GetLastError();
// dwErrorCode = 87 (ERROR_INVALID_PARAMETER, "The parameter is incorrect")
Application.MessageBox(
PAnsiChar(Format('WinError = %d', [dwErrorCode])), 'Ping failed', MB_ICONEXCLAMATION);
end;
{$ENDIF}
// Success?
Result := (dwReplies <> 0);
end;
// Usage elsewhere in the application...
Ping('200.1.2.121'); // Works on Windows XP, but fails on Windows 10
Based on the comment from #FredS (thanks!), the answer is simply to make the last parameter for the IcmpSendEcho non-zero (eg. "200").
The MSDN documentation for IcmpSendEcho does not make this clear, so Microsoft have probably changed the internal implementation of this method from the version in Windows XP so that a non-zero Timeout is now required.

Data transfer between different Delphi version apps via WM_COPYDATA

I'm trying to make to Delphi applications communicate with each other via WM_COPYDATA. The problem I'm having though is is that the sender app is written in Delphi 7 and the receiver is written in Delphi 10.2 . I copied my Delphi 7 program's code into Delphi 10 and the communication worked perfectly. Using the exact same code in Delphi 7 however caused my string being passed to the receiver app to get corrupted. The codes I use are as follows:
One the sending side I have:
procedure TSenderApp.SendString(ToSend: string);
var
copyDataStruct : TCopyDataStruct;
receiverHandle : THandle;
res : integer;
begin
copyDataStruct.dwData := 140500; //use it to identify the message contents
copyDataStruct.cbData := (1+ Length(ToSend))* SizeOf(Char) ;
copyDataStruct.lpData := pchar(ToSend) ;
receiverHandle := FindWindow(PChar('TRecieverApp'),PChar('RecieverApp')) ;
if receiverHandle = 0 then
begin
ShowMessage('CopyData Receiver NOT found!') ;
Exit;
end;
res := SendMessage(receiverHandle, WM_COPYDATA, Integer(Handle),
LPARAM(#copyDataStruct)) ;
end;
And on the receiving side I have:
procedure TRecieverApp.WMCopyData(var Message: TMessage);
var
p : PCopyDataStruct;
l : Integer;
s : string;
begin
p := PCopyDataStruct( Message.lParam );
if (p <> nil) then
begin
ShowMessage('New Message Recieved!');
l := p^.cbData;
SetLength( s, (l+1) );
StrLCopy( PChar(s), PChar(p^.lpData), l );
Edit1.Text := s;
end
else
Edit1.Text := 'ERROR';
end;
What am I doing wrong? Or why is the message string being corrupted when sent from the Delphi 7 written SenderApp and not from the Delphi 10 written SenderApp?
You are sending and processing data using the native encoding of Char, which is AnsiChar in Delphi 7 but is WideChar in Delphi 10.2 Tokyo (Delphi switched everything to Unicode in D2009). When Delphi 7 sends the data as ANSI, Delphi 10.2 misinterprets it as UTF-16. And vice versa. So you end up with corruption either way.
You need to convert the data to an agreed-upon character encoding before sending it, and convert it from that encoding after receiving it.
Try something more like this:
{$IF CompilerVersion >= 24} // XE3+
{$LEGACYIFEND ON}
{$IFEND}
var
MyDataID: UINT = 0;
procedure TSenderApp.SendString(const ToSend: string);
var
copyDataStruct : TCopyDataStruct;
receiverHandle : HWND;
res : LRESULT;
s : UTF8String;
begin
if MyDataID = 0 then
begin
ShowMessage('CopyData ID NOT registered!');
Exit;
end;
receiverHandle := FindWindow('TRecieverApp', 'RecieverApp');
if receiverHandle = 0 then
begin
ShowMessage('CopyData Receiver NOT found!');
Exit;
end;
{$IF CompilerVersion >= 20} // D2009+
s := UTF8String(ToSend);
{$ELSE}
s := UTF8Encode(ToSend);
{$IFEND}
copyDataStruct.dwData := MyDataID; //use it to identify the message contents
copyDataStruct.cbData := Length(s) * SizeOf(AnsiChar);
copyDataStruct.lpData := PAnsiChar(s);
res := SendMessage(receiverHandle, WM_COPYDATA, WPARAM(Handle), LPARAM(#copyDataStruct));
end;
initialization
MyDataID := RegisterWindowMessage('MyDataID');
{$IF CompilerVersion >= 24} // XE3+
{$LEGACYIFEND ON}
{$IFEND}
var
MyDataID: UINT = 0;
procedure TRecieverApp.WMCopyData(var Message: TMessage);
var
p : PCopyDataStruct;
s : UTF8String;
begin
p := PCopyDataStruct(Message.lParam);
if (p <> nil) and (MyDataID <> 0) and (p^.dwData = MyDataID) then
begin
SetString(s, PAnsiChar(p^.lpData), p^.cbData);
{$IF CompilerVersion >= 20} // D2009+
Edit1.Text := String(s);
{$ELSE}
Edit1.Text := UTF8Decode(s);
{$IFEND}
ShowMessage('New Message Received!');
end else
inherited;
end;
initialization
MyDataID := RegisterWindowMessage('MyDataID');
The difference between the two Delphi versions is string format. In Delphi 2007 and earlier, string uses 1-byte AnsiChar characters in ANSI format. In Delphi 2009 and later, string uses 2-byte WideChar characters in UTF-16 format. You need to convert the data to a common character encoding when sending it.

How lock CTRL+ALT+DEL using SetWindowHookEx api?

Good afternoon,
I need lock CTRL+ALT+DEL combination using SetWindowsHookEx and today i have done a code and don't is working until now.
This code is executing in a dll ( this dll is my software ) that is injected in other process.
So, how i can adapt this code below to work?
const
WH_KEYBOARD_LL = 13;
LLKHF_ALTDOWN = $20;
type
KBDLLHOOKSTRUCT = record
vkCode: DWORD;
scanCode: DWORD;
flags: DWORD;
time: DWORD;
dwExtraInfo: Longint ;
end;
var
hhkLowLevelKybd : HHOOK;
FoldProc : LongInt;
hSASWnd : HWND;
hThread : Cardinal;
{$R *.dfm}
Function LowLevelKeyboardProc(nCode : Integer; wParam : Longint; var LParam: KBDLLHOOKSTRUCT) : Longint; stdcall;
var
fEatKeystroke : Boolean;
dwThreadId : Cardinal;
begin
If (nCode = HC_ACTION) Then
begin
If (wParam = WM_KEYDOWN) Or
(wParam = WM_SYSKEYDOWN) Or
(wParam = WM_KEYUP) Or
(wParam = WM_SYSKEYUP) Then
begin
fEatKeystroke :=
(((GetKeyState(VK_CONTROL) And $8000) <> 0) And
((LParam.flags And LLKHF_ALTDOWN ) <> 0) And
(LParam.vkCode = VK_DELETE));
End;
If fEatKeystroke Then
Result := -1
Else
Result := CallNextHookEx(0, nCode, wParam, LongInt(#LParam));
End;
end;
////////// FormCreate event here ///////////
hhkLowLevelKybd := 0;
hhkLowLevelKybd := SetWindowsHookEx(WH_KEYBOARD_LL, #LowLevelKeyboardProc,
HInstance, 0);
end.
Windows does not allow you to intercept Ctrl+Alt+Del for security reasons. Earlier versions (pre-Vista?) used to allow it by replacing the GINA DLL, but it's not been allowed for years.
That key combination is known as a secure attention sequence which is guaranteed to be trustworthy as part of the login process.
If your goal is to only allow your application to be run, you can configure it to act in kiosk mode if you're running a suitable version of Windows, as shown in Set up a device for anyone to use (kiosk mode) at TechNet which #LURD kindly provided.
By design it's impossible to trap or block Ctrl+Alt+Del (The Secure Attention Sequence). There is however a commercial library available (disclaimer: I am the author), SasLibEx.
SasLibEx: a library that can simulate or block the Secure Attention
Sequence (Ctrl+Alt+Del) but it can even unlock a
workstation or session without entering or needing the user’s
credentials (and many more things)
See this screencast for a demo.
Impossible. The Ctl-Alt-Del gets trapped in the Kernel and never makes it to the user mode space where your app is running.
I have had to do this on kiosks systems (using Win XP and Vista) and I did it with a keyboard filter driver (which runs in the kernel) that swaps out the scan codes when the key are pressed.
Not is impossible, see the following code:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils,
Windows,
Registry,
vcl.Dialogs;
procedure DisableCtrAltDel(boolState: Boolean);
var
SystemReg: TRegistry;
Data: Array [1 .. 48] of Byte;
i: Byte;
begin
try
for i := 1 to 48 do
Data[i] := $00;
Data[9] := $09;
Data[15] := $5B;
Data[16] := $E0;
Data[19] := $5C;
Data[20] := $E0;
Data[23] := $5D;
Data[24] := $E0;
Data[27] := $44;
Data[31] := $1D;
Data[35] := $38;
Data[39] := $1D;
Data[40] := $E0;
Data[43] := $38;
Data[44] := $E0;
try
SystemReg := TRegistry.Create;
with SystemReg do
begin
RootKey := HKEY_LOCAL_MACHINE;
OpenKey('\System\CurrentControlSet\Control\Keyboard Layout', True);
if boolState then
WriteBinaryData('Scancode Map', Data, SizeOf(Data))
else
DeleteValue('Scancode Map');
MessageDlg('Restart Windows in order the changes to take effect!',
mtInformation, [mbOK], 0);
CloseKey;
end;
finally
SystemReg.Free;
end;
except
MessageDlg
('Error occurred while trying to disable ctrl+alt+del and Task Manager',
mtWarning, [mbOK], 0);
end;
end;
begin
try
DisableCtrAltDel(True);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Reference

VerQueryValue() does not work with single character values?

I use VerQueryValue() to get the value of the "PrivateBuild" key and this works fine.
Except when the value is only one character: e.g. 'b' which is not unusual for a beta-version. In that case the function returns False.
I've also tested this with a 3rd party Delphi program that can read file-version (to make sure, that my file-reading logic is not the problem):
http://www.delphidabbler.com/articles?article=20
this has the same problem.
Can anyone verify that this is a problem with the Windows function VerQueryValue()?
It could also be a problem of the Delphi XE3 IDE - maybe it has a bug and does not write the single character into the dll file-version info.
I can confirm this is a problem in XE3 & 4. It appears to be an issue between the W (Unicode) version and the A (ANSI) versions, as the same code in Delphi 2007 I used to test XE3 & 4 correctly reads a single character PrivateBuild value. As #DavidHeffernan mentions in the comments, this could be an issue with the resource compiler, although I'm not sure that the 32-bit resource compiler has changed between D2007 and XE. (Using a resource with a language ID that required Unicode and then Unicode values works in D2007, so that resource compiler version supports Unicode as well as Ansi.)
The test code, grabbed quickly from an old unit I had sitting around, added to the implementation section of a new VCL Forms Application with a TMemo and TButton on it, and quickly setting up test version info using the normal Delphi dialogs:
type
TVersionInfo=record
// Name of company
CompanyName: string;
// Description of file
FileDescription: string;
// File version
FileVersion: string;
// Internal name
InternalName: string;
// Legal copyright information
LegalCopyright: string;
// Legal trademark information
LegalTradeMarks: string;
// Original filename
OriginalFilename: string;
// Product name
ProductName : string;
// Product version
ProductVersion: string;
// Private build
PrivateBuild: string;
// Comments
Comments: string;
end;
const
ItemList: array [0..10] of string = ( 'CompanyName',
'FileDescription',
'FileVersion',
'InternalName',
'LegalCopyright',
'LegalTradeMarks',
'OriginalFilename',
'ProductName',
'ProductVersion',
'PrivateBuild',
'Comments' );
function GetVerInfo(const FileName: string; var VersionInfo: TVersionInfo): Boolean;
var
i: Integer;
dwLen: Word;
lpdwHandle: Cardinal;
pValue: PChar;
lpData: Pointer;
uiLen: UInt;
LCID: string;
begin
dwLen := GetFileVersionInfoSize(PChar(FileName), lpdwHandle);
Result := (dwLen > 0);
if not Result then
Exit;
GetMem(lpData, (dwLen + 1) * SizeOf(Char));
try
LCID := 'StringFileInfo\' + IntToHex(GetUserDefaultLCID, 4) + IntToHex(GetACP, 4) + '\';
GetFileVersionInfo(PChar(FileName), 0, dwLen, lpData);
for i := Low(ItemList) to High(ItemList) do
begin
if (VerQueryValue(lpData, PChar(LCID + ItemList[i]), Pointer(pValue), uiLen)) then
case i of
0: VersionInfo.CompanyName := pValue;
1: VersionInfo.FileDescription := pValue;
2: VersionInfo.FileVersion := pValue;
3: VersionInfo.InternalName := pValue;
4: VersionInfo.LegalCopyright := pValue;
5: VersionInfo.LegalTradeMarks := pValue;
6: VersionInfo.OriginalFilename := pValue;
7: VersionInfo.ProductName := pValue;
8: VersionInfo.ProductVersion := pValue;
9: VersionInfo.PrivateBuild := pValue;
10: VersionInfo.Comments := pValue;
end;
end;
finally
FreeMem(lpData);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
VI: TVersionInfo;
begin
Memo1.Clear;
GetVerInfo(ParamStr(0), VI);
Memo1.Lines.Add('Company name: ' + VI.CompanyName);
Memo1.Lines.Add('File version: ' + VI.FileVersion);
Memo1.Lines.Add('Private build: ' + VI.PrivateBuild);
end;

Serial port enumeration in Delphi using SetupDiGetClassDevs

I'm trying to enumerate "friendly names" for COM ports. The ports may dynamically change as USB-serial devices are connected and disconnected at runtime.
Based on the possible methods described in this question, I am attempting to use the SetupDiGetClassDevs method.
I found this example code, but it is written for an older version of the setupapi unit (the original link to homepages.borland.com doesn't work of course).
I tried using the setupapi unit from the current JVCL(JVCL340CompleteJCL221-Build3845), but it doesn't seem to be compatible with Delphi 7. I get compiler errors:
if SetupDiGetDeviceRegistryProperty(DevInfoHandle,DeviceInfoData,
RegProperty,
#PropertyRegDataType,
#S1[1],RequiredSize,#RequiredSize) then begin
In the call to function SetupDiGetDeviceRegistryProperty,
I get the error "Types of actual and formal parameters must be identical" on the parameters #PropertyRegDataType, and #RequiredSize.
The Delphi3000 site says the code was written in 2004 and is intended for Delphi 7, so I'm not sure why it doesn't work with Delphi 7 now, unless setupapi has changed. Is anyone familiar with the changes to setupapi that could cause these problems?
I'm testing with a simple console program. The uses statement is " windows,
sysutils,
classes,
setupAPI,
Registry;"
The main program is:
begin
ComPortStringList := SetupEnumAvailableComPorts;
for Index := 0 to ComPortStringList.Count - 1 do
writeln(ComPortStringList[Index]);
end;
end.
The following procedure is working correctly for me (in Windows 8.1). It is important to use the parameter KEY_READ in the TRegistry.Constructor.
procedure EnumComPorts(const Ports: TStringList);
var
nInd: Integer;
begin { EnumComPorts }
with TRegistry.Create(KEY_READ) do
try
RootKey := HKEY_LOCAL_MACHINE;
if OpenKey('hardware\devicemap\serialcomm', False) then
try
Ports.BeginUpdate();
try
GetValueNames(Ports);
for nInd := Ports.Count - 1 downto 0 do
Ports.Strings[nInd] := ReadString(Ports.Strings[nInd]);
Ports.Sort()
finally
Ports.EndUpdate()
end { try-finally }
finally
CloseKey()
end { try-finally }
else
Ports.Clear()
finally
Free()
end { try-finally }
end { EnumComPorts };
I was able to get some more specific suggestions by asking the question a different way with different tags.
It turns out there were errors in the delphi3000.com example code, and possibly errors in the JVCL code. After fixing the example code errors, I got it to work. I have not addressed the potential JVCL errors.
Here is the working code (as a simple console app) for enumerating the names of com ports:
{$APPTYPE CONSOLE}
program EnumComPortsTest;
uses
windows,
sysutils,
classes,
setupAPI,
Registry;
{$R *.RES}
var
ComPortStringList : TStringList;
(*
The function below returns a list of available COM-ports
(not open by this or an other process), with friendly names. The list is formatted as follows:
COM1: = Communications Port (COM1)
COM5: = NI Serial Port (Com5)
COM6: = NI Serial Port (Com6)
COM7: = USB Serial Port (COM7)
COM8: = Bluetooth Communications Port (COM8)
COM9: = Bluetooth Communications Port (COM9)
This code originally posted at http://www.delphi3000.com/articles/article_4001.asp?SK=
errors have been fixed so it will work with Delphi 7 and SetupAPI from JVCL
*)
function SetupEnumAvailableComPorts:TstringList;
// Enumerates all serial communications ports that are available and ready to
// be used.
// For the setupapi unit see
// http://homepages.borland.com/jedi/cms/modules/apilib/visit.php?cid=4&lid=3
var
RequiredSize: Cardinal;
GUIDSize: DWORD;
Guid: TGUID;
DevInfoHandle: HDEVINFO;
DeviceInfoData: TSPDevInfoData;
MemberIndex: Cardinal;
PropertyRegDataType: DWord;
RegProperty: Cardinal;
RegTyp: Cardinal;
Key: Hkey;
Info: TRegKeyInfo;
S1,S2: string;
hc: THandle;
begin
Result:=Nil;
//If we cannot access the setupapi.dll then we return a nil pointer.
if not LoadsetupAPI then exit;
try
// get 'Ports' class guid from name
GUIDSize := 1; // missing from original code - need to tell function that the Guid structure contains a single GUID
if SetupDiClassGuidsFromName('Ports',#Guid,GUIDSize,RequiredSize) then begin
//get object handle of 'Ports' class to interate all devices
DevInfoHandle:=SetupDiGetClassDevs(#Guid,Nil,0,DIGCF_PRESENT);
if Cardinal(DevInfoHandle)<>Invalid_Handle_Value then begin
try
MemberIndex:=0;
result:=TStringList.Create;
//iterate device list
repeat
FillChar(DeviceInfoData,SizeOf(DeviceInfoData),0);
DeviceInfoData.cbSize:=SizeOf(DeviceInfoData);
//get device info that corresponds to the next memberindex
if Not SetupDiEnumDeviceInfo(DevInfoHandle,MemberIndex,DeviceInfoData) then
break;
//query friendly device name LIKE 'BlueTooth Communication Port (COM8)' etc
RegProperty:=SPDRP_FriendlyName;{SPDRP_Driver, SPDRP_SERVICE, SPDRP_ENUMERATOR_NAME,SPDRP_PHYSICAL_DEVICE_OBJECT_NAME,SPDRP_FRIENDLYNAME,}
SetupDiGetDeviceRegistryProperty(DevInfoHandle,
DeviceInfoData,
RegProperty,
PropertyRegDataType,
NIL,0,RequiredSize);
SetLength(S1,RequiredSize);
if SetupDiGetDeviceRegistryProperty(DevInfoHandle,DeviceInfoData,
RegProperty,
PropertyRegDataType,
#S1[1],RequiredSize,RequiredSize) then begin
KEY:=SetupDiOpenDevRegKey(DevInfoHandle,DeviceInfoData,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ);
if key<>INValid_Handle_Value then begin
FillChar(Info, SizeOf(Info), 0);
//query the real port name from the registry value 'PortName'
if RegQueryInfoKey(Key, nil, nil, nil, #Info.NumSubKeys,#Info.MaxSubKeyLen, nil, #Info.NumValues, #Info.MaxValueLen,
#Info.MaxDataLen, nil, #Info.FileTime) = ERROR_SUCCESS then begin
RequiredSize:= Info.MaxValueLen + 1;
SetLength(S2,RequiredSize);
if RegQueryValueEx(KEY,'PortName',Nil,#Regtyp,#s2[1],#RequiredSize)=Error_Success then begin
If (Pos('COM',S2)=1) then begin
//Test if the device can be used
hc:=CreateFile(pchar('\\.\'+S2+#0),
GENERIC_READ or GENERIC_WRITE,
0,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if hc<> INVALID_HANDLE_VALUE then begin
Result.Add(Strpas(PChar(S2))+': = '+StrPas(PChar(S1)));
CloseHandle(hc);
end;
end;
end;
end;
RegCloseKey(key);
end;
end;
Inc(MemberIndex);
until False;
//If we did not found any free com. port we return a NIL pointer.
if Result.Count=0 then begin
Result.Free;
Result:=NIL;
end
finally
SetupDiDestroyDeviceInfoList(DevInfoHandle);
end;
end;
end;
finally
UnloadSetupApi;
end;
end;
var
index : integer;
begin
ComPortStringList := SetupEnumAvailableComPorts;
if (ComPortStringList <> nil) and (ComPortStringList.Count > 0) then
for Index := 0 to ComPortStringList.Count - 1 do
writeln(ComPortStringList[Index]);
end.
Looks like some arguments of type PDWord were replaced by var DWord in SetupApi.pas. All you need is to remove '#' from these arguments in your code like that:
if SetupDiGetDeviceRegistryProperty(DevInfoHandle,DeviceInfoData,
RegProperty,
PropertyRegDataType,
#S1[1],RequiredSize,RequiredSize) then begin
Do you have "typed # operator" turned on? Project options, Compiler tab under "Syntax options". A lot of third party code breaks if that option is enabled.
For easier operation you might consider simply using the registry where those names are listed eg:
ErrCode := RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
'HARDWARE\DEVICEMAP\SERIALCOMM',
0,
KEY_READ,
KeyHandle);
(I've ommited the hand-waving stuff).
You might also consider using WMI - see this example from Magenta Systems - you can get a pretty much everything hardware-related now.
I adapted below code from RRUZ answer for Serial Port class. Works fine under Win10 20H2.
{$APPTYPE CONSOLE}
uses
SysUtils,
ActiveX,
ComObj,
Variants;
procedure GetWin32_SerialPortInfo;
const
WbemUser ='';
WbemPassword ='';
WbemComputer ='localhost';
wbemFlagForwardOnly = $00000020;
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObjectSet: OLEVariant;
FWbemObject : OLEVariant;
oEnum : IEnumvariant;
iValue : LongWord;
begin;
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
FWMIService := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM Win32_SerialPort','WQL',wbemFlagForwardOnly);
oEnum := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
while oEnum.Next(1, FWbemObject, iValue) = 0 do
begin
// for other fields: https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-serialport
Writeln(Format('DeviceID %s',[String(FWbemObject.DeviceID)]));// String
Writeln(Format('Name %s',[String(FWbemObject.Name)]));// String
Writeln(Format('Description %s',[String(FWbemObject.Description)]));// String
FWbemObject:=Unassigned;
end;
end;
begin
try
CoInitialize(nil);
try
GetWin32_SerialPortInfo;
finally
CoUninitialize;
end;
except
on E:EOleException do
Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.
Output:
DeviceID COM7
Name Silicon Labs CP210x USB to UART Bridge (COM7)
Description Silicon Labs CP210x USB to UART Bridge
Press Enter to exit

Resources