I used the Resource DLL Wizard in Delphi 2010 to generate resource only dll's for my program. When I look at them using Notepad++ it seems they are using ANSI encoding. Is there some setting I missed? It doesn't seem like a unicode program should store it's resources in ANSI especially for Asian languages.
I was looking specifically at the TABOUTBOX RT_RCDATA record. I tried to load it using the following code,
procedure LoadFromResFile(const FileName: string);
var
LibHandle: THandle;
ResourceLocation: HRSRC;
ResourceSize: dword;
ResourceHandle: THandle;
ResourcePointer: pointer;
ResStr: string;
begin
LibHandle := LoadLibraryEx(PWideChar(FileName), 0, LOAD_LIBRARY_AS_DATAFILE or LOAD_LIBRARY_AS_IMAGE_RESOURCE);
if LibHandle > 0 then
begin
ResourceLocation := FindResource(LibHandle, 'TABOUTBOX', RT_RCDATA);
ResourceSize := SizeofResource(LibHandle, ResourceLocation);
ResourceHandle := LoadResource(LibHandle, ResourceLocation);
ResourcePointer := LockResource(ResourceHandle);
if ResourcePointer <> nil then
begin
SetLength(ResStr, ResourceSize);
CopyMemory(#ResStr[1], ResourcePointer, ResourceSize);
FreeResource(ResourceHandle);
end;
FreeLibrary(LibHandle);
end else
begin
ResStr := SysErrorMessage(GetLastError);
ShowMessage(ResStr);
end;
I got garbage, but when I changed the type of ResStr to AnsiString, it showed up correctly. Opening the file in Notepad++ I can see that the dialog resources appear to be ansi, including the label captions.
The Resource DLL wizard creates RCDATA resources for localized DFMs. The RCDATA resource named TABOUTBOX is a binary DFM resource. String values stored within a DFM (component names, captions, etc) are encoded using UTF8 in modern Delphi versions, including 2010. But the DFM data itself is binary in nature, it represents the complete structure of serialized components. It is not itself Unicode data, so you can't load it as-is into a UnicodeString (it "works" when you change ResStr to an AnsiString, but only because of its 8bit nature). DFM resources are meant for TForm/TDataModule/TFrame-derived classes (in this case, TAboutBox) to load and de-serialize at runtime.
If you want to view a DFM resource as human-readible text, you have to use the ObjectBinaryToText() or ObjectResourceToText() function to decode it. For example:
var
LibHandle: THandle;
ResStrm: TResourceStream;
StrStrm: TStringStream;
ResStr: string;
begin
LibHandle := LoadLibraryEx(PChar(FileName), 0, LOAD_LIBRARY_AS_DATAFILE or LOAD_LIBRARY_AS_IMAGE_RESOURCE);
if LibHandle > 0 then
begin
try
ResStrm := TResourceStream.Create(LibHandle, 'TABOUTBOX', RT_RCDATA);
try
StrStrm := TStringStream.Create;
try
ObjectBinaryToText(ResStrm, StrStrm);
StrStrm.Position := 0;
ResStr := StrmStrm.DataString;
finally
StrStrm.Free;
end;
finally
ResStrm.Free;
end;
finally
FreeLibrary(LibHandle);
end;
end else
begin
ResStr := SysErrorMessage(GetLastError);
end;
ShowMessage(ResStr);
end;
Related
Using Delphi 10.4 Community Edition, VCL, Windows 10 64bit, although the compiled .exe application is 32bit.
The VCL's TMediaPlayer seems to have a file path/name length limit of 128 characters. Is this really an internal limitation? Is there any way to access longer file paths/names?
I was coding a small soundPad player by using the TMediaPlayer component.
The installer I am using installs the .exe program in the user's home directory, and at the same time a few sample audio files in the program's root directory.
In this case, the path to the audio file may be quite long. For example:
C:\Users\user\AppData\Local\Programs\MySoundPlayer\ThisIsMySoundWithAVeryLongFileNameThereIsSomeCopyrightInfoAndSomeOther.wav
When trying to play such a file, TMediaPlayer will give an error message:
Exception class name = 'EMCIDeviceError'
Exception message = 'Invalid filename. Make sure the filename has 8 characters, a period, and an extension.'
I tried different lengths in the file name, and it looks like 127 is the maximum length.
So, the VCL TMediaPlayer component does not recognize file paths / names longer than 127 characters?
I tried the same code with a Delphi FMX app, and FMX's TMediaPlayer worked ok. It seems that the maximum file path and name length of the FMX TMediaPlayer is 259, which is quite sufficient.
The length 259 seem to be the limit of the File Explorer overall...
It is said that the VCL's TMediaPlayer component is starting to become obsolete, and is only involved in backward compatibility reasons. But what can replace it in the future?
So, I guess I have to move on to FMX and learn its secrets. Is VCL a receding component system?
procedure TForm1.PlayButtonClick(Sender: TObject);
var
pathstring, playerfilename, playstring : string;
begin
try
pathstring := ExtractFilePath(Application.ExeName);
playerfilename := 'ThisIsMySoundWithAVeryLongFileNameThereIsSomeCopyrightInfoAndSomeOther.wav';
playstring := pathstring + playerfilename;
MediaPlayer1.FileName := playstring;
MediaPlayer1.Open;
MediaPlayer1.Play;
except
on E : Exception do
begin
ShowMessage('Exception class name = ' + E.ClassName);
ShowMessage('Exception message = ' + E.Message);
end;
end;
end;
Per this answer to mciSendString() won't play an audio file if path is too long:
Here, mmioOpen is called with MMIO_PARSE flag to convert file path to fully qualified file path. According to MSDN, this has a limitation:
The buffer must be large enough to hold at least 128 characters.
That is, buffer is always assumed to be 128 bytes long. For long filenames, the buffer turns out to be insufficient and mmioOpen returns error, causing mciSendCommand to think that sound file is missing and return MCIERR_FILENAME_REQUIRED.
The Invalid filename error message you are seeing is the system text for the MCIERR_FILENAME_REQUIRED error code.
The VCL's TMediaPlayer is based on MCI and internally uses mciSendCommand(), which is just the binary version of mciSendString(). They both suffer from the same problem.
The preferred fix is to either use shorter paths, or use a more modern audio API.
However, since mmioInstallIOProc() can be used to let TMediaPlayer play media files from memory instead of files, I think a similar solution could be used to play files with long file paths, since you could take over the responsibility of opening/reading/seeking a file, bypassing the path limitation of the troublesome mmioOpen(). Just replace the TResourceStream in that code with a TFileStream, and update the MMIOM_READ and MMIOM_SEEK handlers accordingly to read/seek that TFileStream.
For example (untested, might need some tweaking):
uses
Winapi.MMSystem;
var
ccRES: FOURCC;
playstring: string;
function MAKEFOURCC(ch0, ch1, ch2, ch3: BYTE): FOURCC;
begin
Result := DWORD(ch0) or (DWORD(ch1) shl 8) or (DWORD(ch2) shl 16) or (DWORD(ch3) shl 24);
end;
function MyLongFileIOProc(lpMMIOInfo: PMMIOInfo; uMessage: UINT; lParam1, lParam2: LPARAM): LRESULT; stdcall;
var
FStrm: TFileStream;
NumRead: Integer;
function GetFileStream: TFileStream;
begin
Move(lpMMIOInfo.adwInfo, Result, SizeOf(TFileStream));
end;
procedure SetFileStream(Stream: TFileStream);
begin
Move(Stream, lpMMIOInfo.adwInfo, SizeOf(TFileStream));
end;
begin
if uMessage = MMIOM_OPEN then
begin
try
FStrm := TFileStream.Create(playstring, fmOpenRead or fmShareDenyWrite);
except
SetFileStream(nil);
Exit(MMIOM_CANNOTOPEN);
end;
SetFileStream(FStrm);
lpMMIOInfo.lDiskOffset := 0;
end else
begin
FStrm := GetFileStream;
case uMessage of
MMIOM_CLOSE: begin
SetFileStream(nil);
FStrm.Free;
end;
MMIOM_READ: begin
NumRead := FStrm.Read(Pointer(lParam1)^, lParam2);
Inc(lpMMIOInfo.lDiskOffset, NumRead);
Exit(NumRead);
end;
MMIOM_SEEK: begin
FStrm.Seek(Int64(lParam1), TSeekOrigin(lParam2));
lpMMIOInfo.lDiskOffset := FStrm.Position;
Exit(lpMMIOInfo.lDiskOffset);
end;
end;
Exit(MMSYSERR_NOERROR);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ccRES := MAKEFOURCC(Ord('L'), Ord('F'), Ord('N'), Ord(' '));
mmioInstallIOProc(ccRES, TFNMMIOProc(MyLongFileIOProc), MMIO_INSTALLPROC);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
mmioInstallIOProc(ccRES, nil, MMIO_REMOVEPROC);
end;
procedure TForm1.PlayButtonClick(Sender: TObject);
var
pathstring, playerfilename : string;
begin
try
pathstring := ExtractFilePath(Application.ExeName);
playerfilename := 'ThisIsMySoundWithAVeryLongFileNameThereIsSomeCopyrightInfoAndSomeOther.wav';
playstring := pathstring + playerfilename;
MediaPlayer1.DeviceType := dtWaveAudio;
MediaPlayer1.FileName := 'playstring.LFN+';
MediaPlayer1.Open;
MediaPlayer1.Play;
except
on E : Exception do
begin
ShowMessage('Exception class name = ' + E.ClassName);
ShowMessage('Exception message = ' + E.Message);
end;
end;
end;
I have a webpage which has various tables on it. These tables are Javascript components, not just pure HTML tables. I need to process the text of this webpage (somewhat similar to screen scraping) with a Delphi program (Delphi 10.3).
I do a Ctrl-A/Ctrl-C to select all the webpage and copy everything to the clipboard. If I paste this into a TMemo component in my program, I am only getting text outside the table. If I paste into MS Word, I can see all the content, including the text inside the table.
I can paste this properly into TAdvRichEditor (3rd party), but it takes forever, and I often run out of memory. This leads me to believe that I need to directly read the clipboard with an HTML clipboard format.
I set up a clipboard HTML format. When I inspect the clipboard contents, I get what looks like all Kanji characters.
How do I read the contents of the clipboard when the contents are HTML?
In a perfect world, I would like ONLY the text, not the HTML itself, but I can strip that out later. Here is what I am doing now...
On initialization.. (where CF_HTML is a global variable)
CF_HTML := RegisterClipboardFormat('HTML Format');
then my routine is...
function TMain.ClipboardAsHTML: String;
var
Data: THandle;
Ptr: PChar;
begin
Result := '';
with Clipboard do
begin
Open;
try
Data := GetAsHandle(CF_HTML);
if Data <> 0 then
begin
Ptr := PChar(GlobalLock(Data));
if Ptr <> nil then
try
Result := Ptr;
finally
GlobalUnlock(Data);
end;
end;
finally
Close;
end;
end;
end;
** ADDITIONAL INFO - When I copy from the webpage... I can then inspect the contents of the Clipboard buffer using a free tool called InsideClipBoard. It shows that the clipboard contains 1 entry, with 5 formats: CT_TEXT, CF_OEMTEXT, CF_UNICODETEXT, CF_LOCALE, and 'HTML Format' (with Format ID of 49409). Only 'HTML Format' contains what I am looking for.... and that is what I am trying to access with the code that I have shown.
The HTML format is documented here. It is placed on the clipboard as UTF-8 encoded text, and you can extract it like this.
{$APPTYPE CONSOLE}
uses
System.SysUtils,
Winapi.Windows,
Vcl.Clipbrd;
procedure Main;
var
CF_HTML: Word;
Data: THandle;
Ptr: Pointer;
Error: DWORD;
Size: NativeUInt;
utf8: UTF8String;
Html: string;
begin
CF_HTML := RegisterClipboardFormat('HTML Format');
Clipboard.Open;
try
Data := Clipboard.GetAsHandle(CF_HTML);
if Data=0 then begin
Writeln('HTML data not found on clipboard');
Exit;
end;
Ptr := GlobalLock(Data);
if not Assigned(Ptr) then begin
Error := GetLastError;
Writeln('GlobalLock failed: ' + SysErrorMessage(Error));
Exit;
end;
try
Size := GlobalSize(Data);
if Size=0 then begin
Error := GetLastError;
Writeln('GlobalSize failed: ' + SysErrorMessage(Error));
Exit;
end;
SetString(utf8, PAnsiChar(Ptr), Size - 1);
Html := string(utf8);
Writeln(Html);
finally
GlobalUnlock(Data);
end;
finally
Clipboard.Close;
end;
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
I have embedded several resources into the executable, for instance language (text) files.
Below you can see the contents of Languages.rc file:
Language_English RCDATA Languages\English.ini
Language_German RCDATA Languages\German.ini
Language_Czech RCDATA Languages\Czech.ini
I found this answer, which definitely helps, however I have rather hard time implementing it.
Suppose you want to get the list of those resources as a EOL-delimited string, then the first step would be defining EnumRCDataProc function:
function EnumRCDataProc(hModule: HMODULE; lpszType, lpszName: PChar; lParam: NativeInt): BOOL; stdcall;
begin
TStrings(lParam).Add(lpszName);
Result := True;
end;
Once we have that done, we can get to work:
function EnumerateRCDataResourceNames: string;
var
ExecutableHandle: HMODULE;
ResourcesList: TStringList;
begin
ExecutableHandle := LoadLibraryEx(PChar(Application.ExeName), 0, LOAD_LIBRARY_AS_DATAFILE);
try
ResourcesList := TStringList.Create;
try
EnumResourceNames(ExecutableHandle, RT_RCDATA, #EnumRCDataProc, NativeInt(ResourcesList));
Result := ResourcesList.Text;
finally
ResourcesList.Free;
end;
finally
FreeLibrary(ExecutableHandle);
end;
end;
Remarks:
As is in the original answer (see question), it is not possible to use LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE or LOAD_LIBRARY_AS_IMAGE_RESOURCE as these types are no longer defined in Delphi XE6, at least AFAIK.
You can, however, define those constants, according to MSDN:
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = $00000040
LOAD_LIBRARY_AS_IMAGE_RESOURCE = $00000020
I have an INI file in UTF-8 format.
I am using Delphi 2010 to read the INI file and populate a TStringGrid with the values in the INI file.
var
ctr : Integer;
AppIni : TIniFile;
begin
AppIni := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'test.ini');
for ctr := 1 to StringGrid1.RowCount do begin
StringGrid1.Cells[0,ctr] := AppIni.ReadString('Column1','Row'+IntToStr(ctr),'');
StringGrid1.Cells[1,ctr] := AppIni.ReadString('Column2','Row'+IntToStr(ctr),'');
end;
AppIni.Free;
The problem is that the unicode characters are appearing in the TStringGrid displaying 2 characters, rather than the 1 unicode character.
How do I resolve this?
The TIniFile class is a wrapper of the Windows API for INI files. This does support Unicode INI files, but only if those files are encoded as UTF-16. Michael Kaplan has more details here: Unicode INI function; Unicode INI file?
So, you are out of luck with TIniFile. Instead you could use TMemIniFile which allows you to specify an encoding in its constructor. The TMemIniFile class is a native Delphi implementation of INI file support. There are various pros and cons between the two classes. In your situation, only TMemIniFile can serve your needs, so it's looking like its pros are going to outweigh its cons.
Uses IniFiles;
const
SZ_APP_NAME = 'demo_test';
Procedure TForm1.GetSettings;
var
_MemIniU: TMemIniFile;
_SettingsPath: string;
begin
try
_SettingsPath := GetHomePath + PathDelim + SZ_APP_NAME + PathDelim;
if ForceDirectories(_SettingsPath) then
begin
_MemIniU := TMemIniFile.Create(ChangeFileExt(_SettingsPath,
'Settings.ini'), TEncoding.UTF8);
try
if _MemIniU.ReadInteger(SZ_APP_NAME, 'WindowLeft', -1) = -1 then
Form1.Position := poScreenCenter
else
begin
Form1.Left := _MemIniU.ReadInteger(SZ_APP_NAME, 'WindowLeft', 10);
Form1.Top := _MemIniU.ReadInteger(SZ_APP_NAME, 'WindowTop', 10);
Form1.Width := _MemIniU.ReadInteger(SZ_APP_NAME, 'WindowWidth', 594);
Form1.Height := _MemIniU.ReadInteger(SZ_APP_NAME,
'WindowHeight', 342);
end;
Edit1.Text := _MemIniU.ReadString(SZ_APP_NAME, 'UnicodeText', 'ąčę');
finally
_MemIniU.Free;
end;
end;
except
on E: Exception do
MessageDlg(PWideChar(E.Message), TMsgDlgType.mtError,
[TMsgDlgBtn.mbOK], 0);
end;
end;
Procedure TForm1.SaveSettings;
var
_MemIniU: TMemIniFile;
_SettingsPath: string;
begin
try
_SettingsPath := GetHomePath + PathDelim + SZ_APP_NAME + PathDelim;
_MemIniU := TMemIniFile.Create(ChangeFileExt(_SettingsPath, 'Settings.ini'),
TEncoding.UTF8);
try
if Form1.WindowState <> TWindowState.wsMaximized then
begin
_MemIniU.WriteInteger(SZ_APP_NAME, 'WindowLeft', Form1.Left);
_MemIniU.WriteInteger(SZ_APP_NAME, 'WindowTop', Form1.Top);
_MemIniU.WriteInteger(SZ_APP_NAME, 'WindowWidth', Form1.Width);
_MemIniU.WriteInteger(SZ_APP_NAME, 'WindowHeight', Form1.Height);
_MemIniU.WriteString(SZ_APP_NAME, 'UnicodeText', Edit1.Text);
end;
_MemIniU.UpdateFile;
finally
_MemIniU.Free;
end;
except
on E: Exception do
MessageDlg(PWideChar(E.Message), TMsgDlgType.mtError,
[TMsgDlgBtn.mbOK], 0);
end;
end;
In an application were I was using TIniFile i had the need to start storing Unicode chars.
To do this i simply changed the variable type from TIniFile to TMemIniFile and in the constructor, after the filename i added the second parameter TEncoding.UTF8.
Then before freeing the object i called UpdateFile. If Ini File is opened for reading, call to UpdateFile is not needed.
// ANSI version
var myIniFile: TIniFile;
begin
myIniFIle := TIniFile.Create('c:\Temp\MyFile.ini');
myIniFile.WriteString(par1,par2,par3);
// [...]
myIniFile.Free;
end
// Unicode version
//1) "Mem" added here
var myIniFile: TMemIniFile;
begin
// 2) Enconding added
myIniFIle := TIniFile.Create('c:\Temp\MyFile.ini', TEncoding.UTF8);
myIniFile.WriteString(par1,par2,par3);
// [...]
// 3) call to UpdateFile to save to disc the changes
myIniFile.UpdateFile;
myIniFile.Free;
end
The good news is that UpdateFile causes the ini file to be saved with the proper encoding, this means that if a ini file encoded in ANSI already exists it is overwriten so it becomes UTF-8, so the transaction between ANSI and UTF-8 is smooth and not painful at all.
How can I get content of an exe file and convert it into Base64 encoding ?
Edit
I use D2010 and I want to know how is it possible exactly ?
open an exe file
convert its content into base64
In Delphi 2009/2010/XE there is unit EncdDecd.pas (Soap.EncdDecd.pas for Delphi XE2) containing the functions EncodeBase64 and DecodeBase64. You can load the exe file into a memorystream and then call EncodeBase64.
function EncodeFile(const FileName: string): AnsiString;
var
stream: TMemoryStream;
begin
stream := TMemoryStream.Create;
try
stream.LoadFromFile(Filename);
result := EncodeBase64(stream.Memory, stream.Size);
finally
stream.Free;
end;
end;
In ancient Delphi versions, you can use synapse (link here)
Just put synacode.pas in your uses e call EncodeBase64/EncodeBase64.
Cheers
As also mentioned in the comments, since Delphi XE8 you can use the System.NetEncoding.TNetEncoding.Base64 class property.
It also returns a string instead of an AnsiString:
function TryEncodeFile(const AFileName: string; out ABase64string: string): Boolean;
var
MemStream: TMemoryStream;
begin
MemStream := TMemoryStream.Create;
try
MemStream.LoadFromFile(AFileName);
ABase64string :=
TNetEncoding.Base64.EncodeBytesToString(MemStream.Memory, MemStream.Size);
Result := True;
finally
MemStream.Free;
end;
end;