Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations wOOdy-Soft on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Passing function value from one file to another in same project

Status
Not open for further replies.

jtheis

Technical User
Sep 9, 2004
41
US
I am stuck on something that I'm pretty sure is a basic concept...

I'm using Delphi 7 and I want to pass the resulting value of a function from one Delphi file in a project to another file in the same project. In the code below (OPCDEFS.PAS) I call the function OPCMaxTrend, to establish the maximum limit that the OPC server should look for.

Code:
unit OpcDefs;

interface

const
  Opc_Device1       = 1002;
  Opc_Device2       = 1005;
  Opc_Device3       = 1006;
  Opc_Device4       = 1010;

  {Attempt to make each device configurable for trend tag limits}
 
  OPC_Device1_MaxTrend       = 99;
  OPC_Device2_MaxTrend       = 79;
  OPC_Device3_MaxTrend       = 39;
  OPC_Device4_MaxTrend       = 19;


{.PA}
(**)
(**)
implementation
(**)
(**)
uses
  SysUtils;

{Attempt to make a variable that is for the maximum trend address}

function OPCMaxTrend(const Device : integer) : integer;
begin
   Result := 0;
   case Device of
      Opc_Device1       : Result := OPC_Device1_MaxTrend;
      Opc_Device2       : Result := OPC_Device2_MaxTrend;
      OPC_Device3       : Result := OPC_Device3_MaxTrend;
      Opc_Device4       : Result := OPC_Device4_MaxTrend;
   else
     if (Device <> 0) then Result := 99;
   end;
end;

end.

In the piece of code below from another file (DEVICE_OPC.PAS) I want to use the value that the function OPCMaxTrend returns where now there is a constant (99).

Code:
 procedure TDeviceOpcTnd.AddTrendTags;
var
  Reg : integer;
begin
  if not Assigned(OpcGroup) then Exit;

  for Reg := 0 to 99 do {<--- Need to make the 99 a variable that changes based on OPCMaxTrend value}
    try
      OpcGroup.OPCItems.AddItem(OpcTopicStr + ':' + IntToStr(Reg)); //Device Type
    except

    end;
end;

If I change the 99 to OPCMaxTrend it doesn't work and gives me an error (Undeclared identifier: 'OPCMaxTrend') when it compiles. There's a good chance I'm either trying to do something that can't be done or I'm missing a very basic concept. I'm very new to programming of any sort and would appreciate any help. If this is the wrong place to be posting a message like this, please just let me know and if you do know where I can ask basic questions like this, I'd appreciate you letting me know.
 
There are a couple of ways to do that. Perhaps the simplest is to make it a global function.

Put a prototype function definition in the interface section of unit OpcDefs:
Code:
unit OpcDefs
interface
    :
    :
  function OPCMaxTrend(const Device : integer) : integer;
    :
    :
implementation
Then add OpcDefs to the uses clause of unit DEVICE_OPC

