I am trying to create a shortcut (on the Desktop) that contains a long argument string (> MAX_PATH).
The MSDN documentation clearly states that for Unicode string the string can be longer than MAX_PATH.
The resulting shortcut is cut exactly after MAX_PATH characters (that is the Path + the Arguments).
Is there something wrong with my implementation or is this some Windows limitation?
procedure CreateShortcut(APath: WideString;
AWorkingDirectory: WideString; AArguments: WideString; ADescription: WideString;
ALinkFileName: WideString);
var
IObject : IUnknown;
ISLink : IShellLinkW;
IPFile : IPersistFile;
begin
IObject := CreateComObject(CLSID_ShellLink);
ISLink := IObject as IShellLinkW;
ISLink.SetPath( PWideChar(APath));
ISLink.SetWorkingDirectory(PWideChar(AWorkingDirectory));
ISLink.SetArguments( PWideChar(AArguments));
ISLink.SetDescription( PWideChar(ADescription));
IPFile := IObject as IPersistFile;
IPFile.Save(PWideChar(ALinkFileName), False);
end;
PS: OS is Windows XP (and above).
It turns out that this issue is in fact solely a limitation in the Explorer shell dialog. The generated shortcut file does not have a 260 character limitation. It's simply that the dialog refuse to display a Target with more characters than that. Presumably it calls GetPath with a fixed length buffer.
procedure TForm11.Button1Click(Sender: TObject);
var
sl: IShellLinkW;
pf: IPersistFile;
begin
CoCreateInstance(CLSID_ShellLink, nil,
CLSCTX_INPROC_SERVER, IID_IShellLinkW, sl);
sl.SetPath('c:\desktop\test.bat');
sl.SetWorkingDirectory('c:\desktop\');
sl.SetArguments(PChar(StringOfChar('x', 300)+'_the_end'));
pf := sl as IPersistFile;
pf.Save('c:\desktop\test.lnk', False);
end;
My test.bat looks like this:
echo %1> test.out
The resulting test.out goes right the way to _the_end!
Thanks all who contributed to this thread - it helped me immensely.
However, if I may, I would like to add the below information I discovered in crafting my solution:
On Windows 7 Enterprise ~SP1, it would seem that using VBS to create the shortcut there is still a limit on maximum characters in (at least) the arguments field. I tested up to 1023 chars before it got trunicated. I presume the same limit would apply to the Delphi method likewise.
On Windows XP Professional ~SP3, while the VBS method will create a shortcut longer than 260 characters (lnk file contains the data), it seems to trunicate it at about this number when executing it.
Related
The following code gets different results on different machines. One machine just gives the desktop folder (not desired) the other gives the desktop folder and Computer, mapped drives (desired).
procedure TForm1.Button1Click(Sender: TObject);
var
Directory : String;
begin
FileCtrl.SelectDirectory('Caption', 'Desktop', Directory, [sdNewUI, sdShowEdit]);
end;
One one machine it gives:
On another it gives:
This feels like a windows setting, but I am not sure where to start. Using Delphi XE, Windows 10.
Any thoughts are appreciated. Thanks for your time.
Workaround
Use a TFileOpenDialog instead*.
Set FileOpenDialog1.Options:= [fdoPickFolders,fdoPathMustExist]
Now you have a dialog that:
Always works.
Allows copy paste
*) Not to be confused with the TOpenDialog, which does not allow you to only select folders.
Solution for Windows XP
Note that the new TFileOpenDialog only works for Vista and above.
Your program will not work on XP if you include this control.
If you start the dialog on XP it will generate an EPlatformVersionException.
You may want to use the following code instead if you want to be backward compatible:
uses JclSysInfo; //because you have XE use JCL.
...
var
WinMajorVer: Integer;
Directory: string;
FileDialog: TFileOpenDialog;
begin
WinMajorVer:= GetWindowsMajorVersionNumber;
if WinMajorVer < 6 then begin //pre-vista
//To show the root Desktop namespace, you should be setting the Root parameter to an empty string ('') instead of 'Desktop'
FileCtrl.SelectDirectory('Caption', '', Directory, [sdNewUI, sdShowEdit]);
end else begin
FileDialog:= TFileOpenDialog.Create(self);
try
FileDialog.Options:= [fdoPickFolders,fdoPathMustExist];
if FileDialog.Execute then Directory:= FileOpenDialog1.FileName;
finally
FileDialog.Free;
end;
end;
Result:= Directory;
end;
Recommended reading:
detect windows version
EDIT
FileCtrl.SelectDirectory('Caption', 'Desktop', Directory, [sdNewUI, sdShowEdit]);
The 'Desktop' goes into the Root parameter, which is handled like so:
...
SHGetDesktopFolder(IDesktopFolder);
IDesktopFolder.ParseDisplayName(Application.Handle, nil,
Root, Eaten, RootItemIDList, Flags);
...
Here's what MSDN for IDesktopFolder.ParseDisplayName has to say:
pszDisplayName [in]
Type: LPWSTR
A null-terminated Unicode string with the display name. Because each Shell folder defines its own parsing syntax, the form this string can take may vary. The desktop folder, for instance, accepts paths such as "C:\My Docs\My File.txt". It also will accept references to items in the namespace that have a GUID associated with them using the "::{GUID}" syntax.
Note that the documentation states that the desktop folder will accept paths and guids. It does not accept 'Desktop'. Because that's neither.
The fact that 'Desktop' as a root works on one system but not another is some undocumented fix made in an older/newer version of the IDesktopFolder interface.
Technical solution
Use '' as a 'root' as shown in my code above.
Obviously SelectDirectory is a really bad design by Microsoft that should never be used. It just sucks in so many ways. I recommend it not be used whenever possible.
The Question: I recently acquired a 1989 IBM PS2 and I am trying move large files from my newer UNIX-based machine to this IBM via floppy. I have a bash script that splits my files into ~2MB chunks, now I am trying to write a pascal program to reconstruct these files after they have been transferred.
I am unable to find the correct read/write to file methods on this computer. I have tried various pascal tutorial sites, but they are all for newer versions (the site I followed with File Handling In Pascal). I am able to create an empty file (as described below), but I am unable to write to it. Does anyone know the correct pascal read and write methods for this type of computer?
I know this is an obscure question, so thank you in advance for any help you can give me!
The Details:
The current test code that creates a file correctly is this:
program testingFiles;
uses Crt, Win;
const FILE_NAME = 'testFile.txt';
var outFile : File;
begin
writeln('creating file ...');
Assign(outFile, FILE_NAME);
rewrite(outFile);
end.
This is some test code that does not work, the method's append() and close() could not be found:
program testingFiles;
uses Crt, Win;
const FILE_NAME = 'testFile.txt';
var outFile : File;
begin
writeln('creating file ...');
Assign(outFile, FILE_NAME);
append(outFile);
writeln('this should be in the file');
close(outFile);
end.
This is an alternative that also did not work, the writeln() method only ever prints to the terminal. But otherwise this does compile.
program testingFiles;
uses Crt, Win;
const FILE_NAME = 'testFile.txt';
var outFile : File;
begin
writeln('creating file ...');
Assign(outFile, FILE_NAME);
rewrite(outFile);
writeln('this should be in the file');
close(outFile);
end.
The system: As was previously mentioned, this is a 1989 IBM PS2.
It has Windows 3.0 installed and can also run DOS and MS-DOS terminals.
It has Microsoft SMARTDrive Disk Cache version 3.06
It has Turbo Pascal 5.5 installed and I am using turbo as my command line pascal editor. (the readme was last updated in 1989)
It has Turbo debugger 1.5 installed.
Again, I know this is an obscure question, so thank you in advance for any help you can give me!
My Pascal memory is VERY rusty... but as other have pointed out, here is what you should consider:
program testingFiles;
uses Crt, System;
//No need of importin Win Win is for Windows enviorment, however I'm not sure if you need to use System, Sysutils or was there a Dos class???
const FILE_NAME = 'testFile.txt';
var outFile,inFile : File;
begin
writeln('creating file ...');
Assign(outFile, FILE_NAME);
rewrite(outFile);
//Now Open the first chunk of the file you want to concatenate
AssignFile(inFile, "fisrt_chunk.dat");
reset(inFile);
while not eof(inFile) do
begin
readln(inFile, s);
writeln(outFile,s);
end;
close(inFile);
end.
I don't have Turbo/Borland Pascal installed any longer so I couldn't compile it myself, no promise that it will work it is more like an idea:
Key thing to remember, readln and writeln will ALWAYS add a return at the end of the string/line, read and write on the other hand will leave the cursor wherever it is without jumping to a new line.
Here's some old Delphi code that should be at least close to syntax-compatible that will give you the gist of copying a file (with limited error checking and resource handling in case of error - I'll leave that as an exercise for you). It works to copy both binary and text content.
program Project2;
uses
SysUtils;
var
NumRead, NumWritten: LongInt;
pBuff : PChar;
SrcFile, DstFile: File;
const
BuffSize = 2048; // 2K buffer. Remember not much RAM available
InFileName = 'somefile.txt';
OutFileName = 'newfile.txt';
begin
NumRead := 0;
NumWritten := 0;
AssignFile(SrcFile, InFileName);
AssignFile(DstFile, OutFileName);
// Allocate memory for the buffer
GetMem(pBuff, BuffSize);
FileMode := 0; // Make input read-only
Reset( SrcFile, 1 );
FileMode := 2; // Output file read/write
Rewrite( DstFile, 1 );
repeat
// Read a buffer full from input
BlockRead(SrcFile, pBuff^, BuffSize, NumRead);
// Write it to output
BlockWrite(DstFile, pBuff^, NumRead, NumWritten);
until (NumRead = 0) or (NumWritten <> NumRead);
// Cleanup stuff. Should be protected in a try..finally,
// of course.
CloseFile(SrcFile);
CloseFile(DstFile);
FreeMem(pBuff);
end.
The above code compiles under Delphi 2007 currently (the oldest version I have installed). (See the note below.)
As a side note, this was from an archived version of some code I had that compiled both for 16-bit Delphi 1 and was extended to also compile under 32-bit Delphi 2 back in the mid-to-late 90s. It's still hanging around in my source repositories in an old tagged branch. I think I need to do some pruning. :-) I cleaned it up to remove some other functionality and removed a lot of {$IFDEF WIN32} ... {$ELSE} ... {$ENDIF} stuff before posting.)
In Delphi, I would like to open a file in OS X. My approach is as follows:
const
Filename = 'test.bmp';
procedure SaveAndOpen;
begin
Chart.SaveToBitmapFile(Filename);
{$IFDEF MSWINDOWS}
ShellExecute(0, 'open', Filename, '', '', SW_Normal);
{$ELSE}
_System(Filename);
{$ENDIF}
end;
But nothing happens. What am I doing wrong?
This article from Embarcadero's Malcolm Groves covers this topic: Opening files and URLs in default applications in OS X.
In summary, all you need is this:
uses
Macapi.Appkit, // for NSWorkspace
Macapi.Foundation; // for NSSTR
....
var
Workspace: NSWorkspace; // interface, no need for explicit destruction
....
Workspace := TNSWorkspace.Create;
Workspace.openFile(NSSTR(FileName));
For sake of completeness, should you wish to open a URL rather than a file, then you call openURL instead:
Workspace.openURL(NSSTR(URL));
Regarding your Windows code, I would recommend not using ShellExecute. That function does not have reasonable error reporting. Use ShellExecuteEx in its place.
And finally, you should probably abstract this functionality away so that it can be re-used by other parts of your program. You want to write that IFDEF as few times as possible.
You must add the open verb like so
_System(PAnsiChar('open ' + Filename));
Is it possible to trigger the Flip 3D mode on Windows Vista above systems programmatically?
It is the same as if you manually press CTRL + WIN + TAB
The Shell object has the WindowSwitcher method which can invoke this mode.
Here is the Delphi code example:
uses
ComObj;
procedure EnterWindowSwitcherMode;
var
Shell: OleVariant;
begin
try
Shell := CreateOleObject('Shell.Application');
Shell.WindowSwitcher;
finally
Shell := Unassigned;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if Win32MajorVersion >= 6 then // are we at least on Windows Vista ?
begin
try
EnterWindowSwitcherMode;
except
on E: Exception do
ShowMessage(E.ClassName + ': ' + E.Message);
end;
end;
end;
Update:
Or as Norbert Willhelm mentioned here, there is also IShellDispatch5 object interface which in fact introduces the WindowSwitcher method. So here's another version of the same...
The following piece of code requires the Shell32_TLB.pas unit, which you can in Delphi create this way (note, that you must have at least Windows Vista where the IShellDispatch5 interface was used the first time):
go to menu Component / Import Component
continue with selected Import a Type Library
select Microsoft Shell Controls And Automation and finish the wizard
And the code:
uses
Shell32_TLB;
procedure EnterWindowSwitcherMode;
var
// on Windows Vista and Windows 7 (at this time :)
// is Shell declared as IShellDispatch5 object interface
AShell: Shell;
begin
try
AShell := CoShell.Create;
AShell.WindowSwitcher;
finally
AShell := nil;
end;
end;
Summarization:
The terminology that I have been
looking for seems to be "natural
sort".
For behaviors in operating systems:
For Windows (version >= XP), Windows Explorer utilizes natural
sort.
For Linux terminals: use "ls -v" instead of plain "ls" to get natural
sort.
For programing in Delphi, use StrCmpLogicalW Windows API to get natural sort.
For programing in Delphi & Kylix & Lazarus, use hand-crafted functions to get
natural sort:
(1) Delphi wrapper for Natural Order String Comparison by Martin Pool.
http://irsoft.de/web/strnatcmp-and-natsort-for-delphi
(2) Codes of alphanum sorting algorithm in other languages from davekeolle site.
http://www.davekoelle.com/alphanum.html
(3) Other knowledgable pages:
http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html
http://objectmix.com/delphi/722211-natural-sorting-optimizing-working-solution.html
http://groups.google.com/group/borland.public.delphi.language.delphi.general/browse_thread/thread/1141d49f8bbba577
http://objectmix.com/delphi/401713-alphanumeric-sort-routine-delphi.html
==========================
The following file names will be ordered in the Windows Explorer as shown below:
test_1_test.txt
test_2_test.txt
test_11_test.txt
test_12_test.txt
test_21_test.txt
test_22_test.txt
If, for example, I put them in a TStringList instance and call Sort, the sorted order is as below:
test_1_test.txt
test_11_test.txt
test_12_test.txt
test_2_test.txt
test_21_test.txt
test_22_test.txt
And for record, the above file names will be ordered in the rxvt terminal of Cygwin or xterm terminal of Linux distributions such as CentOS as shown below:
test_11_test.txt
test_12_test.txt
test_1_test.txt
test_21_test.txt
test_22_test.txt
test_2_test.txt
Could you help to comment on how to understand this difference of sorting behaviors? Furthermore, is it possible to get the same order as in Windows Explorer? Any suggestion is appreciated!
PS: My Windows locale is set to Chinese but I would think the same for English locale.
StrCmpLogicalW is able to handle numbers, the other alternative is CompareString
Thanks to Anders - the answer is StrCmpLogicalW; I have not found it's declaration in Delphi 2009 sources, so I declared it myself in the test below:
type
TMyStringList = class(TStringList)
protected
function CompareStrings(const S1, S2: string): Integer; override;
end;
function StrCmpLogicalW(P1, P2: PWideChar): Integer; stdcall; external 'Shlwapi.dll';
function TMyStringList.CompareStrings(const S1, S2: string): Integer;
begin
Result:= StrCmpLogicalW(PChar(S1), PChar(S2));
end;
procedure TForm11.Button2Click(Sender: TObject);
var
SL: TMyStringList;
begin
SL:= TMyStringList.Create;
try
SL.Add('test_1_test.txt');
SL.Add('test_11_test.txt');
SL.Add('test_12_test.txt');
SL.Add('test_2_test.txt');
SL.Add('test_21_test.txt');
SL.Add('test_22_test.txt');
SL.Sort;
Memo1.Lines:= SL;
finally
SL.Free;
end;
end;