Delphi - unit segregation - delphi

My new component (TComponent) uses DsgnIntf since I use custom property editors. The problem is when using the component in a custom VCL application - DSGNINTF.DCU not found! One solution is to add a command line switch to a compiler (dont remember any more what is it), but I don't like that solution. The second solution is a unit segregation. I found this:
http://edn.embarcadero.com/article/27717
The problem is - I don't understand this article so well.. I don't know what exactly I need to do in my component unit to separate design-time from runtime code. Could someone please make the simplest example and explain it? I just want to avoid problems with "dsgnintf.dcu not found" when people using my component. Thank you.
EDIT:
I looked the article a bit more and I realize the second unit registers the first one. To avoid dsgnintf.dcu problem I presume the second unit must be in it's own .pas file?

Usually you create a single unit to register your package in IDE, something like that:
unit RegPackage;
interface
uses
Classes, MyUnit;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('MyPage', [TMyComponent]);
end;
end.
and include this unit into design-only package:
package MyPackD;
{$R *.res}
..
requires
rtl, MyPackR; // your runtime package
contains
RegPackage in 'RegPackage.pas';
end.
The article you link covers also property editors. All package code not related to IDE should be contained in run-only package:
package MyPackR;
{$R *.res}
..
requires
rtl,
vcl;
contains
MyUnit in 'MyUnit.pas';
end.

You must separate your run-time code and design-time code into separate packages. Create a runtime-only package that contains just your component code. Create a designtime-only package that specifies your runtime-only package and the IDE's DesignIDE package in its requires list, and contains just your property editor and registration code. Then you can install the designtime-only package into the IDE, and refer to the runtime-only package in your projects. The DesignIDE package resolves the DsgnInf reference, but it is NOT allowed to be linked into runtime executables! It is for the IDE's use only.

Related

Is it possible to use a library just for design time in delphi?

