Library ACCDB Calls to Referencing Project Procedures? (1 Viewer)

riktek

Member
Local time
Yesterday, 22:27
Joined
Dec 15, 2023
Messages
64
I've been refactoring a number of projects and migrating generalized code to a library ACCDB over time. It's gone practically without a hitch and many ACCDB projects reference it and instance its classes like a charm.

As this refactoring has progressed, I have encountered one difficulty, however. The standard code depends on a small number of standardized functions to do project-specific things, chiefly object name transformations. These functions are public and have identical names and parameters from one project to the next, so as to work with the common code. They reside in a local standard module in each project. The only difference is that one project might need to conditionally return "blue" or "yellow" for parameter "green", and the next project might need to return "tacos" or "sushi" for parameter "dinner" on other conditions. I.e., the functions all have a common interface.

The problem, now that I've migrated the common calling code to the library, is that it regards the public procedures in the local standard module to be undefined.

I should add that the calling code is in library classes that local objects instance and bind themselves to for configuration. It isn't clear to me what the scope and visibility of those class instances is, beyond not seeing local standard modules.

The question is how to get procedures in a library class to recognize public procedures in a local standard module. I.e., this isn't a question of getting the primary ACCDB to work successfully with code in a library ACCDB, but of getting the library ACCDB to reciprocally work successfully with primary ACCDB code.

The VBE offers the option of adding a reciprocal reference to the primary ACCDB in the library ACCDB. It isn't clear to me whether that is a good idea in the to begin with, or if so whether that would persist if done while running the primary ACCDB. It seems clumsy to open the library ACCDB to add references to each project that might reference it. An alternative might be somehow to create the reciprocal reference programmatically on startup but I'm not clear where to start with that. Perhaps the instancing must be done differently to achieve the necessary visibility. Perhaps it would be best somehow to create a local class implementing a library interface class, but this starts getting wooly and may still beg the question of visibility. Regardless, Google isn't much help and I'm in the realm of casting about and not knowing what I don't know, so it seems best to ask. Any thoughts or direction would be welcome.
 
Last edited:
It seems clumsy to open the library ACCDB to add references to each project that might reference it.
you are right, because add-ins doesn't need to know each objects that referenced it. it must be "generic", in the sense that it "knows" itself rather than the class/object that reference it. i am sure this is true with any other high level languages.

take the case of an Excel object (reference in MSAccess). does it knows that Access is referencing it? it is "knows" on itself rather than the outside world.
 
you are right, because add-ins doesn't need to know each objects that referenced it. it must be "generic", in the sense that it "knows" itself rather than the class/object that reference it.
Yes, of course, just for starters. I'll sacrifice orthodoxy for function but it seems to me this is the road to perdition.

There is no difficulty in designing an object to accept parameters, however, including the identity of the referencing (or instantiating) object. Or, for that matter, to expose its members. My object wrapper / "subclass" framework depend on both.

To the point, though, I'm still stuck on how to get library class code to recognize procedures in local standard modules.
 
To the point, though, I'm still stuck on how to get library class code to recognize procedures in local standard modules.
Because of the order (and separation) of compiling the class objects, I'm not sure you can do this without "breaking" the class apart and then regenerating it in the specific context.

I have never tried to make a subroutine's formal parameter include an entry point in a module before because I don't, offhand, remember seeing a way to declare that entry-point as an object type. In true-compiled languages, for instance OpenVMS Basic, I have seen entry-point addresses as a valid data type. I have not seen that for VBA, which is more of a pseudo-compiled language.

I checked the MS VBA Language Reference document dated 2014-04-24, and I can't find anything that would declare an entry-point name as a valid thing to be passed as a parameter. There is no "entry-point" data type in VBA. If someone has a newer VBA language specification and can look up that kind of data type, don't hesitate to chime in here, because what I'm using is a 10-year-old reference.
 
I can't find anything that would declare an entry-point name as a valid thing to be passed as a parameter.
VBA succeeds in boxing out the notion of an entry point other than a document in a COM app, in a variety of ways. One can get close by understanding event progression but even it has constraints.

