How to determine dependencies for all units? (Estimate refactoring cost) - delphi

Unfortunately I have inherited a piece of unlucky design based on a {$IFDEF} "hell".
What I have roughly looks like this (where MyDbSystemB is what I want to add)
unit MyTable;
interface
uses
// ...
{$IFDEF MyDbSystemA}
DbSystemA ,
{$ENDIF}
{$IFDEF MyDbSystemB}
DbSystemB ,
{$ENDIF}
type
TMyTable = class(
{$IFDEF MyDbSystemA}
TSystemATable
{$ENDIF}
{$IFDEF MyDbSystemB}
TSystemBTable
{$ENDIF}
)
// A public interface implementation
end;
implementation
// ...
end.
A number of units reference TMyTable, but rely on specific functions provided with TSystemATable:
unit oldUnit;
interface
uses MyDbTable;
type
TXy = class(TXyz)
public
procedure Foo();
end;
implementation
procedure TXy.Foo();
var
table : TMyTable;
begin
table := TMyTable.Create();
table.SomeSystemASpecificFunction;
end;
I'd like to find all of these references within a single reference/syntax check. But as I read here that's apparently not really possible
Find all compilation errors in a Delphi project.
What would be the best strategy to go for finding these files to estimate the efforts of porting?
A plain file grep over all *.pas files (there may be a lot of either dead code, or just poorly decoupled stuff)?
I'm also able to provide a surrogate implementation of
TMyTable = class( { ... like above } )
{$IFDEF MyDbSystemB}
public
procedure SomeSystemASpecificFunction; deprecated;
{$ENDIF}
end;
with the implementation of TMyTable, but I'd need to estimate the refactoring cost to do that properly anyways.
Regarding that I also could add a deprecated attribute along with the SomeSystemASpecificFunction surrogate, that will at least give me warnings.

If you know the names of the members of TMyTableA that code takes a dependency on then use Find in Files... (or your favorite alternative grep-like tool) to identify references to those members in the files in your project.
This is likely to be more reliable than any compilation based check anyway.
Any tool which claims to find "all compilation errors in a project" is more often than not lying to you since there is no reliable way to discriminate between errors that have not arisen as a result of some other error earlier in the compilation.
e.g. in a C# solution it is quite common for a simple change to result in dozens if not hundreds of compilation errors which are fixed by resolving only the first error.
i.e. the compiler reports (e.g.) 224 errors when in fact there is only 1 error with 223 errors as a side-effect.
For the same reason, you cannot be sure that the list of errors contains all of the genuine errors you might eventually uncover. In some cases the compiler might yet be defeated to the point of not even attempting to compile some code that contains the errors you are looking for, as a consequence of those side-effect errors.
Certainly, in the list of dozens or hundreds of errors you might then be able to grep to find ones that appear to be candidates for the "genuine" errors you are looking for (i.e. that reference the specific members you know to be involved in your change). But all that does is change the data set you are searching for to locate those references. i.e. the compilation error list rather than your project sources.

Related

Method in Delphi to set database connections to disconnected upon compile

