Jump to content
Willicious

Problems with Delphi class structure / visibility

Recommended Posts

EDIT: Apologies for the rant-like nature of this post, but... I'm getting frustrated with Delphi programming. Rather than toss my laptop out the window, I'd rather express my frustration here on the Forums and hopefully it comes to something a bit more productive than a broken laptop and a feeling of regret.

 

-----

 

Why can I sometimes access properties, functions, variables etc from other units, and sometimes not?

 

"Private" and "public" doesn't seem to make any difference, either. Sometimes I'll start to type the name of a class, followed by "." and a list of all the available items comes up. I assure you, sometimes even when I make something "public", it still won't appear in that list. Why???

Also, the practice of creating "instances" of classes within procedures is ridiclously cumbersome and unintuitive. If I've created a property/variable/field/whatever in Class1.pas, it should be accessible from Class2.pas without me having to create a "version" of Class1 first. If it's declared in uses, that should be that!

 

Forgive me for the rant, but aren't we as programmers supposed to be in charge of the program, not the other way around?!

Edited by Willicious

Share this post


Link to post

private and protected are "friends" for classes within the same unit. If you don't want that, use "strict private" and "strict protected".

 

For your second problem, you should provide an example. It is not clear to me what doesn't work.

 

And by the way: When you write questions like this in a forum where most of the answers come from volunteers, you probably end up on the ignore list of many of them.

 

Regards
Christian

  • Like 1

Share this post


Link to post
9 minutes ago, Willicious said:

we as programmers supposed to be in charge of the program,

for sure, BUT when you dont "declare" the objects/var/procedure/etc.. you DONT BE! right? then, for this, you NEED declare all that you need use!

  • Like 1

Share this post


Link to post

In fairness, I said it was a rant, not a question. I think Delphi is a very poorly structured programming language which very quickly renders programs impenetrable to anyone other than the original developer, who may even end up struggling with it if they've forgotten what is supposed to be accessible from where.

 