It is fairly liberal about passing objects, if not initializing them, and the object model includes References and CodeProject objects. The solution may lie there, at least in part. I don't think it's so much a question of passing an entry point per se so much as, perhaps, CurrentProject.
 
> object name transformations
One idea is to look up the transformations in a Name/Value style table.
 
A procedure in a standard module could be called with Application.Run if necessary.
However, I would try to avoid this if possible.

Perhaps it is possible to react to events of the class in the library and, for example, return values to the class via ByRef parameters.

Another possibility would be to pass an instance of a "local" class to the library, which then accepts callback calls (dependency injection).
Call LibProcedure(Param1, Param2, MyWorkerClassInstance) (Ideally secured with an interface)
 
Last edited:
A library function can call a front-end function using Eval().

This is also helpful if you have a library form that sometimes has the focus, in which case the library form does not see front-end functions that might be called from the ribbon. Instead, the ribbon can call a library function that uses Eval to executed a front-end function, which works no matter what form has the focus.

Steve
 
You can define an interface in the Library, and then in the front end, implement that interface with custom functionality.

In the library, create a class called IAction that exposes a single method Public Function Execute(Optional dcn as Scripting.Dictionary) as Variant.

Now, in clients of the Library, create a class that Implements IAction. Load it up with whatever functionality and/or FE classes you need it to perform, and any consumer that references your library--which is to say any consumer that knows what an IAction is--can run its Execute method. The consumer doesn't need to know anything about what is inside your IAction.

Code:
Implements Lib.IAction

private a_ as long
private b_ as long


Public Function Load(opA as Long, opB as Long) as cFE_IActionMultiply
' this method is only visible in the FE when you create an instance of cFE_IActionMultiply
' and the private members a_ and b_, which could as easily have been class instances,
' are private to the FE.
    a_ = opA
    b_ = opB
    Set Load = Me  ' return this instance
End Function

Private IAction_Execute(Optional dcn as Scripting.Dictionary) as Variant
    IAction_Execute = a_ * b_
End Function

So I can create an instance of the above class on a form as an area calculator....
Code:
Property Get AreaCalculator() as IAction
    With New cFE_IActionMultiply
        Set AreaCalculator = .Load(Me.tbWidth.Value, Me.tbHeight.Value)
    End With
End Property

And then on a button click on the form...
Code:
Private Sub cmdCalcArea_Click()
    MsgBox Me.AreaCalculator.Execute
End Sub

In this way you can abstract functionality. You can write generic code that executes an IAction. At a later time, you can determine which IAction you will pass to the code that performs that execution.
 
> object name transformations
One idea is to look up the transformations in a Name/Value style table.
The difficulty is that that table must exist in the referencing project or its back end because it is specific to it, and it would need to be visible to the library project's code in the same way.
 
A library function can call a front-end function using Eval().

This is also helpful if you have a library form that sometimes has the focus, in which case the library form does not see front-end functions that might be called from the ribbon. Instead, the ribbon can call a library function that uses Eval to executed a front-end function, which works no matter what form has the focus.

Steve
If I understand correctly, this still contemplates the front-end ribbon calling the library function.

My problem is getting the library to call the front-end function.

The library code in question is a framework of object wrappers sinking front-end object events. E.g., if I double-click on a front-end form's combo box, the library combo box class sinks the event and calls other code, including the front-end project-specific code I'm having difficulties with.

So, while the events are occurring in the front end, the code responding to those events is in the library.
 
any consumer that knows what an IAction is--can run its Execute method
If I understand correctly, you're describing a library interface class implemented and instantiated by referencing projects' classes.

My problem differs, I believe, because it involves the library calling code in the referencing (i.e., front-end) project.

As I noted in response to another suggestion, the library code in question is a framework of object wrappers that sink front-end object events. So, front-end forms are bound to a library form wrapper class, and front-end combo boxes to a library combo box class, etc.. When, for example, I double-click on a front-end form's combo box, an instance of the library combo box class executes suitable code, which includes calls to the front-end procedures I'm having difficulty with.

