I have an application compiled for Compact Framework which I need to find the version number for using Delphi 2006. I am using the code below.
function VersionNumber(ExeFile: string): string;
var
Size: Longint;
Dummy: Cardinal;
Buffer: Pointer;
FileInfo: Pointer;
begin
Size := GetFileVersionInfoSize(PChar(ExeFile), Dummy);
GetMem(Buffer, Size);
if (GetFileVersionInfo(PChar(ExeFile), Dummy, Size, Buffer)) then begin
//VerQueryValue(Buffer, '\\', FileInfo, Dummy);
//with PVSFixedFileInfo(FileInfo)^ do
//Result := IntToStr(dwFileVersionMS div $10000) + '.' +
// IntToStr(dwFileVersionMS mod $10000) + '.' +
// IntToStr(dwFileVersionLS div $10000) + ' (' +
// IntToStr(dwFileVersionLS mod $10000) + ')';
end
else begin
Result := 'No version info available.';
end;
FreeMem(Buffer, Size);
end;
If I view the file details using Windows 7 I cannot see the version number there either, so it's not surprising that I cannot get it from Delphi.
Just on the off chance someone knows a way to get the version number it would be greatly appreciated.
UPDATE
This code was written more then a decade ago by an ex staff member. Up until now, it has worked fine, but has never been tried on an executable compiled for Compact Framework.
The GetFileVersionInfo function is returning false, so I am getting 'No version info available.' result.
CF does not put C/C++ FileVersionInfo into exe or DLL files. The AssemblyInfo is something different from the FileVersionInfo resource you are looking for with your code sample.
ctacke has a usefull post about this at http://blog.opennetcf.com/2014/01/03/howto-add-the-win32-file-version-to-your-net-compact-framework-assemblies/ which can be found via Version information missing from .NET assembly (Compact Framework 3.5/VS2008), what graymatter already posted.
Related
I have been using this code for more than 10 years and it suddenly stopped working.
The program reads a list of pdf files and output a single pdf file containing all the files in the list. It uses gsapi.pas wrapper unit. over the years I have only had to download an updated version gsdll32.dll file from Ghostscript.to be compatible with newer versions of pdf file format. but it is not working anymore
Any ideas of what is going on?
procedure PDFMerge(input : Array of String;output : string);
var
code:integer;
instance:Pointer;
argv:PPChar;
N,I : smallint;
begin
new(instance);
N:=length(input);
setlength(argv,6+N);
code:=gsapi_new_instance(#instance,nil);
if code<>0 then
begin
raise Exception.Create('Impossible to open an instance of ghostscript. Error code: '+IntToStr(code));
end;
try
//try adding dNothing as first parameter argv[1] := pchar('dNothing');
argv[0] := pchar('AppendPdf');
argv[1] := pchar('-dNOPAUSE');
argv[2] := pchar('-dBATCH');
argv[3] := pchar('-dSAFER');
argv[4] := pchar('-sDEVICE=pdfwrite');
argv[5] := pchar('-sOutputFile='+ output);
argv[6] := pchar('-c');
argv[7] := pchar('.setpdfwrite');
argv[8] := pchar('-f');
for I := 0 to N - 1 do
argv[9+I] := pchar(input[I]);
//Call gs32.dll
code := gsapi_init_with_args(instance, length(argv), argv);
if code<0 then
raise Exception.Create('ERROR: init_args: '+IntToStr(code));
gsapi_exit(instance);
gsapi_delete_instance(instance);
finally
end;
end;
I am using Ghostscript V10.0. I found that they have a new interpreter perhaps it has to do with That? Although I did try addidng the switch "-dNEWPDF=false" but no luck. I keep getting error code -100.
Note using the equivalent command line in Ghostscript itself works fine. It seems to me they changed the way the API needs to be called. Whould that be the case?
I would appreciate any help.
I did try addidng the switch "-dNEWPDF=false" but no luck.
I need to monitor real time if a new file is created on a folder. System.IO.FileSystemWatcher seems a perfect solution. But on Delphi 11 it is reporting
[dcc32 Fatal Error] F2613 Unit 'System.IO' not found.
Do I have to download something to have the .pas unit?
P.S. I have explored using the windows API FindFirstChangeNotification but this does not provide the filename created.
System.IO.FileSystemWatcher is a .net class and not part of the Delphi RTL. Therefore you won't find it anywhere.
I think the API function you need is ReadDirectoryChangesW.
You can also use DDNRuntime(Delphi .NET Framework/.NET Core Runtime)
https://github.com/ying32/DDNRuntime-examples
It can help you call .net fuctions easyly
Just like
procedure TestMemoryStream;
var
LMem: DNMemoryStream;
LBytes: TArray<Byte>;
B: Byte;
LReadLen: Integer;
begin
LMem := TDNMemoryStream.Create;
LMem.Write([1,2,3,4,5], 0, 5);
LMem.WriteByte(121);
LMem.Flush;
LMem.Position := 0;
Writeln('data Length: ', LMem.Length);
SetLength(LBytes, LMem.Length);
LReadLen := LMem.Read(LBytes, 0, Length(LBytes));
Writeln('len: ', LReadLen);
for b in LBytes do
Write(b, ' ');
Writeln;
end;
In old Delphi applications which use the old and deprecated but still used BDE database engine with Paradox database files residing on a Windows 10 computer that's updated to the 1803 "Spring Creators Update" version, but the client computers using any older version of Windows like Windows 10 1709 or Windows 7, opening a Paradox table sometimes fails with a "No more files" error, idapi32.dll error code DBIERR_OSENMFILE. This raises a EDBEngineError exception in DBTables.pas / TTable.GetHandle(), which is called by TTable.CreateHandle, called by TBDEDataSet.OpenCursor().
The error seems to be caused by some file-sharing related changes in the Windows 10 1803 update. Removing the 1803 update from the file-sharing Windows 10 computer, or updating all the client computers to Windows 10 + 1803 seems to make the error go away.
People have speculated that the changes have something to do with the SMB protocol, maybe Windows Defender and/or other security related issues. Here's a Google Plus discussion
https://plus.google.com/106831056534874810288/posts/F4nsoTz2pDi
How could the "No more files" error be worked around by some reasonably easily doable changes in the Delphi application, while allowing the file-sharing client and server computers to keep using heterogeneous Windows versions?
Please try to refrain from answering or commenting self-evident things like "the sky is blue" or "BDE is old and deprecated". Keeping BDE is a decision that cannot be changed, certainly not as a "bug fix".
As an emergency fix, we have resorted to simply re-trying DbiOpenTable, when it returns the DBIERR_OSENMFILE error code. I posted an answer with source code to the idapi32.dll hack. So far it seems that if the first DbiOpenTable says "No more files", the second try succeeds, and the application works without noticing anything.
WARNING: what follows is a hack. A kludge. Band-aid, glue, duct tape and chewing gum. BDE is old. You are completely on your own if you use BDE and/or if you try this hack. I accept no responsibility over its use. If it works for you, good for you. If it ruins your business, bad for you.
Since the Paradox tables still mostly worked and the error seemed to be slightly randomly triggered, and since someone suspected Windows Defender having something to do with it, I thought maybe it just needs some kicking around. If DbiOpenTable() suddenly starts sometimes failing over a certain combination of SMB client/server versions, because "No more files" ... then why not just try the file operation again. I put an "if it returns a DBIERR_OSENMFILE error, then Sleep() and try again" logic around the DbiOpenTable function, and guess what - it seemed to work.
Hacking around the BDE's "features" is familiar to anyone who has to maintain BDE based applications. So I made a patching hook around idapi32.dll's DbiOpenTable function, starting from an old routine written by Reinaldo Yañez originally to fix the "insufficient disk space" error with BDE when the free disk space is at a 4 GB boundary. See https://cc.embarcadero.com/Item/21475
To use this, add Fix1803 in a uses clause, and call PatchBDE somewhere before starting to open Paradox tables. Maybe call UnPatchBDE when you're done, though I don't think that's necessary.
But remember, you're on your own, and this is highly experimental code.
unit Fix1803;
// * KLUDGE WARNING *
// Patch (hack) idapi32.dll DbiOpenTable() to try harder, to work with Windows 10 1803 "Spring Creators Update".
//
// The patching routine is an extension of code originally written by Reinaldo Yañez.
// see https://cc.embarcadero.com/Item/21475
//
// Some original Spanish comments are left in place.
interface
procedure PatchBDE;
procedure UnPatchBDE;
implementation
uses
Windows, Db, DbTables, BDE, SysUtils;
// ------------------------------------------- DbiOpenTable hook
var DbiOpenTable_address_plus_9 : Pointer;
function Actual_DbiOpenTable_CallStub(hDb: hDBIDb; pszTableName: PChar; pszDriverType: PChar; pszIndexName: PChar; pszIndexTagName: PChar; iIndexId: Word; eOpenMode: DBIOpenMode; eShareMode: DBIShareMode; exltMode: XLTMode; bUniDirectional: Bool; pOptParams: Pointer; var hCursor: hDBICur): DBIResult stdcall; assembler;
asm
// these two instructions are implicitly contained in the start of the function
// push ebp
// mov ebp, esp
add esp, $fffffee8
jmp dword ptr [DbiOpenTable_address_plus_9]
end;
function LogHook_DbiOpenTable (hDb: hDBIDb; pszTableName: PChar; pszDriverType: PChar; pszIndexName: PChar; pszIndexTagName: PChar; iIndexId: Word; eOpenMode: DBIOpenMode; eShareMode: DBIShareMode; exltMode: XLTMode; bUniDirectional: Bool; pOptParams: Pointer; var hCursor: hDBICur): DBIResult stdcall;
var
i : Integer;
begin
Result := Actual_DbiOpenTable_CallStub(hDb, pszTableName, pszDriverType, pszIndexName, pszIndexTagName, iIndexId, eOpenMode, eShareMode, exltMode, bUniDirectional, pOptParams, hCursor);
// if we got the "No more files" error, try again... and again.
i := 1;
while (Result = DBIERR_OSENMFILE) and (i < 10) do
begin
Windows.Sleep(i);
Result := Actual_DbiOpenTable_CallStub(hDb, pszTableName, pszDriverType, pszIndexName, pszIndexTagName, iIndexId, eOpenMode, eShareMode, exltMode, bUniDirectional, pOptParams, hCursor);
Inc(i);
end;
end;
// ------------------------------------------- Patching routines
const // The size of the jump instruction written over the start of the original routine is 5 bytes
NUM_BYTES_OVERWRITTEN_BY_THE_PATCH = 5;
type
TRYPatch = record
OrgAddr: Pointer;
OrgBytes: array[0..NUM_BYTES_OVERWRITTEN_BY_THE_PATCH-1] of Byte;
end;
procedure TRYPatch_Clear(var ARYPatch : TRYPatch);
begin
FillChar(ARYPatch, SizeOf(TRYPatch), 0);
end;
function RedirectFunction(OldPtr, NewPtr, CallOrigStub : Pointer; var OriginalRoutineAddressPlusN: Pointer; NumBytesInCompleteInstructionsOverwritten : Integer): TRYPatch;
type
PPtr=^pointer;
PPPtr=^PPtr;
TByteArray=array[0..maxint-1] of byte;
PByteArray=^TByteArray;
function SameBytes(Ptr1, Ptr2 : Pointer; NumBytes : Integer) : Boolean;
var
i : Integer;
begin
Result := true;
i := 0;
while (Result) and (i < NumBytes) do
begin
Result := Result and ((PByteArray(Ptr1)^[i] = PByteArray(Ptr2)^[i]));
Inc(i);
end;
end;
var
PatchingAddress : Pointer;
OldProtect,
Protect : DWORD;
p: PByteArray;
i : Integer;
begin
PatchingAddress := OldPtr;
if PWord(PatchingAddress)^ = $25FF then
begin {Es un JMP DWORD PTR [XXXXXXX](=> Esta utilizando Packages)}
p := PatchingAddress;
PatchingAddress := (PPPtr(#p[2])^)^; // PatchingAddress now points to the start of the actual original routine
end;
// Safety check (as if this thing was "safe"). The given replacement routine must start with the same bytes as the replaced routine.
// Otherwise something is wrong, maybe a different version of idapi32.dll or something.
if (CallOrigStub <> nil) and not SameBytes(PatchingAddress, CallOrigStub, NumBytesInCompleteInstructionsOverwritten) then
raise Exception.Create('Will not redirect function, original call stub doesn''t match.');
// Change memory access protection settings, so we can change the contents
VirtualProtect(PatchingAddress, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH, PAGE_READWRITE, #OldProtect);
// Save the old contents of the first N bytes of the routine we're hooking
Result.OrgAddr := PatchingAddress; // Save the address of the code we're patching (which might not be the same as the original OldPtr given as parameter)
for i := 0 to NUM_BYTES_OVERWRITTEN_BY_THE_PATCH-1 do
result.OrgBytes[i] := PByte(Integer(PatchingAddress) + i)^;
// Replace the first bytes of the original function with a relative jump to the new replacement hook function
// First write the instruction opcode, $E9 : JMP rel32
PByte(PatchingAddress)^:= $E9;
// Then write the instruction's operand: the relative address of the new function
PInteger(Integer(PatchingAddress)+1)^ := Integer(NewPtr) - Integer(PatchingAddress) - 5;
// Address to jump to, for the replacement routine's jump instruction
OriginalRoutineAddressPlusN := Pointer(Integer(PatchingAddress) + NumBytesInCompleteInstructionsOverwritten);
// Restore the access protection settings
VirtualProtect(PatchingAddress, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH, OldProtect, #Protect);
FlushInstructionCache(GetCurrentProcess, PatchingAddress, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH);
end;
procedure RestorePatch(RestorePatch: TRYPatch);
var
OldProtect,
Protect : DWORD;
OldPtr: Pointer;
i : Integer;
begin
OldPtr := RestorePatch.OrgAddr;
VirtualProtect(OldPtr, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH, PAGE_READWRITE, #OldProtect);
for i := 0 to NUM_BYTES_OVERWRITTEN_BY_THE_PATCH-1 do
PByte(Integer(OldPtr) + i)^ := RestorePatch.OrgBytes[i];
VirtualProtect(OldPtr, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH, OldProtect, #Protect);
FlushInstructionCache(GetCurrentProcess, OldPtr, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH);
end;
var
idapi32_handle: HMODULE;
Patch_DbiOpenTable : TRYPatch;
procedure PatchBDE;
begin
if idapi32_handle <> 0 then Exit; // already_patched
idapi32_handle := LoadLibrary('idapi32');
if idapi32_handle <> 0 then
begin
Patch_DbiOpenTable := RedirectFunction(GetProcAddress(idapi32_handle, 'DbiOpenTable'), #LogHook_DbiOpenTable, #Actual_DbiOpenTable_CallStub, DbiOpenTable_address_plus_9, 9);
end;
end;
procedure UnPatchBDE;
begin
if idapi32_handle <> 0 then
begin
{Leave everything as before, just in case...}
if Patch_DbiOpenTable.OrgAddr <> nil then
RestorePatch(Patch_DbiOpenTable);
FreeLibrary(idapi32_handle);
idapi32_handle := 0;
end;
end;
initialization
idapi32_handle := 0;
TRYPatch_Clear(Patch_DbiOpenTable);
end.
VMWare, Virtual Box, etc to virtualize an Windows 7. If, as you say, W7 work flawlessly that would solve the problem.
I'm writing an app I'd like to be backwardly compatible to some extent on XP, or at the very least windows vista.
EDIT FOR CLARITY: I need to be able to do what the first code snippet below does, but in XP. "Does anybody know the best approach to take under XP, given the functions aren't available in USER32.DLL.?"
My initial prototype code on windows 7 just called CreateProcess to start up displayswitch.exe, which is deployed with windows 7.
if you are not familiar with it, it's a handy little utility that is what gets invoked when you press the windows key and the letter P. you can read more about it here.
while this was adequate, i subsequently needed to sense the current state (eg internal vs external or extend vs clone), so i have now coded up a winapi solution that works well on windows 7 (and i presume 8). it involves making calls to SetDisplayConfig and QueryDisplayConfig in User32.DLL
The pertinent section of it is here (minus the many, many structures i had to hand craft in pascal code from the original klingon).
function getTopology : DISPLAYCONFIG_TOPOLOGY_ID ;
var NumPathArrayElements,
NumModeInfoArrayElements : UINT32;
var PathArrayElements_Size,
ModeInfoArrayElements_Size : UINT32;
error : Longint;
paths : PDISPLAYCONFIG_PATH_INFO_array;
info : PDISPLAYCONFIG_MODE_INFO_array;
begin
NumModeInfoArrayElements := 0;
Result := DISPLAYCONFIG_TOPOLOGY_EXTERNAL;
inc(result);
error := GetDisplayConfigBufferSizes(QDC_DATABASE_CURRENT,NumPathArrayElements,NumModeInfoArrayElements);
case error of
ERROR_SUCCESS :
begin
PathArrayElements_Size := sizeof(DISPLAYCONFIG_PATH_INFO) * NumPathArrayElements ;
ModeInfoArrayElements_Size := sizeof(DISPLAYCONFIG_MODE_INFO) * NumModeInfoArrayElements;
GetMem(paths,PathArrayElements_Size);
try
GetMem(info,ModeInfoArrayElements_Size );
try
error := QueryDisplayConfig(QDC_DATABASE_CURRENT,NumPathArrayElements, paths,NumModeInfoArrayElements, info,result);
case error of
ERROR_SUCCESS :;
else
Result := DISPLAYCONFIG_TOPOLOGY_EXTERNAL;
inc(result);
end;
finally
FreeMem(info,ModeInfoArrayElements_Size );
end;
finally
FreeMem(paths,PathArrayElements_Size);
end;
end;
end;
end;
function setTopology ( top : DISPLAYCONFIG_TOPOLOGY_ID) : boolean;
var flags : dword;
begin
result := false;
flags := DecodeDISPLAYCONFIG_TOPOLOGY_ID_SDC(top);
if flags <> 0 then
begin
result := SetDisplayConfig(0,nil,0,nil,SDC_APPLY or flags) = ERROR_SUCCESS;
end;
end;
Since these functions don't exist in XP (as far as I know), I am looking for a stable way of achieving a similar thing in XP. whilst i am coding in Delphi, it's not necessary that the solution be presented as such. i am quite happy to just look at how it's done, or read a description of the appropriate steps, and implement it myself.
(removed full listing as it was confusing the issue as it did not appear like a question)
How can i get the version of my running application?
i have been using GetFileVersionInfo(ParamStr(0), ...):
filename := PChar(ExtractShortPathName(ParamStr(0)));
//Get the number of bytes he have to allocate for the file information structure
dwInfoLength := GetFileVersionInfoSize(lptstrFilename, {var}dwHandle);
//Get version info
GetMem(pInfoData, dwInfoLength);
GetFileVersionInfo(lptstrFilename, dwHandle, dwInfoLength, pInfoData);
//Set what information we want to extract from pInfoData
lpSubBlock := PChar(Chr(92)+Chr(0));
//Extract the desired data from pInfoData into the FileInformation structure
VerQueryValue(pInfoData, lpSubBlock, PFileInformation, LengthOfReturned);
The problem with this technique is that it requires the Windows loader to load the image before the resources can be read. i build my applications with the IMAGE_FILE_NET_RUN_FROM_SWAP image flag (in order to avoid in-page exceptions on a fiddly network).
This causes the Windows loader to load the entire image across the network again, rather than just looking at "me". Since i check, and save, my own version at startup, a 6 second application startup turns into a 10 second application startup.
How can i read the version of me, my running application?
i would assume Windows has no API to read the version of a running process, only the file that i loaded from (and if the file no longer exists, then it cannot read any version info).
But i also assume that it might be possible to read version resources out of my processes own memory (without being a member of the Administrators or Debuggers group of course).
Can i read the version of my process?
Associated Bonus Question: How can i load PE Image resources from me rather than across the network?
Found it, right here on Stackoverflow:
How to determine Delphi Application Version
i already knew how to determine an application version, but #StijnSanders suggested the "better" way, for exactly the reasons i was hitting:
I most strongly recommend not to use GetFileVersion when you want to know the version of the executable that is currently running! I have two pretty good reasons to do this:
The executable may be unaccessible (disconnected drive/share), or changed (.exe renamed to .bak and replaced by a new .exe without the running process being stopped).
The version data you're trying to read has actually already been loaded into memory, and is available to you by loading this resource, which is always better than to perform extra (relatively slow) disk operations.
Which i adapted into:
function GetModuleVersion(Instance: THandle; out iMajor, iMinor, iRelease, iBuild: Integer): Boolean;
var
fileInformation: PVSFIXEDFILEINFO;
verlen: Cardinal;
rs: TResourceStream;
m: TMemoryStream;
resource: HRSRC;
begin
//You said zero, but you mean "us"
if Instance = 0 then
Instance := HInstance;
//UPDATE: Workaround bug in Delphi if resource doesn't exist
resource := FindResource(Instance, 1, RT_VERSION);
if resource = 0 then
begin
iMajor := 0;
iMinor := 0;
iRelease := 0;
iBuild := 0;
Result := False;
Exit;
end;
m := TMemoryStream.Create;
try
rs := TResourceStream.CreateFromID(Instance, 1, RT_VERSION);
try
m.CopyFrom(rs, rs.Size);
finally
rs.Free;
end;
m.Position:=0;
if not VerQueryValue(m.Memory, '\', (*var*)Pointer(fileInformation), (*var*)verlen) then
begin
iMajor := 0;
iMinor := 0;
iRelease := 0;
iBuild := 0;
Exit;
end;
iMajor := fileInformation.dwFileVersionMS shr 16;
iMinor := fileInformation.dwFileVersionMS and $FFFF;
iRelease := fileInformation.dwFileVersionLS shr 16;
iBuild := fileInformation.dwFileVersionLS and $FFFF;
finally
m.Free;
end;
Result := True;
end;
Warning: The above code crashes sometimes due to a bug in Delphi:
rs := TResourceStream.CreateFromID(Instance, 1, RT_VERSION);
If there is no version information, Delphi tries to raise an exception:
procedure TResourceStream.Initialize(Instance: THandle; Name, ResType: PChar);
procedure Error;
begin
raise EResNotFound.CreateFmt(SResNotFound, [Name]);
end;
begin
HResInfo := FindResource(Instance, Name, ResType);
if HResInfo = 0 then Error;
...
end;
The bug, of course, is that PChar is not always a pointer to an ansi char. With non-named resources they are integer constants, cast to a PChar. In this case:
Name: PChar = PChar(1);
When Delphi tries to build the exception string, and dereferences the pointer 0x00000001 it triggers and access violation.
The fix is to manually call FindResource(Instance, 1, RT_VERSION) first:
var
...
resource: HRSRC;
begin
...
resource := FindResource(Instance, 1, RT_VERSION);
if (resource = 0)
begin
iMajor := 0;
iMinor := 0;
iRelease := 0;
iBuild := 0;
Result := False;
Exit;
end;
m := TMemoryStream.Create;
...
Note: Any code is released into the public domain. No attribution required.
You might want to try FindResource/LockResource API in order to access VERSIONINFO resource of your running module. MSDN article links an example in Examples section, and there is also a community comment with C++ sample code which does exactly this. This starts from already loaded module, not from file name (which is supposedly loaded separately with flag indicating "load resources only", and thus possibly ignoring the fact that image is already mapped into process).
Note that - per provided code snippet - you can find resource of your module and then reuse standard VerQueryValue API to continue resource parsing from there.
I suggest you to read the code JclFileUtils.pas, from JCL.
The "TJclFileVersionInfo" class has some overloaded constructors that allows work from file and from the module handle to get version information.
You can use the JCL class, or at least read it for check how it works in details.
Delphi 10.3.3 provides a reasonable method to read ProductVersion from running exe. Set this value in [Options] [ProductVersion] of project exe.
set variables as:
var
major, minor, build: Cardinal;
Version: String;
set code as:
// [GetProductVersion] uses 1st 3 values separated by decimal (.)
// as: major, minor, build. 4th optional parameter is ignored.
//
// Gui may display -- Major, Minor, Release, Build
// but reports as -- Major, Minor, Build
if GetProductVersion(Application.ExeName, major, minor, build) then
Version := 'v' + major.ToString() + '.' + minor.ToString()
+ ' Beta Build ' + build.ToString();