I am trying to write a proxy server in Indy to receive HTTPS calls from external clients and forward them in HTTP to another server application on the same machine. Reason is that the other application does not support SSL and so I want to wrap its traffic in an SSL layer for external security.
My current approach is to use a TIdHTTPserver with an SSL IOHandler, and in its OnCommandGet handler I create a TIdHTTP client on the fly which fetches a TFileStream ContentStream from the internal application and returns that stream as the Response.ContentStream to the external caller.
The problem with this approach is the latency caused by having to wait for the internal content stream to be fully received before the external stream can start to be sent. And for example it cannot work for streaming media.
My question is: is there a better way to proxy HTTPS to HTTP that would work for streams? I.e without having to use an intermediate file stream.
If the requesting client supports HTTP 1.1 chunking (see RFC 2616 Section 3.6.1), that would allow you to read data from the target server and send it immediately to the client in real-time.
If you are using a fairly recent version of Indy, TIdHTTP has an OnChunkReceived event, and an hoNoReadChunked flag in its HTTPOptions property:
New TIdHTTP flags and OnChunkReceived event
In your TIdHTTPServer.OnCommand... event handler, you can populate the AResponseInfo as needed. Make sure to:
leave AResponseInfo.ContentText and AResponseInfo.ContentStream unassigned
set AResponseInfo.ContentLength to 0
set AResponseInfo.TransferEncoding to 'chunked'
Then call the AResponseInfo.WriteHeader() method directly, such as in the TIdHTTP.OnHeadersRecceived event, to send the response headers to the client.
Then you can read the target server's response body using OnChunkedReceived or hoNoReadChunked, and write each received chunk to the client using AContext.Connection.IOHandler directly.
However, there are some caveats to this:
if you use the TIdHTTP.OnChunkReceived event, you still need to provide an output TStream to TIdHTTP or else the event is not triggered (this restriction might be removed in a future release). However, you can use TIdEventStream without assigning an OnWrite event handler to it. Or write a custom TStream class that overrides the virtual Write() method to do nothing. Or, just use any TStream you want, and have the OnChunkReceived event handler clear the received Chunk so nothing is available to write to the TStream.
if you use the hoNoReadChunked flag, this allows you to manually read the HTTP chunks from TIdHTTP.IOHandler directly after TIdHTTP exits. Just make sure you enable HTTP keep-alives or else TIdHTTP will close the connection to the server before you have a chance to read the server's response body.
If you are using an older version of Indy, or if the target server does not support chunking, all is not lost. You should be able to write a custom TStream class that overwrites the virtual Write() method to write the provided data block to the client as an HTTP chunk. And then you can use that class as the output TStream for TIdHTTP.
If the client does not support HTTP chunking, or if these approaches do not work for you, then you will likely have to resort to using TIdTCPServer directly instead of TIdHTTPServer and implement the entire HTTP protocol yourself from scratch, then you can handle your own streaming as needed. Have a look at the source code for TIdHTTPProxyServer for some ideas (TIdHTTPProxyServer itself is not suitable for your particular situation, but it will show you how to pass HTTP requests/responses between connections in near real-time in general).
Thank you for the very comprehensive answer. The way I finally solved it was to create a TStream descendent to use as the ContentStream for the server response. The TStream wis a wrapper around a TIdTcpClient which contains a rudimentary HTTP implementation and whose TStream.Read function fetches the HTTP contents for the Tcp connection.
type
TTcpSocketStream = class(TStream)
private
FAuthorization: string;
FBuffer: TBytes;
FBytesRead: Int64;
FCommand: string;
FContentLength: Int64;
FContentType: string;
FDocument: string;
FHeaders: TIdHeaderList;
FHost: string;
FIntercept: TServerLogEvent;
FPort: Integer;
FResponseCode: Integer;
FQueryParams: string;
FTcpClient: TIdTCPClient;
FWwwAuthenticate: string;
public
constructor Create;
destructor Destroy; override;
procedure Initialize;
function Read(var Buffer; Count: Longint): Longint; override;
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
property Authorization: string read FAuthorization write FAuthorization;
property Command: string read FCommand write FCommand;
property ContentType: string read FContentType;
property ContentLength: Int64 read FContentLength;
property Document: string read FDocument write FDocument;
property Host: string read fHost write FHost;
property Intercept: TServerLogEvent read FIntercept write FIntercept;
property Port: Integer read FPort write FPort;
property QueryParams: string read FQueryParams write FQueryParams;
property ResponseCode: Integer read FResponseCode;
property WWWAuthenticate: string read FWwwAuthenticate
write FWwwAuthenticate;
end;
const
crlf = #13#10;
cContentSeparator = crlf+crlf;
implementation
{ TTcpSocketStream }
constructor TTcpSocketStream.Create;
begin
inherited;
FHeaders := TIdHeaderList.Create(QuoteHTTP);
FTcpClient := TIdTcpClient.Create(nil);
FTcpClient.ConnectTimeout := 5000;
FTcpClient.ReadTimeout := 5000;
FCommand := 'GET';
FPort := 443;
FResponseCode := 404;
end;
destructor TTcpSocketStream.Destroy;
begin
if FTcpClient.Connected then
FTcpClient.Disconnect;
if FTcpClient.Intercept <> nil then
begin
FTcpClient.Intercept.Free;
FTcpClient.Intercept := nil;
end;
FTcpClient.Free;
FHeaders.Free;
SetLength(FBuffer, 0);
inherited;
end;
procedure TTcpSocketStream.Initialize;
var
s: string;
LLog: TClientLogEvent;
LRespText: string;
begin
try
if FQueryParams <> '' then
FQueryParams := '?' + FQueryParams;
FTcpClient.Port := FPort;
FTcpClient.Host := FHost;
if FIntercept <> nil then
begin
LLog := TClientLogEvent.Create;
LLog.OnLog := FIntercept.OnLog;
FTcpClient.Intercept := LLog;
end;
FTcpClient.Connect;
if FTcpClient.Connected then
begin
FTcpClient.IOHandler.Writeln(Format('%s %s%s HTTP/1.1',
[FCommand, FDocument, FQueryParams]));
FTcpClient.IOHandler.Writeln('Accept: */*');
if FAuthorization <> '' then
FTcpClient.IOHandler.Writeln(Format('Authorization: %s',
[FAuthorization]));
FTcpClient.IOHandler.Writeln('Connection: Close');
FTcpClient.IOHandler.Writeln(Format('Host: %s:%d', [FHost, FPort]));
FTcpClient.IOHandler.Writeln('User-Agent: Whitebear SSL Proxy');
FTcpClient.IOHandler.Writeln('');
LRespText := FTcpClient.IOHandler.ReadLn;
s := LRespText;
Fetch(s);
s := Trim(s);
FResponseCode := StrToIntDef(Fetch(s, ' ', False), -1);
repeat
try
s := FTcpClient.IOHandler.ReadLn;
except
on Exception do
break;
end;
if s <> '' then
FHeaders.Add(s);
until s = '';
FContentLength := StrToInt64Def(FHeaders.Values['Content-Length'], -1);
FContentType := FHeaders.Values['Content-Type'];
FWwwAuthenticate := FHeaders.Values['WWW-Authenticate'];
end;
except
on E:Exception do ;
end;
end;
function TTcpSocketStream.Read(var Buffer; Count: Integer): Longint;
begin
Result := 0;
try
if FTcpClient.Connected then
begin
if Length(FBuffer) < Count then
SetLength(FBuffer, Count);
FTcpClient.IOHandler.ReadBytes(FBuffer, Count, False);
Move(FBuffer[0], PChar(Buffer), Count);
Inc(FBytesRead, Count);
Result := Count;
end;
except
on Exception do ;
end;
end;
function TTcpSocketStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
Result := 0;
case Origin of
soBeginning: Result := Offset;
soCurrent: Result := FBytesRead + Offset;
soEnd: Result := FContentLength + Offset;
end;
end;
Related
The code below works fine if executed as a single command. I use it to download PDFs that are typically a few megabytes in size.
However, when I execute it multiple times in a loop, all the downloaded files are exactly the same size (truncated # ~50KB). Is there an issue with my code or is this the server not being able to deliver as expected?
type
TKeyValuePair = record
Key: String;
Value: String;
end;
TKeyValueSet = Array of TKeyValuePair;
// ...
function TDM.MultipartPost(const URL, LocalFile: String; const MultipartData: TKeyValueSet): Boolean;
var
i: Integer;
httpclient: THTTPClient;
formdata: TMultipartFormData;
fstream: TFileStream;
begin
httpclient := THTTPClient.Create;
formdata := TMultipartFormData.Create();
fstream := nil;
try
fstream := TFileStream.Create(LocalFile, fmCreate);
except
// removed
end;
try
for i := Low(MultipartData) to High(MultipartData) do
formdata.AddField(MultipartData[i].Key, MultipartData[i].Value);
try
httpclient.Post(url, formdata, fstream);
except
// removed
end;
finally
FlushFileBuffers(fstream.Handle);
fstream.Free;
formdata.Free;
httpclient.Free;
//
Result := FileExists(LocalFile) and (GetFileSize(LocalFile) > 0);
end;
end;
Mea culpa - my apologies to all that read/replied to my post. I just built a minimal, but complete application to demonstrate the issue and, voila, it worked just fine.
1 hour or so of not seeing the forest for all the trees later:
I accidentally used an incorrect URL to post to :(
I call a function from DLL-file in Inno Setup Script and its return type is PAnsiChar.
In order to get the whole string I need to dereference the pointer but the standard pascal syntax doesn't work here.
Is it even possible to do that?
function SQLDLL : PAnsiChar;
external 'GetSQLServerInstances#files:IsStartServer.dll stdcall setuponly';
function NextButtonClick(CurPage: Integer): Boolean;
var
hWnd: Integer;
Str : AnsiString;
begin
if CurPage = wpWelcome then begin
hWnd := StrToInt(ExpandConstant('{wizardhwnd}'));
MessageBox(hWnd, 'Hello from Windows API function', 'MessageBoxA', MB_OK or MB_ICONINFORMATION);
MyDllFuncSetup(hWnd, 'Hello from custom DLL function', 'MyDllFunc', MB_OK or MB_ICONINFORMATION);
Str := SQLDLL;
try
{ if this DLL does not exist (it shouldn't), an exception will be raised }
DelayLoadedFunc(hWnd, 'Hello from delay loaded function', 'DllFunc', MB_OK or MB_ICONINFORMATION);
except
{ handle missing dll here }
end;
end;
Result := True;
end;
I have only the DLL-file. The original language is Delphi.
I updated to the latest version of Inno Setup 6.0.3 and tested this code on my home Windows 10 Pro machine:
[Setup]
AppName=My Program
AppVersion=1.5
WizardStyle=modern
DefaultDirName={autopf}\My Program
DisableProgramGroupPage=yes
DisableWelcomePage=no
UninstallDisplayIcon={app}\MyProg.exe
OutputDir=userdocs:Inno Setup Examples Output
[Files]
Source: "MyProg.exe"; DestDir: "{app}"
Source: "MyProg.chm"; DestDir: "{app}"
Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme
Source: "IsStartServer.dll"; Flags: dontcopy
[Code]
function SQLDLL : PAnsiChar;
external 'GetSQLServerInstances#files:IsStartServer.dll stdcall';
function NextButtonClick(CurPage: Integer): Boolean;
var
Str : PAnsiChar;
begin
Str := SQLDLL;
Result := True;
end;
and now I'm having this kind of error:
I don't understand why does it have to look into my 'temp' directory? I've also heard that this problem may somehow be connected with the group policies in Windows 10 UAC, but I'm not really sure what should I do here to get rid of this error.
If I understand correctly, your SQLDLL manages some memory buffer itself and returns a pointer to a Unicode string (not ANSI, that's why you got only one character when you tried PAnsiChar, according to your comment).
Inno Setup doesn't support this directly and doesn't even have a PWideChar type. However, we can handle it ourselves. We just have to allocate a Inno string with the right size and copy the data manually.
Here is a working example how to do that. It uses GetCommandLineW as an example function that returns a PWideChar, but you can do the same with your SQLDLL function.
Get the pointer from the external function and store it in a variable (a Cardinal - in my example I created a typedef PWideChar for it).
Get the string length using lstrlenW.
Create an empty regular String, but set it to the right length using SetLength. This will reserve enough capacity that we can write the actual contents into it in the next step.
Use lstrcpyW to copy the string that's referenced by the pointer to your regular String variable.
(In case you use the ANSI version of Inno Setup: Use WideCharToMultiByte instead, see my update at the end of this post.)
The trick is to import lstrcpyW in such a way that the destination pointer is declared as String but the source pointer is declared as Cardinal (or my typedef PWideChar here).
type
PWideChar = Cardinal; { Inno doesn't have a pointer type, so we use a Cardinal instead }
{ Example of a function that returns a PWideChar }
function GetCommandLineW(): PWideChar;
external 'GetCommandLineW#kernel32.dll stdcall';
{ This function allows us to get us the length of a string from a PWideChar }
function lstrlenW(lpString: PWideChar): Cardinal;
external 'lstrlenW#kernel32.dll stdcall';
{ This function copies a string - we declare it in such a way that we can pass a pointer
to an Inno string as destination
This works because Inno will actually pass a PWideChar that points to the start of the
string contents in memory, and internally the string is still null-terminated
We just have to make sure that the string already has the right size beforehand! }
function lstrcpyW_ToInnoString(lpStringDest: String; lpStringSrc: PWideChar): Integer;
external 'lstrcpyW#kernel32.dll stdcall';
function InitializeSetup(): Boolean;
var
returnedPointer: PWideChar; { This is what we get from the external function }
stringLength: Cardinal; { Length of the string we got }
innoString: String; { This is where we'll copy the string into }
begin
{ Let's get the PWideChar from the external function }
returnedPointer := GetCommandLineW();
{ The pointer is actually just a renamed Cardinal at this point: }
Log('String pointer = ' + IntToStr(returnedPointer));
{ Now we have to manually allocate a new Inno string with the right length and
copy the data into it }
{ Start by getting the string length }
stringLength := lstrlenW(returnedPointer);
Log('String length = ' + IntToStr(stringLength));
{ Create a string with the right size }
innoString := '';
SetLength(innoString, stringLength);
{ This check is necessary because an empty Inno string would translate to a NULL pointer
and not a pointer to an empty string, and lstrcpyW cannot handle that. }
if StringLength > 0 then begin
{ Copy string contents from the external buffer to the Inno string }
lstrcpyW_ToInnoString(innoString, returnedPointer);
end;
{ Now we have the value stored in a proper string variable! }
Log('String value = ' + innoString);
Result := False;
end;
If you put this into an installer and run it, you see output like this:
[15:10:55,551] String pointer = 9057226
[15:10:55,560] String length = 106
[15:10:55,574] String value = "R:\Temp\is-9EJQ6.tmp\testsetup.tmp" /SL5="$212AC6,121344,121344,Z:\Temp\testsetup.exe" /DEBUGWND=$222722
As you can see, the command line string (which we get as a PWideChar) is copied to a regular string variable correctly and can be accessed normally at the end.
Update: In case you are using the ANSI version of Inno Setup and not Unicode, this code alone won't work. The change needed is this: Instead of using lstrcpyW, you'd use WideCharToMultiByte:
function WideCharToMultiByte_ToInnoString(CodePage: Cardinal; dwFlags: Cardinal; lpWideCharStr: PWideChar; cchWideChar: Cardinal; lpMultiByteStr: String; cbMultiByte: Cardinal; lpDefaultChar: Cardinal; lpUsedDefaultChar: Cardinal): Integer;
external 'WideCharToMultiByte#kernel32.dll stdcall';
{ Later on: Instead of calling lstrcpyW_ToInnoString, use this:
Note: The first parameter 0 stands for CP_ACP (current ANSI code page), and the
string lengths are increased by 1 to include the null terminator }
WideCharToMultiByte_ToInnoString(0, 0, returnedPointer, stringLength + 1, innoString, stringLength + 1, 0, 0);
You cannot dereference a pointer in Inno Setup Pascal Script.
But there are numerous hacks that allow that. Those hacks are very specific, so it depends on particular use case.
Though in your specific case, as pointers to character arrays are widespread in APIs, Inno Setup Pascal Script (similarly to Delphi) can assign a pointer to a character array to a string.
So, you should be able to simply assign the PChar to AnsiString:
function ReturnsPAnsiChar: PAnsiChar; extern '...';
var
Str: AnsiString;
begin
Str := ReturnsPAnsiChar;
end;
See How to return a string from a DLL to Inno Setup?
what is the best method to do this: i have a txt file filled with web addresses, i have to check all of them using idHTTP component,only a simple check from a web server, downloading the html and finding a match, i want it to to be fast, there is different types of threads and i am not sure what is the best to use, a TParallel for or Task threads or regular threads?
i tried before TParallel for and i got stuck at AV, also i've tried Task threads but its not fast, the http request becomes slower by time, i tried also the regular threads and i didnt know how to use it because its complicated to use.
note: Please do NOT downvote i just need advice from the experts. Thank you
First advice: do not use Indy. Use THTTPClient (unit System.Net.HttpClient) -- native for Delphi XE?+
I am still using old TThreads. I could make suggestion only with TThread.
Workflow:
Main thread -- reading your TXT file line by line.
After line was readed, you create NEW thread, which are downloading information from WWW.
Sample of application:
unit ufmMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants,
{ TThread }
System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TLoad = class(TThread)
protected
FURL,
FOutputFileName: String;
procedure Execute; override;
public
constructor Create(const AURL, AOutputFileName: String); overload;
end;
HTTP = class
public
class procedure Get(const AURL: String; out AOutputStream: TMemoryStream);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
{ THTTPClient }
System.Net.HttpClient;
procedure TForm1.Button1Click(Sender: TObject);
var
LLoad: TLoad;
LFile: TextFile;
LCycle: Integer;
LUrl: String;
begin
LCycle := 0;
AssignFile(LFile, 'urls.txt');
try
Reset(LFile);
while not Eof(LFile) do
begin
{ Using for generate file name. All file names must be unique }
Inc(LCycle);
{ Read next URL }
ReadLn(LFile, LUrl);
{ Create new thread }
LLoad := TLoad.Create(LUrl, 'Output file No ' + LCycle.ToString + '.htm');
LLoad.FreeOnTerminate := True;
LLoad.Start;
end;
finally
CloseFile(LFile);
end;
end;
{ TLoad }
constructor TLoad.Create(const AURL, AOutputFileName: String);
begin
inherited Create(True);
FURL := AURL;
FOutputFileName := AOutputFileName;
end;
procedure TLoad.Execute;
var
LResponse: TMemoryStream;
begin
inherited;
LResponse := TStringStream.Create;
try
HTTP.Get(FURL, LResponse);
{ Save result to file }
LResponse.SaveToFile(GetCurrentDir + PathDelim + FOutputFileName);
finally
LResponse.Free;
end;
end;
{ HTTP }
class procedure HTTP.Get(const AURL: String; out AOutputStream: TMemoryStream);
var
LStream: TStream;
LHTTPClient: THTTPClient;
begin
LHTTPClient := THTTPClient.Create;
try
LStream := LHTTPClient.Get(AURL).ContentStream;
AOutputStream.CopyFrom(LStream, LStream.Size);
finally
LHTTPClient.Free;
end;
end;
end.
Why I against Indy:
1) THTTPClient do not required additional DLL for works with SSL protocol
2) THTTPClient is modern from Delphi XE8
3) My subjective opinion: THTTPClient works much more smoothly (with less issues) then Indy library. I used Indy for last 10 years, but now all my supported project moved to THTTPClient.
You can use TTask and Indy (TIdHTTP). Example:
function GetUrl(const aUrl: string): ITask;
begin
Result := TTask.Run(
procedure
var
FOutput: string;
FHTTP: TIdHTTP;
begin
FHTTP:=TIdHTTP.Create(nil);
try
try
FOutput:=FHTTP.Get(aUrl);
except
// handle errors
end;
finally
FHTTP.Free;
end;
TThread.Synchronize(nil,
procedure
begin
ProcessOutput(FOutput); // send your output/result to main thread
end);
end );
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
list: TStringList;
begin
list:=TStringList.Create;
try
list.LoadFromFile('yourfile.txt');
// get all your urls
// you should control how many threads run at the same time
for i := 0 to list.Count-1 do
GetUrl(list[i]);
finally
list.Free;
end;
end;
I'm using the excellent Inno Setup installer and I notice that some Applications (often from Microsoft) get installed with their launch icon already highly visible ('pinned?') in the start menu (in Windows 7). Am I totally reliant on the most-recently-used algorithm for my icon to be 'large' in the start menu, or is there a way of promoting my application from the installer please?
It is possible to pin programs, but not officially. Based on a code posted in this thread (which uses the same way as described in the article linked by #Mark Redman) I wrote the following:
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
const
// these constants are not defined in Windows
SHELL32_STRING_ID_PIN_TO_TASKBAR = 5386;
SHELL32_STRING_ID_PIN_TO_STARTMENU = 5381;
SHELL32_STRING_ID_UNPIN_FROM_TASKBAR = 5387;
SHELL32_STRING_ID_UNPIN_FROM_STARTMENU = 5382;
type
HINSTANCE = THandle;
HMODULE = HINSTANCE;
TPinDest = (
pdTaskbar,
pdStartMenu
);
function LoadLibrary(lpFileName: string): HMODULE;
external 'LoadLibrary{#AW}#kernel32.dll stdcall';
function FreeLibrary(hModule: HMODULE): BOOL;
external 'FreeLibrary#kernel32.dll stdcall';
function LoadString(hInstance: HINSTANCE; uID: UINT;
lpBuffer: string; nBufferMax: Integer): Integer;
external 'LoadString{#AW}#user32.dll stdcall';
function TryGetVerbName(ID: UINT; out VerbName: string): Boolean;
var
Buffer: string;
BufLen: Integer;
Handle: HMODULE;
begin
Result := False;
Handle := LoadLibrary(ExpandConstant('{sys}\Shell32.dll'));
if Handle <> 0 then
try
SetLength(Buffer, 255);
BufLen := LoadString(Handle, ID, Buffer, Length(Buffer));
if BufLen <> 0 then
begin
Result := True;
VerbName := Copy(Buffer, 1, BufLen);
end;
finally
FreeLibrary(Handle);
end;
end;
function ExecVerb(const FileName, VerbName: string): Boolean;
var
I: Integer;
Shell: Variant;
Folder: Variant;
FolderItem: Variant;
begin
Result := False;
Shell := CreateOleObject('Shell.Application');
Folder := Shell.NameSpace(ExtractFilePath(FileName));
FolderItem := Folder.ParseName(ExtractFileName(FileName));
for I := 1 to FolderItem.Verbs.Count do
begin
if FolderItem.Verbs.Item(I).Name = VerbName then
begin
FolderItem.Verbs.Item(I).DoIt;
Result := True;
Exit;
end;
end;
end;
function PinAppTo(const FileName: string; PinDest: TPinDest): Boolean;
var
ResStrID: UINT;
VerbName: string;
begin
case PinDest of
pdTaskbar: ResStrID := SHELL32_STRING_ID_PIN_TO_TASKBAR;
pdStartMenu: ResStrID := SHELL32_STRING_ID_PIN_TO_STARTMENU;
end;
Result := TryGetVerbName(ResStrID, VerbName) and ExecVerb(FileName, VerbName);
end;
function UnpinAppFrom(const FileName: string; PinDest: TPinDest): Boolean;
var
ResStrID: UINT;
VerbName: string;
begin
case PinDest of
pdTaskbar: ResStrID := SHELL32_STRING_ID_UNPIN_FROM_TASKBAR;
pdStartMenu: ResStrID := SHELL32_STRING_ID_UNPIN_FROM_STARTMENU;
end;
Result := TryGetVerbName(ResStrID, VerbName) and ExecVerb(FileName, VerbName);
end;
The above code first reads the caption of the menu item for pinning or unpinning applications from the string table of the Shell32.dll library. Then connects to the Windows Shell, and for the target app. path creates the Folder object, then obtains the FolderItem object and on this object iterates all the available verbs and checks if their name matches to the one read from the Shell32.dll library string table. If so, it invokes the verb item action by calling the DoIt method and exits the iteration.
Here is a possible usage of the above code, for pinning:
if PinAppTo(ExpandConstant('{sys}\calc.exe'), pdTaskbar) then
MsgBox('Calc has been pinned to the taskbar.', mbInformation, MB_OK);
if PinAppTo(ExpandConstant('{sys}\calc.exe'), pdStartMenu) then
MsgBox('Calc has been pinned to the start menu.', mbInformation, MB_OK);
And for unpinning:
if UnpinAppFrom(ExpandConstant('{sys}\calc.exe'), pdTaskbar) then
MsgBox('Calc is not pinned to the taskbar anymore.', mbInformation, MB_OK);
if UnpinAppFrom(ExpandConstant('{sys}\calc.exe'), pdStartMenu) then
MsgBox('Calc is not pinned to the start menu anymore.', mbInformation, MB_OK);
Please note that even though this code works on Windows 7 (and taskbar pinning also on Windows 8.1 where I've tested it), it is really hacky way, since there is no official way to programatically pin programs to taskbar, nor start menu. That's what the users should do by their own choice.
There's a reason there's no programmatic way to pin things to the taskbar/start menu. In my experience, I have seen the start menu highlight newly-created shortcuts, and that's designed to handle exactly this situation. When you see a newly-installed program show up on the start menu, it's probably because of that algorithm and not because the installer placed it there.
That said, if a new shortcut does not appear highlighted, it may be because the installer extracts a pre-existing shortcut and preserves an old timestamp on it, rather than using the API function to create a shortcut in the start menu.
Have a look at: http://blogs.technet.com/deploymentguys/archive/2009/04/08/pin-items-to-the-start-menu-or-windows-7-taskbar-via-script.aspx
I have the following situation:
function Mach3Code(Str: String): String;
var StrOut: String;
begin
StrOut := '';
try
IdTelnet1.Connect();
IdTelnet1.Write(Str);
StrOut := ''; // assign here return output;
finally
IdTelnet1.Disconnect;
end;
Result := StrOut;
end;
On the line "StrOut := '';" I need to get the text output of the server (which is a tcp server, written in vc 2008 by me as Mach3 plugin).
Normally, the client sends "COMMAND1" and the server replies with "ANSWER1#" or something like this. I need the code to wait for the answer and then return it, synchronously, so I can do something like:
StrResult := Mach3Code('G0X300Y200');
and read what the server part has sent to me.
any ideas how I can solve this problem?
TIdTelnet is an asynchronous componnt, it is not suited for what you are attempting to do. Unless you are dealing with the actual Telnet protocol, then you should use TIdTCPClient instead:
function Mach3Code(const Str: String): String;
begin
Result := '';
try
IdTCPClient1.Connect();
IdTCPClient1.WriteLn(Str);
StrOut := IdTCPClient1.ReadLn('#');
finally
IdTCPClient1.Disconnect;
end;
end;
to receive data assign an event handler of type TIdTelnetDataAvailEvent to the OnDataAvailable property of idtelnet1, i know this is not synchronous but i would re factor your code to work this way as this is how the telnet client is designed to work.
Failing that create your own TIdTCPClientCustom decendant and implement your own read thread with the relevent methods.