Calling an xll UDF from VBA - c++

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)

Related

Delphi 10.1 - Wrong Match.Value in TMatchEvaluator when calling TRegEx.Replace()

I've found one bug with latest Delphi 10.1 Berlin (and in 10.2 Tokyo too).
If you call TRegEx.Replace with TMatchEvaluator specified, you may got wrong TMatch inside Evaluator function. In Delphi XE-XE5 it seems works good.
Evaluator:
function TForm1.EvaluatorU(const Match: TMatch): string;
var
lChar: Word;
lMatchVal: string;
begin
Result := '';
lMatchVal := Match.Groups[1].Value;
lChar := StrToIntDef('$'+lMatchVal, 0);
if lChar <> 0 then
Result := Char(lChar);
end;
Call:
Result := TRegEx.Replace('\u0418\u0443, \u0427\u0436\u044d\u0446\u0437\u044f\u043d', '\\u([0-9a-f]{4})', EvaluatorU, [roIgnoreCase]);
First call of Evaluator will bring right TMatch.Value (or TMatch.Group[].Value) context, but second call will bring wrong Match.Value to callback function(
Do you have some idea about workaround?
I going to check this issue on TPerlRegEx class, maybe something wrong is in wrapper (TRegEx) functions.
Update: with TPerlRegEx Replacement with callback function (OnReplace) works good...
Update2: it seems there is a bug in TPerlRegEx. It returns wrong GroupOffsets. On First call of OnReplace callback this value is correct. Next calls returns +1 offset more than needed. But call of TPerlRegEx.Groups returns correct subgroup value...
Last update and solution found:
I've found a problem in TPerlRegEx.UTF8IndexToUnicode function optimization.
There are LastIndex*/LastIndexResult* fields used to optimize sequential call of function with same params. But after replacement made via callback functions and when MatchAgain is called into TPerlRegEx.ReplaceAll function, this can make a bad trick(
Simple solution is copy System.RegularExpressionsCore.pas from \source\rtl\common to your project directory and replace call of TPerlRegEx.UTF8IndexToUnicode to deprecated unoptimized UTF8IndexToUnicode function... Or clear this internal fields somewhere in ClearStoredGroups function for example.
upd. Here is my embarcadero quality central issue.

Argument not taken into account when using C++ dll in excel

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

"Invalid use of New Keyword" in VBA using old com object written in c++

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.

VBA object-oriented programming

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

Regular Expression Search in Visual Studio 2010

I'm not sure if this is possible within VS, but I'm working with a massive VB.NET file that needs to log every function call for debug purposes. Problem is, that not every function has the Log command in it. I'm trying to use RegEx to find the function definitions that do not have a log within them.
This would NOT be a match:
Public Function Test1() as Boolean
Log.Tracelog("Test1()")
Return True
End Function
This WOULD be a match:
Public Function Test2() as Boolean
Return False
End Function
The closest I've come is using the following:
(function|sub|property) .*\n.*~(Log\.t)
In my own mind, it should work, but no matter how I word it, it's still pulling every function as a match, even those that have the "Log.Tracelog" call in the function.
Is there anyway I can search to find the latter case?
Try this:
(function|sub|property) .*\n~(.*Log\.t)
I moved .* from just before the ~() (preventmatch) to just inside it.
Why not use the debug.WriteLine methods for the functions you want logged. You can also use the stack to get the method name:
Private Function test1() As Boolean
Debug.WriteLine(New System.Diagnostics.StackTrace().GetFrame(0).GetMethod.Name)
Return False
End Function
Then the messages only output when debugging and only in the methods you want.