i := Variants._VarToInteger(TVarData(v));
fails to compile with error E2003 Undeclared identifier: '_VarToInteger'
The _VarToInteger procedure is a perfectly normal looking declaration in the
Variants interface section (in D2006, line 346, implementation starts at
line 415).
Nevertheless, _VarToInteger and all the other underscore-named functions
declared there are invisible outside the Variants unit.
I've never known Delphi to have the ability to limit a declaration that
appears in an interface section to unit-internal visibility before. So how's
it being done?
Is the method extensible--for example, would it be possible to declare a
class in an interface section but not allow it to be seen externally?
And why?
bobD
> In a unit using Variants and the appropriate variable declarations, the
> line
>
> i := Variants._VarToInteger(TVarData(v));
>
> fails to compile with error E2003 Undeclared identifier: '_VarToInteger'
Such functions have special meaning to the compiler. You should remove the
explicit call and let the compiler figure it the conversion for you:
i := v;
That should call _VarToInteger() for you.
Gambit
That only partially answers the why: there are many things that the compiler
might do that one might also want to do directly for a variety of reasons.
For comparison, TypInfo.pas has never really been documented or its direct
use encouraged. But there's nothing there to force you to stay out of it.
And it doesn't answer the how at all--being able to hide a class that has to
be in the interface section to satisfy a single-pass compiler could be a
useful feature. Like declaring the class private or internal.
bobD
> And it doesn't answer the how at all--being able to hide a
> class that has to be in the interface section to satisfy a
> single-pass compiler could be a useful feature. Like declaring
> the class private or internal.
Why would you want to declare a class in the interface section if you are
not going to allow its use? What exactly are you trying to accomplish?
Gambit
To be able to declare and employ internal strategy classes. Nested class
declarations addresses some of this, but not all.
TInternal HelperClass = class
//
end;
TClassTheUserShouldSee = class(TSomeParent)
private
FHelperClass : TInternal HelperClass;
//
end;
TAnotherClassTheUserShouldSee = class(TSomeOtherParent)
private
FHelperClass : TInternal HelperClass;
//
end;
bobD
> To be able to declare and employ internal strategy classes.
That is what polymorphism is for. Define an abstract class or interface
publically, and then derive private classes that inherit from it and use
those internally as needed.
Gambit
> That is what polymorphism is for. Define an abstract class or interface
> publically, and
which still hangs a class or interface out in public where it shouldn't be.
Look at it this way: C# allows one to define a class as 'internal'--usable
by all the other classes within, but not outside, the namespace.
The Delphi way of doing that it is declaring the class in the implementation
section, but it's not really the same, since now the other public classes in
the unit can't reference the hidden class in their definitions--you're left
either creating a totally unnecessary interface or abstract class (and
exposing that--which exposure you're trying to avoid), or you have to cast a
lot, or some other equally messy workaround.
The obvious solution to that would be to have the ability modify or block
the visibility of a class/procedure/variable that has to be in the interface
section of the unit, not because you want it there, but because a single
pass compiler needs it there.
And CodeGear agress, judging fron their code. With the _<name> procedures in
Variants, it's clear that CodeGear also felt such a capability to be useful
enough not to engage in any workarounds, but to actually accomplish it for
their own functions: declared in the interface section and yet invisible
outside the unit.
It's just a shame if, as it appears, they hardwired it there, rather than
making it a language feature we could all use.
bobD
> which still hangs a class or interface out in public where it shouldn't
> be.
Only the portion(s) that you want public. You hav to give the user
SOMETHING to work with. Otherwise, there is no point in putting anything in
the 'interface' section to begin with.
Please provide an actual code example of what you are trying to solve.
> Look at it this way: C# allows one to define a class as 'internal'--usable
> by all the other classes within, but not outside, the namespace.
Defining a class privately inside the 'implementation' section instead of
the 'interface' section accomplishes the same thing.
> The Delphi way of doing that it is declaring the class in the
> implementation section, but it's not really the same, since now the other
> public classes
> in the unit can't reference the hidden class in their definitions
They can if you do what I said earlier - define a public class or interface
that the private class then derives from. The public code will only know
about what you make public, and the rest can be kept private and hidden.
Gambit
> Only the portion(s) that you want public. You hav to give the user
> SOMETHING to work with.
The point is not to satisfy the user, but the compiler.
> Please provide an actual code example of what you are trying to solve.
Here's an example
interface
TFactoryCallParams = record
//
end;
TFactoryImplementation = class
procedure SomeFunction(const params : TFactoryCallParams);
// ...
end;
TFactory = class
private
FFactoryCallParams : TFactoryCallParams;
FRealFactory : TFactoryImplementation;
protected
function CloneParams : TFactoryCallParams;
procedure DoSomeInternalAction(aFactory : TFactoryImplementation);
public
//exposed factory methods
end;
TAlternativeFactory = class
private
FRealFactory : TFactoryImplementation;
protected
procedure DoSomeInternalAction(aFactory : TFactoryImplementation);
public
//exposed factory methods
end;
implementation
var
_refcount : Integer;
_realFactory : TFactoryImplementation;
Constructing and destroying the facade classes TFactory or
TAlternativeFactory handles refcounting and lifetime management of the
hidden factory implementation that will be managed as a singleton.
Ideally you would want the TFactoryImplementation class declared in the
implementation section, but in this case the facade classes use an internal
reference through which calls are relayed. Their declarations therefore
require that the TFactoryImplementation class be declared in the interface
section as well, though there is no design intent, desire, or requirement to
expose it--it just has to be there because the classes that are to be
exposed use internal references to it.
Additionally, suppose that the factory implementation has a number of
parameters required for its main responsibility, and that it's the job of
the facade classes to initialize this as a record structure. The facade
classes maintain this as an internal record, making them individually
stateful although the singleton factory itself is stateless. Again, this
internal piece of business (the TFactoryCallParams record) has to be
declared in the interface section because it's required for the definition
of the TFactory class and has to be known to the TFactoryImplementation
class, so it cannot be declared as a nested class of the TFactory facade
class. So while the internal conversation between the factory facade and the
real factory implementation should be totally hidden, the record structure
passed in that conversation has to be declared in the interface section of
the unit.
But all this is just to satisfy your curiosity--the point is not WHY I need
such a facility, since as I pointed out to start with, CodeGear already
apparently determined that THEY needed such a facility and already include
its use in the rtl. They're declaring functions in the interface section of
a unit and yet NOT exposing them to external calls.
I'm just interested in whether that's something we can do as well, or a
non-extensible piece of hard-coded "compiler magic"
> Defining a class privately inside the 'implementation' section instead of
> the 'interface' section accomplishes the same thing.
But prevents the publicly exposed classes from having internal fields
defined as of that type.
bobD
> Here's an example
That is exactly the kind of scenerio that abstract classes and interfaces
(especially since you want reference counting) are good at handling, ie:
interface
type
TFactoryCallParams = record
//
end;
IFactory = interface(IInterface)
['{C8027A44-8AFD-4A41-89A3-C699B8DA0125}']
procedure SomeFunction(const params : TFactoryCallParams);
// ...
end;
TFactoryBase = class
protected
FRealFactory : IFactory;
procedure DoSomeInternalAction; virtual; abstract;
public
constructor Create;
destructor Destroy; override;
end;
TFactory = class
private
FFactoryCallParams : TFactoryCallParams;
protected
function CloneParams : TFactoryCallParams;
procedure DoSomeInternalAction; override;
public
//...
end;
TAlternativeFactory = class
protected
procedure DoSomeInternalAction; override;
public
//...
end;
implementation
type
TFactoryImplementation = class(TInterfacedObject, IFactory)
public
procedure SomeFunction(const params : TFactoryCallParams);
end;
var
_realFactory : IFactory = nil;
procedure TFactoryImplementation.SomeFunction(const params :
TFactoryCallParams);
begin
// do something...
end;
constructor TFactoryBase.Create;
begin
inherited;
FRealFactory := _realFactory;
end;
destructor TFactoryBase.Destroy;
begin
FRealFactory := nil;
inherited;
end;
procedure TFactory.DoSomeInternalAction;
begin
FRealFactory.SomeFunction(FFactoryCallParams);
end;
procedure TAlternateFactory.DoSomeInternalAction;
begin
//...
end;
initialization
_realFactory := TFactoryImplementation.Create;
finalization
_realFactory := nil;
end.
> Constructing and destroying the facade classes TFactory or
> TAlternativeFactory handles refcounting and lifetime management
> of the hidden factory implementation that will be managed as a
> singleton.
If every instance of the facade classes are going to access the same
singleton, then there is no need to declare the singleton's type in the
'interface' section at all. The singleton can be kept completely private
inside the 'implementation' section, ie:
interface
type
TFactoryCallParams = record
//
end;
TFactoryBase = class
protected
procedure DoSomeInternalAction; virtual; abstract;
public
constructor Create;
destructor Destroy; override;
end;
TFactory = class
private
FFactoryCallParams : TFactoryCallParams;
protected
function CloneParams : TFactoryCallParams;
procedure DoSomeInternalAction; override;
public
//...
end;
TAlternativeFactory = class
protected
procedure DoSomeInternalAction; override;
public
//...
end;
implementation
type
IFactory = interface(IInterface)
['{C8027A44-8AFD-4A41-89A3-C699B8DA0125}']
procedure SomeFunction(const params : TFactoryCallParams);
// ...
end;
TFactoryImplementation = class(TInterfacedObject, IFactory)
public
procedure SomeFunction(const params : TFactoryCallParams);
end;
var
_realFactory : IFactory = nil;
procedure TFactoryImplementation.SomeFunction(const params :
TFactoryCallParams);
begin
// do something...
end;
constructor TFactoryBase.Create;
begin
inherited;
_realFactory._AddRef;
end;
destructor TFactoryBase.Destroy;
begin
_realFactory._Release;
inherited;
end;
procedure TFactory.DoSomeInternalAction;
begin
_realFactory.SomeFunction(FFactoryCallParams);
end;
procedure TAlternateFactory.DoSomeInternalAction;
begin
//...
end;
initialization
_realFactory := TFactoryImplementation.Create;
finalization
_realFactory._Release;
end.
> Ideally you would want the TFactoryImplementation class
> declared in the implementation section, but in this case the
> facade classes use an internal reference through which calls
> are relayed.
The reference does not have to be declared as the actual hidden class type,
though. As shown above, the TFactoryImplementation class is not inside the
'interface' section at all. The user/compiler knows the facade classes have
a reference to something, but the implementation details are still able to
be kept private to the unit.
> Their declarations therefore require that the TFactoryImplementation
> class be declared in the interface section as well
No, they don't. See above.
> though there is no design intent, desire, or requirement to expose it--
> it just has to be there because the classes that are to be exposed use
> internal references to it.
You need to learn more about how to use polymorphism and data hiding. They
are designed for such scenerios.
> Additionally, suppose that the factory implementation has a number
> of parameters required for its main responsibility, and that it's the job
> of the facade classes to initialize this as a record structure. The facade
> classes maintain this as an internal record, making them individually
> stateful although the singleton factory itself is stateless.
That still does not require you to declare the TFactoryImplementation class
in the unit's 'interface' section.
> Again, this internal piece of business (the TFactoryCallParams record)
> has to be declared in the interface section because it's required for the
> definition of the TFactory class
Not really. If you go with the second approach I show above to hide the
singleton completely, then you can hide the TFactoryCallParams record in the
'implementation' section as well, ie:
interface
type
TFactoryBase = class
protected
procedure DoSomeInternalAction; virtual; abstract;
public
constructor Create;
destructor Destroy; override;
end;
TFactory = class
protected
procedure DoSomeInternalAction; override;
public
//...
end;
TAlternativeFactory = class
protected
procedure DoSomeInternalAction; override;
public
//...
end;
implementation
type
TFactoryCallParams = record
//
end;
IFactory = interface(IInterface)
['{C8027A44-8AFD-4A41-89A3-C699B8DA0125}']
procedure SomeFunction(const params : TFactoryCallParams);
// ...
end;
TFactoryImplementation = class(TInterfacedObject, IFactory)
public
procedure SomeFunction(const params : TFactoryCallParams);
end;
var
_realFactory : IFactory = nil;
procedure TFactoryImplementation.SomeFunction(const params :
TFactoryCallParams);
begin
// do something...
end;
constructor TFactoryBase.Create;
begin
inherited;
_realFactory._AddRef;
end;
destructor TFactoryBase.Destroy;
begin
_realFactory._Release;
inherited;
end;
procedure TFactory.DoSomeInternalAction;
var
Params : TFactoryCallParams;
begin
// fill Params as needed...
_realFactory.SomeFunction(Params);
end;
procedure TAlternateFactory.DoSomeInternalAction;
var
Params : TFactoryCallParams;
begin
// fill Params as needed...
_realFactory.SomeFunction(Params);
end;
initialization
_realFactory := TFactoryImplementation.Create;
finalization
_realFactory._Release;
end.
> and has to be known to the TFactoryImplementation class
As long as they are in the same unit, it does not matter whether the actual
declarations are in the 'interface' or 'implementation' sections.
> so it cannot be declared as a nested class of the TFactory facade class.
> So while the internal conversation between the factory facade and the real
> factory implementation should be totally hidden, the record structure
> passed in that conversation has to be declared in the interface section of
> the unit.
See above.
Gambit