Why is COMMON_APPDATA returned as a null string on Windows XP - delphi

One of my users at a large university (with, I imagine, the aggressive security settings that university IT departments general have on their computers) is getting an empty string returned by Windows XP for CSIDL_COMMON_APPDATA or CSIDL_PERSONAL. (I'm not sure which of these is returning the empty string, because I haven't yet examined his computer to see how he's installed the software, but I'm pretty sure it's the COMMON_APPDATA...)
Has anyone encountered this or have suggestions on how to deal with this?
Here's the Delphi code I'm using to retrieve the value:
Function GetSpecialFolder( FolderID: Integer):String;
var
PIDL: PItemIDList;
Path: array[0..MAX_PATH] of Char;
begin
SHGetSpecialFolderLocation(Application.Handle, FolderID, PIDL);
SHGetPathFromIDList(PIDL, Path);
Result := Path;
end; { GetSpecialFolder }
ShowMessage(GetSpecialFolder(CSIDL_COMMON_APPDATA)); <--- This is an empty string
Edit:
Figuring out this API made me feel like I was chasing my tail - I went in circles trying to find the right call. This method and others similar to it are said to be deprecated by Microsoft (as well as by a earlier poster to this question (#TLama?) who subsequently deleted the post.) But, it seems like most of us, including me, regularly and safely ignore that status.
In my searches, I found a good answer here on SO from some time ago, including sample code for the non-deprecated way of doing this: what causes this error 'Unable to write to application file.ini'.

If you want to find out why an API call is failing you need to check the return values. That's what is missing in this code.
You need to treat each function on its own merits. Read the documentation on MSDN. In the case of SHGetSpecialFolderLocation, the return value is an HRESULT. For SHGetPathFromIDList you get back a BOOL. If that is FALSE then the call failed.
The likely culprit here is SHGetSpecialFolderLocation, the code that receives the CSIDL, but you must check for errors whenever you call Windows API functions.
Taking a look at the documentation for CSIDL we see this:
CSIDL_COMMON_APPDATA
Version 5.0. The file system directory that contains application data for all users. A typical path is C:\Documents and Settings\All
Users\Application Data. This folder is used for application data that
is not user specific. For example, an application can store a
spell-check dictionary, a database of clip art, or a log file in the
CSIDL_COMMON_APPDATA folder. This information will not roam and is
available to anyone using the computer.
If the machine has a shell version lower than 5.0, then this CSIDL value is not supported. That's the only documented failure mode for this CSIDL value. I don't think that applies to your situation, so you'll just have to see what the HRESULT status code has to say.

Related

Ctypes mozilla unknown error

In a mozille extension I run:
SecSess.Logger.info("ctypes test");
Components.utils.import("resource://gre/modules/ctypes.jsm");
SecSess.Logger.info("1");
this.lib = ctypes.open("libcrypto.so");
SecSess.Logger.info("2");
var a = new Uint8Array(1<<10);
SecSess.Logger.info("3");
var ptr = new ctypes.uint8_t.ptr(a.buffer);
SecSess.Logger.info("4");
Why this ugly logging after each step you might ask? Well this code fails without showing me an error. (or at least I can't find the error message)
This is printed:
ctypes test
1
2
3
So the 5th log message is never printed which means the following statement never completes:
var ptr = new ctypes.uint8_t.ptr(a.buffer);
This is a simplified version of some old code I have to work with and which I also found online as being valid. However it doesn't work. This add-on wasn't developped using the new SDK with jpm. Quite frankly I don't know how and when it was developped but I need to run some tests on it. It comes with a few source files ordered in a components and a modules directory and a chrome.manifest and install.rdf in the root. I copied these files to the extension directory of Mozilla in order for it to work. The extension executes but there seems to be a problem with ctypes. Aren't ctypes fully supported anymore or are these old style add-on no longer valid for the modern Firefox?
Regards,
Roel
I think they landed a a patch to disallow making a pointer from buffers. I'll double check.
Edit:
Ah per this: https://developer.mozilla.org/en-US/docs/Mozilla/js-ctypes/Using_js-ctypes/Working_with_ArrayBuffers, you don't have to wrap it with a ctypes.uint8_t.ptr just pass it like a.buffer to wherever you need it. :)

What could cause an unexpected 'File In Use' Error

I have a Delphi5 application which exports a file (.pdf) and a very small metadata file to a network location. The intention is that these 2 files should be processed, and then removed, by a polling .NET application.
My approach is to
Write the metadata file with the extension '.part'
Generate the .pdf
Rename the .part file to .dat
The .NET process is looking for files with the extension '.dat' only, so I would expect there to be no conflict between the 2 reader/writers. However, the .NET process is occasionally logging the following error ...
System.IO.IOException: The process cannot access the file '\\server\Path\FileName.dat' because it is being used by another process.
(I say occasionally - we are currently testing, so when volumes increase this may become much more of an issue)
The Delphi code looks like this :
AssignFile(FTextFile, Format('%s\%s.part', [DMSPath, FullFileName]));
try
try
ReWrite(FTextFile);
Writeln(FTextFile, MetaDataString);
finally
CloseFile(FTextFile);
end;
except
raise ELogFileException.Create( LOGFILEWRITEFAILURE );
end;
Then there is a separate method which performs the following lines of code
if FindFirst(Format('%s\*.part',[DMSPath]), faAnyFile, SearchRec) = 0 then begin
repeat
OldName := Format('%s\%s',[DMSPath, SearchRec.Name]);
NewName := Format('%s\%s',[DMSPath, ChangeFileExt(SearchRec.Name, '.dat')]);
RenameFile(OldName, NewName);
until FindNext(SearchRec) <> 0;
FindClose(SearchRec);
end;
I cannot see anything inherently wrong with this code and we have a couple of remedies in mind, but I have 2 questions
Should I try a different technique to more reliably protect the '.dat' file until it is fully ready
What circumstances could be causing this?
So far there has been one suggested cause - Antivirus software.
Any suggestions as to how the file might be produced differently? Note that my application is Delphi5; I wondered if there was a newer, more 'atomic' version of the 'MoveFileA' WinApi call I could use.
In the past, we had a problem with file being locked like that. Investigation pointed to Windows' Prefetch. The file being affected were not on a network directory though.
As far as I know, Prefetch only work on process startup and/or while booting (Controlled by a registry key), so it might not apply to your current situation.
You can check the "C:\"Windows"\Prefetch\" directory. If prefetch is active, it should contains multiple *.pf files. If there is one with your executable filename, it might be worth investigating.
Personally speaking, because there are multiple files involved, I'd create a separate lock file (eg, myfile.lck) that you write first. If the polling app sees it in the folder, then it stops looking for other files. Once that file is gone, then it dives deeper. I don't know if this would solve the problem you're encountering or not, but I'd give it a try. (Files with .dat extensions are frequently created by malicious evildoers, so they can raise spurious issues through other sources, like AV software. A lock file with 0 bytes in it is generally harmless and disregarded.)

Best tool for unit testing in Delphi other than DUnit

What tools would you recommend for unit testing in Delphi.
I've used FastMM4 for memoryleak testing.
And MadExcept, both rule, but it doesn't help me test the logic in my programs.
I would like some alternatives, so don't all rush to suggest DUnit :-).
Any suggestions?
The most commonly used is DUnit. It's actually included in modern versions of Delphi but if your version doesn't come with it you can download it from Sourceforge.
DUnit comes with D2007.
-->File -->New -->Other
Select Unit Test from the dialogue that pops up.
There are a couple of good demo videos on it's use, I'll see if I can dig one up.
This is a pretty good one and others come up on the right:
http://www.youtube.com/watch?v=nyZnfxDqThE
You could take a look at the unit testing classes available in our SynCommons open source unit. It's used in our Open-Source framework for all regression tests. It's perhaps not the best, but it's worth taking a look at it. See http://blog.synopse.info/post/2010/07/23/Unit-Testing-light-in-Delphi
In the up-to-come 1.13 version, there is also a new logging mechanism with stack trace of any raised exception and such, just like MadExcept, using .map file content as source.
It's now used by the unit testing classes, so that any failure will create an entry in the log with the source line, and stack trace:
C:\Dev\lib\SQLite3\exe\TestSQL3.exe 0.0.0.0 (2011-04-13)
Host=Laptop User=MyName CPU=2*0-15-1027 OS=2.3=5.1.2600 Wow64=0 Freq=3579545
TSynLogTest 1.13 2011-04-13 05:40:25
20110413 05402559 fail TTestLowLevelCommon(00B31D70) Low level common: TDynArray "" stack trace 0002FE0B SynCommons.TDynArray.Init (15148) 00036736 SynCommons.Test64K (18206) 0003682F SynCommons.TTestLowLevelCommon._TDynArray (18214) 000E9C94 TestSQL3 (163)
The difference between a test suit without logging and a test suit with logging is only this:
procedure TSynTestsLogged.Failed(const msg: string; aTest: TSynTestCase);
begin
inherited;
with TestCase[fCurrentMethod] do
fLogFile.Log(sllFail,'%: % "%"',
[Ident,TestName[fCurrentMethodIndex],msg],aTest);
end;
The logging mechanism can be used to trace recursive calls. It can use an interface-based mechanism to log when you enter and leave any method:
procedure TMyDB.SQLExecute(const SQL: RawUTF8);
var ILog: ISynLog;
begin
ILog := TSynLogDB.Enter(self,'SQLExecute');
// do some stuff
ILog.Log(sllInfo,'SQL=%',[SQL]);
end; // when you leave the method, it will write the corresponding event to the log
It will be logged as such:
20110325 19325801 + MyDBUnit.TMyDB(004E11F4).SQLExecute
20110325 19325801 info SQL=SELECT * FROM Table;
20110325 19325801 - MyDBUnit.TMyDB(004E11F4).SQLExecute
Here the method name is set in the code ('SQLExecute'). But if you have an associated .map file, the logging mechanism is able to read this symbol information, and write the exact line number of the event.
Note that by default you have time and date written to the log, but it's also possible to replace this timing with high-resolution timestamps. With this, you'll be able to profile your application with data coming from the customer side, on its real computer. Via the Enter method (and its auto-Leave feature), you have all information needed for this. ;)
Like this:
0000000000000B56 + TTestCompression(00AB3570).000E6C79 SynSelfTests.TTestCompression.TestLog (376)
0000000000001785 - TTestCompression(00AB3570).000E6D09 SynSelfTests.TTestCompression.TestLog (385)
I still need to write some tool to compute the profiling, but there is already a dedicated TSynLogFile class able to read the .log file, and recognize its content.
The first time the .map file is read, a .mab file is created, and will contain all symbol information needed. You can send the .mab file with the .exe to your client, or even embed its content to the .exe. This .mab file is optimized: a .map of 927,984 bytes compresses into a 71,943 .mab file.
So this unit could be recognized as the natural child of DUnit and MadExcept wedding, in pure OpenSource. :)
Additional information is available on our forum. Feel free to ask. Feedback and feature requests are welcome! Works from Delphi 6 up to XE.

How to get Excel version and macro security level

Microsoft has recently broken our longtime (and officially recommended by them) code to read the version of Excel and its current omacro security level.
What used to work:
// Get the program associated with workbooks, e.g. "C:\Program Files\...\Excel.exe"
SHELLAPI.FindExecutable( 'OurWorkbook.xls', ...)
// Get the version of the .exe (from it's Properties...)
WINDOWS.GetFileVersionInfo()
// Use the version number to access the registry to determine the security level
// '...\software\microsoft\Office\' + VersionNumber + '.0\Excel\Security'
(I was always amused that the security level was for years in an insecure registry entry...)
In Office 2010, .xls files are now associated with "“Microsoft Application Virtualization DDE Launcher," or sftdde.exe. The version number of this exe is obviously not the version of Excel.
My question:
Other than actually launching Excel and querying it for version and security level (using OLE CreateOLEObject('Excel.Application')), is there a cleaner, faster, or more reliable way to do this that would work with all versions starting with Excel 2003?
Use
function GetExcelPath: string;
begin
result := '';
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
if OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\excel.exe', false) then
result := ReadString('Path') + 'excel.exe';
finally
Free;
end;
end;
to get the full file name of the excel.exe file. Then use GetFileVersionInfo as usual.
As far as I know, this approach will always work.
using OLE CreateOLEObject('Excel.Application'))
you can get installed Excel versions by using the same registry place, that this function uses.
Basically you have to clone a large part of that function registry code.
You can spy on that function call by tools like Microsoft Process Monitor too see exactly how does Windows look for installed Excel - and then to do it exactly the same way.
You have to open registry at HKEY_CLASSES_ROOT\ and enumerate all the branches, whose name starts with "Excel.Application."
For example at this my workstation I only have Excel 2013 installed, and that corresponds to HKEY_CLASSES_ROOT\Excel.Application.15
But on my another workstation I have Excel 2003 and Excel 2010 installed, testing different XLSX implementations in those two, so I have two registry keys.
HKEY_CLASSES_ROOT\Excel.Application.12
HKEY_CLASSES_ROOT\Excel.Application.14
So, you have to enumerate all those branches with that name, dot, and number.
Note: the key HKEY_CLASSES_ROOT\Excel.Application\CurVer would have name of "default" Excel, but what "default" means is ambiguous when several Excels are installed. You may take that default value, if you do not care, or you may decide upon your own idea what to choose, like if you want the maximum Excel version or minimum or something.
Then when for every specific excel branch you should read the default key of its CLSID sub-branch.
Like HKEY_CLASSES_ROOT\Excel.Application.15\CLSID has nil-named key equal to
{00024500-0000-0000-C000-000000000046} - fetch that index to string variable.
Then do a second search - go into a branch named like HKEY_CLASSES_ROOT\CLSID\{00024500-0000-0000-C000-000000000046}\LocalServer ( use the fetched index )
If that branch exists - fetch the nil-named "default key" value to get something like C:\PROGRA~1\MICROS~1\Office15\EXCEL.EXE /automation
The last result is the command line. It starts with a filename (non-quoted in this example, but may be in-quotes) and is followed by optional command line.
You do not need command line, so you have to extract initial commanlind, quoted or not.
Then you have to check if such an exe file exists. If it does - you may launch it, if not - check the registry for other Excel versions.

ZeroConf/Bonjour Code that works in Delphi 7 not working in 2009

I have the following declaration for DNSServiceRegister:
function DNSServiceRegister
(
var sdRef: TDNSServiceRef;
const flags: TDNSServiceFlags;
const interfaceIndex: uint32_t;
const name: PUTF8String; //* may be NULL */
const regType: PUTF8String;
const domain: PUTF8String; //* may be NULL */
const host: PUTF8String; //* may be NULL */
const port: uint16_t;
const txtLen: uint16_t;
const txtRecord: Pointer; //* may be NULL */
const callBack: TDNSServiceRegisterReply; //* may be NULL */
const context: Pointer //* may be NULL */
): TDNSServiceErrorType; stdcall; external DNSSD_DLL;
In my Bonjour framework I have the following response to an announced service being made active (i.e. to actually start announcing itself, via Bonjour):
procedure TAnnouncedService.Activate;
var
flags: Cardinal;
name: UTF8String;
svc: UTF8String;
pn: PUTF8String;
ps: PUTF8String;
begin
fPreAnnouncedServiceName := ServiceName;
inherited;
if AutoRename then
flags := 0
else
flags := kDNSServiceFlagsNoAutoRename; { - do not auto-rename }
if (ServiceName <> '') then
begin
name := ServiceName;
pn := PUTF8String(name);
end
else
pn := NIL;
svc := ServiceType;
ps := PUTF8String(svc);
CheckAPIResult(DNSServiceRegister(fHandle,
flags,
0 { interfaceID - register on all interfaces },
pn,
ps,
NIL { domain - register in all available },
NIL { hostname - use default },
ReverseBytes(Port),
0 { txtLen },
NIL { txtRecord },
DNSServiceRegisterReply,
self));
TBonjourEventHandler.Create(fHandle);
end;
This is more verbose than I think it strictly needs to be, certainly it was working perfectly well in Delphi 7 in a much less verbose form. I have expanded a lot of operations into explicit steps to facilitate debugging, e.g. to be able to identify any implicit transforms of string payloads that may be occuring "under the hood" in Delphi 2009.
Even in this untidy expanded form this code compiles and works perfectly well in Delphi 7, but if I compile and run with Delphi 2009 I get no announcement of my service.
For example, if I run this code as part of a Delphi 7 application to register a _daap._tcp service (an iTunes shared library) I see it pop-up in a running instance of iTunes. If I recompile the exact same application without modification in Delphi 2009 and run it, I do not see my service appearing in iTunes.
I get the same behaviour when monitoring with the dns-sd command line utility. That is, service code compiled with Delphi 7 behaves as I expect, compiled in Delphi 2009 - nothing.
I am not getting any errors from the Bonjour API - the DNSServiceRegisterReply callback is being called with an ErrorCode of 0 (zero), i.e. success, and if I supply a NIL name parameter with AutoRename specified in the flags then my service is allocated the correct default name. But still the service does not show up in iTunes.
I am at a loss as to what is going on.
As you might be able to tell from the expansion of the code, I have been chasing potential errors being introduced by the Unicode implementation in Delphi 2009, but this seems to be leading me nowhere.
The code was originally developed against version 1.0.3 of the Bonjour API/SDK. I've since updated to 1.0.6 in case that was somehow involved, without any success. afaict 1.0.6 merely added a new function for obtaining "properties", which currently supports only a "DaemonVersion" property for obtaining the Bonjour version - this is working perfectly.
NOTE: I'm aware that the code as it stands is not technically UTF8-safe in Delphi 7 - I have eliminated explicit conversions as far as possible so as to keep things as simple as possible for the automatic conversions that Delphi 2009 applies. My aim now is to get this working in Delphi 2009 then work backward from that solution to hopefully find a compatible approach for earlier versions of Delphi.
NOTE ALSO: I originally also had problems with browsing for advertised services, i.e. identifying an actual iTunes shared library on the network. Those issues were caused by the Unicode handling in Delphi 2009 and have been resolved. My Delphi 2009 code is just as capable of identifying an actual iTunes shared library and querying it's TXT records. It's only this service registration that isn't working.
I must be missing something stupid and obvious.
Does anyone have any ideas?!
UPDATE
Having returned to this problem I have now discovered the following:
If I have a pre-D2009 and a D2009+ IDE open (e.g D2006 and D2010) with the same project loaded into both IDE's concurrently:
Build and run under 2006: It works - my service announcement is picked up by iTunes
Switch to D2010 and run (without building): It does a minimal compile, runs and works.
Do a full build in D2010: It stops working
Switch back to D2006 and run (without building): It doesn't work
Do a full build in D2006: It works again
Does this give anyone any other ideas?
The answer to this is mind boggling. On the one hand I made a completely stupid, very simple mistake, but on the other hand it should never - as far as I can see - have worked in ANY version of Delphi!
The problem was nothing what-so-ever to do with the Unicode/non-unicodeness of any strings, but was actually due to a type mismatch in the PORT parameter.
I was passing in the result of ReverseBytes(Port) - that parameter expected a uint16_t, i.e. a Word value. My Port property was however declared (lazily) as an Integer!!
Once I fixed this and had Port declared as a Word, it now works on both D2007- and D2009+ versions of Delphi.
Very weird.
I can only think that some other edge-case behaviour of the compiler that might have somehow affected this was changed when Unicode support was introduced.
Based on the information that we have available here, the situation is this:
When calling the DLL with your code in Delphi 2007, it gives one result.
When calling the same DLL with your code in Delphi 2009, it gives another result.
The suspicion is, that it is related to the Delphi 2009 compiler.
Logically, the difference must therefore be, that Delphi 2009 sends different values as parameters. In order to make the debugging truly Delphi-independent, you therefore need to create a dummy DLL, which reports the values it gets. Other Delphi-dependent methods may be applied, like looking at the disassembly of the function-call into the DLL, and debugging it so that we know exactly what values are passed, and how, to the DLL, in both compilers.
I can't find the declaration instruction for the vars "ServiceName" and "ServiceType" in your code sample.
Assuming a String type (thus a unicode string), I guess (yes... no D2009 available to test this) lazy typecasting could be an issue:
name := ServiceName;
Why not use the following?
name := PAnsiChar(AnsiString(ServiceName))
Anyhow... just my 2 cts.
BTW:
I always use the pre defined "EmptyStr", "EmptyWideStr" ... so the test would look like:
if (ServiceName <> EmptyStr) then
which should be safe and avoid the confusion of types.
On the other side, Delphi may interpret the '' as an ANSIChar like the following declaration do:
const
MyParagraphChar = '§';
Not sure... I'm confused - should go home now ;)
If the DLL is not written using Delphi 2009, you may want to use something else than PUTF8String. The Delphi 2009 Utf8String type is different from Delphi 2007's UTF8String type.
If the DLL was written using C/C++, I strongly suggest to use PAnsiChar() instead of PUtf8String.

Resources