Import Delphi dll in vba - vba

For a project, I need to import a DLL made in Delphi and call some functions.
However, nobody in the projectteam knows how to get this done. Our company supervisor made a program in C++ to open the DLL and call the functions, but he has not the experience in VBA.
Our project is to call the functions and pass some parameters from an excel worksheet to a program (Gas turbine Simulation Project). Our first question is: How do we load a Delphi dll in VBA? I read about the function dllimport, but still i don't know how to use this function.
For example, within the dll there is a function: d4868activateLog
The code in Delphi is as followed:
function activateLog (const filename: PAnsiChar):Integer;
How do we call this function?

You cannot call that function from VBA since it uses the register calling convention. VBA can only import stdcall functions.
You could change the Delphi code to:
function d4868activateLog(const filename: PAnsiChar): Integer; stdcall;
And then in the VBA you would write:
Private Declare Function d4868activateLog Lib "mydll.dll" (ByVal filename As String) As Long

Related

Access .dll library MODULE function in Excel (VBA)

I have a library called PIT_math_tools (written in vb.net) containing math functions which is split up into different modules.
In this case, the function I want to access is located in a Module called geometry_ellipsen_module:
Namespace geometry_ellipsen
Public Module geometry_ellipsen_module
...
Public Function calc_distanz_ellipse_kreis(...) As Double
Now I want to access the Function calc_distanz_ellipse_kreis() in VBA.
I used the following declare statement to accomplish this:
Private Declare PtrSafe Function calc_distanz_ellipse_kreis Lib "C:\Users\path...\PIT_math_tools.dll" (...) As Double
So far, everything looks fine.
When trying to call that function, however, I get runtime error '453'
"DLL-Entry Point calc_distanz_ellipse_kreis in 'PathToLibrary' not found."
When using the function in other .Net code, it could be found either by using the prefix
PIT_math_tools.geometry_ellipsen.geometry_ellipsen_module.calc_distanz_ellipse_kreis()
or an import statement identical to the prefix above.
Based on that, I tried extending the Function name in the Declare Statement by the same prefix:
Private Declare PtrSafe Function geometry_ellipsen.geometry_ellipsen_module.calc_distanz_ellipse_kreis Lib ...
However, this results in a compiler error "Expected: Lib".
How do I get the vba code to find the function inside the library module?
Thanks in advance.
The VBA syntax...
Private Declare Function SomeMethod() Lib "Some.dll"
... is used to prototype standard Win32 DLLs, e.g. Win32 API. Which .NET DLLs (aka "Assemblies") are not.
What you need to do in VB.NET is to make that assembly COM-visible. VB.NET creates a typelib (*.tlb) for the assembly.
In VBA you then add a COM reference (point to that typelib) to your project like you would do with other COM DLLs (ADODB, MSXML etc.). And then you do your standard
Dim myObject As Namespace.ClassName
Set myObject = New Namespace.ClassName
With myObject
.ThisProperty = 123
Dim lRet As Long
lRet = .ThisMethod("abc")
End With

vba Run-time error '429': ActiveX can't create object, with user defined vb.net dll (VB.NET 2017, Excel 2016)

I have written a simple dll in VB.NET 2017, and I am trying to call it from VBA (Excel 2016). I have searched through Stack Overflow and MSDN, but despite many hits for this VBA error, I cannot find information related to a user defined dll that solves this particular problem.
I checked the box for the Register for COM Interop option when compiling the dll. I also verified that the Make assembly COM-Visible box was also checked.
Compilation was successful, and I was able to find my library (called TestLibrary2) in References inside the VBA editor. The class I created in it is called TestVBNET. I checked TestLibrary2 to include it in VBA, and then attempted to run the following code in VBA:
Public Sub mySub()
Dim myTest2 As TestLibrary2.TestVBNet
Set myTest2 = New TestLibrary2.TestVBNet
End Sub
When I ran the sub, I got the pop-up dialog with message: Run-time error '429': ActiveX can't create object
Based on what I have read on MSDN and other sites, the expected result is that the TestLibrary2.TestVBNet object should be instantiated successfully.
The code below is the VB.NET code that I compiled to the dll. The GUID's were generated using guidgen at the Windows 10 command prompt.
Imports System.Runtime.InteropServices
<Guid("2CEBF80D-D5FC-4C52-BCF1-E2F076318240")>
Public Interface ITestVBNet
Function addTrig(x As Double, y As Double) As Double
End Interface
<Guid("60983277-A724-4CDD-8ACE-B94CF9546F43")>
<ClassInterface(ClassInterfaceType.None)>
Public Class TestVBNet
Function addTrig(x As Double, y As Double) As Double
Return Math.Sin(x) + Math.Cos(x)
End Function
End Class
Any help and or references would be greatly appreciated. Thanks in advance.
EDIT (per comments below): I compiled the dll as admin, which in fact was required for the Register for COM Interop option to run. I also tried using regasm -- also as admin -- as follows, in the same directory as the compiled dll:
regasm /codebase /tlb TestLibrary2.dll
The following was returned:
RegAsm : warning RA0000 : Registering an unsigned assembly with /codebase can cause your assembly to interfere with other applications that may be installed on the same computer. The /codebase switch is intended to be used only with signed assemblies. Please give your assembly a strong name and re-register it.
Types registered successfully
RegAsm : error RA0000 : An error occurred while saving the exported type library: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
I apologize for being a newb here with respect to registering COM libraries, but what this seemed to indicate was the dll was registered successfully, but could not be transferred to the GAC. It was my intention not to transfer it. When I tried loading it in VBA, the same error as above occurred.
It turns out that the above code compiled to a dll that was indeed callable from a C# client. The problem with VBA, however, was that I neglected to put Implements ITestVBNet following the derived class declaration. In VB.NET, though, in addition to this, one must also explicitly indicate function overriding in the derived function declaration as well; viz (scroll all the way to the right to see the entire line),
Public Function addTrig(x As Double, y As Double) As Double Implements ITestVBNet.addTrig
By replacing the derived class above with the following, I was able to create an instance of the TestVBNet object in VBA and call the addTrig(.) method successfully:
<Guid("60983277-A724-4CDD-8ACE-B94CF9546F43")>
<ClassInterface(ClassInterfaceType.None)>
Public Class TestVBNet
Implements ITestVBNet
Public Function addTrig(x As Double, y As Double) As Double Implements ITestVBNet.addTrig
Return Math.Sin(x) + Math.Cos(x)
End Function
End Class

