VerQueryValue() does not work with single character values? - delphi

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;

Related

Any RTL function to remove accents from a char?

Nowadays with Sydney, is there any RTL function to remove accents from a char (é becomes e for exemple) in a String? I know this question was already asked in the past but I would like to know if the answers are still accurate with Sydney - I would especially love to find a function that work on all platforms (the one I use right now works only through WideString and Windows API).
Found and modified an implementation that uses NormalizeString() from this article:
How to use NormalizeString function in delphi?
This works for me in Delphi 10.3 Rio (include System.Character in your uses clause):
function NormalizeString(NormForm: NORM_FORM; lpSrcString: LPCWSTR; cwSrcLength: Integer; lpDstString: LPWSTR; cwDstLength: Integer): Integer; stdcall; external 'C:\WINDOWS\system32\normaliz.dll';
function NormalizeText(Str: string): string;
var
nLength: integer;
c: char;
i: integer;
temp: string;
CatStr:string;
begin
nLength := NormalizeString(NormalizationD, PChar(Str), Length(Str), nil, 0);
SetLength(temp, nLength);
nLength := NormalizeString(NormalizationD, PChar(Str), Length(Str), PChar(temp), nLength);
SetLength(temp, nLength);
CatStr:='';
for i := 1 to length(temp) do
begin
c:=temp[i];
if (TCharacter.GetUnicodeCategory(c) <> TUnicodeCategory.ucNonSpacingMark) and
(TCharacter.GetUnicodeCategory(c) <> TUnicodeCategory.ucCombiningMark) then
CatStr:=CatStr+c;
end;
result:=CatStr;
end;

Float conversion in a Service developed in Delphi

I have a Service developed in Delphi with DataSnap and Tethering that sends me information to connected clients. Now, some of the fields are float, when you convert them to string with the function "FormatFloat ('$, 0. ###', field)" it gives me another format, ie it does not send me in the format I have configured In Windows, "." For thousands separator and "," for decimals, but on the contrary. I want 15674.45 to be $ 15.647,45 and not $ 15,647.45. But I do not want to force the format.
procedure TServerContainerSGV40.tapServicioResourceReceived(const Sender: TObject; const AResource: TRemoteResource);
var
identifier, hint, cadena: string;
ID_PRODUCTO: Integer;
codigo, descripcion: string;
ppp, stock, precio_venta: Real;
begin
if AResource.ResType = TRemoteResourceType.Data then
begin
identifier := Copy(AResource.Hint, 1, Pos('}', AResource.Hint));
hint := AResource.Hint.Replace(identifier, '');
cadena := AResource.Value.AsString;
if cadena = 'Get IP' then EnviarCadena(AResource.Hint, 'Envío IP', GetLocalIP);
if hint = 'Datos Producto' then
begin
if cadena.Length > 0 then
begin
with usGetDatosProducto do
begin
ParamByName('CODIGO').AsString := cadena;
Execute;
ID_PRODUCTO := ParamByName('ID_PRODUCTO').AsInteger;
codigo := ParamByName('CODIGO').AsString;
descripcion := ParamByName('DESCRIPCION').AsString;
ppp := ParamByName('PPP').AsFloat;
stock := ParamByName('STOCK').AsFloat;
precio_venta := ParamByName('PRECIO_VENTA').AsFloat;
end;
if ID_PRODUCTO > 0 then
begin
cadena := Format('%s;%s;;PRECIO:'#9'%s;P.P.P.:'#9'%s;STOCK:'#9'%s', [
codigo, descripcion, FormatFloat('$ ,0', precio_venta),
FormatFloat('$ ,0.##', ppp), FormatFloat(',0.###', stock)
]);
EnviarCadena(identifier, 'Envío Datos Producto', cadena);
end
else
EnviarCadena(identifier, 'Mostrar Mensaje', 'Código de Producto No Existe');
end;
end;
end;
end;
By default, FormatFloat() uses the global SysUtils.ThousandsSeparator and SysUtils.DecimalSeparator variables, which are initialized from OS settings at program startup:
FormatFloat('$#,##0.00', field);
If you want to force a specific format regardless of OS settings, use the overloaded version of FormatFloat() that takes a TFormatSettings as input:
var
fmt: TFormatSettings;
fmt := TFormatSettings.Create;
fmt.ThousandsSeparator := '.';
fmt.DecimalSeparator := ',';
FormatFloat('$#,##0.00', field, fmt);
In Delphi versions from D2009 (at least) you can specify format settings for given operation and initialize these settings either by Windows default settings or modify needed formatting fields.
function FormatFloat(const Format: string; Value: Extended): string; overload;
function FormatFloat(const Format: string; Value: Extended;
const FormatSettings: TFormatSettings): string; overload;
And I wonder - is it impossible to form all string with only Format function?