So, while the events are occurring in the front-end project, they're being sunk by instances of library classes. In fact, the library form wrapper instances all the control wrapper classes.

Put otherwise, all the calls are originating with code in the library. It's that code that needs visibility to code in the front-end project.
 
Say I have a class created in the FE based on FE data and the Library know nothing about it, say a cCustomer class with a CreateOrder method that returns a new cOrder instance. And say I want to run cCustomer.CreateOrder from the Library. In this case cCustomer can Implement IAction, which is defined by the Library, and say IAction exposes an Execute method.

Here is my IAction class in the Library...
Code:
Public Function Execute() as Object

Here is partial code from cCustomer in the FE, completely unknown in the library, except it implements a IAction...

Code:
Implements MyLib.IAction

Public Function CreateOrder as cOrder
    ' this is an FE function.  Note that it returns a type unknown to the Library and it exists inside a type unknown
    ' to the Library.
    With New cOrder
        Set CreateOrder = .AddNew(Me)
    End with
End Function

Private Function IAction_Execute() as Object
    ' this is the implementation of the IAction.Execute method well known to the Library
    Set IAction_Execute = Me.CreateOrder
End Function

Now, from the FE, I can pass cCustomer to the Library, and the Library can create an order, all without knowing anything about the cCustomer class or the cOrder class. The Library, if it calls IAction.Execute, will execute code defined in the FE.

Maybe I misunderstand, but it seems like that is what you are trying to do. The process is triggered in the the library, but the process is defined in the FE, with logic and processes specific only to the FE.
 
So, front-end forms are bound to a library form wrapper class, and front-end combo boxes to a library combo box class, etc.
Where is the instance of the framework class stored?
If it is in the form itself, you could also react to events of the framework class.

An example with the 3 variants is attached.
  • Communication via Event
    • Clear definition of the interface
    • Early binding is required
  • Communication via 'ActionWorker' (same method as described in #9 and #13) ... dependency injection
    • Clear definition of the interface (if early binding with implementation of interface)
    • Could be redesigned for late binding
  • Communication via CallBack
    • No recognisable interface in the code
    • Late binding is possible
    • Callback via CallByName (for a method in an object) or Application.Run (for a procedure in a standard codemodule)
 

Attachments

Last edited:
If I understand correctly, this still contemplates the front-end ribbon calling the library function.

My problem is getting the library to call the front-end function.

The library code in question is a framework of object wrappers sinking front-end object events. E.g., if I double-click on a front-end form's combo box, the library combo box class sinks the event and calls other code, including the front-end project-specific code I'm having difficulties with.

So, while the events are occurring in the front end, the code responding to those events is in the library.

A simple test will verify that VBA code in the library can call a function in the front-end using code such as Eval("MyFrontEndFunction()").

I use this feature to emulate call-back functions that exist in the front-end but are called from the library.
 
A simple test will verify that VBA code in the library can call a function in the front-end using code such as Eval("MyFrontEndFunction()").

I use this feature to emulate call-back functions that exist in the front-end but are called from the library.
Surprising and very interesting!
Thanks for sharing.
 
To ask for the sake of interest: What advantage does Eval(...) have over Application.Run(...)?
In my opinion, Application.Run can do more (parameters, return of a reference).
 
To ask for the sake of interest: What advantage does Eval(...) have over Application.Run(...)?
In my opinion, Application.Run can do more (parameters, return of a reference).
Eval() returns a function value. You can include parameters as part of the input string.

Application.Run is normally used for calling a procedure in an external database that you open using automation. At least in my understanding...

Steve
 
To ask for the sake of interest: What advantage does Eval(...) have over Application.Run(...)?
In my opinion, Application.Run can do more (parameters, return of a reference).
After some tests, I see no difference: Both accept parameters and both can return a value.
 

Users who are viewing this thread

Back
Top Bottom