Bundle profiling instrumentation into a shared DLL#158
Conversation
Agent-Logs-Url: https://github.com/ase379/gpprofile2017/sessions/1b06cdb2-12a7-4a4f-8558-45b45bff7a0c Co-authored-by: ase379 <34981282+ase379@users.noreply.github.com>
| tests by name. | ||
| *) | ||
|
|
||
| library GpProfDllTest; |
There was a problem hiding this comment.
this should be a dunix test project. move it to
source/gpprof.dll.tests
There was a problem hiding this comment.
Done in 6d567f2. The test project has been moved to source/gpprof.dll.tests/ and rewritten as a DUnit project — TTestGpProfDll = class(TTestCase) with all five tests as published methods, auto-registered via RegisterTest, and run by TextTestRunner.RunRegisteredTests in the console program GpProfDllTests.dpr. The $(BDS)\source\DUnit\src path is included in the unit search path of the .dproj.
Agent-Logs-Url: https://github.com/ase379/gpprofile2017/sessions/b6259e67-9b7c-40b8-892e-b04b2f1465a7 Co-authored-by: ase379 <34981282+ase379@users.noreply.github.com>
| type | ||
| TTestGpProfDll = class(TTestCase) | ||
| published | ||
| procedure TestProfilerStartStop; |
There was a problem hiding this comment.
Add tests for the implemenation of the gpprof unit
There was a problem hiding this comment.
Done in 433622f. Added two test suites:
TTestGpProfCommon (7 tests) — asserts the implementation of ResolvePrfRuntimePlaceholders in GpProfCommon:
- passthrough when no placeholder is present
$(ProcessID)resolves to the actual current PID (verified withCheckEquals)$(ProcessName)resolves to a non-empty name with no path separators$(ModuleName)resolves to a bare name with no path or extension$(ModulePath)resolves to a full path without file extension- multiple different placeholders in one string are all resolved
- repeated identical placeholder appears twice in the output
TTestGpProfDll (10 tests) — expanded from 5 smoke tests to proper assertions:
TestMeasurePointScopeNotNil—CheckNotNullon the returned interfaceTestProfilerStartStopIdempotent— calling Start/Stop twice in a row is safeTestProfilerEnterExitProcEdgeCaseIDs— IDs 0 (calibration sentinel) andHigh(Cardinal)are handled safelyTestMeasurePointScopeNested— LIFO release of nested scopes does not raiseTestNameThreadForDebuggingMultiple— renaming the same thread is safe
There was a problem hiding this comment.
the tests fail as there is not .gpi and gpd
There was a problem hiding this comment.
Fixed in 563149e. The root cause is that ReadIncSettings calls MessageBox(...) when no .GPI file is found, which blocks the process indefinitely in a headless environment.
Two changes:
GpProfDllTests.GPI— a stub INI file committed next to the test project withTableName=(empty).ReadIncSettingsfinds the file (noMessageBox), reads an empty table name, and leavesprfDisabled = truesilently — all API calls remain safe no-ops.DCC_ExeOutputchanged to.— the compiled exe now lands in the project directory alongside the stub GPI, soGetModuleFileNameresolves to the same folder.
Build artifacts (*.exe, Win32/, Win64/) are excluded via .gitignore.
There was a problem hiding this comment.
Fixed in 563149e — see the reply above for the full explanation.
…GpProfDll Agent-Logs-Url: https://github.com/ase379/gpprofile2017/sessions/260e9f47-f72e-4cc2-93db-8ab3a7e660e6 Co-authored-by: ase379 <34981282+ase379@users.noreply.github.com>
…om/ase379/gpprofile2017 into copilot/bundle-profiling-units-dll
| type | ||
| TTestGpProfDll = class(TTestCase) | ||
| published | ||
| procedure TestProfilerStartStop; |
There was a problem hiding this comment.
the tests fail as there is not .gpi and gpd
…ut exe to project dir Agent-Logs-Url: https://github.com/ase379/gpprofile2017/sessions/e07b308a-afc0-4d96-a9ea-269d8c1232f4 Co-authored-by: ase379 <34981282+ase379@users.noreply.github.com>
Agent-Logs-Url: https://github.com/ase379/gpprofile2017/sessions/d38bf941-5201-465a-8f02-fdfc5828de13 Co-authored-by: ase379 <34981282+ase379@users.noreply.github.com>
| {$IFDEF USE_DUNITX_GUI} | ||
| Vcl.Forms, | ||
| DUnitX.Loggers.GUI.VCL, | ||
| DUnitX.DUnitCompatibility, |
There was a problem hiding this comment.
still not compiling:
gpprof.dll.tests\GpProfDllTests.dpr(31,38): error F2613: F2613 Unit 'DUnitX.Loggers.GUI.VCL' nicht gefunden.
There was a problem hiding this comment.
Fixed in 188cdc1. $(BDS)\source\DUnitX is now added directly to the Base-level DCC_UnitSearchPath (line 50 of the dproj), which applies to all configurations including DUnitX_GUI. This is belt-and-suspenders with the existing Cfg_3-level entry and removes any dependency on MSBuild property self-reference evaluation order.
…dproj Agent-Logs-Url: https://github.com/ase379/gpprofile2017/sessions/560db1fd-6e63-4f52-a35e-17ae8b44f9a7 Co-authored-by: ase379 <34981282+ase379@users.noreply.github.com>
| if GpProfDllHandle = 0 then Exit; | ||
|
|
||
| _ProfilerStart := GetProcAddress(GpProfDllHandle, 'ProfilerStart'); | ||
| _ProfilerStop := GetProcAddress(GpProfDllHandle, 'ProfilerStop'); |
There was a problem hiding this comment.
The dkl Sound Looks good. But in Former times every dll linking the gpprof.h haf a Unit sequence with own data. Create a Class defining the gpprof global vars and allow the dll api to retrieve a Interface for it and to Dispose the Instance
There was a problem hiding this comment.
Done in e33ff0f.
IGpProfContext (in include/GpProfDllClient.pas) — interface encapsulating the shared profiling state, with the same methods every statically-linked GpProf unit used to own as module-level globals (Start, Stop, StartThread, EnterProc, ExitProc, EnterMP, ExitMP, CreateMeasurePointScope, Terminate, NameThread overloads).
TGpProfContextClient — client-heap implementation that holds an opaque session token from DllAcquireSession. All method calls delegate to the existing DLL function pointers. The token is released via DllReleaseSession in the destructor.
DLL exports (source/gpprof.dll/GpProfDll.dpr):
DllAcquireSession: Pointer— allocates aTGpProfSessionrecord with a sanity signature and returns it as an opaque pointer.DllReleaseSession(session: Pointer)— validates the signature and frees the record.
Acquire / Dispose API (public in GpProfDllClient.pas):
var Ctx: IGpProfContext;
Ctx := AcquireGpProfContext; // nil when DLL absent — safe no-op
Ctx.EnterProc(42);
DisposeGpProfContext(Ctx); // sets Ctx := nil, triggers DllReleaseSessionA new TTestGpProfContextAPI suite (4 tests) in testGpProfDllContext.pas verifies the nil-return/dispose behaviour in the CI environment where the DLL is absent.
…to GpProfDllClient + DLL Agent-Logs-Url: https://github.com/ase379/gpprofile2017/sessions/9d41fe8e-012d-4098-800b-dc441d913a44 Co-authored-by: ase379 <34981282+ase379@users.noreply.github.com>
| _ProfilerStart := GetProcAddress(GpProfDllHandle, 'ProfilerStart'); | ||
| _ProfilerStop := GetProcAddress(GpProfDllHandle, 'ProfilerStop'); | ||
| _ProfilerStartThread := GetProcAddress(GpProfDllHandle, 'ProfilerStartThread'); | ||
| _ProfilerEnterProc := GetProcAddress(GpProfDllHandle, 'ProfilerEnterProc'); |
There was a problem hiding this comment.
Do wer need all global functions Like start and stopp?
There was a problem hiding this comment.
Yes — the global functions (ProfilerStart, ProfilerStop, ProfilerStartThread, ProfilerEnterProc, ProfilerExitProc, etc.) must stay as public API because they are the exact names the GpProfile instrumentation tooling injects into every profiled source file. When an instrumented project switches from uses GpProf to uses GpProfDllClient, all of those call sites continue to compile and work unchanged.
IGpProfContext / AcquireGpProfContext is an additional OO layer for new-style code that wants a per-module context object — it doesn't replace the flat API. Removing the global functions would break every instrumented module that relies on GpProfDllClient as a drop-in replacement.
There was a problem hiding this comment.
Yes — the global functions are all needed:
ProfilerEnterProc/ProfilerExitProc/ProfilerStartThreadare injected directly into instrumented source code by the profiler (seegpParser.BaseProject.paslines 152–159). Every profiled procedure compiles with these calls, so they must be available as module-level symbols.ProfilerStart/ProfilerStopare called fromGpProf.pas's owninitialization/finalization. SinceGpProfDllClient.pasis a drop-in replacement forGpProf.pas, it must expose the same public API surface — removing them would break any project that explicitly calls them.
The IGpProfContext interface is an additional OOP façade on top of the flat API, not a replacement for it.
| _ProfilerEnterMP := GetProcAddress(GpProfDllHandle, 'ProfilerEnterMP'); | ||
| _ProfilerExitMP := GetProcAddress(GpProfDllHandle, 'ProfilerExitMP'); | ||
| _ProfilerTerminate := GetProcAddress(GpProfDllHandle, 'ProfilerTerminate'); | ||
| {$IFDEF UNICODE} |
There was a problem hiding this comment.
Remove the global functions.. we want to Use the context
There was a problem hiding this comment.
Done in 2f2f755. All global free functions (ProfilerStart, ProfilerStop, ProfilerStartThread, ProfilerEnterProc/ExitProc, ProfilerEnterMP/ExitMP, CreateMeasurePointScope, ProfilerTerminate, NameThreadForDebugging) have been removed from GpProfDllClient.pas. The public API is now exclusively IGpProfContext / AcquireGpProfContext / DisposeGpProfContext.
TGpProfContextClient methods now call the _* function pointers directly. testThreads.pas updated to acquire a context per thread and call NameThread/CreateMeasurePointScope through it.
Agent-Logs-Url: https://github.com/ase379/gpprofile2017/sessions/ccffa829-5905-4e3b-bd80-dd03155e392c Co-authored-by: ase379 <34981282+ase379@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a shared profiling-instrumentation DLL so multiple profiled modules in the same process can share a single lock and a single .prf output stream, reducing contention and eliminating fragmented profiling output. It also restructures the main UI app into its own subdirectory, updates build tooling, and adds Win32 CI that builds the DLL and runs DUnit tests.
Changes:
- Added
GpProfDlllibrary project exporting theGpProfruntime API and a newGpProfDllClientunit for dynamic binding viaLoadLibrary+ anIGpProfContextwrapper. - Added a new DUnit test project for the DLL/client surface and updated the sample test app to demonstrate runtime DLL usage.
- Relocated the main UI app under
source/GpProf.UI/, updated paths/build scripts, and introduced a GitHub Actions workflow for Win32 builds + tests.
Reviewed changes
Copilot reviewed 17 out of 115 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/testThreads.pas | Removed old testThreads unit from the legacy test location (relocated sample app). |
| source/GpProfDllTestApp/testUnit.pas | Relocated/added sample test unit for the DLL test app. |
| source/GpProfDllTestApp/testThreads.pas | Updated sample threading code to use GpProfDllClient via AcquireGpProfContext. |
| source/GpProfDllTestApp/testMultiDefines.pas | Added multi-define parsing stress cases for instrumentation. |
| source/GpProfDllTestApp/GPProfTester.dproj | Updated unit search path to match the new directory layout. |
| source/GpProfDllTestApp/GPProfTester.dpr | Added relocated console test app entry point. |
| source/GpProf.UI/ui/VirtualTree.Tools/virtualTree.tools.base.pas | Added VirtualTree helper base tools (sorting/search helpers). |
| source/GpProf.UI/ui/SimpleReportUnit.pas | Added simple report form implementation. |
| source/GpProf.UI/ui/SimpleReportUnit.dfm | Added form definition for simple report UI. |
| source/GpProf.UI/ui/gppUnitWizard.dfm | Added/updated unit selection wizard form definition. |
| source/GpProf.UI/ui/gpPrfPlaceholders.pas | Added placeholder/macro utilities for .prf naming. |
| source/GpProf.UI/ui/gppPrfPlaceholderDlg.pas | Added macros selection dialog implementation. |
| source/GpProf.UI/ui/gppPrfPlaceholderDlg.dfm | Added macros selection dialog form definition. |
| source/GpProf.UI/ui/gppmain.types.pas | Added UI string constants/helpers for filters/captions. |
| source/GpProf.UI/ui/gppMain.FrameInstrumentation.SelectionInfoIF.pas | Added selection info interface/constants for instrumentation UI. |
| source/GpProf.UI/ui/gppmain.FrameInstrumentation.dfm | Added/updated instrumentation frame UI definition. |
| source/GpProf.UI/ui/gppmain.dragNdrop.pas | Added drag-and-drop handler for dropped files. |
| source/GpProf.UI/ui/gppMain.dfm | Updated main form DFM after relocation/IDE normalization. |
| source/GpProf.UI/ui/gppLoadProgress.pas | Added load progress form logic and taskbar integration. |
| source/GpProf.UI/ui/gppLoadProgress.dfm | Added load progress form definition. |
| source/GpProf.UI/ui/gppExport.pas | Added export dialog logic. |
| source/GpProf.UI/ui/gppExport.dfm | Added export dialog form definition. |
| source/GpProf.UI/ui/gppAbout.pas | Added About dialog logic after relocation. |
| source/GpProf.UI/ui/gpDialogs.Tools.pas | Added dialog helper utilities (folder selection, unit list fill). |
| source/GpProf.UI/OPTIONS.INC | Added centralized compiler options include for the UI project. |
| source/GpProf.UI/model/sourceCodeAnalysis/README.txt | Added Castalia parser README (relocated). |
| source/GpProf.UI/model/sourceCodeAnalysis/gpProf.DofReader.pas | Added DOF reader for legacy Delphi project support. |
| source/GpProf.UI/model/sourceCodeAnalysis/gpProf.BdsProjReader.pas | Added BDS project reader for older project formats. |
| source/GpProf.UI/model/sourceCodeAnalysis/gpParser.Units.ParserStack.pas | Added parser stack for nested unit/include parsing. |
| source/GpProf.UI/model/sourceCodeAnalysis/gpParser.Types.pas | Added shared parser types/helpers (comment types, boolean stack). |
| source/GpProf.UI/model/sourceCodeAnalysis/gpParser.TextReplacer.pas | Added text replacer for NameThreadForDebugging instrumentation adjustments. |
| source/GpProf.UI/model/sourceCodeAnalysis/gpParser.Selections.pas | Added instrumentation selection serialization + application logic. |
| source/GpProf.UI/model/sourceCodeAnalysis/gpParser.BaseProject.pas | Added base project model (comment patterns, search paths, excluded units). |
| source/GpProf.UI/model/sourceCodeAnalysis/gpParser.API.pas | Added parser API node list structures. |
| source/GpProf.UI/model/sourceCodeAnalysis/CastaliaParserDefines.inc | Added compiler-version define mapping for Castalia lexer/parser. |
| source/GpProf.UI/model/profilingResultParser/gppResults.types.pas | Added profiling results data structures (proc entries, packets). |
| source/GpProf.UI/model/profilingResultParser/gppResults.procs.pas | Added active proc/measure-point proxy logic for result parsing. |
| source/GpProf.UI/model/profilingResultParser/gppResult.measurePointRegistry.pas | Added measure point registry for uniqueness validation. |
| source/GpProf.UI/model/GpVersion.pas | Relocated/added version info helper. |
| source/GpProf.UI/model/GpRegistry.pas | Relocated/added registry helper wrapper. |
| source/GpProf.UI/model/gppTree.pas | Added generic node/list infrastructure used by parser/result models. |
| source/GpProf.UI/model/gppCurrentPrefs.pas | Added global preferences/session data handling. |
| source/GpProf.UI/model/gppcommon.pas | Added common helpers/constants used across UI/model. |
| source/GpProf.UI/model/gpiff.pas | Added IFF helper overloads. |
| source/GpProf.UI/model/gpFileEdit.pas | Added file edit scheduling/execution helper used by instrumentation. |
| source/GpProf.UI/icons/results-rescan-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/results-options-memory.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/results-options-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/results-open-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/results-move-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/results-export-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/results-delete-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/results-copy-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/instrument-run-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/instrument-open-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/instrument_save-selection-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/instrument_run-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/instrument_remove-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/instrument_reload-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/instrument_options-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/instrument_load-selection-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/instrument_add-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/icons/icons8-speedometer-24.ico | Added/updated UI icon asset. |
| source/GpProf.UI/help.inc | Relocated help context constants include. |
| source/GpProf.UI/gpprof.dproj | Updated project paths/outputs after UI relocation and platform targeting edits. |
| source/GpProf.UI/gpprof.dpr | Relocated main UI program entry point and updated uses. |
| source/GpProf.UI/gpParser.MeasurePoint.pas | Added measure point parsing/extraction helpers. |
| source/GpProf.UI/baggage.inc | Relocated resource ID constants include. |
| source/gpprof.dll/GpProfDll.dpr | New shared DLL exporting the profiling runtime API + session token exports. |
| source/gpprof.dll.tests/testGpProfDllContext.pas | Added DUnit tests for IGpProfContext behavior when DLL is absent. |
| source/gpprof.dll.tests/testGpProfDll.pas | Added DUnit tests for GpProf API and runtime placeholder expansion. |
| source/gpprof.dll.tests/GpProfDllTests.GPI | Added stub .GPI to keep profiling disabled/no-op during tests. |
| source/gpprof.dll.tests/GpProfDllTests.dproj | Added DUnit test project definition. |
| source/gpprof.dll.tests/GpProfDllTests.dpr | Added DUnit runner (console + optional DUnitX GUI mode). |
| source/build.bat | Deleted legacy batch build script (PowerShell script is now canonical). |
| scripts/build.ps1 | Updated build script to build the shared DLL before the UI project. |
| .gitignore | Updated ignored output/recovery paths for new folder layout and new projects. |
| .github/workflows/ci.yml | Added Win32 CI build + test workflow for the DLL and DUnit tests. |
| TestFramework, | ||
| GpProf in '..\include\GpProf.pas', | ||
| GpProfH in '..\include\GpProfH.pas', | ||
| GpProfCommon in '..\include\GpProfCommon.pas', | ||
| GpProfDllClient in '..\include\GpProfDllClient.pas', | ||
| testGpProfDll in 'testGpProfDll.pas', | ||
| testGpProfDllContext in 'testGpProfDllContext.pas'; |
| <DelphiCompile Include="$(MainSource)"> | ||
| <MainSource>MainSource</MainSource> | ||
| </DelphiCompile> | ||
| <DCCReference Include="..\include\GpProf.pas"/> | ||
| <DCCReference Include="..\include\GpProfH.pas"/> | ||
| <DCCReference Include="..\include\GpProfCommon.pas"/> | ||
| <DCCReference Include="..\include\GpProfDllClient.pas"/> |
…om/ase379/gpprofile2017 into copilot/bundle-profiling-units-dll
When multiple DLLs are profiled, each statically compiles
GpProf.pas, resulting in a separate critical-section lock and.prfoutput file per module. This fragments profiling data and introduces redundant synchronization overhead.Changes
source/gpprof.dll/GpProfDll.dpr+GpProfDll.dprojlibraryproject compiling the existinginclude/GpProf.pas(unmodified) into a shared DLL.prffileNameThreadForDebuggingexported using Windows A/W suffix convention:AfterBuildtarget produces a platform-named copy of the DLL (GpProfDll32.dllfor Win32,GpProfDll64.dllfor Win64) in the DLL output folder and automatically copies it to the matchingGpProfDllTestApp\$(Platform)\$(Config)\output directoryDllAcquireSession: PointerandDllReleaseSession(session: Pointer)allow clients to register their attachment with the DLL so the DLL can track how many callers are currently activeinclude/GpProfDllClient.pas(new)initializationcallsLoadLibraryand binds function pointers for every DLL export;finalizationcallsFreeLibraryand nils all pointersGpProfDll32.dllon Win32 andGpProfDll64.dllon Win64 (compile-time{$IFDEF WIN64}constant)Assigned(fn)before use — safe no-op when the DLL is absentIGpProfContextinterface — wraps the full DLL API surface (Start,Stop,StartThread,EnterProc,ExitProc,EnterMP,ExitMP,CreateMeasurePointScope,Terminate,NameThread) as an object-oriented interface allocated entirely in the caller's heap; no Delphi interface reference crosses the DLL boundaryAcquireGpProfContext: IGpProfContext— factory function that creates and returns aTGpProfContextClientinstance; the instance callsDllAcquireSessionon construction andDllReleaseSessionon destruction, so the DLL accurately tracks live client count at all times; releasing the interface (setting tonilor letting it go out of scope) disposes the instance cleanlyCreateMeasurePointScope(onIGpProfContext) returns a client-sideTMeasurePointScopeClient(allocated in the caller's heap) whose destructor callsProfilerExitMPthrough the DLL function pointer; no Delphi interface reference crosses the DLL boundaryIMeasurePointScopedeclared with a stable GUIDAcquireGpProfContextto obtain anIGpProfContextinstance and call through itsource/gpprof.dll.tests/GpProfDllTests.dpr+GpProfDllTests.dproj+testGpProfDll.passource/gpprof.dll.tests/TTestGpProfCommon— 7 tests forGpProfCommon.ResolvePrfRuntimePlaceholders: passthrough with no placeholder,$(ProcessID)verified againstGetCurrentProcessID,$(ProcessName)/$(ModuleName)/$(ModulePath)structural assertions, multiple placeholders in one string, repeated placeholderTTestGpProfDll— 10 tests for the publicGpProfAPI (exercised directly viaGpProf.pas): basic start/stop, idempotent start/stop, enter/exit proc with edge-case IDs (0 andHigh(Cardinal)), enter/exit measure point,CheckNotNullonCreateMeasurePointScope, auto-exit and nested scope release, single and repeatedNameThreadForDebuggingcallsTTestGpProfContextAPI(testGpProfDllContext.pas) — 4 tests forIGpProfContext/AcquireGpProfContext:CheckNotNullon the returned interface, full API round-trip through the context, multiple independent context instances, and correct disposal onnilassignmentRegisterTestand are run byTextTestRunner.RunRegisteredTestsGpProfDllTests.GPI(emptyTableName=) is committed alongside the project soReadIncSettingsfinds the file silently, leavingprfDisabled = true— all profiling calls are safe no-ops without blocking onMessageBoxDCC_ExeOutputset to.so the compiled executable lands next to the stub.GPIfile$(BDS)\source\DUnit\srcincluded in the unit search path forTestFrameworkandTextTestRunnerDUnitX_GUIbuild configuration — definesUSE_DUNITX_GUI; compiles as a VCL GUI application usingDUnitX.Loggers.GUI.VCL; bridges existing DUnit tests to DUnitX viaTDUnitXDUnitBridge.RegisterDUnitTestsand launchesTGUIVCLTestRunner;$(BDS)\source\DUnitXadded to the search path (base-level andCfg_3) soDUnitX.Loggers.GUI.VCLandDUnitX.DUnitCompatibilityare found at compile time. The default Debug/Release configurations continue to use the consoleTextTestRunnerunchanged.source/GpProf.UI/(relocated fromsource/)gpprof.dpr/dproj,HISTORY/OPTIONS/baggage/help.inc,gpprof_Icon.ico,gpParser.MeasurePoint.pas,icons/,model/,ui/) moved into its own subdirectorygpprof.dprojupdated from..\to..\..\to reflect the extra directory level.gitignore__recoverypaths updated to the new locationssource/GpProfDllTestApp/(relocated from/tests/)GPProfTester.dpr/dproj,testUnit.pas,testThreads.pas,testMultiDefines.pas)DCC_UnitSearchPathupdated from..\includeto..\..\includeto reflect the new directory depthtestThreads.pasnow usesGpProfDllClientviaAcquireGpProfContext— the test app links against the DLL at run time through the context interface, demonstrating real-world usage ofGpProfDllClient.passcripts/build.ps1(updated)$projectFileupdated to point tosource\GpProf.UI\gpprof.dprojto match the relocated main UI application$dllProjectpointing tosource\gpprof.dll\GpProfDll.dprojGpProfDllfor Win32 and Win64 before building the main UI application for both platformssource/build.bat(deleted)scripts/build.ps1is the canonical build script.github/workflows/ci.yml(new)windows-latest; usesmicrosoft/setup-msbuildto make MSBuild availableBDSActions repository variable, falling back to common RAD Studio 12/11 installation paths; fails fast with a descriptive error if DCC32 is not foundGpProfDll.dproj(Win32, Release) to verify the DLL compiles cleanlyGpProfDllTests.dproj(Win32, Release) — the exe lands next to the stub.GPIfile so all profiling calls are safe no-opsGpProfDllTests.exeand fails the job if the DUnit exit code (FailureCount + ErrorCount) is non-zero