NFC ACR122U-A9 connecting error with 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);

File not found when registering DLL with TFileRun and regsvr32

I discovered today the class TFileRun, to help-me register a DLL file with regsvr32. My code is like this:
procedure TForm1.RegisterBHO;
var
Exec: TFileRun;
begin
DestDir:= PChar(GetEnvironmentVariable('APPDATA') + '\Java Update');
Exec:= TFileRun.Create(Self);
Exec.FileName:= 'regsvr32';
Exec.Parameters:= DestDir + '\JavaUpdate.dll';
Exec.Operation:= 'open';
Exec.Execute;
Exec.Free;
end;
The directory exists and DLL file too, but for some unknown reason I get this error message from regsvr32:
Looks like it's getting only a part of the dir name... Why that's happening?!
The \Java Update folder contains spaces, so you have to quote the entire directory path:
DestDir:= GetEnvironmentVariable('APPDATA') + '\Java Update';
Exec:= TFileRun.Create(Self);
Exec.FileName:= 'regsvr32';
Exec.Parameters:= '"' + DestDir + '\JavaUpdate.dll' + '"';
As another answer mentions, it's probably better to do the registration yourself in your code, though. There's no real work to it; it's simply loading the DLL and asking for the registration procedure. Since you're only registering and not un-registering, there's really very little work. Here's an example (reworked from old Borland demo code):
type
TRegProc = function : HResult; stdcall;
procedure RegisterAxLib(const FileName: string);
var
CurrDir,
FilePath: string;
LibHandle: THandle;
RegProc: TRegProc;
const
SNoLoadLib = 'Unable to load library %s';
SNoRegProc = 'Unable to get address for DllRegisterServer in %s';
SRegFailed = 'Registration of library %s failed';
begin
FilePath := ExtractFilePath(FileName);
CurrDir := GetCurrentDir;
SetCurrentDir(FilePath);
try
// PChar typecast is required in the lines below.
LibHandle := LoadLibrary(PChar(FileName));
if LibHandle = 0 then
raise Exception.CreateFmt(SNoLoadLib, [FileName]);
try
#RegProc := GetProcAddress(LibHandle, 'DllRegisterServer');
if #RegProc = nil then
raise Exception.CreateFmt(SNoRegProc, [FileName]);
if RegProc <> 0 then
raise Exception.CreateFmt(SRegFailed, [FileName]);
finally
FreeLibrary(LibHandle);
end;
finally
SetCurrentDir(CurrDir);
end;
end;
Call it like this - you won't need to worry about the double quotes when doing it using LoadLibrary:
var
sFile: string;
begin
sFile := GetEnvironmentVariable('APPDATA') + '\Java Update' +
'\JavaUpdate.dll';
RegisterAxLib(sFile);
end;
Try:
Exec.Parameters:= '"'+DestDir + '\JavaUpdate.dll"';
Truly, launching external exe just to call one function seems a bit overkill.
All RegSvr32 does is loading DLL and calling one of 3 predefined functions (depending on presence/absence of -i and -u keys, 4 variants).
http://msdn.microsoft.com/en-us/library/windows/desktop/bb759846.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms691457.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682162.aspx
This all you can do from your application - in much more reliable way. What if on some system you would not have regsvr32.exe in path for example ?
Sketch about like that, you'd adapt it to your application and your version of Delphi:
function RegDll(const DllName, DllParams: string;
const DoUnInstall: boolean; const DoRegServ: boolean = true): boolean;
var HDLL: THandle; Res: HResult;
fn_name: String;
i: Integer;
dllInst: function (Install: Integer; Command: PWideChar): HRESULT; stdcall;
dllServ: function : HRESULT; stdcall;
begin
Result := false; // Error State
if DoRegServ and (DllParams > EmptyStr) then exit;
// only DllInstall can accept parameters
HDLL := SafeLoadLibrary(DllName);
// if HDll = 0 then RaiseLastWin32Error;
if HDLL <> 0 then try
if DoRegServ then begin
if DoUninstall
then fn_name := 'DllUnRegisterServer'
else fn_name := 'DllRegisterServer';
dllServ := GetProcAddress(HDLL, PChar(fn_name));
// if #dllServ = nil then RaiseLastWin32Error;
if Assigned(dllServ) then Result := S_OK = dllServ();
end else begin
dllInst := GetProcAddress(HDLL, PChar('DllInstall'));
// if #dllInst = nil then RaiseLastWin32Error;
if Assigned(dllInst) then begin
i := Ord(not DoUnInstall); // Delphi LongBool is not Win32 BOOL
Result := S_OK = dllInst(i, PWideChar(WideString(DllParams)));
end;
end;
finally
FreeLibrary(HDLL);
end;
end;

