I am trying to call CreateDC to create a printer device context:
printerDC := CreateDC('WINSPOOL', 'HP DeskJet 930C/932C/935C', nil, nil);
The code works in Windows 7, but fails in Windows 10 - returning null.
I really am using a real printer:
What to pass
The MSDN documentation says that if you want to get a printer device context you must pass WINSPOOL as the driver:
How To: Retrieve a Printer Device Context
To render to a specific printer, you must specify "WINSPOOL" as the device and pass the correct name of the printer to CreateDC. You can also pass a DEVMODE structure in the call to CreateDC if you want to provide device-specific initialization data for the device driver when you create the device context.
The following example shows a call to CreateDC in which the "WINSPOOL" driver is selected and the printer name is specified by name.
C++
printerDC = CreateDC( L"WINSPOOL", printerName, NULL, NULL);
I mention it, because there is a lot of code out there that passes:
empty string
null
the name of the device
the name of the driver
winspool
WINSPOOL
Background
Deep in the Delphi VCL framework of Vcl.Printers is a call to the Windows function CreateIC. On my Windows 10 desktop, the calls fails (returns NULL rather than a valid information context).
The CreateIC function does not document any way in which it can fail. Nor does it document that it supports calling GetLastError to get the error. But if i call GetLastError i get error code 50:
ERROR_NOT_SUPPORTED
50 (0x32)
The request is not supported.
Minimum reproducible
I extracted the code from Vcl.Printers.pas and boiled it down to a simple reproducable sample:
procedure TForm1.Button1Click(Sender: TObject);
var
driver, device, output: string;
dc: HDC;
le: DWORD;
begin
driver := '';
device := 'Microsoft XPS Document Writer';
output := '';
dc := CreateIC(PChar(driver), PChar(device), PChar(output), nil);
if dc = 0 then
begin
le := GetLastError;
raise Exception.CreateFmt('Could not get information context for printer "%s": %s (%d)', [device, SysErrorMessage(le), le]);
end;
end;
The code fails on my Windows 10 desktop, but works on Windows 7.
And in both cases i'm using the same printer:
Windows 7
Windows 10
What am i1 doing wrong?
CreateDC also fails
CreateIC is just a "lightweight" form of CreateDC (you can use it to get information about a device, but you can't draw GDI with it). It also means that CreateDC also fails on Windows 10:
procedure TForm1.Button1Click(Sender: TObject);
var
driver, device: string;
dc: HDC;
le: DWORD;
begin
driver := '';
device := 'Microsoft XPS Document Writer';
dc := CreateDC(PChar(driver), PChar(device), nil, nil);
if dc = 0 then
begin
le := GetLastError;
raise Exception.CreateFmt('Could not get device context for printer "%s": %s (%d)', [device, SysErrorMessage(le), le]);
end;
end;
Some people have suggested duplicating the device name into the driver name:
CreateDC('Microsoft XPS Document Writer', 'Microsoft XPS Document Writer', nil, nil);
and i've also seen try placing WINSPOOL in the driver name:
CreateDC('WINSPOOL', 'Microsoft XPS Document Writer', nil, nil);
It's all printers
I started out this question thinking it was just CreateIC.
then i discovered that CreateDC also fails, so i updated the title
then i thought it was because i happened to be using the Microsoft XPS Document Writer, so i updated the title
then i discovered it's all printers on Windows 10, so i updated the title again
This is the natural evolution as people suggest things, and i discover more about the nature of the problem.
| Driver | Device | Result |
|------------------------------------|---------------------------------|--------|
| '' (empty string) | 'Microsoft XPS Document Writer' | Fails |
| 'Microsoft XPS Document Writer' | 'Microsoft XPS Document Writer' | Fails |
| 'WINSPOOL' | 'Microsoft XPS Document Writer' | Fails |
| nil | 'Microsoft XPS Document Writer' | Fails |
| 'Microsoft XPS Document Writer v4' | 'Microsoft XPS Document Writer' | Fails |
| '' (empty string) | 'HP DeskJet 930C/932C/935C' | Fails |
| 'HP DeskJet 930C/932C/935C' | 'HP DeskJet 930C/932C/935C' | Fails |
| 'WINSPOOL' | 'HP DeskJet 930C/932C/935C' | Fails |
| nil | 'HP DeskJet 930C/932C/935C' | Fails |
For all i know, calling CreateDC to create a display device context is also broken in Windows 10. Like you, i've not tested it.
Bonus Reading
QC127390: Operation not supported on selected printer
Random "Printer selected is not valid" errors on Win2K8R2
MSDN Forums: XPS and StartDoc
GDI Drawing and Printing
CreateDC() problem with printer drivers on Windows Server 200x
Printing from a Windows Service
MSDN: How To: Retrieve a Printer Device Context
1 Not me; Embarcadero
Related
I have a program that uses SetCursorPos to position the cursor. The program operates as it is supposed to when running on real hardware but, when running in a VM (VMware workstation 10.0.7) it doesn't work. The cursor does not move. I tried using SendInput instead (the syscall it makes is different, because of that, I thought it might work), the result is the same as with SetCursorPos, it works on real hardware, does not work when running in a VM.
The question is: does anyone know if either SetCursorPos or SendInput can be made to work in a VM and if yes, how ? Any other way to position the cursor at a specific place that works in a VM would be welcome as well.
Thank you for your help.
For anyone who cares to try, here is some of the code I've tried.
{$APPTYPE CONSOLE}
program ConsoleSetCursorPos;
uses
Windows
;
function GetConsoleWindow : HWND; stdcall; external kernel32;
procedure DoIt;
var
ConsoleWindow : HWND;
ClientRect : TRECT;
CursorPosRetVal : BOOL;
LastError : dword;
Desktop : HDESK;
begin
// the code below is not normally necessary - for testing only
Desktop := OpenInputDesktop(0, false, WINSTA_WRITEATTRIBUTES);
LastError := GetLastError;
writeln;
writeln('From OpenInputDesktop');
writeln('Last error (decimal) : ', LastError);
if Desktop = 0 then
begin
writeln('Program terminated due to OpenInputDesktop failure');
halt(255);
end;
if not SetThreadDesktop(Desktop) then
begin
writeln('Program terminated due to SetThreadDesktop failure');
halt(255);
end;
writeln;
// end of normally unnecessary code
SetLastError(0);
ConsoleWindow := GetConsoleWindow;
GetClientRect(ConsoleWindow, ClientRect);
ClientToScreen(ConsoleWindow, ClientRect.TopLeft);
CursorPosRetVal := SetCursorPos(ClientRect.Left, ClientRect.Top);
LastError := GetLastError;
if not CursorPosRetVal
then writeln('SetCursorPos returned false (failed)')
else writeln('SetCursorPos returned true (succeeded)');
writeln('Last error (decimal) : ', LastError);
if Desktop <> 0 then CloseDesktop(Desktop);
end;
begin
DoIt;
end.
As the remarks on SetCursorPos doc:
The cursor is a shared resource. A window should move the cursor only
when the cursor is in the window's client area.
The calling process must have WINSTA_WRITEATTRIBUTES access to the
window station.
The input desktop must be the current desktop when you call
SetCursorPos. Call OpenInputDesktop to determine whether the current
desktop is the input desktop. If it is not, call SetThreadDesktop with
the HDESK returned by OpenInputDesktop to switch to that desktop.
Or you can take the same try to un-installed the mouse driver from the VM as this answer.
I am trying to write a small program in Delphi 2007 to access files off of a portable USB drive whenever it is plugged in to a Windows 7 machine. This drive does not show up as a standard drive letter though. It appears under Portable Devices in Windows Explorer. I have written the following code to enumerate all the items under 'Computer':
Procedure TfrmMain.ComputerChanged(Var Msg: TMessage);
Var
Enum: IEnumIDList;
Fetched: Longword;
Item: PItemIDList;
Path: String;
Computer: IShellFolder;
StrRet: TSTRRET;
Begin
Status('Computer changed... Checking folders.');
fDesktop.BindToObject(fCompPidl, Nil, IID_IShellFolder, Computer);
If Assigned(Computer) And
(Computer.EnumObjects(Self.Handle, SHCONTF_FOLDERS, Enum) = NOERROR) Then
Begin
While (Enum.Next(1, Item, Fetched) = NOERROR) Do
Begin
FillChar(StrRet, SizeOf(StrRet), #0);
Computer.GetDisplayNameOf(Item, SHGDN_FORADDRESSBAR or SHGDN_NORMAL, StrRet);
Path := StrRetToStr(StrRet, Item);
Status(Path);
End;
End;
End;
(note: the Status procedure just outputs a message to a TMemo.)
This is called whenever my application is notified of a change by the Windows shell subsystem. It enumerates all of the local drives and network drives but nothing else (iCloud Photos drive is missing as well).
Does anyone know how I can access the files on these virtual drives?
You more than likely aren't initializing COM correctly. Your code will work as-is if you don't call CoInitializeEx or if you call it with a bad value, but the Portable Device drivers require apartment threading to work.
Based on your code, here's a sample app that works correctly and shows portable devices. If you comment out the CoInitializeEx/CoUninitialize calls or pass in COINIT_MULTITHREADED instead it will still work, but it only shows the drives.
program ListMyComputer;
{$APPTYPE CONSOLE}
uses
ComObj, ShlObj, ShellApi, ShLwApi, ActiveX, Windows, SysUtils;
var
Enum: IEnumIDList;
Fetched: Longword;
CompPidl, Item: PItemIDList;
Path: PWideChar;
Desktop, Computer: IShellFolder;
StrRet: TSTRRET;
begin
CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
try
WriteLn('Computer changed... Checking folders.');
SHGetDesktopFolder(Desktop);
SHGetFolderLocation(0, CSIDL_DRIVES, 0, 0, CompPidl);
Desktop.BindToObject(CompPidl, Nil, IID_IShellFolder, Computer);
CoTaskMemFree(CompPidl);
If Assigned(Computer) And
(Computer.EnumObjects(0, SHCONTF_FOLDERS, Enum) = NOERROR) Then
Begin
While (Enum.Next(1, Item, Fetched) = NOERROR) Do
Begin
FillChar(StrRet, SizeOf(StrRet), #0);
Computer.GetDisplayNameOf(Item, SHGDN_FORADDRESSBAR or SHGDN_NORMAL, StrRet);
StrRetToStr(#StrRet, Item, Path);
WriteLn(Path);
CoTaskMemFree(Path);
End;
End;
WriteLn('Enumeration complete');
ReadLn;
finally
CoUninitialize
end;
end.
Thanks to #SertacAkyuz for pointing out the need to use Windows Portable Device API which lead me to this Experts Exchange question discussing the same thing. Sinisa Vuk supplied an awesome code example to answer that question which I have linked (it's too long to embed) here with permission: http://pastebin.com/0hSWv5pE
Using Windows Print Spooler API's, we can print out XPS files.
The problem is that the print spooler ignores the default printer settings.
(We've given up on trying to apply printer settings. Default printer settings will just have to suffice.)
For example... the printouts always come out in colour and on one paper per page, regardless of what the settings are set to in the control panel: black & white/colour, duplex/not duplex, multiple pages per sheet/single page per sheet.
Other applications such as MS Word and Adobe respect the default printer settings.
We're using Delphi XE2 and Windows 7 64-bit.
This test code is self contained, so you can just paste it in to test it...
Populate a combo box with printer names using:
uses
Printers
ComboBox1.Items.Assign(Printer.Printers);
Printing procedure:
uses
Winapi.WinSpool
procedure PrintXPS(PrinterName, FileNameXPS: string; ParentFormHandle: THandle = 0);
// Printer handle
procedure Printer_Open(out Printer: THandle; Defaults: PPrinterDefaultsW = nil);
begin
if not OpenPrinterW(PWideChar(PrinterName), Printer, Defaults) then
RaiseLastOSError;
end;
procedure Printer_Close(Printer: THandle);
begin
if not ClosePrinter(Printer) then
RaiseLastOSError;
end;
// Print jobs
function JobCreate(Printer: THandle; FileName: string): Cardinal;
var
lBufferSize: Cardinal;
lAddJobInfo: PAddJobInfo1W;
begin
// Create job
AddJobW(Printer, 1, nil, 0, lBufferSize);
GetMem(lAddJobInfo, lBufferSize);
try
if not AddJobW(Printer, 1, lAddJobInfo, lBufferSize, lBufferSize) then
RaiseLastOSError;
Result := lAddJobInfo.JobId;
// Copy the file into place
CopyFile(PWideChar(FileName), lAddJobInfo.Path, True);
finally
FreeMem(lAddJobInfo, lBufferSize);
end;
end;
procedure JobStart(Printer: THandle; JobID: Cardinal);
begin
if not ScheduleJob(Printer, JobID) then
RaiseLastOSError;
end;
var
PrinterA: THandle;
JobID: Cardinal;
begin
if not FileExists(FileNameXPS) then
raise Exception.Create('File not found: ' + FileNameXPS);
Printer_Open(PrinterA, nil);
try
JobID := JobCreate(PrinterA, FileNameXPS);
JobStart(PrinterA, JobID);
finally
Printer_Close(PrinterA);
end;
end;
Much as I know you can not change the appearance of a .xps file.
XPS stands for XML Paper Specification, it is virtually an "electronic paper", the document on screen and in print is exactly the way it was intended by the author. Anyone who has ever experienced, how the page layout of an Office document on a shared computer because of a different default printer has shifted, appreciate it.
EDIT
Test
1.) Default black and white printer settings. Open the. xps file Print.
With IE == colored output.
With XPS Viewer EP == colored output.
Default printer settings == ignored.
2.) Dialog: Printer-settings manually to print black and white set.
IE == black and white output.
XPS Viewer EP == black and white output.
I have an NT service that calls a console program written in Delphi 7, let's call it failover.exe that in turn calls NETSH using a procedure I found:
procedure ExecConsoleApp(CommandLine: ansistring; Output, Errors: TStringList);
Note: ExecConsoleApp uses CreateProcess, see the following link for full code: http://www.delphisources.ru/pages/faq/base/createprocess_console.html
I would pass the following to CommandLine before calling ExecConsoleApp:
cmd.exe /c "C:\Windows\system32\netsh.exe interface delete address "Wireless Network Connection" 192.168.0.36"
ExecConsoleApp will return an error:
The system cannot find the file specified
But if I were to run it in Command Prompt, it runs perfectly.
The strange thing is that I remembered it working on the first attempt on that 2003 Server, but after that, it failed regardless of the number of times I tried. In one of the attempt, I've also tried assigning logon as administrator user to the service but to no avail. Neither does fiddling with file security help.
I don't have a Win 2003 server to test with in office, but I have tested it on XP and Win7 and ExecConsoleApp works perfectly, although on XP, I had to amend ExecConsoleApp to execute from system32\wbem in order for it work work:
Res := CreateProcess(nil, PChar(CommandLine), nil, nil, True,
// **** Attention: Amended by to point current directory to system32\wbem, this is to solve an error returned by netsh.exe if not done otherwise.
// CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, #env, nil, si, pi);
CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, #env, pchar(GetSystemPath(WindRoot) + 'system32\wbem'), si, pi);
I've researched for a day but no clues, hope someone can help. Thanks.
Additional remarks -
Server is 32 bit Win2k3.
Tried domain administrator, doesn't work.
Code snippets:
Procedure ExecConsoleApp(CommandLine: ansistring; Output, Errors: TStringList);
var
sa: TSECURITYATTRIBUTES;
si: TSTARTUPINFO;
pi: TPROCESSINFORMATION;
hPipeOutputRead: THANDLE;
hPipeOutputWrite: THANDLE;
hPipeErrorsRead: THANDLE;
hPipeErrorsWrite: THANDLE;
Res, bTest: boolean;
env: array[0..100] of char;
szBuffer: array[0..256] of char;
dwNumberOfBytesRead: DWORD;
Stream: TMemoryStream;
begin
sa.nLength := sizeof(sa);
sa.bInheritHandle := True;
sa.lpSecurityDescriptor := nil;
CreatePipe(hPipeOutputRead, hPipeOutputWrite, #sa, 0);
CreatePipe(hPipeErrorsRead, hPipeErrorsWrite, #sa, 0);
ZeroMemory(#env, SizeOf(env));
ZeroMemory(#si, SizeOf(si));
ZeroMemory(#pi, SizeOf(pi));
si.cb := SizeOf(si);
si.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
si.wShowWindow := SW_HIDE;
si.hStdInput := 0;
si.hStdOutput := hPipeOutputWrite;
si.hStdError := hPipeErrorsWrite;
(* Remember that if you want to execute an app with no parameters you nil the
second parameter and use the first, you can also leave it as is with no
problems. *)
Res := CreateProcess(nil, PChar(CommandLine), nil, nil, True,
CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, #env, nil, si, pi);
// Procedure will exit if CreateProcess fail
if not Res then
begin
CloseHandle(hPipeOutputRead);
CloseHandle(hPipeOutputWrite);
CloseHandle(hPipeErrorsRead);
CloseHandle(hPipeErrorsWrite);
Exit;
end;
CloseHandle(hPipeOutputWrite);
CloseHandle(hPipeErrorsWrite);
//Read output pipe
Stream := TMemoryStream.Create;
try
while True do
begin
bTest := ReadFile(hPipeOutputRead, szBuffer, 256, dwNumberOfBytesRead, nil);
if not bTest then
begin
break;
end;
OemToAnsi(szBuffer, szBuffer);
Stream.Write(szBuffer, dwNumberOfBytesRead);
end;
Stream.Position := 0;
Output.LoadFromStream(Stream);
finally
Stream.Free;
end;
//Read error pipe
Stream := TMemoryStream.Create;
try
while True do
begin
bTest := ReadFile(hPipeErrorsRead, szBuffer, 256, dwNumberOfBytesRead, nil);
if not bTest then
begin
break;
end;
OemToAnsi(szBuffer, szBuffer);
Stream.Write(szBuffer, dwNumberOfBytesRead);
end;
Stream.Position := 0;
Errors.LoadFromStream(Stream);
finally
Stream.Free;
end;
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(hPipeOutputRead);
CloseHandle(hPipeErrorsRead);
end;
cmdstring :=
'cmd.exe /c "' + GetSystemPath(WindRoot) + 'system32\netsh.exe interface ' +
ip + ' delete address "' + NetworkInterfaceName + '" ' + VirtualFailoverIPAddress + '"';
logstr('cmdstring: ' + cmdstring);
ExecConsoleApp(cmdstring, OutP, ErrorP);
if OutP.Text <> '' then
begin
logstr('Delete IP Result: ' + OutP.Text);
end
else
begin
logstr('Delete IP Error: ' + ErrorP.Text);
end;
Tried running netsh.exe directly instead of "cmd.exe /c C:\Windows\system32\netsh.exe...", and got the same "The system cannot find the file specified." error. I also accidentally discovered that if I were to issue a wrong netsh command, netsh will actually return an error, e.g.
netsh interface ip delete address "LocalArea Connection" 10.40.201.65
Invalid interface LocalArea Connection specified.
The following is returned if i correct the typo "LocalArea" to "Local Area".
netsh interface ip delete address "Local Area Connection" 10.40.201.65
The system cannot find the file specified.
Again, I must repeat that the same command works perfectly fine if I issue it via Command Prompt instead of from my application.
Have you tried this?
if not CreateProcess(PChar('C:\Windows\system32\netsh.exe'), PChar(Arguments), ...) then
begin
// Do somehting with `GetLastError`
end;
Of course it would be better to detect the path of C:\Windows\system32 at runtime as this could be on another driver or in another directory.
When you run it this way you can get an error message from Windows using the GetLastError call right after CreateProcess.
The ExecConsoleApp procedure is flawed, because it doesn't return the GetLastError or even any indication that CreateProcess failed.
You should fix this first. Maybe add raise EExecConsoleAppCreateProcessFailed.Create(SysErrorMessage(GetLastError)) before Exit to the code.
You shouldn't use cmd.exe /c as a prefix. It's redundant and it makes error diagnostics more difficult. GetLastError might not reflect the correct error code, because you're delegating the creation of the acutal netsh.exe process to cmd.
The "cannot find the file specified" error may also occur if an implicitly loaded DLL required by the executable is not available. In this situation, that is the most likely cause - some essential DLL is not being found when netsh.exe is being run in a non-interactive context.
Use Process Monitor (available for download from Microsoft's web site) to record the file system operations that are taking place during the attempt. Look for file not found errors either in the context of your service process or in the context of the netsh.exe process.
I think this question was already asked, but I couldn't find a solution which works for me. I use Delphi 7 under Windows 7 Ultimate, 64 bit. Actually I started writing application under 32 bit OS, but then changes PC, so now its 64. In my program I use registration process with Licence ID generated from PROGID value of Windows. Unfortunately it doesn't read the value, seems like it is looking in a different folder, probably redirected by Windows 64 to 32 bit registry. Can you help? This is the code I use:
Registry := TRegistry.Create(KEY_READ OR $0100);
try
Registry.Lazywrite := false;
Registry.RootKey := HKEY_LOCAL_MACHINE;
if CheckForWinNT = true then
Begin
if not Registry.OpenKeyReadOnly('\Software\Microsoft\Windows NT\CurrentVersion') then showmessagE('cant open');
end
else
Registry.OpenKeyReadOnly('\Software\Microsoft\Windows\CurrentVersion');
result := Registry.ReadString('ProductID');
Registry.CloseKey;
finally
Registry.Free;
end; // try..finally
Also, do you know how to find whether program is running under 64 bit or 32 bit computer in Delphi 7?
You already asked this question see Registry ReadString method is not working in Windows 7 in Delphi 7.
So you know that you have to add $0100 in the TRegistry.Create. The problem with your code is that you use OpenKeyReadOnly which resets the Access property of the registry to KEY_READ, so KEY_READ or $0100 is lost.
Just use OpenKey instead of OpenKeyReadOnly, this won't reset your Access property.
Here is some Delphi 7 code to detect whether you are running in a 64-bit OS:
function Is64BitOS: Boolean;
type
TIsWow64Process = function(Handle:THandle; var IsWow64 : BOOL) : BOOL; stdcall;
var
hKernel32 : Integer;
IsWow64Process : TIsWow64Process;
IsWow64 : BOOL;
begin
// we can check if the operating system is 64-bit by checking whether
// we are running under Wow64 (we are 32-bit code). We must check if this
// function is implemented before we call it, because some older versions
// of kernel32.dll (eg. Windows 2000) don't know about it.
// see http://msdn.microsoft.com/en-us/library/ms684139%28VS.85%29.aspx
Result := False;
hKernel32 := LoadLibrary('kernel32.dll');
if (hKernel32 = 0) then RaiseLastOSError;
#IsWow64Process := GetProcAddress(hkernel32, 'IsWow64Process');
if Assigned(IsWow64Process) then begin
IsWow64 := False;
if (IsWow64Process(GetCurrentProcess, IsWow64)) then begin
Result := IsWow64;
end
else RaiseLastOSError;
end;
FreeLibrary(hKernel32);
end;
(Shamelessly plagiarized from myself, here)
It looks like you are passing KEY_WOW64_64KEY ($0100), so you should be looking at the 64-bit registry branch. If you want to look at the 32-bit registry branch, you should pass KEY_WOW64_32KEY ($0200).
As to your side question, whether it's a 64-bit computer (which is not the same thing as running on a 64-bit OS), have a look at the answers to this question.
I know this topic is about delphi 7, but I thought I was having problems reading the registry and came here to learn.. I ended up using Key_Read instead of all the extras suggested here.
I'm using Delphi 2010 and I used Key_Read just fine.
Here is my part of my source that works:
//Search registry
reg:=TRegistry.Create(KEY_READ);
with reg do begin
try
RootKey := HKEY_LOCAL_MACHINE;
if OpenKey('\SOFTWARE\Wow6432Node\Blizzard Entertainment\World of Warcraft',false) then
begin
memo.Lines.Add('HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Blizzard Entertainment\World of Warcraft - exists');
wowdir1 := readstring('InstallPath');
memo.Lines.Add('InstallPath - ' + wowdir1);
newline;
closekey;
end;
if OpenKey('\SOFTWARE\Wow6432Node\Blizzard Entertainment\World of Warcraft',false) then
begin
memo.Lines.Add('HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Blizzard Entertainment\World of Warcraft - exists');
wowdir2 := readstring('GamePath');
memo.Lines.Add('GamePath - ' + wowdir2);
newline;
wowdir1 := readstring('');
closekey;
end;
if OpenKey('\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\World of Warcraft',false) then
begin
memo.Lines.Add'HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\World of Warcraft - exists');
wowdir3 := readstring('InstallLocation');
memo.Lines.Add('InstallLocation - ' + wowdir3);
newline;
wowdir1 := readstring('');
closekey;
end;
finally
reg.Free;
end;
I tried the other Keys that are displayed here and found I don't need KEY_WOW64_64KEY OR KEY_WOW64_32KEY. This must have been a bug that has been corrected in Delphi 2010.