StephenM
Members-
Content Count
8 -
Joined
-
Last visited
Everything posted by StephenM
-
I'm running into a DLL issue that has me perplexed. The DLL is written and compiled in Delphi XE. It ingests a data structure that identifies a report, which is then retrieved from a repository and displayed. This all occurs within the DLL. The only value passed back to the app is an integer indicating success or failure. The DLL is called from a complex Delphi 5 app and works to this day. Given that Delphi 5 is a bit old (heh), I embarked on a project to compile the app in Delphi 12. This is done and this is where the problem starts. The function for the DLL is statically defined. When I run the complex app, I receive an access violation in the DLL when the DLL loads. Keep in mind I'm not calling the DLL function. It's failing upon load. To see what's going on, I comment the static definition and go the LoadLibrary/GetProcAddress route. The LoadLibrary call also fails with access violations. Now here is where it gets interesting. I created a simple Delphi 12 test app that calls the DLL, and the DLL works perfectly. For fun, I tried both static definitions and dynamic loading. Both methods work. At this point I wondered if there was some compiler setting that was different between the complex app and the test app. I performed a side-by-side comparison of the compiler and linker options and made sure they were identical. I deleted the dcus of the complex app and performed a full build. The problem still remains. The DLL does not load with the complex app. I don't have the source for the DLL, so compiling it in the latest version is not an option. Clearly there is something about the complex app that is causing an issue. I'm at a loss on how to proceed to debug the problem given it occurs upon DLL load. Suggestions are warmly welcome.
-
There's a joke there. I agree. The best way forward, now that I've isolated the cause, is to place the charting code to the side. Most users of the app export data into Excel for charting purposes. Thanks everyone for all your suggestions and help. You are all the best.
-
I've discovered the issue and can easily replicate it in a sample project. And it's bizarre. A quick recap. The simple D12 project loading the DLL works. It's composed of a main form and a unit with the DLL function declarations. No issue calling the DLL methods. I drop a second VCL form into the project. To this form I add a TDBChart from the 'TeeChart Std' components. Compile, run, and after a while the access violations in my DLL are reported. If I remove the form with the TDBChart from the project, the simple app works once again. Any ideas?
-
@A.M. Hoornweg, thanks. All three projects, D5, D12 simple, D12 complex, all have the same field alignment.
-
@A.M. Hoornweg The structure looks like this: TViewerParams = record ArchiveFileName: WideString; zoomFactor: integer; windowState: TWindowState; serverURL: WideString; isModal: boolean; caption: WideString; end; Given that the D12 simple app works and the complex one does not, I'm led to the conclusion there is some unit in the complex app that is causing the issue. I've embarked upon the following: 1) Replicate the D12 simple app into a new test app. 2) Start adding units from the complex into the test app. After each unit of work, test to see if the DLL loads and works. 3) Rinse and repeat until the error shows up. Or not. Either way I'll get to a solution of some sort. Stay tuned. 912 units to sort through.
-
Thanks Remy. I'll take a look at that article. We are thinking along the same lines, Die. Once the D12 test app worked, I tried the approach of a clean project. Sadly it didn't work. The same access violations occur.
-
I'm looking for best practices or guidance for the following scenario. The front end is a page control with multiple pages. The user completes the form and presses 'Save'. The app takes the contents of the controls, populates an XML document, and sends the document to a server for validation and processing. Let's say a validation error occurs. One of the XML fields contained a negative number and only positive numbers are allowed. How does the client know which page and control to highlight for a given validation? I think there needs to be some association between the data entry controls and the XML fields, but I'm unsure what is the best way to build this. Have you handled this situation before? What algorithm or data structure did you use? Regards, Stephen
-
Hello Gabr, I'm seeing a race condition in TOmniResourceCount in the TryAllocate function. I created a logger following your advice in: https://stackoverflow.com/questions/8134526/implement-thread-safe-logging Two methods in the TOmniLogger code relevant to this discussion are: procedure TOmniLogger.Log(const Text: string); begin FLogQueue.Enqueue(Text); FLogMsgCount.Allocate; end; procedure TOmniLogger.Logger(const task: IOmniTask); var LogFile: TOmniLogFile; procedure Flush; var logData: TOmniValue; begin while FLogQueue.TryDequeue(logData) do begin FLogMsgCount.Release; LogFile.Log(logData.AsString); end; end; begin LogFile := TOmniLogFile.Create(FFileName); FTaskName := Format('%s:%s [ThreadId:%d]', [Self.ClassName, SysUtils.ExtractFileName(FFileName), Windows.GetCurrentThreadId]); try Log(Format('Task "%s" on thread [%d, %d]', [Task.Name, Task.UniqueID, Windows.GetCurrentThreadId])); while DSiWaitForTwoObjects(task.TerminateEvent, FLogMsgCount.Handle, false, CMaxLogTimeout_ms) <> WAIT_OBJECT_0 do Flush; Flush; finally LogFile.Free; end; end; FLogMsgCount is an instance of TOmniResourceCount. The race condition occurs in the tight while DSiWaitForTwoObjects loop and involves FLogMsgCount.Handle. This code calls two methods from TOmniResourceCount in OtlSync.pas, Release and TryAllocate: function TOmniResourceCount.Release: cardinal; begin orcLock.Acquire; try Result := cardinal(orcNumResources.Increment); if Result = 1 then begin ResetEvent(orcHandle); SetEvent(orcAvailable); end; finally orcLock.Release; end; end; { TOmniResourceCount.Release } ///<summary>Like Allocate, but with a timeout.</summary> function TOmniResourceCount.TryAllocate(var resourceCount: cardinal; timeout_ms: cardinal): boolean; var startTime_ms: int64; waitTime_ms : int64; begin Result := false; startTime_ms := DSiTimeGetTime64; //TODO: Rewrite this with a faster, non-locking clock orcLock.Acquire; repeat if orcNumResources.Value = 0 then begin orcLock.Release; if timeout_ms <= 0 then Exit; if timeout_ms = INFINITE then waitTime_ms := INFINITE else begin waitTime_ms := startTime_ms + timeout_ms - DSiTimeGetTime64; if waitTime_ms <= 0 then Exit; end; if WaitForSingleObject(orcAvailable, waitTime_ms) <> WAIT_OBJECT_0 then Exit; // skip final Release orcLock.Acquire; end; if orcNumResources.Value > 0 then begin Result := true; resourceCount := cardinal(orcNumResources.Decrement); if resourceCount = 0 then begin ResetEvent(orcAvailable); //reset before release - otherwise there's a race condition between this code and .Release orcLock.Release; //prevent race condition - another thread may wait on orcHandle and destroy this instance SetEvent(orcHandle); Exit; // skip final Release end; break; //repeat end; until false; orcLock.Release; end; { TOmniResourceCount.TryAllocate } Here's the scenario where I see the race condition occurring: 1) resourceCount is 0. This code executes in TryAllocate: if resourceCount = 0 then begin ResetEvent(orcAvailable); //reset before release - otherwise there's a race condition between this code and .Release orcLock.Release; //prevent race condition - another thread may wait on orcHandle and destroy this instance SetEvent(orcHandle); Exit; // skip final Release end; 2) After the orLock.Release and before the SetEvent a thread switch occurs. 3) A call to TOmniResourceCount.Allocate is made via the TOmniLogger.Log procedure above. ResetEvent(orcHandle) is called. 4) A thread switch occurs. Now SetEvent(orcHandle) in TryAllocate executes. This causes: while DSiWaitForTwoObjects(task.TerminateEvent, FLogMsgCount.Handle, false, CMaxLogTimeout_ms) <> WAIT_OBJECT_0 do Flush; To execute. The end result is FLogMsgCount.Handle is stuck in the signaled state and this loop takes over a core. My workaround is to move the SetEvent(orcHandle) into the critical section (before orcLock.Release in TryAllocate), thus preventing this scenario. Does this seem like the correct approach to you? Regards, Stephen