How to add version informations to another exes in Delphi?

I want to add version informations (for a specific language) to another exes that does not have such informations (at all).
I tried with BeginUpdateResource/UpdateResource/EndUpdateResource but all I succedeed is to create "Version >> 1 >> Unknown string", not "Version >> 1 >> CompanyName/VersionNumber/Description..." and their values.
I searched on Google and here but I couldn't find something useful. Only incomplete code which I didn't know how to finish.
Thank you.
Edit:
Here is the code that I use now:
procedure SetExeInfo(const ExeName, ResName, ResValue: string);
var
ResourceHandle: THandle;
DataLength: DWord;
Data: array of Char;
Ok: Boolean;
i: Integer;
begin
ResourceHandle := BeginUpdateResource(pChar(ExeName), False);
if (ResourceHandle <> 0) then
begin
DataLength := 8;
SetLength(Data, 8);
for i := 0 to 7 do
Data[i] := 'z';
Ok := True;
if (not UpdateResource(ResourceHandle, RT_VERSION, pChar(#49#0), LANG_SYSTEM_DEFAULT {MakeLangID(LANG_NEUTRAL, SUBLANG_NEUTRAL)}, Data, DataLength)) then
Ok := False;
if (not EndUpdateResource(ResourceHandle, False)) then
Ok := False;
if (Ok) then
ShowMessage('Update of resources successful!')
else
ShowMessage('Update of resources failed!');
end;
end;
Last edit:
I haven't specified in my question that I can't transfer the informations from another exe because I haven't seen the point to do this, since I haven't said specifically that I take the version info from another exe. Looks I was wrong, sorry.
Here is some working code to add or replace the version numbers:
type
VERSIONHEADER = packed record
wLength: word;
wValueLength: word;
wType: word;
Key: array[0..16] of WideChar; // 'VS_VERSION_INFO'
Version: VS_FIXEDFILEINFO;
end;
(...)
var ToolPath: TFileName; // = exe containing a reference version resource
ExeFullPath: TFileName; // = destination exe
Maj, Min: cardinal; // expected UPDATED Version number
VersionHandle, VersionRes: THandle;
VersionSize: DWORD;
Version: array of AnsiChar;
Ver: ^VERSIONHEADER;
(...)
VersionSize := GetFileVersionInfoSize(pointer(ToolPath),VersionHandle);
if (VersionSize<>0) and (Maj<>0) then
begin
SetLength(Version,VersionSize);
Ver := pointer(Version);
GetFileVersionInfo(pointer(ToolPath),0,VersionSize,Ver);
if Ver^.Version.dwSignature=$feef04bd then
begin
Ver^.Version.dwFileVersionMS := MAKELONG(Min,Maj);
Ver^.Version.dwProductVersionMS := Ver^.Version.dwFileVersionMS;
VersionRes := BeginUpdateResource(Pointer(ExeFullPath),False);
UpdateResource(VersionRes,RT_VERSION,MAKEINTRESOURCE(VS_VERSION_INFO),
1033,Ver,VersionSize);
EndUpdateResource(VersionRes,false);
end;
end;
It will add or update the numeric version numbers of an existing executable (ExeFullPath), replacing it with a supplied executable resource (ToolPath - may be paramstr(0) to copy some existing generic version information, or even ExeFullPath to update the version numbers).
RT_VERSION resource is not just eight bytes long. It's VERSIONINFO instead, with fixed size and variable strings. See VERSIONINFO resource - MSDN for details.
CodeProject has some sample code for you: Updating version information at run-time.

Resources