then replace the "99" as follows:
Code:
for Reg := 0 to OPCMaxTrend( nMyDevice ) do
where nMyDevice is a local variable that contains the device number. (Your sample code does not indicate where that comes from, so I can't say much more.)
 
Thank you so much for the help! I think I'm almost there now. Here's how I've got DEVICE_OPC.PAS rewritten.

Code:
procedure TDeviceOpcTnd.AddTrendTags;
var
  Reg : integer;
  n : integer;  {<--- Added}
begin
  n := GetRegValue(0); {<--- Added}
  if not Assigned(OpcGroup) then Exit;
{TODO : change from fixed to variable maximum trend tag}
  for Reg := 0 to OPCMaxTrend(n) do {<--- Added}
    try
      OpcGroup.OPCItems.AddItem(OpcTopicStr + ':' + IntToStr(Reg)); //Device Type
    except

    end;
end;

GetRegValue is a function that collects from the OPC. Here's that function, also in DEVICE_OPC.PAS.

Code:
function TDeviceOpcTnd.GetRegValue(const Reg : integer) : OleVariant;
var
  OI : TDOPCItem;
begin
  Result := -1;

  if Assigned(OpcGroup) then begin
    OI := OpcGroup.OPCItems.FindOPCItem(OpcTopicStr + ':' + IntToStr(Reg)); //Device Type

    if not Assigned(OI) then begin
      Result := -2;
      Exit;
    end;

    if not OI.IsGoodQuality then begin
      Result := -3;
      isOpcDataError := True;
    end;

    if OI.IsGoodQuality then begin
      Result := OI.Value;
    end;

  end;
end;

So what I am ultimately trying to do is get the value from register N20:0 (the 20 is the OpcTopicStr) in the PLC, because that is where I set the 1002, 1005, 1006 and 1010 values that I am looking for in OPCMaxTrend. I'm not getting the value though.

What I'm looking for is two things now:

1. Obviously, what am I doing wrong?
2. Is there a way that I can look at the source while the program is running and see what the value is that is coming back to GetRegValue(0)?

Thanks again for all the help!
 
This line of code:
Code:
OI := OpcGroup.OPCItems.FindOPCItem(OpcTopicStr ...);
implies that there is are instance of TDOPCItem objects that have been pre-created such that the method can return a pointer to the specific one wanted according to the parameter. Perhaps these exist in an object list somewhere. Without seeing more code, it's difficult for me to comment further.

You can easily set a break point by clicking on any dot in the left-hand margin of the code window. You can then use the mouse to hover over any variable name you wish to see expressed, or you can bring up a window with Ctrl-G to evaluate any expression you wish.
 
Zathras,

Thanks (as always) for the help. Here's what I've found based on your last message...

I type n := OpcGroup.OPCItems. to get the list to come up. One of the items in the list is called Items. It is a property and the format is

property Items : [Index: Integer] : TdOPCItem;

So if I make the line of code
Code:
n := OpcOPCItems.Items[0];
I am thinking that this will call the value (N#:0) I'm looking for from the PLC. Now, my problem (one of many!) is that I'm now trying to put a TdOPCItem value where the code is looking for an integer value. Is there something I'm missing here? Am I going about this all wrong? It's nice inheriting this code that I'm working with since the program already mostly works, but it's difficult not being the one who has worked with it since day one.

As for seeing the value while it is running, I'm either trying to do something that can't be done or doing something wrong. When the program is running, I highlight the n in the above code and put the mouse over it and it doesn't show me anything. I was hoping to see what value was getting put in to n.

Thanks for your patience and help!


 
Since you say
property Items : [Index: Integer] : TdOPCItem;
then
Code:
n := OpcOPCItems.Items[0];
suggests to me that n should be defined as TdOPCItem

You can't view variables with a mouse hover while the program is running, you need to set a break point. (Click on a dot in the left-hand margin.)
 
Whoops, I've got a typo in the last message. The code should have been

Code:
n := OpcGroup.OPCItems.Items[0];

I've tried to set the variable n as a TdOPCItem, but when I try to compile and run it I get an error because I'm trying to put a TdOPCItem value where an integer is expected.

Code:
procedure TDeviceOpcTnd.AddTrendTags;
var
  Reg : integer;
  n : TdOPCItem;  {<--- Changed}
begin
  n := OpcGroup.OPCItems.Items[0]; {<--- Changed}
  if not Assigned(OpcGroup) then Exit;
{TODO : change from fixed to variable maximum trend tag}
  for Reg := 0 to OPCMaxTrend(n) do
    try
      OpcGroup.OPCItems.AddItem(OpcTopicStr + ':' + IntToStr(Reg)); //Device Type
    except

    end;
end;

Sorry to beat this one to death and not have all the information, but this code is so big that I'm buried in it.

Thanks again!


 
I'm having a hard time understanding what you're doing, but let's try to work together...

(1) The two lines of code:
Code:
  n := OpcGroup.OPCItems.Items[0]; {<--- Changed}
  if not Assigned(OpcGroup) then Exit;
are in the wrong order. If OpcGroup is not assigned, then an error will already have occured.

{2) if this code is going to work:
Code:
OpcGroup.OPCItems.AddItem(OpcTopicStr + ':' + IntToStr(Reg));
then the items that are in OpcGroup.OPCItems are strings, not TdOPCItem (unless TdOPCItem is really just a string that has been redefined for "easier" use.)

Perhaps things might be clearer if you posted the definitions of the TOpcGroup and TdOPCItem objects.

It would also be helpful to know whether this is "home grown" code or supplied by a third-party software house.
 
Don't worry, you're not the only one having a hard time understanding what I'm doing! Some quick history that might be beneficial...

This code was written by a guy who used to work at the same company I work for. He was essentially the only one who ever worked on the program. Now it has fallen to me, a mechanical engineer, to move over to the electrical department and become a programmer. So this code is 'home grown' but the grower no longer works here and I haven't been able to get much support from him. That's how I eneded up posting here. What I'm ultimately trying to do is set the range of addresses I want read back from the PLC. Since some of our PLCs have limited memory, we have to shift the data and shrink or expand the ranges. Back to the code...

1. I've swapped the code around so that it is now in the right order.
2. I think you're right, that TdOPCItem is just a string redefined, as that line of code does work.
3. I'm not 100% sure what you mean when you ask about the definitions of the objects. I hope this is what you're looking for:

TOpcGroup -
type dOPC.TdOPCGroup : class(TCollectionItem) - dOPC.pas

TdOPCItem -
type dOPC.TdOPCItem : class(TdOPCItemData) - dOPC.pas
TdOPCItemData -
type dOPCIntf.TdOPCItemData : class(TCollectionItem) - dOPCIntf.pas

TCollectionItem -
type Classes.TCollectionItem : class(TPersistent) - classes.pas

Now, the dOPC.pas and dOPCIntf.pas are NOT home grown; that is something that is third party.

Thanks again for being so patient with me through all of this. I am obviously a total newbie to this stuff and all of the help is greatly appreciated!
 
Not sure where we are now. I didn't see any question in your last post.

I see references to a couple of TCollectionItem objects, but no TCollection. Look around. There should be someplace where a TCollection (or TOPCGroup) is created. The procedure TDeviceOpcTnd.AddTrendTags; appears to be where items are added to the collection and the function TDeviceOpcTnd.GetRegValue... appears to be where the collection is referenced. But it's not clear to me what the benefit is.

The object definition consists of more than just the one line of code. Can you post the complete type definition for

[tt] type dOPCIntf.TdOPCItemData : class(TCollectionItem) [/tt]

from dOPCIntf.pas? That would include all of the sections private/published/public down to the end; statement. (Not all sections are required, just take what's there.)

 
You're right, the procedure TDeviceOpcTnd.AddTrendTags; is where the items are added to the collection and function TDeviceOpcTnd.GetRegValue is what we use to pick out the values from the collection. The benefit is that the data in the collection contains setpoints, actual values, outputs, etc. that we want to trend. However, we also have in the collection, in the first value, the number that identifies the PLC as belonging to Device1, Device2, etc. as referenced in my first post. Depending on whether it is Device1 or Device2 affects how big the collection should be.

Here's the whole definition of TdOPCItemData

Code:
 // class to store informations about an OPC item
  TdOPCItemData = class(TCollectionItem)
  protected
    FIsActive: boolean;
    FEUType: integer;
    FCanonicalDatatype: integer;
    FTimeStamp: TDateTime;
    FServerHandle: integer;
    FRequestDatatype: dOPCADatatype;
    FRequesttype: integer;
    FItemID: string;
    FAccessPath: string;
    FAccessRights: integer;
    FQuality: integer;
    FLastError : HResult;
    FData      : Pointer;
    FWantValue : OleVariant;
    FGUIItems  : TList;
    FChanged   : boolean;
    FTag       : integer;
  public
    FValue: OleVariant;
    FEUInfo: OleVariant;
    property ServerHandle     : integer read FServerHandle write FServerHandle;
    property AccessRights     : integer read FAccessRights write FAccessRights;
    property LastError        : HResult read FLastError write FLastError;
    property Data             : Pointer read FData write FData;
    property WantValue        : OleVariant read FWantValue write FWantValue;
    property ItemID           : string read FItemID;
    property IsActive         : boolean read FIsActive write FIsActive;
    property Value            : OleVariant read FValue;
    property Quality          : integer read FQuality write FQuality;
    property TimeStamp        : TDatetime read FTimeStamp write FTimeStamp;
    property CanonicalDatatype: integer read FCanonicalDatatype write FCanonicalDatatype;
    property EUType           : integer read FEUType write FEUType;
    property EUInfo           : OleVariant read FEUInfo write FEUInfo;
    property AccessPath       : string read FAccessPath write FAccessPath;
    property RequestType      : integer read FRequestType write FRequestType;
    property ItemChanged      : boolean read FChanged write FChanged;
    property Tag              : integer read FTag write FTag;
  end;

My question goes back to this code

Code:
n := OpcGroup.OPCItems.Items[0];

I believe this should be returning the value I'm looking for that defines Device1, Device2, etc. However, it is not an integer and therefore I cannot use it with OPCMaxTrend. So how do I manipulate what comes in to the n value and turn it in to the integer I need? Or is there some other way I should be doing this?

Thanks again for the help. I'll be sure to give you credit in the code!
 

I've not used TCollection that way. I generally use TObjectList or TStringList for what I think it is the program is doing. TObjectList is a relatively new object (D5, I think) so if the code is older than that, that would explain why TCollection is being used.

I don't know if it would be possible to re-write using TObjectList (and/or TStringList), but that might be best for the long run. Meanwhile, perhaps you should start a new post requesting specific help using the TCollection object.

Alternatively, if you post the complete definition for the TCollection object I may be able to carry on. I'm quite confused at this point about the two flavors of collections and collection objects: e.g. TdOPCItem vs. TOPCItem
 
I'll start a new thread as you suggest and see what happens. The code does date back quite a while, so it may have originally been done pre-D5.

You mention rewriting the code using TObjectList or TStringList as being beneficial in the long run. Since it looks like I'm the one who's responsible for the long run, can you shed a little light on what you're thinking about using either of those objects?

In addition, here's the code for the TCollection object.
Code:
TCollection = class(TPersistent)
  private
    FItemClass: TCollectionItemClass;
    FItems: TList;
    FUpdateCount: Integer;
    FNextID: Integer;
    FPropName: string;
    function GetCount: Integer;
    function GetPropName: string;
    procedure InsertItem(Item: TCollectionItem);
    procedure RemoveItem(Item: TCollectionItem);
  protected
    procedure Added(var Item: TCollectionItem); virtual; deprecated;
    procedure Deleting(Item: TCollectionItem); virtual; deprecated;
    property NextID: Integer read FNextID;
    procedure Notify(Item: TCollectionItem; Action: TCollectionNotification); virtual;
    { Design-time editor support }
    function GetAttrCount: Integer; dynamic;
    function GetAttr(Index: Integer): string; dynamic;
    function GetItemAttr(Index, ItemIndex: Integer): string; dynamic;
    procedure Changed;
    function GetItem(Index: Integer): TCollectionItem;
    procedure SetItem(Index: Integer; Value: TCollectionItem);
    procedure SetItemName(Item: TCollectionItem); virtual;
    procedure Update(Item: TCollectionItem); virtual;
    property PropName: string read GetPropName write FPropName;
    property UpdateCount: Integer read FUpdateCount;
  public
    constructor Create(ItemClass: TCollectionItemClass);
    destructor Destroy; override;
    function Owner: TPersistent;
    function Add: TCollectionItem;
    procedure Assign(Source: TPersistent); override;
    procedure BeginUpdate; virtual;
    procedure Clear;
    procedure Delete(Index: Integer);
    procedure EndUpdate; virtual;
    function FindItemID(ID: Integer): TCollectionItem;
    function GetNamePath: string; override;
    function Insert(Index: Integer): TCollectionItem;
    property Count: Integer read GetCount;
    property ItemClass: TCollectionItemClass read FItemClass;
    property Items[Index: Integer]: TCollectionItem read GetItem write SetItem;
  end;

Thanks again for all the help.

 
Hmmmm. Copying the definition from classes.pas doesn't really help me. (I could have done that myself.) What I was looking for was the definition of the derived classes the application is actually defining and using.

Take a look at thread102-712482. You may be able to get some ideas from there. (Note that TList was referenced -- probably because this pre-dated TObjectList.)

 
I finally was able to get in touch with the original author and he showed me the quick way to do this. I think I was maybe asking the wrong questions to begin with...

What I had to do was make procedure AddTrendTags a virtual procedure. I then just called it in the specific device code and changed it there as needed. I hope this explanation makes sense.

Thanks again for all the help!
 
Can I add a comment.
I work in an engineering electronics enviroment and come across PLC's, it seems that the original writter of this code was trying to simulate the way that PLC programmers try to think about the data structure in these devices.
This could even be a (I am going to use this letter again, sorry)'C' port over.
And it is totaly alien to the way that applictions programmers would do things in most cases.
What we are actually talking about is data in a sequential set(an array) of 32bit words, and whenever I/we write apps to handle this we would never try to simulate the PLC structures verbatum. But its too late to change what you have now, good luck with this jtheis you have taken on quite a task for a newbie to programming.




Steve
Be excellent to each other and Party on!
 
sggaunt,

You're absolutely right about the code being written to simulate the way PLC programmers think. That's the whole reason it was written - to work with what the PLC guys had already done. I appreciate all the help I've received here so far and will definitely be leaning on the forums in the future!


Joe
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top