When pressing Ctrl-c there is almost always additional output. I'd like to ensure that after receiving the Ctrl-c the program doesn't show anything other than possibly "^C".
I found what is mostly the same question but, it was for Linux and my attempts to "port" the solution from Linux to Windows have not succeeded.
At this point, I'm out of things to try and can use some help, which I will definitely appreciate. Thank you.
The short example program below suffers from that problem.
{$APPTYPE CONSOLE}
program _SetConsoleCtrlHandler;
uses
Windows,
SysUtils
;
function CtrlHandler(CtrlType : DWORD) : BOOL; stdcall;
begin
result := FALSE;
case CtrlType of
CTRL_C_EVENT,
CTRL_BREAK_EVENT:
begin
result := TRUE;
ExitProcess(7);
end;
end;
end;
var
s : shortstring;
begin
SetConsoleCtrlHandler(#CtrlHandler, TRUE);
while TRUE do
begin
write('press <ctrl-c> to end this program : ');
readln(s);
end;
end.
The way I usually do this is to have a separate unit that is signaled and a simple wait, like the following. In the main console project you call WaitForCtrlC instead of Readln(). You could also use a TEvent and wait on the event instead of looping, like I show in this example:
uses
{$IFDEF LINUX}
Posix.Signal,
{$ENDIF}
{$IFDEF MSWINDOWS}
Windows,
{$ENDIF}
SysUtils;
procedure WaitForCtrlC;
implementation
var
Control_C: Boolean = False;
{$IFDEF MSWINDOWS}
function ConsoleCtrlHandler(dwCtrlType: DWORD): BOOL; stdcall;
begin
if (dwCtrlType = CTRL_C_EVENT) then
Control_C := True;
Result := True;
end;
{$ENDIF}
{$IFDEF LINUX}
var
sigIntHandler: sigaction_t;
procedure SigHandler(SigNum: Integer); cdecl;
begin
Control_C := True;
end;
{$ENDIF}
procedure WaitForCtrlC;
begin
while not Control_C do
Sleep(25);
end;
initialization
{$IFDEF MSWINDOWS}
Windows.SetConsoleCtrlHandler(#ConsoleCtrlHandler, True);
{$ENDIF}
{$IFDEF LINUX}
sigIntHandler._u.sa_handler := #SigHandler;
sigemptyset(sigIntHandler.sa_mask);
sigIntHandler.sa_flags := 0;
sigaction(SIGINT, #sigIntHandler, nil);
{$ENDIF}
Related
I've read the Embarcadero and other docs, searched the net, and obviously something isn't sinking in. I have a significantly more complex Application and DLL/Dylib, but am now just using this simple example to try and get it to work. When I build the App and DLL for Win32 it works fine and it works fine if I do not make a function call to the DLL. As soon as I make a call to the DLL the error Unable to register Class TFMXApplicationDelegate is raised and the application terminates. This behaviour is the same whether the guest OS is on a VM (parallels) or a physical device (MacBook Pro 15 mid-2015).
QUESTION: How can I ensure that TFMXApplicationDelegate gets Registered, is there a setting or permission I need to set. It seems fairly basic since, according to the Apple documentation:
The app delegate is effectively the root object of your app.
The DPR:
Library pTestDLL;
uses
uTestDLL in 'uTestDLL.pas';
{$R *.res}
end.
And here's the simple PAS file (for the Dylib):
unit uTestDLL;
interface
uses
FMX.Dialogs;
// External functions and procedures
{$IFDEF MSWINDOWS}
function say_Hello(Hello: string): boolean; stdcall; forward;
{$ENDIF MSWINDOWS}
{$IFDEF MACOS}
function _say_Hello(Hello: string): boolean; cdecl; forward;
{$ENDIF MACOS}
exports
{$IFDEF MSWINDOWS}
say_Hello;
{$ENDIF MSWINDOWS}
{$IFDEF MACOS}
_say_Hello;
{$ENDIF MACOS}
Implementation
{$IFDEF MSWINDOWS}
function say_Hello(Hello: string): boolean; stdcall;
{$ENDIF MSWINDOWS}
{$IFDEF MACOS}
function _say_Hello(Hello: string): boolean; cdecl;
{$ENDIF MACOS}
begin
Result := True;
showmessage('In DLL: ' + Hello);
end;
end.
And lastly the simple test application:
unit uDylibTest1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
FMX.Controls.Presentation;
const
// Windows DLL Names
{$IFDEF MSWINDOWS}
TestDLL = 'pTestDLL.dll';
{$ENDIF MSWINDOWS}
// macOS DYLIB Names
{$IFDEF MACOS}
TestDLL = 'libpTestDLL.dylib';
{$ENDIF MACOS}
type
TfDylibTest = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TuDylibTest = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
{$IFDEF MSWINDOWS}
function say_Hello(Hello: string): boolean; stdcall; external TestDLL Delayed;
{$ENDIF MSWINDOWS}
{$IFDEF MACOS}
function _say_Hello(Hello: string): boolean; cdecl; external TestDLL;
{$ENDIF MACOS}
var
fDylibTest: TfDylibTest;
implementation
{$R *.fmx}
procedure TfDylibTest.Button1Click(Sender: TObject);
var
b:boolean;
begin
showmessage('B4 function call);
b := False;
// Call the DLL Function
{$IFDEF MSWINDOWS}
b := say_Hello('The string passed to the DLL') then
{$ENDIF MSWINDOWS}
{$IFDEF MACOS}
b := _say_Hello('The string passed to the DLL');
{$ENDIF MACOS}
if b then
showmessage('Say Hello OK')
else
showmessage('Say Hello Failed');
end;
procedure TfDylibTest.FormCreate(Sender: TObject);
begin
showmessage('onCreate');
end;
procedure TfDylibTest.FormShow(Sender: TObject);
begin
showmessage('onShow');
end;
end.
I'm answering tis on behalf of Alexander Brazda López who posted this on the G+ Delphi forum (so I'm not taking credit for someone else's answer):
ShowMessage() use FMX.Dialogs inside a dll. I remember there were some
limitations about this and use bpl instead dll was recomended. I would
try to find some information about this. Just for check, try to use a
reversestring function for test open and calling dll functions its
working.
I cannot comment WRT the veracity of the next part of the response, but to be fair will include it. However the bottom line was that with FMX.Dialogs or FMX.Forms in the uses clause, I got the error. Remove those and the TFMXApplicationDelegate error did not occur.
In the non-trivial 'real' example, I was using the unit for showmessage and for application.processmessages which was in a cross platform function called WaitNoFreeze() which, as the name suggests, puts a wait in the main thread, but does not stop all other activity like sleep().
The problem is not that the dylib is not found, this can be checked by
calling a basic function in the dylib as function sum (a, b: integer):
integer
The problem is when we invoke the UI through some function or use
embedded resources such as forms. This is because if we do not use
bpl, local instances are created instead of shared global instances,
this prevents functions such as RegisterClass or GetMem from sharing
the data. In the case of FMX there are differences due to the api for
the example that Kevin has exposed, in Windows the Handle of the
window is not necessary, so 0 is a valid value, in MacOSX it is
necessary but the value in the dynlib is NULL since it has not been
started, although in the application, but because the FMX instances
are not shared, it is not possible to access this information. It is
the same thing that happens when we try to free a block of memory in a
dll in windows that has been allocated by the application, that is why
we must use a bpl so that the memory manager of the application and
the dll is the same and maintain the same instances regardless of
whether their methods are invoked from the dll or from the exe
I've been writing some code using Lazarus 1.6.4 and FPC 3.0.2 to get USERNAME in Windows and USER in Linux. To achieve this I used SysUtils.GetEnvironmentVariable function. In Linux it works perfect, but in Windows it returns something corrupted that looks like UTF8 string opened in wrong encoding. My USERNAME in Windows has Cyrillic symbols so instead of the actual name GetEnvironmentVariable returns ???????? but it should be Пользователь.
Here my code:
function GetUserName: string;
{$IFDEF MSWINDOWS}
const
envVar = 'USERNAME';
{$ENDIF}
{$IFDEF UNIX}
envVar = 'USER';
{$ENDIF}
begin
Result := SysUtils.GetEnvironmentVariable(envVar);
{$IFDEF MSWINDOWS}
{ TODO : BUG: Does not work correct for non-latin strings }
Result := LazUTF8.UTF8ToWinCP(Result)
{$ENDIF}
end;
And it returns corrupted strings when it contains non-latain symbols inside.
How can I GetEnvironmentVariable in correct encoding in Windows OS?
Afaik in 3.0+ getenvironmentvariable is overloaded for unicodestring on windows, and that uses the -W variant.
var res,tag : unicodestring;
begin
tag:='HOME';
res:=getenvironmentvariable(tag);
end;
Just
getenvironmentstring (unicodestring('whatever'));
might also work, but make sure you either set utf8 as default encoding, or assign the result to an unicodestring.
After a day run I found the solution that work for me. You should use Unicode version of SysUtils.GetEnvironmentVariable and convert the result to the Windows current code page. Here the code below:
uses
{$IFDEF MSWINDOWS}
LazUTF8
{$ENDIF}
;
function GetUserName: string;
const
envVar: UnicodeString =
{$IFDEF MSWINDOWS}
'USERNAME'
{$ENDIF}
{$IFDEF UNIX}
'USER'
{$ENDIF};
begin
// USE Unicode String Version only!
Result := SysUtils.GetEnvironmentVariable(envVar);
{$IFDEF MSWINDOWS}
Result := LazUTF8.UTF8ToWinCP(Result)
{$ENDIF}
end;
UPDATE 01.12.2017
My Solution depends on ENV as David mentioned in comments. During the discussion I found more reliable solution here. The code below:
function GetCurrentUserName: String;
{$IFDEF WINDOWS}
const
MaxLen = 256;
var
Len: DWORD;
WS: WideString;
Res: windows.BOOL;
{$ENDIF}
begin
Result := '';
{$IFDEF UNIX}
{$IF (DEFINED(LINUX)) OR (DEFINED(FREEBSD))}
Result := SysToUtf8(GetUserName(fpgetuid)); //GetUsername in unit Users, fpgetuid in unit BaseUnix
{$ELSE Linux/BSD}
Result := GetEnvironmentVariableUtf8('USER');
{$ENDIF UNIX}
{$ELSE}
{$IFDEF WINDOWS}
Len := MaxLen;
{$IFnDEF WINCE}
if Win32MajorVersion <= 4 then
begin
SetLength(Result,MaxLen);
Res := Windows.GetuserName(#Result[1], Len);
//writeln('GetUserNameA = ',Res);
if Res then
begin
SetLength(Result,Len-1);
Result := SysToUtf8(Result);
end
else SetLength(Result,0);
end
else
{$ENDIF NOT WINCE}
begin
SetLength(WS, MaxLen-1);
Res := Windows.GetUserNameW(#WS[1], Len);
//writeln('GetUserNameW = ',Res);
if Res then
begin
SetLength(WS, Len - 1);
Result := Utf16ToUtf8(WS);
end
else SetLength(Result,0);
end;
{$ENDIF WINDOWS}
{$ENDIF UNIX}
end;
I have some code which uses EnumFontFamiliesEX to determine whether a particular font (using its "facename") is installed. The code was working fine in 32-bit. When I compile and run it as 64-bit, it kept throwing an exception in the callback routine.
I have now gotten it to work under both BUT only if instead of passing the function FindFontbyFaceName's result as the 4th parameter to EnumFontFamiliesEX, I pass a local (or global) variable - MYresult in this case. (And then set result from it). I don't understand what is going on? Can anyone explain or point me to a better way. (I'm not so much interested in the mechanics of the fonts, as the basic callback mechanics).
// single font find callback
function FindFontFace( {$IFDEF CPUX86} lpelf: PLogFont; {$ENDIF}
{$IFDEF CPUX64} lpelf: PEnumLogFontEx; {$ENDIF}
lpntm: PNewTextMetricEx;
AFontType: DWORD; var Aresult: lparam): integer ; stdcall;
begin
result := 0; // 1 shot only please - not interested in any variations in style etc
if (lpelf <> nil) then
Aresult := -1 // TRUE
else
Aresult := 0;
end;
function FindFontbyFaceName(ACanvas: TCanvas; const AFacename: string): boolean;
var
lf: TLogFont;
Myresult: boolean;
begin
MYresult := false;
FillChar(lf, SizeOf(lf), 0);
StrLCopy(lf.lfFaceName, PChar(AFacename), 32);
lf.lfCharSet := DEFAULT_CHARSET;
// this works in both 32 and 64 bit
EnumFontFamiliesEX(ACanvas.Handle, lf, #FindFontFace, lparam(#MYresult), 0);
result := MYresult;
// this works in 32 bit but throws exception in callback in 64 bit
// EnumFontFamiliesEX(ACanvas.Handle, lf, #FindFontFace, lparam(#result), 0);
end;
function FindFont(const AFacename: string): boolean;
var
AImage: TImage;
begin
AImage := Timage.Create(nil);
try
result := FindFontbyFaceName(AImage.Canvas, Afacename);
finally
Aimage.Free;
end;
end;
Your callback function is not declared correctly. You are declaring the last parameter as a var LPARAM, which is wrong. The lParam parameter is passed by value, not by reference. When calling EnumFontFamiliesEx() you are passing a pointer to a Boolean as the lParam value.
Your callback is trying to write sizeof(LPARAM) number of bytes to a memory address that only has SizeOf(Boolean) bytes available (and why are you trying to write a -1 to a Boolean?). So you are overwriting memory. When using a pointer to a local variable as the lParam, you are likely just overwriting memory on the calling function's call stack that does not really matter, so you don't see a crash.
You need to either:
remove the var and typecast the lParam parameter to a PBoolean:
function FindFontFace( lpelf: PLogFont;
lpntm: PTextMetric;
FontType: DWORD;
lParam: LPARAM): Integer ; stdcall;
begin
PBoolean(lParam)^ := True;
Result := 0; // 1 shot only please - not interested in any variations in style etc
end;
Or:
function FindFontFace( lpelf: PLogFont;
lpntm: PTextMetric;
FontType: DWORD;
lParam: PBoolean): Integer ; stdcall;
begin
lParam^ := True;
Result := 0; // 1 shot only please - not interested in any variations in style etc
end;
leave the var but change the parameter type to Boolean instead of LPARAM:
function FindFontFace( var lpelf: TLogFont;
var lpntm: TTextMetric;
FontType: DWORD;
var lParam: Boolean): Integer ; stdcall;
begin
lParam := True;
Result := 0; // 1 shot only please - not interested in any variations in style etc
end;
Either approach will allow you to pass #Result as the lParam to EnumFontFamiliesEx() in both 32bit and 64bit:
function FindFontbyFaceName(ACanvas: TCanvas; const AFacename: string): Boolean;
var
lf: TLogFont;
begin
Result := False;
FillChar(lf, SizeOf(lf), 0);
StrLCopy(lf.lfFaceName, PChar(AFacename), 32);
lf.lfCharSet := DEFAULT_CHARSET;
EnumFontFamiliesEX(ACanvas.Handle, lf, #FindFontFace, LPARAM(#Result), 0);
end;
On a side note, creating a TImage just to have a canvas to enumerate with is wasteful. You don't need it at all:
function FindFontFace( lpelf: PLogFont;
lpntm: PTextMetric;
FontType: DWORD;
lParam: LPARAM): integer ; stdcall;
begin
PBoolean(lParam)^ := True;
Result := 0; // 1 shot only please - not interested in any variations in style etc
end;
function FindFont(const AFacename: string): Boolean;
var
lf: TLogFont;
DC: HDC;
begin
Result := False;
FillChar(lf, SizeOf(lf), 0);
StrLCopy(lf.lfFaceName, PChar(AFacename), 32);
lf.lfCharSet := DEFAULT_CHARSET;
DC := GetDC(0);
EnumFontFamiliesEx(DC, lf, #FindFontFace, LPARAM(#Result), 0);
ReleaseDC(0, DC);
end;
That being said, you can simplify the code if you use the TScreen.Fonts property instead of calling EnumFontFamiliesEx() directly:
function FindFont(const AFacename: string): Boolean;
begin
Result := (Screen.Fonts.IndexOf(AFacename) <> -1);
end;
On linux endtime return results 0. It should be return number in miliseconds for given sleep function. On label on GUI it shows 0.
Here is code:
var starttime: longint;
var endtime: longint;
function GetTickCount : DWORD;
begin
{$IFDEF MSWINDOWS}
Result := Windows.GetTickCount mod High(LongInt);
{$ELSE}
Result := GetTickCount mod High(LongInt);
{$ENDIF}
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
starttime:=getTickCount;
// something do...
Sleep(1500); // sleep in miliseconds, works!
endtime:=getTickCount-starttime;
Label1.Caption:=inttostr(endtime); // returns 0 ?
end;
The gettickcount in the {$else} of the gettickcount function is interpreted as the return value of a procedure. (result in Delphi)
Depending on mode, this might lead to eternal recursion
Solution: just like in the windows case, qualify with unitname so
{$else}
Result := SysUtils.GetTickCount mod High(LongInt);
I need to get the volume serial number for a drive letter during an installation created with Inno Setup. I know that DLL functions can be imported into Inno, but I'm fairly new to it and having some problems getting it to work. I know that the GetVolumeInformation function in kernel32 can do what I need. Could someone show me how to import and use that functionality in an Inno script to retrieve the volume serial number?
Thanks!
Inno-Setup code::
[Code]
function GetVolumeInformation(
lpRootPathName: PChar;
lpVolumeNameBuffer: PChar;
nVolumeNameSize: DWORD;
var lpVolumeSerialNumber: DWORD;
var lpMaximumComponentLength: DWORD;
var lpFileSystemFlags: DWORD;
lpFileSystemNameBuffer: PChar;
nFileSystemNameSize: DWORD
): BOOL;
external 'GetVolumeInformationA#kernel32.dll stdcall';
function LoWord(dw: DWORD): WORD;
begin
Result := WORD(dw);
end;
function HiWord(dw: DWORD): WORD;
begin
Result := WORD((dw shr 16) and $FFFF);
end;
function WordToHex(w: WORD): string;
begin
Result := Format('%.4x', [w]);
end;
function FindVolumeSerial(const Drive: string): string;
var
FileSystemFlags: DWORD;
VolumeSerialNumber: DWORD;
MaximumComponentLength: DWORD;
begin
Result := '';
// Note on passing PChars using RemObjects Pascal Script:
// '' pass a nil PChar
// #0 pass an empty PChar
if GetVolumeInformation(
PChar(Drive),
'', // nil
0,
VolumeSerialNumber,
MaximumComponentLength,
FileSystemFlags,
'', // nil
0)
then
Result := WordToHex(HiWord(VolumeSerialNumber)) + '-' + WordToHex(LoWord(VolumeSerialNumber));
end;
function InitializeSetup(): Boolean;
begin
MsgBox(FindVolumeSerial('c:\'), mbInformation, mb_Ok);
end;
Tested with Inno-setup version 5.2.3
In Unicode versions of Inno-Setup replace PChar with PAnsiChar
Since the InnoSetup doesn't support pointers you will have to create the external library for the call of the GetVolumeInformation function. The following code samples should work for all combinations of the Delphi and InnoSetup (from the Unicode support point of view).
Here's the Delphi library code:
library VolumeInformation;
uses
Types, Classes, SysUtils, Windows;
var
SerialNumber: AnsiString;
function GetVolumeSerial(Drive: PAnsiChar): PAnsiChar; stdcall;
var
FileSystemFlags: DWORD;
VolumeSerialNumber: DWORD;
MaximumComponentLength: DWORD;
begin
SerialNumber := '';
GetVolumeInformationA(Drive, nil, 0, #VolumeSerialNumber,
MaximumComponentLength, FileSystemFlags, nil, 0);
SerialNumber := IntToHex(HiWord(VolumeSerialNumber), 4) + ' - ' +
IntToHex(LoWord(VolumeSerialNumber), 4);
Result := PAnsiChar(SerialNumber);
end;
exports
GetVolumeSerial;
end.
And here's the InnoSetup code:
[Files]
Source: "VolumeInformation.dll"; Flags: dontcopy
[Code]
function GetVolumeSerial(Drive: PAnsiChar): PAnsiChar;
external 'GetVolumeSerial#files:VolumeInformation.dll stdcall setuponly';
procedure ButtonOnClick(Sender: TObject);
var
S: string;
begin
S := GetVolumeSerial('c:\');
MsgBox(S, mbInformation, mb_Ok);
end;