I am trying to write a component which is loading 3D objects from obj files.
I am using ToolsAPI library for GetActiveProject.FileName. I added designide.dcp to Requiers part in the bpl. I registered my object and in design when I put an instance of this object on a TViewPort3D which I put before everything is OK and I can see the object from the obj file is loaded in the scene, but when I try to compile the project I get an error that says ToolsAPI.dcu not found.
The procedure that I use for loading the obj file is (Type of Model variable is TModel3D) :
procedure TMyObject.LoadModel(fileName: string);
begin
if(csDesigning in ComponentState)then
Model.LoadFromFile(IncludeTrailingPathDelimiter(ExtractFilePath(GetActiveProject.FileName))+'Obj\'+filename)
else
Model.LoadFromFile(IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)))+'Obj\'+filename);
end;
This procedure is used in constructor as follow (TMyObject inherited from TDummy):
constructor TMyObject.Create(AOwner:TComponent)
begin
inherited;
Model:=TModel3D.Create(Self);
Model.Parent:=Self;
LoadModel('Object1.obj');
end;
Is there anyway to prevent using the ToolsAPI library when the host project for the component is about to compile?
I just thinking about something like directives as follow.
{$IFDEF DESIGNTIME}
uses ToolsAPI;
{$ENDIF}
But is it possible to do such a thing?
It sounds as though you are trying to compile the design time code into a run time project. Either a run time package, or an executable. That's not allowed. You simply cannot compile any of the ToolsAPI units into a project that is not a design time package.
You can certainly use conditional compilation to exclude the ToolsAPI units, but you will have to define your own conditional define. There is no built-in conditional that will serve your needs.
But using conditional compilation is probably not the best solution. Typically you would separate the code that used Tools API into distinct units, and only include those units in the design time projects.
So the code for your component would be split into, say, two units. The first unit, uMyComp.pas, say, contains the bulk of the code. This unit declares the component and provides its implementation. Nothing in uMyComp.pas makes any reference to ToolsAPI. The second unit, uMyCompReg.pas say, performs the component registration and any other tasks that require the ToolsAPI. There is a dependency between these units in that uMyCompReg.pas uses uMyComp.pas. Then your design time package will include both units, and any other projects that are not design time will include only uMyComp.pas.
You could achieve the same effect using conditionals. The design time project would define a conditional to indicate that this was design time. So the project settings might include a definition of a conditional named DESIGNTIME. Then all the code for your component would reside in a unit named uMyComp.pas, say. Any code related to design time would be conditional on DESIGNTIME. And any other projects that included uMyComp.pas would not have DESIGNTIME defined and so would omit the design time only code.
Whilst this is possible it is not, in my view, the best way to solve the problem. Indeed if you look around the wealth of open source examples of component development I'd be surprised if you found any that handled the separation of design time code from run time code using conditionals.
How would you separate the ToolsAPI code into a design time unit? Here's the problem method:
procedure TMyObject.LoadModel(fileName: string);
begin
if csDesigning in ComponentState then
Model.LoadFromFile(IncludeTrailingPathDelimiter(
ExtractFilePath(GetActiveProject.FileName))+'Obj\'+filename)
else
Model.LoadFromFile(IncludeTrailingPathDelimiter(
ExtractFilePath(ParamStr(0)))+'Obj\'+filename);
end;
First of all, let's look at the commonality of this code. The first think to observe is that the outsides of the call to LoadFromFile are the same. Only in the middle, the choice of directory, is there variation. So let's write it like this:
procedure TMyObject.LoadModel(fileName: string);
var
ModelDir: string;
begin
if csDesigning in ComponentState then
ModelDir := ExtractFilePath(GetActiveProject.FileName)
else
ModelDir := ExtractFilePath(ParamStr(0));
Model.LoadFromFile(IncludeTrailingPathDelimiter(ModelDir)+'Obj\'+filename);
end;
The problem for you is how to move GetActiveProject.FileName into the design time code. You need to use dependency injection (DI) to do this. Allow some other party to supply the logic. You need to make TMyObject ignorant of this particular detail. You could use a DI framework for this, but that's perhaps a little heavyweight just for this one task. So instead let's declare a class variable that holds a function pointer:
type
TMyObject = class(...)
...
public
class var GetModelDir: TFunc<string>;
end;
This function point allows other parties, external to the class, to specify how the model directory is located. Now LoadModel becomes:
procedure TMyObject.LoadModel(fileName: string);
var
ModelDir: string;
begin
if Assigned(GetModelDir) then
ModelDir := GetModelDir()
else
ModelDir := ExtractFilePath(ParamStr(0));
Model.LoadFromFile(IncludeTrailingPathDelimiter(ModelDir)+'Obj\'+filename);
end;
At this point, your code can now be used outside of a design time package. The next step is to add code to specify GetModelDir at design time. This code goes in the design time only unit that also registers the component. The obvious place for the code is in the initialization section of that unit. It looks like this:
initialization
TMyObject.GetModelDir :=
function: string
begin
Result := GetActiveProject.FileName;
end;
I've used anonymous methods here, but you could equally use method of object, or plain old functional types, depending on your Delphi version.
Yes, but preferably not with conditional defines as this would create far more complications and restrictions than it's worth.
You need to separate your code into different units according to whether it's design-time code or run-time code.
E.g. For a single component, the bulk of the (with no ToolsAPI dependency) goes into one unit.
A second unit performs component registration and perhaps provides custom design-time editors for the component.
The second unit uses the first and you have a clean separation without conditional defines.
You then create 2 separate packages: design-time and run-time.
The design time package will have a dependency on the ToolsAPI.
Make sure that none of the run-time units use any of the design-time units.
If any design-time units use run-time units (very likely) then the design-time package will require the run-time package.
With the above package structure, your application that uses your new components should only have dependencies on the run-time units.

Add unit to project is removing a compiler directive from the project source

Should this work this way or am I doing something wrong?
I have this code in my project source:
{$IFDEF DEBUG}
ADebugUnit,
{$ELSE}
ARelaseUnit,
{$ENDIF}
I want ADebugUnit to be used when in debug mode but AReleaseUnit to be used when compiling in release mode. This works great EXCEPT when I select to add a new unit to the project. When I do that it will basically process the code and only keep the unit that pertains to whichever configuration the project is currently set to.
For example, if the configuration is set to Debug then after adding a new unit to my project the above code changes to just:
ADebugUnit,
Or if my configuration is set to Release it will change to the following after adding a new unit:
ARelaseUnit,
I have to always restore it back to the conditional statements after adding a new unit. Is there a way to accomplish this without having the add new unit interfere?
The problem is that the DPR will not respect any $ifdef's in the uses list and will in fact remove them (as you have found) when it re-writes the uses list in response to certain IDE operations.
One option is to never use those IDE operations, such as "Add/Remove Unit" etc and to only ever manage the DPR uses list manually.
Alternatively with a bit of care you may be able to use unit aliases to achieve what you want.
Consider two units where you wish to use either on or the other depending upon the build configuration (debug or release):
DebugUnit.pas
ReleaseUnit.pas
In your project options add a Unit Alias for:
DEBUG configuration:
UnitToUse=DebugUnit
RELEASE configuration:
UnitToUse=ReleaseUnit
In your DPR add an entry to the uses list:
uses
UnitToUse,
This entry in DPR cannot identify a filename using the "in '' syntax and must instead rely on the actual units required being on the project search path.
Anywhere that you would normally use the DebugUnit or ReleaseUnit, refer instead to UnitToUse. Obviously the name for the alias is entirely up to you.
If the two units have the same interface "contracts" then your builds will switch between these two units simply by changing the target configuration.
If they have different interface contracts then you can still use $ifdef directives in your application code to work with the contents of whichever unit UnitToUse refers to, as appropriate, e.g.
uses
UnitToUse;
procedure DoSomethingInvolvingAliasedUnit;
begin
{$ifdef DEBUG}
// code which relies on the debug unit
{$else}
// code which relies on the release unit
{$endif}
end;
The IDE owns much of the DPR file. Be careful of what you do to it, or you risk exactly what you've observed (or worse — depending on the nature of the changes, the IDE might sometimes decide not to allow the file to be compiled at all!).
Among other things, what this means is that you cannot conditionally include units in the DPR file. You'll have to find another solution to whatever problem you were trying to solve.
For example, maybe you could use the unit from somewhere else in your project instead of the DPR file.
Or maybe you could consolidate the two units into one, and then have its contents conditionally compiled instead.
Or maybe you could just use the debug code all the time since that increases the chances that you ship the same code you tested.
Or, if this problem only occurs when you use the "add unit" dialog, you could just forego that dialog and edit the DPR file manually. There's no other magic behind adding a unit to a project, except that the uses clause gets rewritten, as you've noticed.
To build onto Rob's answer, whenever I have situations when I need to do something like this, I migrate all the DPR code over to another unit, for example AppInit.pas.
unit AppInit;
interface
uses
Vcl.Forms,
Unit1,
{$IFDEF DEBUG}
ADebugUnit
{$ELSE}
AReleaseUnit
{$ENDIF}
;
procedure RunApp;
implementation
procedure RunApp;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.Title := 'Sample Application';
Application.CreateForm(TForm1, Form1);
Application.Run;
end;
end.
Then your project unit would only have
program SampleApp;
uses
Vcl.Forms,
Unit1 in 'Unit1.pas' {Form1},
AppInit in 'AppInit.pas';
{$R *.res}
begin
RunApp;
end.
The down-side of this is that the IDE will then be confused as to what type of application it is, and when you go to Project > Options, some features will be disabled, such as VCL Styles. With the right coding, such things can still be implemented though.
PS - Please pardon the fact that I wrote this 100% directly into StackOverflow, so sorry if I goofed something up in that code.

how to get a component class in DLL

I'm making a mdi application with many child forms, one of which is the form to display the report.
on the report form I use dll files to display all the components on the form and look for value
in each component, I use the following code to do that.
// this code i write in dll or bpl file
procedure getReportParams(Form : Tform); stdcall;
var
i : integer;
str, cbstr : string;
b : boolean;
begin
for i:=0 to Form.ComponentCount-1 do
begin
str:=str+Form.Components[i].Name+' - '+Form.Components[i].ClassName+', ';
if (Form.Components[i] is TcxLookupComboBox) then
begin
showmessage('test 1');
// if i uncomment the code below, the program get error Einvalidcast
// cbstr:=(Form.Components[i] as TcxDBLookupComboBox).Text;
// if (Form.Components[i] as TcxDBLookUpCombobox).Parent=Form.FindComponent('pnledit') then
// showmessage((Form.Components[i] as TcxDBLookUpCombobox).Name);
end;
end;
showmessage(str);
// this showmessage work well in dll, bpl, or other unit
if b then
showmessage(cbstr+' true') else showmessage(cbstr+' false');
end;
simple question is how to write code cbstr:=(Form.Components[i] as TcxDBLookupComboBox).Text; with corecly without get EInvalidCast error?
Btw if i write this code in other unit, dll and bpl program get error but if i write that code in same unit (unit report) the code work well. thank for advance.
Your problem is that the classes in your DLL are different from the classes in your executable. You have two instances of these classes, even thought they are compiled from the same code. The compiler is accurate when it says that the object is not the class that you cast it to. You simply cannot share Delphi classes using DLLs.
The solution is either:
Compile all your code into a single executable.
Use runtime packages to share classes.
In your scenario it's not enough that you put your code in a package. The problem are the devexpresses classes. You need to link to those using runtime packages. Because you are not doing so you have multiple different versions of those classes.
You note that the results of the is operator appear to be at odds with the ClassName function. Well, that's because all the different versions of the class have the same name.
I also note that the issue you are encountering is the same as in your earlier question: How can I pass TForm to a DLL as parameter? The explanation and advice from the answer you accepted there apply equally here.
If you already used a (Foo is TSomething) type check, then you know that foo is a TSomething and you can use a static cast: TSomething(Foo)
If you are trying to link this code in another Executable or dll, you probably have not included the correct units IF IT FAILS TO COMPILE, AND IF it fails at runtime, you didn't turn the BPL link option on (Use Runtime PACKAGES, and make sure the list of package names is complete). Remember that checking "something is TSomething" you are comparing a class declaration with another live object's class. A class is not defined by the string name. It's actually type information linked into your application.
When you link a DLL (without runtime packages) you actually may have linked TSomething into your main EXE and into your DLL, and they are TWO DIFFERENT copies of the class with the same name and the name matters not one bit. When you compare for identity, there's no way to know at runtime that they were the same thing. SO they aren't.
You think about code the way you see it written on the screen. When it runs, it's been compiled into code, and the types are simply data in the exe or DLL. So TSomething-in-myexe.exe is not the same class as TSomething-in-mydll.dll.
If you want them to be the same, turn on Use Runtime Packages (BPLs) for all places where you want to compare type information between different compiled parts. In particular passing pointers or references to VCL types between non-bpl-enabled linked targets is not going to work the way you thought it would.
You should also make sure that the list of runtime packages contains the package that defines that class you're using. (TcxSomething is probably a developer express component, go find what package BPL it is defined in.)

Overriding Component Create Constructor in Separate Design-Time package

Programming Environment: Delphi 6 and upwards
I am aware of the fact that since Delphi 6, custom components must have separate design- and run-time package. All run time features of the a component must therefore be in a separate unit and packaged separately to the component's design-time package.
My problem is the following: My component has code that needs to be run both when it is created on the form at run time and, additional code that needs to be run at design-time, when the component is placed on the form. I have managed to put the run time code into the separate run time unit, package it and deploy it successfully.
However, in the separate design-time module unit, how do I reference and add the design-time code that needs to be included into the component's create constructor during design-time, when the component gets placed onto the form?
You can separate design time behaviour from run time behaviour with
if [not] (csDesigning in ComponentState) then
But if your constructor code needs the DesignIDE design time package, e.g. from the units DesignEditors, DesignIntf, etc..., then I think you are stuck. Maybe some IOTA involvement can help. But since there does not seem to exist a notifier interface for the creation of components, that would require a custom IOTAFormEditor. Not that easy, if not impossible.
Why not use callbacks?
From your designtime package initialization code do this:
unit MyDsgnUnit;
interface
//TMyHook defined in AnImplUnit
TMyDesignHandlerObject = class
procedure MyMethod(Sender:TObject;ParentForm:TObject); { must match TMyHook }
end;
implementation
uses AnImplUnit, DesignUnitNamesHere;
procedure TMyDesignHandlerObject.MyMethod(Sender:TObject);
var
newObject:TMyComponent;
begin
newObject := TMyComponent(Sender);
DoSomethingThatneedsDesigntimeStuff(newObject);
end;
finalization
ADesignHandlerObject.Free;
initialization
ADesignHandlerObject := TMyDesignHandlerObject.Create;
AnImplUnit.AfterConstructionHook := TDesignHandlerObject.MyMethod;
and in your component do something like this:
unit AnImplUnit;
interface
type
TMyHook = procedure(Sender:TObject;ParentForm:TObject) of object;
var
AfterConstructionHook:TMyHook;
implementation
...
procedure TMyComponent.Create(AOwner:TComponent);
begin
inherited Create(AOwner);
DOMyStuff;
if Assigned(AfterConstructionHook)
AfterConstructionHook(Sender,Parent);
end;
Update Flushed out example more. There is no reason you can't add more parameters to the AfterConstructionHook, but since your reference to Sender is already of type TMyComponent, I don't see the point, when you can access everything public or protected, in TMyComponent(Sender) from within your hook function, and if you inherit locally (known as a protected-access class), you can access the protected stuff too.
Make sure your DPK defines a symbol of your choice, for example DESIGNTIME. Then you'll be able to use something like this to only include design-time units when needed:
uses Windows, Whatever, Something
{$IFDEF DESIGNTIME}
,DesignIntf
{$ENDIF}
;
Then to the same in your constructor code:
constructor TMyClass.Create(aOwner:TComponent);override;
begin
inherited;
{$IFDEF DESIGNTIME}
// I'm at design time.
{$ENDIF}
end;
When using this technique you should either use a separate DCU directory for your pacakge and your normal executable, or do a build each time you switch from the design time package to other projects. That's because Delphi will only re-build a DCU if the PAS has changed, when in this case the PAS doesn't tell the whole story, defined symbols also matter. If you have a DCU on disk that was compiled by Delphi while you were building your design time project, you might see a ToolsApi.DCU not found when trying to compile a normal project. Rebuilding re-compiles the DCU and makes the message go away. Just as well, if you re-compile (not re-build) the design time project after you built a normal project, your DCU might be stuck in it's non-DESIGNTIME state, leaving you without your special design-time behavior.
As long as your code does not require the IDE tools, like design interface and so on, then all you need to do is check the component flag and you can use it anywhere within the component, as follows...
procedure TNewEdit.Loaded;
begin
inherited;
if (csDesigning in ComponentState) then
ShowMessage('Designing')
else
ShowMessage('Running');
end;
However without really knowing what you are attempting to do, there are several doors left open... for example, if you want to change a property value at design time, and a different value at run time, then there are streaming issues you have to deal with.

How do I use the descendant's form without the presence of ancestor's form visually

I just created a form within bpl project and place it on repository, named AncForm.
When I made its descendant in a new project (program Inheritance1) named DecForm.
Normally, AncForm will be included in the new project automatically when DecForm just inherited from AncForm.
program Inheritance1;
{$R *.res}
uses
Forms,
cAncForm in 'cAncForm.pas' {AncForm}, //-----> Ancestor ..... Line A
uDecForm in 'uDecForm.pas' {DecForm}; //-----> Descendant ..... Line B
begin
Application.Initialize;
Application.CreateForm(TDecForm, DecForm);
Application.Run;
end.
The question is: is there any way to link the DecForm to AncForm within this project
without the presence of "Line A"?
I mean the AncForm is not visually linked to project but still be able to provide reference to DecForm within IDE, without "error creating form...".
I hope there is a way to fully wrap the ancestor inside BPL.
I would gratefully thanks for any idea.
You need to add the package project in which the ancestor form lives to the same project group as your applications. The IDE will notice that the form is in the package project and not add it to the uses list of the application project.

Resources