-
Content Count
2685 -
Joined
-
Last visited
-
Days Won
113
Everything posted by Remy Lebeau
-
Runtime create new "fields" with RTTI on a tComponent
Remy Lebeau replied to microtronx's topic in RTL and Delphi Object Pascal
That is not possible. Delphi is a compiled language, you can't alter object layouts at runtime. The best you could do is store your data in another class/record object that you create at runtime, and then use the component's Tag property to point at that object. -
Batch Reading Emails from Windows Explorer
Remy Lebeau replied to Mark Williams's topic in General Help
Then you don't have your project setup to include that folder in its search paths. Simply adding the unit to the Protocols folder won't magically add it to Indy, since it is already compile. That would require adding the unit to Indy's Protocols package, and then recompiling and reinstalling Indy. -
Binary compatible, in that they have the same memory layout, yes. But you can't pass a TBytes to a 'var TIdBytes' function parameter, and vice versa, unless you use a type-cast, eg: type PIdBytes = ^TIdBytes; var Bytes: TBytes; SetLength(Bytes, 6 + Buffersize); CopyTIdString('Audio1', PIdBytes(@Bytes)^, 0); CopyTIdBytes(RawToBytes(Buffer^, Buffersize), 0, PIdBytes(@Bytes)^, 6, Buffersize); Or maybe just: var Bytes: TBytes; SetLength(Bytes, 6 + Buffersize); CopyTIdString('Audio1', TIdBytes(Bytes), 0); CopyTIdBytes(RawToBytes(Buffer^, Buffersize), 0, TIdBytes(Bytes), 6, Buffersize);
-
CopyTIdBytes() is just a Move() from one TIdBytes to another, using starting source/target indexes and a byte length: procedure CopyTIdBytes(const ASource: TIdBytes; const ASourceIndex: Integer; var VDest: TIdBytes; const ADestIndex: Integer; const ALength: Integer); {$IFDEF USE_INLINE}inline;{$ENDIF} begin {$IFDEF DOTNET} System.array.Copy(ASource, ASourceIndex, VDest, ADestIndex, ALength); {$ELSE} //if these asserts fail, then it indicates an attempted buffer overrun. Assert(ASourceIndex >= 0); Assert((ASourceIndex+ALength) <= Length(ASource)); Move(ASource[ASourceIndex], VDest[ADestIndex], ALength); {$ENDIF} end; CopyTIdString(), on the other hand, is a little more involved, as it first has to convert the input String to bytes in a specified encoding, and then it moves those bytes into the target TIdBytes where specified: procedure CopyTIdString(const ASource: String; var VDest: TIdBytes; const ADestIndex: Integer; const ALength: Integer = -1; ADestEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); overload; {$IFDEF USE_INLINE}inline;{$ENDIF} begin CopyTIdString(ASource, 1, VDest, ADestIndex, ALength, ADestEncoding {$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF} ); end; procedure CopyTIdString(const ASource: String; const ASourceIndex: Integer; var VDest: TIdBytes; const ADestIndex: Integer; const ALength: Integer = -1; ADestEncoding: IIdTextEncoding = nil {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF} ); overload; {$IFDEF USE_INLINE}inline;{$ENDIF} var LLength: Integer; {$IFDEF STRING_IS_ANSI} LTmp: TIdWideChars; {$ENDIF} begin {$IFDEF STRING_IS_ANSI} LTmp := nil; // keep the compiler happy {$ENDIF} LLength := IndyLength(ASource, ALength, ASourceIndex); if LLength > 0 then begin EnsureEncoding(ADestEncoding); {$IFDEF STRING_IS_UNICODE} ADestEncoding.GetBytes(ASource, ASourceIndex, LLength, VDest, ADestIndex); {$ELSE} EnsureEncoding(ASrcEncoding, encOSDefault); LTmp := ASrcEncoding.GetChars(RawToBytes(ASource[ASourceIndex], LLength)); // convert to Unicode ADestEncoding.GetBytes(LTmp, 0, Length(LTmp), VDest, ADestIndex); {$ENDIF} end; end; So, given your example, the simplest translation to TBytes would look like this: var Bytes: TBytes; SetLength(Bytes, 6 + Buffersize); TEncoding.Default.GetBytes('Audio1', 1, 6, Bytes, 0); Move(Buffer^, Bytes[6], Buffersize);
-
Batch Reading Emails from Windows Explorer
Remy Lebeau replied to Mark Williams's topic in General Help
Like I said, the IdCoderTNEF unit has been in the Protocols folder for years. But if you are using the version of Indy that shipped pre-installed with each IDE, and they don't have the unit present, then Embarcadero must have chosen not to distribute it. The unit is not actually referenced by the IndyProtocols package, as it is not part of the main Indy packages, just a standalone utility unit, so Embarcadero may have just skipped it as a non-dependency. In which case, you will have to download it from Indy's GitHub repo instead (https://github.com/IndySockets/Indy/blob/master/Lib/Protocols/IdCoderTNEF.pas). -
Batch Reading Emails from Windows Explorer
Remy Lebeau replied to Mark Williams's topic in General Help
Which version of Indy are you looking at? TIdCoderTNEF was introduced in Indy 10, are you looking at Indy 9, perhaps? TIdCoderTNEF is not documented in the help files. No, TIdCoderTNEF still exists in Indy 10. TIdCoderTNEF has been in Indy 10 for over a decade. It is in the IndyProtocols package. There is no documentation available for TIdCoderTNEF, other than comments in the unit itself. I think it was created after the last time the documentation was updated, which was sadly a very long time ago. In a nutshell, you would simply extract a TNEF attachment from a normal email, and pass that attachment to TIdCoderTNEF.Parse(), which will then populate a TIdMessage with the relevant message data (headers, body, attachments, etc) from the TNEF. -
Batch Reading Emails from Windows Explorer
Remy Lebeau replied to Mark Williams's topic in General Help
Since the MSG format is publicly documented, it would be feasible to add support for it in Indy, in a manner that does not depend on Outlook APIs. Similar to how Indy already does for TNEF (winmail.dat) attachments via the TIdCoderTNEF class. But having the TIME to make such an implementation is an issue, for me at least. -
Messages are used, yes (LB_ADDSTRING, etc). But not the message queue, no. The messages in question would be SENT directly to the ListBox's window procedure for immediate processing, not POSTED to the message queue of the thread that owns the ListBox awaiting dispatching to the ListBox's window procedure.
-
Batch Reading Emails from Windows Explorer
Remy Lebeau replied to Mark Williams's topic in General Help
No, it does not, only emails in RFC822 format, like .EML files. -
Why do these 2 statments produce different results ?
Remy Lebeau replied to AndrewHoward's topic in Delphi IDE and APIs
'#$02' (w/ quotes) is a string literal consisting of 4 distinct characters: '#', '$', '0', '2'. The string you are searching, 'bla,bla,#$02,bla', contains that 4-char substring in it, hence the result is > 0. #$02 (w/o quotes) is a single Char whose numeric value is 2. The string you are searching does not contain that character in it, hence the result is 0. -
Support for OSX 64-bit was introduced in 10.3.2: https://blog.marcocantu.com/blog/2019-july-building-mocOS64-apps-delphi.html Support for Android 64-bit was introduced in 10.3.3: https://blog.marcocantu.com/blog/2019-delphi-android-64bit-rad-1033.html
-
Subnet broadcasting is supported only in IPv4, not in IPv6, you have to use multicasting instead.
-
IOTAProcess.ReadProcessMemory / .WriteProcessMemory
Remy Lebeau replied to dummzeuch's topic in Delphi IDE and APIs
That parameter is what I was referring to. -
My custom component becomes NI>
Remy Lebeau replied to alogrep's topic in RTL and Delphi Object Pascal
Run the component code in the debugger, and put a DATA BREAKPOINT on the pointer variable that is turning nil unexpectedly, and when the breakpoint is hit then look at the call stack to see who is setting the pointer to nil. -
IOTAProcess.ReadProcessMemory / .WriteProcessMemory
Remy Lebeau replied to dummzeuch's topic in Delphi IDE and APIs
That would be my assumption as well, since the underlying Win32 API (Read|Write)ProcessMemory() functions return the number of bytes actually read/written. -
It is generally considered bad practice to ask the same question across multiple forums at the same time. It is rude to the people who decide to help you. If you are getting help somewhere, STAY THERE. I spent the last 3 days of my time helping you, just for you to now throw away everything I had given you and go somewhere else to start over. That is a slap in my face. Thanks for nothing.
-
Did you post this same issue on StackOverflow? https://stackoverflow.com/questions/64581935/
-
And your point of showing that code is .... what, exactly?
-
Why is it hard? TTask has constructors that take a TThreadPool as input. Create a single TThreadPool, configure it as needed, such as MaxWorkerThreads, and then pass that pool to each TTask you create.
-
Connect the TTask objects to a shared TThreadPool that limits how many threads can run at a time. Or, use a shared Win32 semaphore to limit how many TTask threads can send HTTP requests at a time.
-
Try something like this: procedure TForm1.Button1Click(Sender: TObject); var i: integer; lPaths: array of string; begin SetLength(lPaths, ListBox1.Items.Count); for i := 0 to ListBox1.Items.Count-1 do begin lPaths[i] := ListBox1.Items.Strings[i]; end; TParallel.&For(0, ListBox1.Items.Count-1, procedure(AIndex: Integer) var lPath: string; lHTTP: TIdHTTP; IdSSL: TIdSSLIOHandlerSocketOpenSSL; URI: TIdURI; begin lPath := lPaths[AIndex]; lHTTP := TIdHTTP.Create(nil); try lHTTP.ReadTimeout := 30000; lHTTP.HandleRedirects := True; IdSSL := TIdSSLIOHandlerSocketOpenSSL.Create(lHTTP); IdSSL.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2]; IdSSL.SSLOptions.Mode := sslmClient; lHTTP.IOHandler := IdSSL; lHTTP.AllowCookies := True; lHTTP.Request.UserAgent := 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36'; lHTTP.Request.Accept := 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; lHTTP.Request.AcceptLanguage := 'en-GB,en;q=0.5'; lHTTP.Get(lPath); except on E: EIdHTTPProtocolException do begin if E.ErrorCode = 404 then begin TThread.Queue(nil, procedure begin Form1.ListBox2.Items.Add(lPath); end ); end; Exit; end; end; TThread.Queue(nil, procedure begin Form1.Memo1.Lines.Add(lPath); end ); end ); end; Alternatively: function TForm1.Download(lPath: string): ITask; begin Result := TTask.Run( procedure var lHTTP: TIdHTTP; IdSSL: TIdSSLIOHandlerSocketOpenSSL; URI: TIdURI; begin lHTTP := TIdHTTP.Create(nil); try lHTTP.ReadTimeout := 30000; lHTTP.HandleRedirects := True; IdSSL := TIdSSLIOHandlerSocketOpenSSL.Create(lHTTP); IdSSL.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2]; IdSSL.SSLOptions.Mode := sslmClient; lHTTP.IOHandler := IdSSL; lHTTP.AllowCookies := True; lHTTP.Request.UserAgent := 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36'; lHTTP.Request.Accept := 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; lHTTP.Request.AcceptLanguage := 'en-GB,en;q=0.5'; lHTTP.Get(lPath); except on E: EIdHTTPProtocolException do begin if E.ErrorCode = 404 then begin TThread.Queue(nil, // or Synchronize() procedure begin ListBox2.Items.Add(lPath); end ); end; Exit; end; end; TThread.Queue(nil, // or Synchronize() procedure begin Memo1.Lines.Add(lPath); end ); end ); end; procedure TForm1.Button1Click(Sender: TObject); var i: integer; TaskList: TList<ITask>; T: ITask; begin TaskList := TList<ITask>.Create; try for i := 0 to ListBox1.Items.Count-1 do begin T := Download(ListBox1.Items.Strings[i]); TaskList.Add(T); end; while not TTask.WaitForAll(TaskList.ToArray, 500) do begin CheckSynchronize(); Update; end; finally TaskList.Free; end; end; TThread.Synchronize() is synchronous. It sends a request to the main UI thread and waits for it to be processed before exiting back to the calling thread. But the main UI thread is blocked waiting for the TParallel.For() loop to finish running, and so it cannot process TThread.Synchronize() requests in the meantime. Deadlock occurs. See Freeze when use TThread.Synchronize with TParallel or TTask.WaitForAll. TThread.Queue() is asynchronous. It posts a request to an internal queue and then exits immediately, allowing the calling code to continue running. The main UI thread will process the queue at a later time, in this case after the TParallel.For() loop has finished. That won't work, because TThread.Queue() is asynchronous. When you issue the request to read the ListBox string, it won't be retrieved in time for your TIdHTTP to actually use it. What you could do instead is copy the ListBox strings to a local array before starting the TParallel.For() loop, and then you can access that array directly in your anonymous procedure without needing to synchronize access to it. See the example above. Otherwise, don't use TParallel.For() when you need to perform actions synchronously in the main UI thread. See Using TThread.Synchronize with TTask.WaitForAll and the example above. That is because your lPath variable has not been assigned a value yet when you pass it to TIdHTTP.Get().
-
Showing secondary form from the main form OnShow event
Remy Lebeau replied to Mowafaq's topic in Cross-platform
No, I meant what I posted. An anonymous procedure is not necessary. TThread.ForceQueue() accepts both standard method closures and anonymous procedures as input, same as TThread.Synchronize() and TThread.Queue() do: type TThreadMethod = procedure of object; TThreadProcedure = reference to procedure class procedure ForceQueue(const AThread: TThread; const AMethod: TThreadMethod; ADelay: Integer = 0); overload; static; class procedure ForceQueue(const AThread: TThread; const AThreadProc: TThreadProcedure; ADelay: Integer = 0); overload; static; What I had left off by accident in my example was just the nil TThread parameter: TThread.ForceQueue(nil, SignForm.Show); -
Did you try profiling your code to see where it is actually spending its time? You are starting a new TTask thread for each ListBox item, but running 100 threads simultaneously will not be faster than processing 100 items in batches of, say, 4-8 threads at a time. Creating more simultaneous threads does not mean the job will be completed faster. If anything, doing so will slow it down, because the OS can only handle so much work simultaneously, the more threads you have running the more time the OS has to spend switching between them. In general, you should not have more threads running than you have CPU cores. Have you tried using TParallel.For() instead? It uses a smaller pool of threads and will manage them according to its actual work load. Or, at the very least, you can use the TTask constructor that allows your tasks to use threads from a TThreadPool that you create. Also, don't pass TThread.Current to TThread.Queue(). That will link the queued operation to the thread, and if the thread terminates before the queued operation is performed, the operation will be canceled, and you won't see the thread's result in your UI. Better to pass nil instead in this case. Also, you are leaking the TIdHTTP object, and thus the TIdSSLIOHandlerSocketOpenSSL and TIdCookieManager objects. Also, there is no need to invoke your TRegEx logic in the context of the main UI thread (I wouldn't even use TRegEx at all), it should be invoked in the context of the TTask thread instead. Only the final ListBox addition (the only part of the thread code that actually touches the UI) should be queued, if it is to be performed at all.
-
Ideally yes. Or, you can just reset the variables locally before CoUninitialize() is called (don't forget to clear the PROPVARIANT, too), eg: var Store: IPropertyStore; v: PropVariant; InitHr: HResult; begin if OpenDialog1.Execute then begin InitHr := CoInitialize(nil); if FAILED(InitHr) and (InitHr <> RPC_E_CHANGED_MODE) then OleError(InitHr); try OleCheck(SHGetPropertyStoreFromParsingName(PChar(OpenDialog1.FileName), nil, GPS_READWRITE, IPropertyStore, store)); try OleCheck(store.GetValue(PKEY_Music_AlbumTitle, v)); try if v.vt = VT_EMPTY then Showmessage('Album Title not found') else Showmessage(v.bstrVal); finally PropVariantClear(v); // <-- add this end; finally store := nil; // <-- calls Release() end; finally if SUCCEEDED(InitHr) then CoUninitialize; end; end; end; However, Co(Un)Initialize() really should not be called more than 1 time per thread. For instance, in this case, they should be called at program startup/cleanup only, eg: private InitHr: HResult; procedure TMainForm.FormCreate(Sender: TObject); begin InitHr := CoInitialize(nil); if FAILED(InitHr) and (InitHr <> RPC_E_CHANGED_MODE) then OleError(InitHr); end; procedure TMainForm.FormDestroy(Sender: TObject); begin if SUCCEEDED(InitHr) then CoUninitialize; end; // the above would be better handled by moving Co(Uninitialize)() into // the main DPR directly, before TApplication is initialized, and after // TApplication.Run() is finished, respectively... ... procedure TMainForm.DoSomething; var Store: IPropertyStore; v: PropVariant; begin if OpenDialog1.Execute then begin OleCheck(SHGetPropertyStoreFromParsingName(PChar(openDialog1.FileName), nil, GPS_READWRITE, IPropertyStore, store)); OleCheck(store.GetValue(PKEY_Music_AlbumTitle, v)); try if v.vt = VT_EMPTY then Showmessage('Album Title not found') else Showmessage(v.bstrVal); finally PropVariantClear(v); // <-- still needed end; end; end;