Why not just have everything dynamically accessible from everywhere else? Why even the need for "private" and "public" (which, as I said in the OP, doesn't always make a difference). Yes, other languages have this going on as well but nowhere else have I struggled with it as much as with Delphi/Pascal. I'm constantly running into problems that largely involve wrestling with the structure of units & classes.

Here's an example:

I have a unit called "Replay.pas" which deals with saving, loading and interpreting data from text-based game replay files. Then, another unit called "ControlPanel.pas" which deals with the in-game controls and how they relate to what's happening in the game. I made a public property in the TReplay class which I need to call from ControlPanel. Even though Replay is declared in uses within ControlPanel, I can not call the property. It's brought weeks of progress to a grinding halt, and it's frustrating, hence the rant.

Edited by Willicious

Share this post


Link to post
9 minutes ago, Willicious said:

I said it was a rant, not a question. I think Delphi is a very poorly structured programming language which very quickly renders programs impenetrable to anyone other than the original developer, who may even end up struggling with it if they've forgotten what is supposed to be accessible from where.

It's possible to write poor code in any language. 

  • Like 3

Share this post


Link to post

@Willicious

 

I think your dissatisfaction is leading you to discuss a rhetoric that has existed as long as programming has existed!
As for your dissatisfaction with your class, I think it only proves that it is not consistent and has been wrongly constructed, and possibly being misunderstood by you!

 

Better rethink your code!

 

As for using everything anywhere, then give up on object-oriented programming languages, or close to it. Better go back to "Basic for MSX"... lots of bits and bytes, no predefined structure, go forward or backward as you wish! And at the end, save your code on cassette tapes!

 

If you want, try to post your code here or in some repository that, surely, someone will be able to give you a hand!

  • Like 1

Share this post


Link to post

OK, OK, I get it. I'm the problem, not the programming language. All praise the wonderful and mighty programming language that is Delphi (or, indeed, any other)!

 

But seriously, I can spend hours on this thing and achieve so little because of problems with the code structure. I appreciate that people on here are there to help, that's a good thing. ChatGPT is also a decent tool, but it's by no means perfect.

 

Regarding examples/context... it's difficult to provide when the context happens to be thousands and thousands of lines of code sprawling dozens of .pas unit files, none of which I created myself.

Edited by Willicious

Share this post


Link to post

OK, I managed to get it working. I needed to declare an instance of the class in the new unit, not just declare the unit itself. For future reference, and for anyone else struggling witn this:

The new instance needs to be declared at the top of the page in the current class (i.e. the one in which you want to use the instance). Like this:
 

unit TheCurrentUnit

type

class TTheCurrentClass

private/public (doesn't matter whether it's private or public, but public means you should in theory be able to access it elsewhere)

	fDeclareHereAndCallItWhateverYouWant: TTheNameOfTheOriginClass;
	property DeclareHereAndCallItWhateverYouWant: TReplay read fDeclareHereAndCallItWhateverYouWant;

procedure TTheCurrentClass.Create;
begin
	fDeclareHereAndCallItWhateverYouWant = TTheNameOfTheOriginClass.Create;
end;

procedure TTheCurrentClass.Destroy;
begin
	fDeclareHereAndCallItWhateverYouWant.Free;
end;

By following the above, an instance of the class has been created in the unit which can then, in theory, be called to make use of the properties from the other unit. I'm not entirely sure why it's sometimes necessary to do this; in other examples I've seen, it's enough just to declare the unit under "implementation / uses". But, if that fails, try the example here.

Edited by Willicious

Share this post


Link to post
1 hour ago, Willicious said:

I said it was a rant, not a question. I think Delphi is a very poorly structured programming language which very quickly renders programs impenetrable to anyone other than the original developer, who may even end up struggling with it if they've forgotten what is supposed to be accessible from where.

Delph is a strongly-typed language with well-defined and understood private, protected, and public clauses of classes. Other than getting used to the "friend class" behavior of classes defined in the same unit, I can't imagine what is so mysterious about it. My guess is that you are confused due to the "friend class" behavior you have seen in some work and didn't realize what was happening. 

 

You can certainly come across code that is "impenetrable to anyone other than the original developer," but that has little to do with Delphi, at least not in the areas you have expressed frustration over in this thread.

  • Like 1

Share this post


Link to post
5 minutes ago, Brandon Staggs said:

Other than getting used to the "friend class" behavior of classes defined in the same unit, I can't imagine what is so mysterious about it

It's the idea of instances-of-classes that keeps catching me out. If a class is defined somewhere, it should be callable as that class. Why do I have to redefine/redeclare it in a different unit? Why not this instead:

Unit 1
    Class1

          PropertyA

 

Unit 2

    uses

         Class1

 

if Class1.PropertyA etc...

Anyway, never mind. I've already accepted it's a "me" problem. I seemed doomed to forever see ways in which things could be better rather than actually getting to know and use them as they are.

Edited by Willicious

Share this post


Link to post
2 minutes ago, Willicious said:

It's the idea of instances-of-classes that keeps catching me out. If a class is defined somewhere, it should be callable as that class. Why do I have to redefine/redeclare/re-whatever it in a different unit? Why not this instead:

Unit 1
    Class1

          PropertyA

 

Unit 2

    uses

         Class1

 

if Class1.PropertyA etc...

The uses clause is not for classes, it's for units. The only "visibility" issue with regard to CLASSES defined in UNITS is what you put in INTERFACE vs IMPLEMENTATION. But since you are refusing to post ACTUAL CODE (nothing you have posted would compile, so it can't be ACTUAL CODE), there is little anyone here can do to help you understand.

Edited by Brandon Staggs
  • Like 4

Share this post


Link to post
32 minutes ago, Willicious said:

Anyway, never mind. I've already accepted it's a "me" problem. I seemed doomed to forever see ways in which things could be better rather than actually getting to know and use them as they are.

To be fair, I think it's only when one has a comprehensive understanding can one take an informed critical view. I think there are plenty of design flaws with the language, but I don't think that they are holding you back yet. 

Share this post


Link to post
1 hour ago, Willicious said:

It's the idea of instances-of-classes that keeps catching me out. If a class is defined somewhere, it should be callable as that class. Why do I have to redefine/redeclare it in a different unit? Why not this instead:

the key-words is: "instances-of-classes"

  • when you define the structure of a class, in fact, you are just defining a "layout" of future objects that will be created in memory.

  •  

    To take advantage of this structure, you need to create the object in memory. Or simply "instantiate the object"!

    For this reason, it is not possible to perform any actions on the object without first creating it!

  • Like 1

Share this post


Link to post
2 hours ago, Brandon Staggs said:

