-
Content Count
3031 -
Joined
-
Last visited
-
Days Won
139
Everything posted by Remy Lebeau
-
Why use TWICImage and not TPNGImage?
-
As it should. That is one of the pre-defined places where the DLL loader looks for dependant DLLs. That means the DLL could not be loaded. Either you used the wrong path, or a dependency could not be found, etc. Did you try using a Delay Load failure hook to find out exactly what is failing? See the example in Delphi's documentation
-
No, it will not work. Although it will load the DLLs at process startup, you have no control over the order in which static-linked DLLs are loaded. More importantly, you CANNOT specify the paths where to search for static-linked DLLs, The resulting IMPORTS table simply does not contain paths, only filenames. Statically-linked DLLs are always searched for in system-defined paths only, see: Dynamic-link library search order. So, if your app needs to control the paths where DLLs are loaded from, then you MUST use dynamic/delayed loading.
-
Memo lines{i] to labelv ok. Labels to Memo lines nope
Remy Lebeau replied to Freeeee's topic in General Help
Why are your DELETING lines you are trying to WRITE to? Also, you are not taking into account that the line indexes will CHANGE when you delete lines, so you are actually deleting the 1st 3rd 5th lines, not the 1st 2nd 3rd lines. Simply don't do this. You don't need the Delete() calls. procedure TForm1.Button1Click(Sender: TObject); begin Memo1.Lines [0] := Label1.Caption; Memo1.Lines [1] := Label2.Caption; Memo1.Lines [2] := Label3.Caption; end; procedure TForm1.Button2Click(Sender: TObject); begin Label1.Caption := Memo1.Lines [0]; Label2.Caption := Memo1.Lines [1]; Label3.Caption := Memo1.Lines [2]; end; But, if you must delete lines, then you can simply Insert() them back in, eg: procedure TForm1.Button1Click(Sender: TObject); begin Memo1.Lines.Delete (0); Memo1.Lines.Delete (0); Memo1.Lines.Delete (0); Memo1.Lines.Insert(0, Label1.Caption); Memo1.Lines.Insert(1, Label2.Caption); Memo1.Lines.Insert(2, Label3.Caption); end; But, I still stand by my earlier comment that this is really the wrong UI choice to use in the first place. -
What you are asking for can't be done if you use static linking. You would have to modify the system PATH in order to find the secondary DLLs that libheif.dll uses. You need to load libheif.dll dynamically at runtime instead. You can either rewrite your code to load libheif's function(s) explicitly via manual calls to LoadLibrary()+GetProcAddress(), or you can simply mark the functions as delayed to let the RTL handle the loading for you. But either way, this will allow you to call SetDllDirectory() or AddDllDirectory() at runtime before you call any of libheif's functions for the first time.
-
Memo lines{i] to labelv ok. Labels to Memo lines nope
Remy Lebeau replied to Freeeee's topic in General Help
In a VCL TMemo, if you try to read from a line index that doesn't exist then the output string will be blank, and if you try to write to a line index that doesn't exist then the input string will be ignored. No errors are raised. So, in your case, for example, if you try to write to Memo1.Lines[2] when Memo1 does not have at least 3 lines, then your LblLastName.Caption string will be ignored. So, like Brian said, you have to make sure a given line actually exists before you can read/write it. That being said, using a TMemo probably isn't the best choice for this task to begin with. Why not use 3 TEdit's instead? Or maybe a TStringGrid, or a TValueListEditor, or an editable TListView instead? There are many choices, depending on your UI needs. -
They are the same product. The original author passed away. Embarcadero had to renegotiate a new deal with his estate in order to continue providing the product. So any emails should be directed to Embarcadero. Embarcadero will continue developing and providing FMXLinux as an option, but only for Enterprise and Architect with Active subscription, AFAIK.
-
That's a little overkill just for loading an ini file. And you'd still need startup code to start the thread.
-
If you need to run code that is related to your UI, you can use your MainForm's OnCreate event. If you need to run code sooner than that, then you can add code in your project's .dpr file before Application.Run() is called. Or, you can put code in the initialization section of any .pas file in your project.
-
How do I avoid the error message 'is not a valid date.' ?
Remy Lebeau replied to JohnLM's topic in Databases
Why are you reading the mDate field as a String and not as a TDateTime to begin with? And are you populating your UI controls manually instead of using data-aware controls? If using a data-aware UI, you can configure a data field to *display* in a particular format, without having to actually pull the data and format it yourself. You say you have a date/time field, but then you talk about a Memo field. Which one are you actually parsing? I'm still not clear where your error is. Your screenshots don't match the code snippet you provided. -
How do I avoid the error message 'is not a valid date.' ?
Remy Lebeau replied to JohnLM's topic in Databases
? There is no mdata in the code you provided. Do you mean dbdate? The code I presented is just a simplification of the code you provided, so it should compile. StrToDate() returns a TDateTime, and TryStrToDate() outputs a TDateTime. TDate is just an alias for TDateTime. -
How do I avoid the error message 'is not a valid date.' ?
Remy Lebeau replied to JohnLM's topic in Databases
If you use TryStrToDate() instead then you can avoid the try..except, eg: if TryStrToDate(qry.FieldByName('mDate' ).AsString, dbDate) then dateS := FormatDateTime('yyyy-mm-dd ddd', dbDate) else sb1.Panels[0].Text := 'Error: invalid date!'; -
A Conditional Ternary Operator for the Delphi
Remy Lebeau replied to EugeneK's topic in RTL and Delphi Object Pascal
I didn't say it was *submitted* for merge into FPC. I said it was *created*. There is a patch file attached to the forum post that I linked to: "It's roughly 30 lines of code and took 15 minutes to create the attached patch for fpc main" -
A Conditional Ternary Operator for the Delphi
Remy Lebeau replied to EugeneK's topic in RTL and Delphi Object Pascal
Actually, a patch was already created for FreePascal to add this very same conditional operator (FPC already had an IfThen() intrinsic); https://forum.lazarus.freepascal.org/index.php/topic,71398.msg556926.html#msg556926 -
Need help investigating an app crash
Remy Lebeau replied to Der schöne Günther's topic in General Help
Not really, not without knowing what code actually crashed, and what it was actually trying to do at the time of the crash. Debugging via forum isn't a good way to handle this. You need to actually debug the program directly and try to reproduce the issue. You say you have 2 threads trying to talk to the same device, presumably over a single connection. Best to give each thread its own connection if possible. Otherwise, I would suggest delegating all I/O on that connection to a single thread and let it accept queries from, and return responses to, other threads as needed. -
Need help investigating an app crash
Remy Lebeau replied to Der schöne Günther's topic in General Help
Correct. Agreed. Do the I/O normally and let Indy tell you when the connection is closed. -
Need help investigating an app crash
Remy Lebeau replied to Der schöne Günther's topic in General Help
There is really no way for anyone to diagnose this problem for you with such limited information. An AV near memory address 0 almost always means a nil pointer was accessed. But you would need to debug your program to figure out what code was actually running at memory address 009863BC when the AV occurred. The fact that Indy's TIdTask and TIdThreadWithTask classes appear on the call stack could be related or benign, there's just no way to tell from the error message alone. Potentially quite old. Actually, it does. GitHub has Indy 10's full change history going back to 2005 (Indy migrated from SVN to GitHub in 2019). Indy 10 rolled to 10.6.2.0 in commit f60999f on April 22 2013, and rolled to 10.6.3.0 in commit b079cb2 on Mar 2 2024. That gives you an 11 year range of history, available on GitHub, for the entire 10.6.2 run. Sorry, I couldn't tell you exactly which commits belong to 10.6.2.5311 specifically. The build numbers were generated by SVN scripts until the GitHub migration. If it was a version shipped with the IDE, you can use the IDE's release date to narrow down the commits. Or, if not a shipped version, then the date the files were downloaded. -
Access violation with move command
Remy Lebeau replied to Andre1's topic in RTL and Delphi Object Pascal
That is wrong. Your original call was correct. It is your Move() call inside of TargetMethod() that was wrong to begin with. It needs to be this instead: Move(Events[0], p^^, TotalSize); Inside of TargetMethod(): - p is a pointer to Main's Data variable. - p^ refers to the Data variable itself. - p^^ refers to the memory block that the Data variable is pointing at. The 2nd parameter of Move() takes a var reference to the memory it will write to. So it needs a reference to the memory block that the Data variable is pointing at, but you are giving it a reference to the Data variable itself. Thus, this call to Move() writes to the call stack of Main(), corrupting it. That is why the 2nd call to Move() inside of Main() crashes when it tries to read from the memory pointed at by the Data variable which resides in the now-corrupted call stack. You likely overwrote the Data variable during the 1st Move. Imagine how your Main() would need to use its Data and DataSize variables if it didn't have to pass them to TargetMethod() at all: procedure Main; var Data: Pointer; DataSize: Integer; ByteArray: TBytes; TotalBytes: Integer; begin ... GetMem(Data, ...); // <-- No ^ here Move(..., Data^, ...); // <-- 1 ^ here DataSize := ...; // <-- No ^ here ... if (Data <> nil) and (DataSize > 0) then try TotalBytes := DataSize; SetLength(ByteArray, TotalBytes); Move(Data^, ByteArray[0], TotalBytes); // <-- 1 ^ here finally FreeMem(Data); end; ... end; Now, because you added @ to Data and DataSize when passing them to TargetMethod(), you need an extra ^ on all accesses to Data and DataSize inside of TargetMethod(): GetMem(p^, TotalSize); // <-- 1 ^ here Move(..., p^^, ...); // <-- 2 ^ here len^ := ...; // <-- 1 ^ here -
Are TInterlocked.Exchange and CompareExchange implementation really Atomic with class types??
Remy Lebeau replied to @AT's topic in RTL and Delphi Object Pascal
That is not true. During a task switch between two threads, CPU register values are preserved for the thread that is being switched from, and they are restored when that thread is switched back to. So threads cannot overwrite each other's register values. And the variables in question are all local to the calling thread's call stack, so they can't be overwritten by other threads, either (at least, in this example, anyway). The only way that values could possibly be overwritten are from variables that are being shared across thread boundaries (which is not the case in your example), and such overwriting is going to be sensitive to the timings between thread switches, so you are not guaranteed a particular result one way or the other whether you use the intrinsic exchange or class-wrapped exchange. I think you are misdiagosing the problem (if there even is a problem). Yes, the class version of the exchange is clearly less efficient than the intrinsic, but that doesn't mean the class version is any less thread-safe. -
Strange problem with mutexes
Remy Lebeau replied to hukmac's topic in Algorithms, Data Structures and Class Design
Why are you creating and destroying the mutexes over and over? Don't do that! Create them one time and then just acquire and release them as needed, eg: function CreateMutexBarrier(const resource_mutex_id : String) : THandle; begin Result := CreateMutex(nil, False, PChar(resource_mutex_id)); if Result = 0 then RaiseLastOSError; end; procedure EnterMutexBarrier(mutex : THandle); begin if WaitForSingleObject(mutex, INFINITE) = WAIT_FAILED then RaiseLastOSError; end; procedure LeaveMutexBarrier(mutex : THandle); begin if not ReleaseMutex(mutex1) then RaiseLastOSError; end; procedure Main; var mutex1, mutex2 : THandle; begin mutex1 := CreateMutexBarrier('Mutex1_constant_unique_name'); try mutex2 := CreateMutexBarrier('Mutex2_constant_unique_name'); try repeat EnterMutexBarrier(mutex1); try Prepare; finally LeaveMutexBarrier(mutex1); end; PerformCalculations; EnterMutexBarrier(mutex2); try SaveDataToDisk; finally LeaveMutexBarrier(mutex2); end; until False; finally CloseHandle(mutex2); end; finally CloseHandle(mutex1); end; end; -
Absolutely not. Why would you consider uninitializing something it when it fails to initialize?
-
Why, though? This is clearly not the best solution for your use-case since you are having so much trouble with it for such a small task. Just as I described earlier. Configure your DLL project's Run settings to specify rundll32 as its Host application. Then you can run the project normally in the IDE, it will execute rundll32, and the debugger will attach to that process and let you debug the DLL code.
-
As mentioned earlier, CoInitialize/Ex() is required to work with COM objects, like ShellLink. But there is no call to CoInitialize/Ex() in your code. In a VCL application, that is called automatically for you. But in this case, rundll32 will not call it for you, so you will have to call it yourself. Call CoInitialize/Ex() at the beginning of your Start() function, and call CoUnintialize() before exiting from Start(), eg: procedure Start(hwnd: HWND; hinst: HINST; lpszCmdLine: PAnsiChar; nCmdShow: Integer); stdcall; begin if Failed(CoInitialize(nil)) then begin // report the error some where ... Exit; end; try ... finally CoUninitialize; end; end; And add some error handling/logging to your CreateLink() function, eg: procedure TForm1.CreateLink(const PathObj, PathLink, Desc, Param: String); var IObject : IUnknown; SLink : IShellLink; PFile : IPersistFile; begin try IObject := CreateComObject(CLSID_ShellLink); SLink := IObject as IShellLink; PFile := IObject as IPersistFile; OleCheck(SLink.SetArguments(PChar(Param))); OleCheck(SLink.SetDescription(PChar(Desc))); OleCheck(SLink.SetPath(PChar(PathObj))); OleCheck(PFile.Save(PWChar(WideString(PathLink)), FALSE)); except // report the error somewhere ... end; end; If all you want is command-line access to the ShellLink object, then why use rundll32 at all? You could use a Powershell script instead. Look at the New-Object command: Creating COM objects with New-Object Have you tried debugging your DLL yet? Are you aware that the IDE can debug a DLL project? Simply set rundll32 as the Host for the project, and then you can run the project and step through your code normally. Where exactly is your code actually failing to behave the way you are expecting?
-
Where does it fail, exactly? Your code is not doing any error handling/reporting. Are you creating a plain DLL, or a Runtime Package BPL? It is not safe to use Delphi-managed types like String across the DLL boundary in a plain DLL, without extra work (ie, like using the ShareMem unit in both EXE and DLL, etc). Did you verify your timer is running correctly inside your DLL? Does the thread that is calling into the DLL first call CoInitialize/Ex()? IShellLink is a COM object, so the COM library needs to be initialized on any thread that wants to access any COM object. DO NOT be tempted to call CoInitialize/Ex() inside your DLL, unless it uses COM in a thread that it creates for itself. It is the responsibility of a thread creator to decide how COM interacts with that thread, not the responsibility of functions called by a thread.
-
Why not use a transparent Label? Otherwise, maybe just get rid of the Panel altogether and draw the text directly on the Form itself using a transparent draw.