vba worksheetfunction memory address

I have written a dll which requires a pointer to a function with a given signature, say
function func(x as double) as double
...
end function
I can write such a function (say Asinh) in vba and pass it with the AddressOf... vba method, and it works with no problems.
But Asinh is a function already existing in the Excel object WorksheetFunction, and it looks silly to me to write (many) proxy vba functions just to call existing functions. It would be nicer to pass directly the address of such worksheetfunctions. However:
AddressOf does not work for (say) WorksheetFunction.Asinh
I can get the address of WorksheetFunction with ObjPtr, but from there I'm not able to go on to the Asinh function address.
A related problem is to reuse a function (with same signature) defined in an external dll, but at least in this case it is possible to resolve it with LoadLibrary / GetProcAddress, sort of
function ExternalAddressOf (dllname, funcname) as LongPtr
....
end function
Or, in case of own-written dll, to implement a function table and use in vba symbolic constants. However, I'd really like to be able to reuse functions already defined in WorksheetFunction. Need help.

VB call to MsiLocateComponent function in msi.dll

I am trying to get the installation path of Office programs as this microsoft kb article suggests (since Office start menu shortcuts don't point to paths anymore; thank you Microsoft).
Of course, the KB example uses C++ and native libraries, which I tried to replicate in VB.NET with the following code
<Runtime.InteropServices.DllImport("msi.dll")> Public Shared Function MsiLocateComponent(szComponent As String, ByRef lpPathBuf As Char(), ByRef pcchBuf As Integer)
End Function
I call this function from the following, which I expect to return a message box with at least the dot (if the rest fails). Instead, I get nothing at all, so I assume that the code runs into some sort of error (which is silent though because I get no exception).
Shared Function DealWithWinInstallerPath(ProgramPath As String) As String
Dim sPath(300) As Char
Dim sSize As Integer = 300
Dim state As Integer = MsiLocateComponent("{019C826E-445A-4649-A5B0-0BF08FCC4EEE}", sPath, sSize)
MsgBox(state & ".")
End Function
(Note that the function has an argument that will be used in the future, but its contents are just for testing purposes).
Am I declaring the function incorrectly? Passing the wrong arguments? Is msi.dll not the right name for the library? The msdn database doesn't help much either.
For managed code, the Microsoft.Deployment.WindowsInstaller interop assembly found in Windows Installer XML's (WiX) Deployment Tools Foundation (DTF) is the way to go. DTF's ComponentInstallation class has a read only property called Path that encapsulates the call to MsiLocateComponent()
Once installed, you can find the DLL in C:\Program Files (x86)\WiX Toolset v3.8\SDK.
You can also read through the source code for pointers on how to P/Invoke MSI API calls.
MsiGetComponentPath is preferred if you read the MSDN docs, and there's an interop example here:
http://www.pinvoke.net/default.aspx/msi.MsiGetComponentPath

What is a public object module in VBA?

I'm trying to get as close to function pointers / abstract classes as I can in VBA.
I have a class called VerificationManager and verifies a bunch of cells in a couple of spreadsheets match up. This will be done in different ways depending on the information and spreadsheets being used with it.
I'd like to be able to make the code reusable by specifying a method to be called in a string using the Application.Run function. So I can rewrite the function that changes.
Now if I was using Java or C# I would be able to extend an abstract class and rewrite the internals to the function. If I was using JavaScript I could store a function in a variable and pass the variable to the class and call it from there.
Inside my class I have a public property called "verificationModule" which I set to the name of the function I want it to call.
Sub VerifyWorkLocations(empLoc As EmployerLocation)
...
For i = 0 To empLoc.numOfEmp
Application.Run verificationModule, empLoc.taxdescmatch, empLoc.employees(i)
Next i
...
End Sub
However, when I try to call Application.Run I receive the following error:
Compile Error:
"Only user-defined types defined in public object modules can be
coerced to or from a variant or passed to late-bound functions"
I already tried placing my User Defined Types in a Class Module but it basically said that a class module was the wrong place for a type.
The error comes from full-fledged VB, where you can create an ActiveX dll project, create a public class there and put a UDT into that class.
In VBA, you use classes instead of UDTs when you need to coerce to or from a variant.
So just declare a class with all the fields you have in your UDT, and delete the UDT.
Alternatively, create a DLL in VB6 that would only contain the declaration of the type, and reference that dll from VBA. Or, if you're comfortable with IDL, just create a TLB file directly.
To add a module to a VBA application, try the following steps (assuming you've already entered the VBA IDE):
From the Project Explorer, right-click on your project.
Click on "Insert..."
Select "Module."
If no modules exist, a new folder within the VBA/Excel project will be created, named "Modules," and a module with a default name of "Module1" will be created.
Double-click the module to open it in the source editor.
This is where you have to place UDT's. I believe this is because of the difference in the way such types are stored internally by VBA relative to the COM-style structure of elements/objects declared/manipulated as Classes...