since you are refusing to post ACTUAL CODE (nothing you have posted would compile, so it can't be ACTUAL CODE), there is little anyone here can do to help you understand

Yes, but surely we can use pseudo-code or non-compilable examples to illustrate the concepts?

The only reason I'm reluctant to post actual code is because I would literally have to post multiple units-worth of code in order for anybody to be able to help. So much of what's going on is very program-specific, multi-faceted and convoluted. I've had help elsewhere on this Forum which would have worked in a newly-created codebase, for example, but which I can't do much with when I have to crowbar it in to what I'm currently dealing with.

 

The program is a clone of the "Lemmings" game from 1991, but with modern QOL features (such as replay functionality, skill projections, etc). I'm more than happy to share the repo if anybody is interested in helping me out with it - it's an open-source project anyway. I could really do an with experienced pair of eyes and someone to help turn ideas into reality. I've already done a lot by myself, but I do continually come up against the types of problems that a seasoned Delphi expert would be able to recognise and troubleshoot quite quickly.

 

I've even considered paying for lessons tbh, such as the difficulty of this project sometimes gets!

Share this post


Link to post

Basically, everything I try (and fail) to do comes down to undeclared identifiers, not being able to call procedures/functions/properties/variables from other classes. It's always the thing that holds me back. All the ideas are there, and should work.

Here's an example:

function TReplay.GetRRChangeOnCurrentFrame: Boolean;
var
  i: Integer;
begin
  Result := False;

  for i := 0 to fSpawnIntervalChanges.Count -1 do
  begin
    if TReplayChangeSpawnInterval(fSpawnIntervalChanges[i]).Frame = LemGame.CurrentIteration then
    begin
      Result := True;
      OutputDebugString(PChar('SpawnIntervalChangeDetected'));
      Exit;
    end;
  end;
end;

In this code, the call for LemGame.CurrentIteration generates an undeclared identifier error. This is in spite of LemGame being one of the uses in this unit, and CurrentIteration being public and callable from other units (elsewhere, it's called as Game.CurrentIteration, but that doesn't work here either).

 

All I want to do is return this value to True if the Spawn Interval has changed on the current frame. All the code is there, the ideas work in theory, but for some reason I just..... can't use this bit of the code in this particular place!

Edited by Willicious

Share this post


Link to post
16 minutes ago, Willicious said:

LemGame.

where is  this object (class/interface/record) created ? in the same unit or where?

 

16 minutes ago, Willicious said:

fSpawnIntervalChanges[i]

this returns really a "TReplayChangeSpawnInterval", or what other type?

Edited by programmerdelphi2k

Share this post


Link to post
54 minutes ago, programmerdelphi2k said:

where is  this object (class/interface/record) created ? in the same unit or where?

LemGame is a different unit, it contains a (variable? property? field?) called CurrentIteration which, essentially, represents the current in-game frame.

Share this post


Link to post

For what it's worth, I've managed to find a solution to the particular problem that's been bothering me today. The answer was in the existing code, I just needed to find the correct function, and the correct way to call it in another unit.

 

However, any advice on calling properties/functions/procedures/fields/etc from another class/unit would be invaluable. I'm willing to learn!

 

Thanks for your patience, everyone. Apologies again for the outpouring of frustration.

Edited by Willicious

Share this post


Link to post
7 hours ago, programmerdelphi2k said:

@Willicious

 

As for using everything anywhere, then give up on object-oriented programming languages, or close to it. Better go back to "Basic for MSX"... lots of bits and bytes, no predefined structure, go forward or backward as you wish! And at the end, save your code on cassette tapes!

 

 

Python is more object-oriented than Delphi (EVERYTHING is an object - types, functions, classes, even numbers) and there is no enforced "private" setting. Privacy is only by naming convention as a guideline. And you don't even need to save its code to a cassette tape! 🙂 There's basically two schools of thought in programming language design - that you have to hide and control everything to "protect" users or developers or the system, and the other school is about giving power to the developers to do whatever they wish and put responsibility for doing it properly and safely on the developer, not the language/compiler. I believe Guido Van Rossum, the creator of Python, dubbed the latter design view the "we're all adults here" philosophy. Which view has more merit is up to designers and developers to decide for themselves, but it's clearly possible to design object-oriented features into languages using either approach.

 

And on a marginally-related note, the Internet Archive had a great write-up recently about programs stored on cassette tape... 🙂

 

https://blog.archive.org/2023/05/02/the-easy-roll-and-slow-burn-of-cassette-based-software/

 

 

 

  • Like 1

Share this post


Link to post
9 hours ago, Willicious said:

t's the idea of instances-of-classes that keeps catching me out. If a class is defined somewhere, it should be callable as that class. Why do I have to redefine/redeclare it in a different unit? Why not this instead:

Unit 1
    Class1

          PropertyA

 

Unit 2

    uses

         Class1

 

if Class1.PropertyA etc...

What kind of programming languages have you actually used before? Because you would have same issues in most languages... Event in javascript or python you need an instance to actually use it.

  • Like 1

Share this post


Link to post
5 hours ago, Willicious said:

LemGame is a different unit, it contains a (variable? property? field?) called CurrentIteration which, essentially, represents the current in-game frame.

Without seeing where LemGame is declared and how it is impossible to say why you have an error. It is also important to note that sometimes you may get error in the IDE (read squiggly lines) in some perfectly valid code. You should always try to compile code to see real errors. also, when you do get compiler error, you should fix them in order they appear as the first error can cause subsequent issues for the compiler and it is possible that compiler will show more errors in otherwise valid code.

 

But, it seems like your main problem is not with Delphi but with OOP principles in general as you are trying to access properties in a class without creating instance of a class. Class is just a template from which object instances are created. Before you create object instance data (class fields and properties) don't exist in memory, so there is nothing you can access there. The exception to that are class fields, properties and methods that are additionally marked with class descriptor. Those can be accessed directly. They are basically global variables that are organized under class namespace. See https://docwiki.embarcadero.com/RADStudio/Sydney/en/Properties_(Delphi)#Class_Properties

 

Purpose behind creating object instances from class before using them is that most of the time you will need more than one. For instance, you can have one global object instance of a game, but you will need more instances of game characters or other game objects. 

 

Think of it, class gives you description about attributes and functionality of a person, and when you construct object instances of that class you will get separate persons: Joe, Mike, Alice... 

 

I would advise you to read a bit about Delphi language and some basic concepts. good place to start is https://learndelphi.org/ and Delphi Language Guide https://docwiki.embarcadero.com/RADStudio/Sydney/en/Delphi_Language_Reference 

Edited by Dalija Prasnikar
  • Like 1

Share this post


Link to post
9 hours ago, Willicious said:

It's the idea of instances-of-classes that keeps catching me out. If a class is defined somewhere, it should be callable as that class.

 

Why? Do you also expect to move all your furniture into the blueprint of a house? No, you would use the house's blueprint to build one or multiple instances of an actual house, then you would move into one of them. The class declaration and all its related code is the blueprint and the instance is the actual house. You design a class and then when you need to do something with that class you would create an instance of it and use it. Remember to at least free/destroy that instance. You are the only one living in it and nobody else will buy your vacant spot and vacant spots lead to bad neighbourhoods (memory leaks).

 

9 hours ago, Willicious said:

Unit 1
    Class1

          PropertyA

 

Unit 2

    uses

         Class1

 

if Class1.PropertyA etc...

 

There are ways to do the above but I don't want to send you down a dark hole. Get to know Delphi and basic Objects/Classes a bit better first and learn to manage the objects properly first.

 

About the game and its development;

  • Whether you were recruited to do development on this project or whether you got involved voluntarily it will be the same... it seems you don't understand Delphi principles yet and your approach to the problem and how you reacted here was not top-notch. I follow the philosophy that it is always my fault if something does not work out the way I wanted in the code I am involved with unless it is proven otherwise. It helps me to feel responsible for it and forces me to investigate properly and whats even cooler is that I just usually fix the issue even if someone else is to blame for it. It saves time and I just learned a whole bucket load and it sharpens the most critical development skill anyone can posess: Debugging.
  • Learn to appreciate source code. If you don't know how something works then go and read the source code. You will get the understanding for it and you will learn again.
  • I suspect most guys here might have had some idea in their heads that made them cringe on working on a project along with another coder that attacked the tools first instead of trying to understand first. If you want people to join the project or lend a hand then the environment for the collaboration should be inviting and free of nonsense.

 

 

Share this post


Link to post
8 hours ago, Willicious said:

if TReplayChangeSpawnInterval(fSpawnIntervalChanges ).Frame = LemGame.CurrentIteration then

I was very struck by this line. 
Why do you have retyping there?
What happens if fSpawnIntervalChanges) is not TReplayChangeSpawnInterval?
Also, I don't like the comparison character = there
These are very gross, severe errors in the code!!!

And do see my signature!

Edited by Stano

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×