Is there a method or compiler directive or some way of assuring certain components, such as queries or database connections get set to active=false or disconnected when you run a build/compile? Seems so often these are turned on by something else and you don't notice it until its too late.
My particular install is Delphi 7
The Set Component Properties feature of GExperts is able to do that.
i think the best option would be to subclass stock connection component and in your own one override .Loaded method like that
if not csDesigning in Self.ComponentState then
if not Self.ActiveInDFM {new boolean published property} then
if Self.Active then Self.Active := false;
inherited;
http://docwiki.embarcadero.com/Libraries/XE3/en/System.Classes.TComponentState
http://docwiki.embarcadero.com/Libraries/XE3/en/System.Classes.TComponent.Loaded
By (ab)using Delphi Form Designer stupidness you can use it even without actually installing your new component into IDE Palette - just give it the same name as to the stock component class, then put your own method as last in the form's interface-uses list: thus in design-time you would have stock component and when compiling it would be transparently substituted with your own one.
Or you can sub-class it right above the very form declaration like (for another component):
type
TPanel = class(ExtCtrls.TPanel)
private
...
TForm1 = class(TForm) ....
I guess this approach might be seen as analogue to aspect-oriented programming, using limitations of IDE in developer-benefitting way.
Another approach might be some script, that cleans .Active properties in DFM on save or before build, but this way is complex for
i may be harder to integrate with stand-alone build severs (new script for each different CI framework tried)
it would reset Active property for design-time as well. This is a proper thing to do, from rigorous point of view. Yet this might be not very convenient.
You may just use similar code in your Form's and DataModule's .Loaded method (you would have to override it instead connection's method then).
You can copy-paste the same code into every Form's Loaded method.
procedure TMyForm.Loaded; // override
var c: TComponent; i: integer;
begin
try
for i := 0 to Self.ComponentsCount - 1 do begin
c := Self.Components[i];
if c is TCustomConnection then
with TCustomConnection(c) do // Hate those redundant typecasts!
if Connected then Connected := false;
if c is TDataSet then
with TDataSet(c) do // Delphi could took a lesson from Component Pascal
if Active then Active := false;
if c is ... // transactions, stored procedures, custom libriaries...
end;
finally
inherited;
end;
end;
This seems to be less sly way - thus most reliable. Yet that is a lot if copy-paste, and if you would later add some new component or library, that may ask for modifying copy-pasted code in all the forms.
You may centralize this code in some MyDBUtils unit into global procedure like Disconnect(const owner: TComponent); and then
procedure TMyForm.Loaded; // override
var c: TComponent; i: integer;
begin
try
MyDBUtils.Disconnect(Self);
finally
inherited;
end;
end;
This approach also has drawbacks though:
This would make MyDBUtils unit tightly coupled with all and every the database-related libs and components you might use. For large inherited projects, consisting of different binary modules, historically based on different db-access libraries and in process of migration, thus pulling all the access libraries into every binary module.
It can be overcome by ad hoc DI framework, but then the opposite can happen: you risk under-delivering, you may just forget to inject some library or component handler into the project that actually use it or got modified to use it.
If your form would have some components, whose connectivity should NOT be reset (object arrays as datasets, in-memory tables, in-memory NexusDB or SQLite databases, etc), you'd have to come up with ad hoc non-obvious convention to opt them out.
In my applications, I set my connection's Tag property to 1 at design time. In the OnBeforeConnect event, I check Tag, and if it is equal to 1, I abort the connection and set it to 0.

Why do I get access violations when a control's class name is very, very long?

I subclassed a control in order so I can add a few fields that I need, but now when I create it at runtime I get an Access Violation. Unfortunately this Access Violation doesn't happen at the place where I'm creating the control, and even those I'm building with all debug options enabled (including "Build with debug DCU's") the stack trace doesn't help me at all!
In my attempt to reproduce the error I tried creating a console application, but apparently this error only shows up in a Forms application, and only if my control is actually shown on a form!
Here are the steps to reproduce the error. Create a new VCL Forms application, drop a single button, double-click to create the OnClick handler and write this:
type TWinControl<T,K,W> = class(TWinControl);
procedure TForm3.Button1Click(Sender: TObject);
begin
with TWinControl<TWinControl, TWinControl, TWinControl>.Create(Self) do
begin
Parent := Self;
end;
end;
This successively generates the Access Violation, every time I tried. Only tested this on Delphi 2010 as that's the only version I've got on this computer.
The questions would be:
Is this a known bug in Delphi's Generics?
Is there a workaround for this?
Edit
Here's the link to the QC report: http://qc.embarcadero.com/wc/qcmain.aspx?d=112101
First of all, this has nothing to do with generics, but is a lot more likely to manifest when generics are being used. It turns out there's a buffer overflow bug in TControl.CreateParams. If you look at the code, you'll notice it fills a TCreateParams structure, and especially important, it fills the TCreateParams.WinClassName with the name of the current class (the ClassName). Unfortunately WinClassName is a fixed length buffer of only 64 char's, but that needs to include the NULL-terminator; so effectively a 64 char long ClassName will overflow that buffer!
It can be tested with this code:
TLongWinControlClassName4567890123456789012345678901234567891234 = class(TWinControl)
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
with TLongWinControlClassName4567890123456789012345678901234567891234.Create(Self) do
begin
Parent := Self;
end;
end;
That class name is exactly 64 characters long. Make it one character shorter and the error goes away!
This is a lot more likely to happen when using generics because of the way Delphi constructs the ClassName: it includes the unit name where the parameter type is declared, plus a dot, then the name of the parameter type. For example, the TWinControl<TWinControl, TWinControl, TWinControl> class has the following ClassName:
TWinControl<Controls.TWinControl,Controls.TWinControl,Controls.TWinControl>
That's 75 characters long, over the 63 limit.
Workaround
I adopted a simple error message from the potentially-error-generating class. Something like this, from the constructor:
constructor TWinControl<T, K, W>.Create(aOwner: TComponent);
begin
{$IFOPT D+}
if Length(ClassName) > 63 then raise Exception.Create('The resulting ClassName is too long: ' + ClassName);
{$ENDIF}
inherited;
end;
At least this shows a decent error message that one can immediately act upon.
Later Edit, True Workaround
The previous solution (raising an error) works fine for a non-generic class that has a realy-realy long name; One would very likely be able to shorten it, make it 63 chars or less. That's not the case with generic types: I ran into this problem with a TWinControl descendant that took 2 type parameters, so it was of the form:
TMyControlName<Type1, Type2>
The gnerate ClassName for a concrete type based on this generic type takes the form:
TMyControlName<UnitName1.Type1,UnitName2.Type2>
so it includes 5 identifiers (2x unit identifier + 3x type identifier) + 5 symbols (<.,.>); The average length of those 5 identifiers need to be less then 12 chars each, or else the total length is over 63: 5x12+5 = 65. Using only 11-12 characters per identifier is very little and goes against best practices (ie: use long descriptive names because keystrokes are free!). Again, in my case, I simply couldn't make my identifiers that short.
Considering how shortening the ClassName is not always possible, I figured I'd attempt removing the cause of the problem (the buffer overflow). Unfortunately that's very difficult because the error originates from TWinControl.CreateParams, at the bottom of the CreateParams hierarchy. We can't NOT call inherited because CreateParams is used all along the inheritance chain to build the window creation parameters. Not calling it would require duplicating all the code in the base TWinControl.CreateParams PLUS all the code in intermediary classes; It would also not be very portable, since any of that code might change with future versions of the VCL (or future version of 3rd party controls we might be subclassing).
The following solution doesn't stop TWinControl.CreateParams from overflowing the buffer, but makes it harmless and then (when the inherited call returns) fixes the problem. I'm using a new record (so I have control over the layout) that includes the original TCreateParams but pads it with lots of space for TWinControl.CreateParams to overflow into. TWinControl.CreateParams overflows all it wants, I then read the complete text and make it so it fits the original bounds of the record also making sure the resulting shortened name is reasonably likely to be unique. I'm including the a HASH of the original ClassName in the WndName to help with the uniqueness issue:
type
TWrappedCreateParamsRecord = record
Orignial: TCreateParams;
SpaceForCreateParamsToSafelyOverflow: array[0..2047] of Char;
end;
procedure TExtraExtraLongWinControlDescendantClassName_0123456789_0123456789_0123456789_0123456789.CreateParams(var Params: TCreateParams);
var Wrapp: TWrappedCreateParamsRecord;
Hashcode: Integer;
HashStr: string;
begin
// Do I need to take special care?
if Length(ClassName) >= Length(Params.WinClassName) then
begin
// Letting the code go through will cause an Access Violation because of the
// Buffer Overflow in TWinControl.CreateParams; Yet we do need to let the
// inherited call go through, or else parent classes don't get the chance
// to manipulate the Params structure. Since we can't fix the root cause (we
// can't stop TWinControl.CreateParams from overflowing), let's make sure the
// overflow will be harmless.
ZeroMemory(#Wrapp, SizeOf(Wrapp));
Move(Params, Wrapp.Orignial, SizeOf(TCreateParams));
// Call the original CreateParams; It'll still overflow, but it'll probably be hurmless since we just
// padded the orginal data structure with a substantial ammount of space.
inherited CreateParams(Wrapp.Orignial);
// The data needs to move back into the "Params" structure, but before we can do that
// we should FIX the overflown buffer. We can't simply trunc it to 64, and we don't want
// the overhead of keeping track of all the variants of this class we might encounter.
// Note: Think of GENERIC classes, where you write this code once, but there might
// be many-many different ClassNames at runtime!
//
// My idea is to FIX this by keeping as much of the original name as possible, but
// including the HASH value of the full name into the window name; If the HASH function
// is any good then the resulting name as a very high probability of being Unique. We'll
// use the default Hash function used for Delphi's generics.
HashCode := TEqualityComparer<string>.Default.GetHashCode(PChar(#Wrapp.Orignial.WinClassName));
HashStr := IntToHex(HashCode, 8);
Move(HashStr[1], Wrapp.Orignial.WinClassName[High(Wrapp.Orignial.WinClassName)-8], 8*SizeOf(Char));
Wrapp.Orignial.WinClassName[High(Wrapp.Orignial.WinClassName)] := #0;
// Move the TCreateParams record back were we've got it from
Move(Wrapp.Orignial, Params, SizeOf(TCreateParams));
end
else
inherited;
end;

Where is maxint defined - duplicate definition?

I have a projectgroup in which all Win32 programs have the same ..\PatchLibs search path.
This folder contains a patched System.Win.Ctrl.pas containing:
{$IFDEF WIN32}
function _malloc(size: size_t): Pointer; cdecl;
begin
if (size > MaxInt) then
begin
Result := Nil
end
else
begin
try
Result := AllocMem(size);
except
Result := Nil;
end;
end;
end;
[This patch suppresses an error in midaslib (QC 104337)]
The issue:
One of the (smaller) projects gives a W1023 ("comparing signed and unsigned types") compiler warning on the 'MaxInt' line, all others build without warnings.
None of the projects have System.Win.Ctrl in their uses statements or in their project files.
Thinking there might be two typed constant definitions for Maxint I wanted to prefix Maxint with the 'correct' unit name, but can't find its definition.
I have searched through all available c:\program files (x86)\embarcadero\rad studio\9.0\source*.* files, but found no definitions.
System.MaxInt works but does not eliminate the warning.
Typecasting Cardinal(MaxInt) removes the warning, but I'd still prefer the 'fully qualified' solution.
(size_t is defined as ULONG_PTR is defined as NativeUInt)
I found Quality Central issue 102873, 69836 and 53202 but these refer to duplicate definitions C++ .h header files
Is my assumption about more than one definition correct? If so, what would/should the unit prefix be?
And most important: why do I get the compiler warning for that one project build only?
MaxInt is declared in the System unit. I'm pretty sure that's the only MaxInt that is in scope here. The warning you see is accurate. MaxInt is signed and size_t is unsigned. You should suppress the warning. For example you could cast MaxInt to size_t:
if size > size_t(MaxInt) then
That's fine because MaxInt is within the range of values of size_t.
As an aside, I'd probably deal with the underlying issue by hooking the function that needs fixing, rather than re-compiling the entire unit. I personally find that to be less invasive and easier to maintain.
Why do I get the compiler warning for that one
project build only?
Some ideas:
You only have one project that includes that unit.
You have different compiler options in different projects. Perhaps only one of your projects has warnings enabled, or only one project has that specific warning enabled.
You only have one project with WIN32 defined.
This file is only compiled once, but used multiple times. Perhaps because you are making rather building.
It's pretty hard to explain that part of your question. Irrespective, when the code in your question is compiled with warnings enabled, you will get that warning. You really are comparing signed and unsigned. So you really do need to suppress the warning with the method I provide above.
Remember that a warning does not mean that your code is broken. Suppressing that warning will not change the behaviour of the code. When you see that warning you analyse the code to check whether or not there is a problem. In this case, the code works fine and you just need to suppress the warning.

Exceptions and DLL in Delphi

What is the right way to handle exceptions thrown from inside a DLL in Delphi?
Something like this
on E : ESomeException do ...
or
if (E is ESomeException) then ...
fails, because of the separate type registries of the DLL and the main application.
For pure DLL's exceptions are not allowed to cross the DLL boundary (like Deltics mentions) - no matter what language.
You get all sorts of trouble there, especially because you don't know which language, RTL, memory manager, etc, is on each side of the boundary.
So you are back to the classic error handling paradigm:
error codes (similar to HResult)
error messages (similar to GetLastError)
Instead of DLL's, you could use BPL packages (as Lars suggested): there you know that both sides will use the same RTL and memory manager.
Both packages and BPL usually give you a versioning nightmare anyway (too many degrees of freedom).
A more rigorous solution is to go for a monolithic executable; this solves both problems:
much easier versioning
guaranteed only one RTL and memory manager
--jeroen
PS: I've made this an additional answer because that allows for easier pasting of links.
The safest way is to not allow exceptions to "escape" from the DLL in the first place.
But if you have no control over the source of DLL and are therefore unable to ensure this, you can still test the exception class name:
if SameText(E.ClassName, 'ESomeException') then ...
If you use runtime packages (at least rtlxx.bpl) for both your application and your dll, then both have the same type and it will work. Of course this limits the use of your dll to Delphi/BCB only.
Another solution is not using exceptions at all like Deltics suggest. Return error codes.
Or use COM. Then you can have exceptions and not limit your dll to Delphi only.
Sometimes you do not have control over a DLL and cannot avoid having trouble with exceptions.
We, for instance, had a problem with a function in an external DLL that was blocked by AV software settings ("ransomware protection") leading to access violations in Delphi.
The following code is working for us:
var
O: TObject;
Msg: AnsiString; //type depending on DLL
begin
try
CallExternalDll(...);
except
//on E: Exception do might lead to access violations
//because the exception might not be of type Exception
O := ExceptObject;
//Depending on the DLL this or an other cast might
//or might not be necessary:
Msg := PAnsiChar(Exception(O).Message);
raise Exception.Create(Msg);
end;
end;
This workaround seems to do it for me:
function ExceptionMatch (Exc : Exception; ExcClass : TClass) : Boolean;
var
CurrClass : TClass;
begin
CurrClass := Exc.ClassType;
while (CurrClass <> nil) do
begin
if SameText (CurrClass.ClassName, ExcClass.ClassName) then
Exit (True);
CurrClass := CurrClass.ClassParent;
end;
Result := False;
end;
I'm prepared for you to destroy this :)
What is wrong with this approach? What is potentially dangerous?

Trouble with THTML file GDIPL2A.pas

Running a project and was getting an error saying
"Not enough actual parameters"
The error is in "C:\Program Files\PBear\HTMLComponents\package\GDIPL2A.pas".
The mistake pointed three times to "inherited Create;" lines 260,270 and 278 . In the file "GDIPL2A.pas".the Code is:-
var
err: integer;
begin
inherited Create;
err := GdipCreateBitmapFromScan0(W, H, 0, PixelFormat32bppARGB, nil, fHandle);
if err <> 0 then
raise EGDIPlus.Create('Can''t create bitmap');
end;
I was wondering why it would show an error in "THTML" files, WHICH ARE NOTHING BUT FILES FROM THE INSTALLATION of THTML.I did not even touch THTML files.
Kindly help
Thanks and Regards
Vas
A "Not enough actual parameters" error on "inherited Create;" means that you're trying to call an inherited constructor but it not supplying any parameters. Check the class you're inheriting from and you'll see a Create that requires some parameters. (If the base class doesn't have one, check its parent, and its parent and so on. You'll find one eventually.) It should be pretty obvious once you find the constructor declaration what you need to pass to it.
Your call needs to look something like:
inherited Create(param1, param2);
I have THTML, and it indeed includes GDIPL2A.pas, which is a wrapper around GDIPlus; apparently THTML uses GDIPlus to display embedded images or something.
A quick look at the declaration of TGPImage and TGpBitmap shows the constructor declarations of each:
// TGpImage
public
constructor Create(FileName: string; TmpFile: boolean = False); overload;
constructor Create(IStr: IStream); overload;
// TGpBitmap
public
constructor Create(W, H: Integer); overload;
constructor Create(IStr: IStream); overload;
You'll see that all of the constructors takes at least one or two parameters; your call to inherited Create passes none. However, since the call to inherited is in another constructor it should work (and indeed does on my machine; I just tried rebuilding one of the demos from THTML and it recompiled GDIPL2A.pas fine), You've got something else going on, like a different version of GDIPL2A in your path that the compiler is using instead of the one you're seeing in the IDE's editor.
As to your question, I answered it in the first paragraph above. It's reporting the error in THTML because that's the copy of GDIPL2A that the compiler is using, which may not be the one your code is expecting it to use.
You can fix that by either:
Reordering units in the uses clause of your code so that all calls that cause GDIPL2A to compile are using the same one;
Copy the GDIPL2A your code thinks it's using into your project's source folder, so it will be compiled from there. This will probably break THTML if you're using it in that same project, so be ready for that;
Find and resolve the competing copies of GDIPL2A so that there's only one copy available on the compiler's search path;
Remove the THTML path from your project's search and library paths, if you're not using it in your problem project. You can also, using Project|Options|Packages, prevent THTML from even being loaded when you open the project if you'd like, to make your project load faster.
I don't know if anybody read this anymore but my problem occurred during installing of ThtmlViewer. My solution was to edit the GDIPL2A.pas file. I just added an emty string and a false boolean, so the 3 create statements looked like this:
inherited Create('', False);
And then everything worked fine (at least so far)
(I have an old Delphi 4 on an old Windows Xp on an old PC, not connected to internet)
Kindly
Erling

Resources