After the knowledge I obtained from my previous question about (Classes tutorial), I started recreating new classes from my own templates which I already created before to cover the functions side.
Today I faced a problem that one of the procedures I defined in the class uses a function created in one of my templates, although the template is added to the application and the function is working fine in the application, after including the class I am getting an error “Unknown function label” although I gave a unique name for the procedure in the class.(the new function in the class is a new one and not found in the templates)
I suspect your class member is generic. IOW, it’s MEMBER statement at the top doesn’t name your program (and it shouldn’t).
In this situation, I suggest you create an empty virtual method in your class. Then generate a global derived class in your application, which also derives that virtual method. Within that virtual method’s code you call the generated procedure that you’re trying to execute.
In the end, the base class will call the empty virtual method, but the derived method will be executed instead, and it calls the generated procedure in your app.
Of course, if you need to instantiate that class, then you’ll have to make sure you instantiate the derived class, not the original bass class, which may be challenging, but may work just fine.
Alternatively, you can define the procedure you need to call as a TYPE, then pass in a reference to it when you instantiate the bass class. Give me a few minutes and I’ll throw together an example program to demonstrate this.
Here’s the INC file for your base class. Note the MAP statement in the class header, where you define what your external procedure is going to look like, and the reference to that type passed into the Init method.
MAP
BaseClass_OutsideProc PROCEDURE(STRING S),LONG,TYPE !Notice TYPE attribute, so this is just a description
END
BaseClass CLASS,TYPE,MODULE('BaseClass.clw'),LINK('BaseClass.clw',_ABCLinkMode_),DLL(_ABCDllMode_)
Init PROCEDURE(BaseClass_OutsideProc OutsideProcToBeCalled) !Class is told what to call
Test PROCEDURE !Class will use the procedure
END
Here’s the base class CLW. Note the reference to the external procedure will be stored in module data, as the compiler will not let you store it as class data. That’s not a problem, as you can still use a class method to initialize that module data. One the procedure reference is initialized, it can be called normally.
MEMBER
INCLUDE('BaseClass.inc'),ONCE
MAP
END
OutsideProc &BaseClass_OutsideProc !Reference to the external procedure
BaseClass.Init PROCEDURE(BaseClass_OutsideProc OutsideProcToBeCalled)
CODE
OutsideProc &= OutsideProcToBeCalled !Set the procedure reference in module memory
BaseClass.Test PROCEDURE
CODE
IF OutsideProc &= NULL
ASSERT(FALSE, 'BaseClass_OutsideProc is not initialized')
HALT(1)
END
MESSAGE('OutsideProc returned '& OutsideProc('Hello World!')) !Call the procedure
Finally, you can use it in your program. Instantiate the object normally, and make sure to call the Init method to set the module’s reference to the external procedure. Note that it’s up to you to ensure that the prototypes match.
PROGRAM
INCLUDE('BaseClass.inc'),ONCE
MAP
MyOutsideProc PROCEDURE(STRING S),LONG !Prototype must match BaseClass_OutsideProc
END
MyObject BaseClass
CODE
MyObject.Init(MyOutsideProc) !Tell the BaseClass about the external procedure
MyObject.Test()
MyOutsideProc PROCEDURE(STRING S)!,LONG
CODE
RETURN LEN(S)
Hello,
I tried to fit your example in my code (although I know it is so clear) but I could not fit it well in my code as I got many errors (surely because of my misplacing your code).
when I added the sk_SplitPath inside the Map in the class, it compiled the application without errors.
But after I added the GetTempName Routine I got this error:
Unresolved External SK_SPLITPATH@Fsbl in sk_class1.obj (which is the minimum error I got in my tries)
The error you have is raised by the linker. The compiler can strip off the unused code. So while you have not used an instance of the sk_class1 CLASS in the GetTempName ROUTINE, all the code for this CLASS could be missed in the produced object file. When the instance of the CLASS was used, its code was not stripped off and the linker - absolutely correctly - raised the error.
The sk_SplitPath procedure is declared in the MAP with the TYPE attribute. Such declaration does not places any record to the object file which linker could resolve as sk_SplitPath function.
Just remove the TYPE attribute from the sk_SplitPath declaration.
Procedures declared in the MAP out of any MODULE…END block must be implemented in the current source file. If function is external for the current source, put its declaration inside the MODULE…END block. Parameter of MODULE can be any string, for example
MAP
MODULE('')
sk_SplitPath PROCEDURE(string,long),string
END
END
This function is one part of my own template which is added to the global extensions in the application and it works fine when I use it anywhere in the applications directly, and this code I trying to create as a class was originally a normal code in the application and I removed it in order to avoid any conflict. That’s why I am using it to create this class.
The function can’t be a part of template. It can be generated by the template or template can reference it in some way.
Is the sk_SplitPath function really implemented in some source/library file listed in the same project as the source module with your CLASS? If the linker can’t resolve the external name of some function or data object, this means that the function or data object with that external name found nowhere in linking OBJ/LIB files.
sk_SplitPath is a function defined in a template and the template is set in the global extensions and it is used all around the project and returns the requested values and this test class is the only class in the project as I creating it to test classes implementation for my future projects. Hope this answers your question.
This function simply splits the path to Filepath, filename, extension.
And I selected it because it is simple code and straight forward.
Hello Everybody,
I think the case is getting more confusing because of the sk_SplitPath function which comes from my template, so I decided to create a new fresh application with no templates used, and created a local source procedure called sk_SplitPath with the proper parameters and set the “Declare Globally flag” ON, also I did not add any code in that procedure except returning the values received.
Words have the sense. A function can’t be defined in templates. Its code can be generated by templates or templates can generate some reference to a function. For example, several functions (CheckOpen, ReportPreview, etc.) are generating by templates in STDFUNC.TPW, but they are not defined in this template file.
Templates are processing by the AppGen and cab used to generate source files. Everything in (Clarion) source files, including files generated by templates, is compiling by the Clarion compiler. The compiler either produces an object file if source has been compiled without errors or reports errors. Objects files are linked by the linker to a DLL or EXE file and the linker resolves all references to functions and to static data objects during this process. The error you’ve mentioned earlier is reported by the linker. This error means that the function with prototype
is referenced in the sk_class1.clw but none object file from the list of files to be linked contains the implementation of such function.
If so, the sk_SplitPath function has incorrect prototype in sk_class1.clw (for example, the second parameter actually has the type other than LONG). But sk_SplitPath has another - correct - prototype in the scope of the GetTempName ROUTINE. If you’ll post the object file produced by compiling the source file containing the GetTempName ROUTINE, I could write the actual prototype of sk_SplitPath.
I already simplified the whole matter in order to minimize the factors:
1 - I excluded the template by creating new fresh app
2 - I created sk_SplitPath2 procedure locally and declared it globally then I changed the values in the class files from sk_SplitPath to sk_SplitPath2
3 - I added only one parameter in sk_SplitPath2 which is “string”
4 - the sk_SplitPath2 procedure contains only one line of code that returns the received value
5 - the GetTempName Routine is only calling the procedure from class (it can be anything).
So now nothing is special or depending on my coding about the case as nothing is required or depending on my environment (except I am using clarion 6.3)