I just added this function which determines which mailmerge method to use. It seems to work on XP and Windows 2000. Is there any reason why it wouldn't work on NT, Vista, 7 and other Windows versions? I'm thinking will there be an issue with the registry?
function GetMSOfficeVersion: String;
var Reg: TRegistry;
begin
Result := 'Office Version Not Found';
// create the registry object
Reg := TRegistry.Create;
try
// set the root key
Reg.RootKey := HKEY_LOCAL_MACHINE;
// check for Office97
if Reg.OpenKey('\SOFTWARE\Microsoft\Office\8.0', False) then
begin
Result := 'Microsoft Office97';
end;
// check for Office2000
if Reg.OpenKey('\SOFTWARE\Microsoft\Office\9.0', False) then
begin
Result := 'Microsoft Office2000';
end;
// check for OfficeXP -- not sure if this is correct
// you have to verify the key on a machine with OfficeXP
if Reg.OpenKey('\SOFTWARE\Microsoft\Office\10.0', False) then
begin
Result := 'Microsoft OfficeXP(regkey10)';
end;
// check for 11.0
if Reg.OpenKey('\SOFTWARE\Microsoft\Office\11.0', False) then
begin
Result := 'Microsoft OfficeXP(regkey11)';
end;
// check for 12
if Reg.OpenKey('\SOFTWARE\Microsoft\Office\12.0', False) then
begin
Result := 'Microsoft Office2010';
end;
finally
// make sure we free the object we created
Reg.Free;
end;
end;
Probably insufficient privileges. Try using OpenKeyReadOnly instead of OpenKey.
Aside from making sure you create the registry in read-only mode, like TOndrej suggests, you'll also want to fix the version matching in that code, as it is wrong.
Here are the right numbers for the parts where things gets shady in your code fragment:
10.0 = Office XP
11.0 = Office 2003
12.0 = Office 2007
13.0 - doesn't exist, obvious Microsoft/US numbering standards.
14.0 = Office 2010
"Is there any reason why it wouldn't work"
Yes, individual products may create a Software\Office\#.0 entry, you should be checking for a Word subkey in the specific version's key. Even then, f.i. 'Word Viewer' might have created the Word subkey which wouldn't do mail merge. If you really want to go with the registry better look for Word.Application keys in HKEY_CLASSES_ROOT. Apart from Word.Application.# keys the Word.Application key itself has a CurVer subkey.
(Previously I suggested the below but Fox's comment to the question is much better I think.)
I would directly try to create the automation object, if it fails then that version is not available, fallback to a lower version. Or sth. like:
function IsWord14: Boolean;
begin
Result := True;
try
CreateOleObject('Word.Application.14');
except on E:EOleSysError do
if E.ErrorCode = HRESULT($800401F3) then // invalid class string
Result := False
else
raise;
end;
end;
Related
In Delphi 2007 I can easily get the version information of the current project using the following ToolsAPI calls:
procedure Test;
var
ProjectOptions: IOTAProjectOptions;
Project: IOTAProject;
Major: Variant;
Minor: Variant;
Release: Variant;
Build: Variant;
begin
// GxOtaGetCurrentProject is a function in GExpert's GX_OTAUtils unit that returns the current IOTAProject
Project := GxOtaGetCurrentProject;
if Assigned(Project) then begin
ProjectOptions := Project.ProjectOptions;
if Assigned(ProjectOptions) then begin
Major := ProjectOptions.Values['MajorVersion'];
Minor := ProjectOptions.Values['MinorVersion'];
Release := ProjectOptions.Values['Release'];
Build := ProjectOptions.Values['Build'];
end;
end;
end;
In Delphi 10.2.3 this will always return the version 1.0.0.0 regardless of the actual version number. This is the "simple" case: A VCL application.
I also tried the "Keys" value which returns a TStrings pointer. There I also get the FileVersion string, but it is always "1.0.0.0".
I guess this has something to do with the support for various platforms and configurations, but I could not find any documentation, on how it should work now. I also searched the ToolsAPI.pas for "version" and "release", but nothing suspicious showed up.
Any hints on how I can get the version information in Delphi 10.2?
The effective values for version info are stored in separate configurations for build configuration and platform. To get access to the configurations, first get an interface to IOTAProjectOptionsConfigurations:
cfgOpt := project.ProjectOptions as IOTAProjectOptionsConfigurations;
Then iterate over each IOTABuildConfiguration:
for I := 0 to cfgOpt.ConfigurationCount - 1 do
begin
cfg := cfgOpt.Configurations[I];
DoWhatEverWith(cfg);
end;
Be aware that each IOTABuildConfiguration can have several platforms and children:
for S in cfg.Platforms do
begin
DoWhatEverWith(cfg.PlatformConfiguration[S]);
end;
for I := 0 to cfg.ChildCount - 1 do
begin
DoWhatEverWith(cfg.Children[I]);
end;
Depending on which platform and build configuration is currently selected, different values for version info may be used. The current platform and configuration can be retrieved from the IOTAProject properties CurrentPlatform and CurrentConfiguration.
To answer my own question after reading Uwe Raabe's very helpful answer:
The simplest code for getting the version information of the currently active configuration and platform is this:
procedure Test;
var
ProjectOptions: IOTAProjectOptionsConfigurations;
Project: IOTAProject;
Major: Variant;
Minor: Variant;
Release: Variant;
Build: Variant;
cfg: IOTABuildConfiguration;
begin
// GxOtaGetCurrentProject is a function in GExpert's GX_OTAUtils unit that returns the current IOTAProject
Project := GxOtaGetCurrentProject;
if Assigned(Project) then begin
// as per Uwe's answer
ProjectOptions := Project.ProjectOptions as IOTAProjectOptionsConfigurations;
if Assigned(ProjectOptions) then begin
// this is the currently active configuration
cfg := ProjectOptions.ActiveConfiguration;
if Assigned(cfg) then begin
// Note that the names of the version properties are different!
Major := cfg.GetValue('VerInfo_MajorVer', True);
Minor := cfg.GetValue('VerInfo_MinorVer', True);
Release := cfg.GetValue('VerInfo_Release', True);
Build := cfg.GetValue('VerInfo_Build', True);
end;
end;
end;
end;
So it is quite easy as long as you only want the values from the current configuration (which in my case is exactly what I need).
Get the current project
Get the IOTAProjectOptionsConfigurations interface
Get the currently active configuration
Read the values using GetValue. Note the new names for the version info properties. Also note, that you must pass True for the IncludeInheritedValues parameter.
Some notes:
You can enumerate the available properties with GetPropertyCount() and GetPropertyName().
Instead of calling GetValue('name', True) as above, you can also use the property Value['name']. It automatically retrieves the resulting value recursing to ancestor configurations as necessary.
There is also the option to get type casted values via the AsInteger property. Again this also takes ancestor configurations into account. Note that this might raise exceptions if the type conversion fails.
If the "Include Version Info" option is not checked for the current configuration or any of the ancestors, the GetValue calls return version 1.0.0.0 for whatever reason. There seems to be no way to check for that option, but I may be wrong here.
Embarcadero could have easily provided backwards compatibility by rerouting the ProjectOptions.Values['MajorVersion'] and related calls to the values of the current configuration. They didn't, OK, their choice, but in that case I would have expected some documentation.
The change goes back to Delphi XE.
In my program, the user completes a form and then presses Submit. Then, a textfile or a random extension file is created, in which all the user's information is written. So, whenever the user runs the application form, it will check if the file, which has all the information, exists, then it copies the information and pastes it to the form. However, it is not working for some reason (no syntax errors):
procedure TForm1.FormCreate(Sender: TObject);
var
filedest: string;
f: TextFile;
info: array[1..12] of string;
begin
filedest := ExtractFilePath(ParamStr(0)) + 'User\Identity\IdentityofMyself.txt';
if FileExists(filedest) then
begin
AssignFile(f,filedest);
Reset(f);
ReadLn(info[1], info[2], info[3], info[4], info[5], info[6], info[7],
info[8], info[9], info[10], info[11], info[12]);
Edit1.Text := info[1];
Edit2.Text := info[2];
ComboBox1.Text := info[3];
ComboBox5.Text := info[4];
ComboBox8.Text := info[4];
ComboBox6.Text := info[5];
ComboBox7.Text := info[6];
Edit3.Text := info[7];
Edit4.Text := info[8];
Edit5.Text := info[11];
Edit6.Text := info[12];
ComboBox9.Text := info[9];
ComboBox10.Text := info[10];
CloseFile(f);
end
else
begin
ShowMessage('File not found');
end;
end;
The file exists, but it shows the message File not found. I don't understand.
I took the liberty of formatting the code for you. Do you see the difference (before, after)? Also, if I were you, I would name the controls better. Instead of Edit1, Edit2, Edit3 etc. you could use eFirstName, eLastName, eEmailAddr, etc. Otherwise it will become a PITA to maintain the code, and you will be likely to confuse e.g. ComboBox7 with ComboBox4.
One concrete problem with your code is this line:
readln(info[1], info[2], info[3], info[4], info[5], info[6], info[7],
info[8], info[9], info[10], info[11], info[12]);
You forgot to specify the file f!
Also, before I formatted your code, the final end of the procedure was missing. Maybe your blocks are incorrect in your actual code, so that ShowMessage will be displayed even if the file exists? (Yet another reason to format your code properly...)
If I encountered this problem and wanted to do some quick debugging, I'd insert
ShowMessage(BoolToStr(FileExists(filedest), true));
Exit;
just after the line
filedest := ...
just to see what the returned value of FileExists(filedest) is. (Of course, you could also set a breakpoint and use the debugger.)
If you get false, you probably wonder what in the world filedest actually contains: Well, replace the 'debugging code' above with this one:
ShowMessage(filedest);
Exit;
Then use Windows Explorer (or better yet: the command prompt) to see if the file really is there or not.
I'd like to mention an another possibility to output a debug message (assuming we do not know how to operate real debugger yet):
{ ... }
filedest := ExtractFilePath(ParamStr(0)) + 'User\Identity\IdentityofMyself.txt';
AllocConsole; // create console window (uses Windows module) - required(!)
WriteLn('"' + filedest + '"'); // and output the value to verify
if FileExists(filedest) then
{ ... }
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();
Been trying to use the following code in order to check if Windows Aero is enabled:
function AeroEnabled: boolean;
var
enabled: bool;
begin
// Function from the JwaDwmapi unit (JEDI Windows Api Library)
DwmIsCompositionEnabled(enabled);
Result := enabled;
end;
...
if (CheckWin32Version(5,4)) and (AeroEnabled) then
CampaignTabs.ColorBackground := clBlack
else begin
GlassFrame.Enabled := False;
CampaignTabs.ColorBackground := clWhite;
end;
However, doing so on a pre-vista machine causes the app to crash because the DWMApi.dll is missing. I've also tried this code however it produces 2 AV's in a row. How can I do this ? I am using Delphi 2010. :)
You've got your versions wrong. Vista/2008 server are version 6.0. Your test should be:
CheckWin32Version(6,0)
I believe that you are using Delphi 2010 or later in which case you should simply call the DwmCompositionEnabled function from the built-in Dwmapi unit. This organises the version check and the delayed binding for you. No need for JEDI.
Edit: Text below was written before the question was edited.
Probably the easiest approach is to check the Windows version. You need Win32MajorVersion>=6 (i.e. Vista or 2008 server) in order to call DwmIsCompositionEnabled.
If you were binding yourself then you would call LoadLibrary with DWMApi.dll and if that succeeded you would then call GetProcAddress to bind. If that succeeded you are good. But, as I said, since you aren't handling the binding yourself then a version check is probably the simplest.
So the function would be:
function AeroEnabled: boolean;
var
enabled: bool;
begin
if Win32MajorVersion>=6 then begin
DwmIsCompositionEnabled(enabled);
Result := enabled;
end else begin
Result := False;
end;
end;
Note, I'm assuming that your library is doing late binding, i.e. explicit linking. If not then you'll need LoadLibrary/GetProcAddress, exactly as is done in #RRUZ's code to which you link.