This question is an extension of a previous question: Return an object in VBA
Now, I'd like to know how to declare and initialize the object in VBA. It seems like I'd do it like so:
Declare Function ConnectMe Lib "C:\Windows\System32\cm.dll" (ByVal Arg1 As String) As ConnectMe
Declare Function login Lib "C:\Windows\System32\cm.dll" (ByVal Arg1 As String, ByVal Arg2 As String) As Boolean
Then, below this line, I could use this code:
dim cm as new ConnectMe
cm.ConnectMe("216.239.51.99")
cm.login("username","password")
However, when I do this, it gives me a "User-defined type not defined" error. How can I declare this C++ class appropriately so that I can create and use an instance in VBA?
Thanks.
This code is never going to work as it is. If you want to create a class in C++ under Windows and use it in any other programming language that is not C++, (VB for example) the "normal" approach is to create a COM class or an ActiveX control if you need to draw graphics.
You may also find this answer in SO helpful.
i here again :D
To use this code with your function use:
Dim cm as Object
set cm = ConnectMe("parameter")
if cm.login("username","password") then
msgbox "Connect!",vbinformation
else
msgbox "not connect!",vbinformation
end if
Related
I would like to call one of my User Defined Function from VBA.
My User Defined Function is declared in C++:
XLOPER12*WINAPI HelloWorld()noexcept{
THROWS();
static XLOPER12 res=[](){
static std::array<wchar_t,13>str={
11,'H','e','l','l','o',' ','W','o','r','l','d','\0'};
XLOPER12 tmp;
tmp.xltype=xltypeStr;
tmp.val.str=str.data();
return tmp;}();
return &res;}
This is a simplified version of a real function from the field which can either return a String or a double or even arrays. Of course here I am only returning a String but this limit the return type of my UDF to LPXLOPER12.
I can successfully register my function with xlfRegister specifying a pxTypeText of "U$". I can then call my UDF from Excel:
=HelloWorld()
And it works!
If I try to call my function from VBA as suggested here:
Sub macro_test()
Dim hw As Variant
hw = Application.Run("D:\Path\MyAddIn.xll!HelloWorld")
End Sub
I get an error message from Application.run:
Run-time error '1004': Application-defined or object-defined error
If I try to call my function from VBA as suggested here:
Private Declare PtrSafe Function HelloWorld Lib "C:\Path\MyAddIn.xll" () As Variant
Sub macro_test()
Dim hw As Variant
hw = HelloWorld()
End Sub
I get an empty result instead of "Hello World".
What am I doing wrong ?
Miscellaneous pieces of information:
Using Excel 2013
Using VS 2017 15.5
Using the first method (Application.Run), VBA does not call my function (I cannot step into my function with the debugger).
Using the second method, VBA calls my function (I can step into my function with the debugger).
Using the second method, when I add xlbitDLLFree to my xltype, function xlAutoFree12 is not called, which makes me think that somehow the return value is not understood properly by VBA.
If your XLL is loaded and its UDFs are registered so that=HelloWord() in a cell works then you should just be able to call it from VBA like this (unless there is a problem with parameterless string functions)
var=Application.run("HelloWorld")
You can also use Evaluate
var=Application.Evaluate("=HelloWorld()")
I tested my REVERSE.TEXT XLL function like this and it worked correctly.
Sub testing()
Dim var As Variant
var = Application.Run("REVERSE.TEXT", "Charles")
var = Application.Evaluate("=REVERSE.TEXT(""Charles"")")
End Sub
Reverse.Text is registered using UQQ$ (there are 2 parameters , the Text and the Number of characters)
Hello I am trying to create a simple C++ dll to be used in Excel.
Here is what I did:
.cpp file:
double _stdcall Test(double z)
{
return z+2.0;
}
.def file:
LIBRARY
EXPORTS
Test
In visual studio:
Project Properties > Configuration properties > Command > "Path/EXCEL.EXE"
Configuration manager > Platform > x64
In VBA:
Declare PtrSafe Function Test Lib _
"Path\MyDLL" (ByVal z As Double) As Double
But when I call Test(2) in Excel, it returns 2, and not 4. It seems like argument are seen as 0 alway (actually if I output the value of z in a file while calling the function, it is 2.122e-314).
Any inputs would be greatly appreciated.
Thanks
Edit 1:
If I change the argument and return value to int and in VBA to Long, Test(2) returns 3.
Anyone has an idea why the argument is 1 for int?
Apologies: Would have posted this as a comment but can't yet:
First: Your function may be exported as a C++ mangled name.
Use extern "C" to prevent name mangling. There are other ways to do this, but this is most direct. However if the Test function is successfully being called I'm not sure this is the issue.
Using extern "C" your C function would look like this.
extern "C"
double _stdcall Test(double z)
{
return z+2.0;
}
Second: Are you absolutely certain you are picking up the right DLL, not an old one which is missing your fixes? Try changing the function name to something more specific in your source and in the Excel VBA wrapper.
Third: If you are not running on a 64bit Excel system remove the PtrSafe declaration from your function specification in VB and recompile as 32-bit.
Fourth: Do not try to call your DLL directly from an Excel Cell. Wrap the function in VBA (example below) or use the CALL function.
The code below is an example how to wrap write a VBA cell function.
Public Function CellFunction_Test(x As Double) As Double
'' Tells excel to call this function only if the inputs change
'' and not on every recalculation
Application.Volatile False
'' Set an error handler, good practise for more complicated functions you might write later
On Error GoTo CellFunction_Test_EH:
'' Call into the DLL
CellFunction_Test = Test(x)
Exit Function
'' Error Handler
CellFunction_Test_EH:
CellFunction_Test = "Error - " & Err.Description
End Function
I've scoured the web and stackoverflow for this answer but can't find anything. I have written a com object in C++ (for the fist time) that works when used in vbscript and through cocreateinstance in an executable file. So I decided to see if it would work in Excel VBA.
So I went into "References" and located my object there. Checked the box and started coding away. The following is the VBA code.
Function doCos(x As Double) As Double
Dim t As SimpleLib.IMath
Set t = New SimpleLib.IMath ' <- "Invalid use of New keyword" error here
doCos = t.Cos(x)
End Function
Intellisense recognizes my object in the Dim statement, but it does not appear when I use a Set statement. Obviously I am using a registered type library or else intellisense wouldn't work at all. Again, the com object can be used in vbscript or an executable, but for some reason can't be used, at least with the new keyword, in VBA.
Does anyone have an idea what may be wrong, or what may have to be added to the com object? Thanks.
One approach is to define a coclass in the IDL that includes the interface needed (IMath in my case). NOTE: That the [default] interface is hidden by default. So I simply defined interface IUnknown as the default. After compiling with MIDL a type library is generated which one should register with regtlibv12.exe.
I then included an additional IF statement in DllGetClassObject like if (rclsid == CLSID_Math) where CLSID_Math is corresponds to the CLSID defined in the file automatically generated from MIDL. All I did was copy and paste the body of the IF statement from if ( rclsid == IID_IMath ), updated the DLLRegisterServer and DLLUnRegisterServer functions, recompiled the project, and regsvr32.exe.
So the following works now.
Function docos(x As Double) As Double
Dim a As SimpleLib.IMath
Set a = New SimpleLib.Math
docos = a.Cos(x)
End Function
Thanks to Hans for the tip about the coclass. Learned something new and useful.
Is it possible to execute C++ code from within Progress ABL?
Specifically, I am looking to use the function SHGetKnownFolderPath (documentation here) to determine the location of the "Documents" folder on a Windows 7 machine that has the documents folder redirected to another location.
Or, is there an alternative way to determine this information without resorting to checking a registry key?
You can call external shared libraries and DLLs.
http://documentation.progress.com/output/OpenEdge113/pdfs/dvpin/dvpin.pdf
Section 3 "External Interfaces" is what you are looking for.
This http://dbappraise.com/ppt/shlib.pptx might also be helpful.
C++ is often problematic due to the way it names things. You might be better off building a "shim" using plain old C to bridge between OpenEdge and C++
Callling Windows system functions is usually easy though. Something like:
procedure SHGetKnownFolderPath external "pathToLibrary":
define parameter a as someType.
define parameter b as someType.
define return parameter x as someType.
end.
Check the "Programming Interfaces" document, "External Program Interfaces" section.
Also, some versions of ABL also support direct .NET calls as an option.
I was able to get this working in 10.2B after consulting some sources:
The C# solution, for a working example: https://stackoverflow.com/a/21953690/763102
Win32 OpenEdge samples, for translation examples: http://www.oehive.org/book/export/html/385.html
The difficult part for SHGetKnownFolderPath is the rfid parameter which needs to be passed by reference. C# has the annotation [System.Runtime.InteropServices.MarshalAs(UnmanagedType.LPStruct)] or the ref keyword. I couldn't figure out how to pass a reference of System.Guid due to Progress OpenEdge's limitations of external procedure parameter datatypes, so I performed a bytewise copy of a .NET Guid and passed that via MEMPTR. I apologize for leaning so heavily on .NET here.
Here is a working example that gets the provided known folder GUID, plus usage to get the Documents folder:
PROCEDURE SHGetKnownFolderPath EXTERNAL "shell32.dll":
DEFINE INPUT PARAMETER rfid AS MEMPTR.
DEFINE INPUT PARAMETER dwFlags AS UNSIGNED-LONG.
DEFINE INPUT PARAMETER hToken AS LONG.
DEFINE OUTPUT PARAMETER ppszPath AS LONG.
DEFINE RETURN PARAMETER result AS LONG.
END PROCEDURE.
FUNCTION prepareGuidPointer RETURNS MEMPTR(
pGuid AS System.Guid):
DEFINE VARIABLE lGuidBytes AS INTEGER EXTENT.
ASSIGN lGuidBytes = pGuid:ToByteArray().
DEFINE VARIABLE lGuidPointer AS MEMPTR NO-UNDO.
SET-SIZE(lGuidPointer) = EXTENT(lGuidBytes).
DEFINE VARIABLE ii AS INTEGER NO-UNDO.
DO ii = 1 TO EXTENT(lGuidBytes):
PUT-BYTE(lGuidPointer, ii) = lGuidBytes[ii].
END.
RETURN lGuidPointer.
END FUNCTION.
FUNCTION deallocatePointer RETURNS INT64(
pPointer AS MEMPTR):
SET-SIZE(pPointer) = 0.
RETURN GET-SIZE(pPointer).
END FUNCTION.
FUNCTION GetKnownFolderPath RETURNS CHARACTER(
pGuidString AS CHARACTER):
DEFINE VARIABLE lDontVerifyFolderFlag AS INT64 NO-UNDO
INITIAL 16384. /* 0x4000 */
DEFINE VARIABLE lUseDefaultUser AS INTEGER NO-UNDO
INITIAL 0.
DEFINE VARIABLE lGuidPointer AS MEMPTR NO-UNDO.
ASSIGN lGuidPointer = prepareGuidPointer( NEW System.Guid(pGuidString) ).
DEFINE VARIABLE lResult AS INTEGER NO-UNDO.
DEFINE VARIABLE lPathResponse AS INTEGER NO-UNDO.
RUN SHGetKnownFolderPath(
INPUT lGuidPointer,
INPUT lDontVerifyFolderFlag,
INPUT lUseDefaultUser,
OUTPUT lPathResponse,
OUTPUT lResult).
deallocatePointer(lGuidPointer).
IF lResult GE 0 THEN
DO:
DEFINE VARIABLE lStringPath AS CHARACTER NO-UNDO.
DEFINE VARIABLE lPathPointer AS System.IntPtr NO-UNDO.
ASSIGN lPathPointer = NEW System.IntPtr(lPathResponse).
ASSIGN lStringPath =
System.Runtime.InteropServices.Marshal:PtrToStringUni(lPathPointer).
System.Runtime.InteropServices.Marshal:FreeCoTaskMem(lPathPointer).
RETURN lStringPath.
END.
ELSE
UNDO, THROW NEW System.Runtime.InteropServices.ExternalException(
"Unable to retrieve the known folder path. It may not be available on this system.",
lResult).
END FUNCTION.
DEFINE VARIABLE lDocumentsGuidString AS CHARACTER NO-UNDO
INITIAL "~{FDD39AD0-238F-46AF-ADB4-6C85480369C7}".
MESSAGE GetKnownFolderPath(lDocumentsGuidString)
VIEW-AS ALERT-BOX.
I am having some problems with a c++ dll i recently got from a friend.
I am trying to import the dll into an Excel VBA Add-in.
(I am using Excel 2010 32bit and the dll is compiled in Visual Studio 2008)
First of all i tried to import it but it failed with a runtime error 49.
I concluded that it possibly comes from a missing c++ declaration (__stdcal).
(i thought visual studio should do this automatically but okay)
Now i have a new dll with __stdcal in its declaration, but the methods are declared with a underscore ( _ ) at its beginning.
So when i try to declare the Sub, Excel tells me something like "invalid character"
is there a possibility to avoid this without altering the c++ files ?
'void _SaveFile(const char *FileName_);'
Declare Sub _SaveFile Lib "D:/DLL/TableController.dll" (ByRef FileName_ As String)
^
|
Thanks in advance
Here are some additional informations which maybe lead to a solution for my problems.
I got now some c++ code to debug my problem...
I still get the runtime error 49 but the dependency walker gives me now the right names of the methods( which is pretty awesome i think ;) )
I tried to use the same dll in an C# program.
As i expected it worked perfectly normal.
Here is my C# code
this is the import:
[DllImport("JLTable.dll",CallingConvention=CallingConvention.StdCall)]
internal static extern void JLReadFile(string _FileName);
[DllImport("JLTable.dll", CallingConvention = CallingConvention.StdCall)]
internal static extern long JLGetRowCount();
annd this happens if i click a Button
TableRunner.JLReadFile(#"D:\file.tab");
long rows = TableRunner.JLGetRowCount();
MessageBox.Show(rows.ToString());
Here is my code in VBA
The declaration:
Declare Function JLReadFile Lib "D:/JLTable.dll" (ByRef FileName_ As String)
Declare Function JLGetRowCount Lib "D:/JLTable.dll" () As Long
And the call:
Sub Read()
Dim path As String
path = "D:/file.tab"
JLReadFile (path)
Dim count As Long
count = JLGetRowCount()
End Sub
And Here is also the c++ code i want to call.
Because of some reason my friend doesn’t want me to show what the code exactly does but it still behaves the same way with this code.
The .h file(the JLTable_API is something like #define JLTable_API __declspec(dllexport))
#define STDCALL
extern "C"
{
JLTABLE_API void STDCALL JLReadFile(const char *FileName_);
JLTABLE_API void STDCALL JLSaveFile(const char *FileName_);
}
The .c++ file
void STDCALL JLReadFile(const char *FileName_)
{
//log something to a file
}
long STDCALL JLGetRowCount()
{
//log something to a file
return 0;
}
I am very grateful for every hint you can give me
And as always
Thanks in advance
I finally have a solution.
i found a blog which showed me what i did wrong.
http://aandreasen.wordpress.com/2008/05/05/how-to-create-a-dll-for-ms-excel-vba-with-microsoft-visual-c-2008-command-line-tools/
As described you need the correct method name, to figure out which is the correct one i used the dependency walker.
This finally led me to this declaration
VBA:
Declare Sub JLReadFile Lib "D:/JLTable.dll" (ByRef FileName_ As String) Alias "_JLReadFile#4" (ByRef FileName_ As String)
C++:
void __declspec (dllexport) _stdcall JLReadFile(const char *FileName_);
From a similar test I ran, you need to use the "Alias" keyword. So that it would be written similar to: Declare Sub SaveFile Lib "D:/DLL/TableController.dll" Alias "_SaveFile" (ByRef FileName_ As String)
Additionally, I don't think you have to pass filename by reference, but I could be wrong. It's been a while since I've used VB.
More information on importing functions from a dynamic library, check this page out