Remove chr(0) from (c++ BSTR) string in VBA - c++

I've written a c++ function in a DLL which exports a string to a VBA program:
BSTR _stdcall myFunc()
{
CRegKey Key;
CString sValue;
BSTR Str;
LONG nA = Key.Open(HKEY_LOCAL_MACHINE, _T("[group path goes here]"), KEY_READ);
ULONG nValueLength = 0;
LONG nB = Key.QueryStringValue(_T("[key I want to read goes here]"), NULL, &nValueLength);
if (nValueLength > 0)
{
LONG nC = Key.QueryStringValue(_T("[key I want to read goes here]"), sValue.GetBufferSetLength(nValueLength - 1), &nValueLength);
}
Str = _bstr_t(sValue.AllocSysString(), false);
return Str;
Now Str is something like a version number: let's say "4.10.122".
If I call the function from VBA, I receive instead "4 . 1 0 . 1 2 2", where the "spaces" between each characters are NULL (in VBA they are Chr(0)).
I don't like the idea of having to use the Replace function in my VBA code, so is there any way I can include that step in my c++ code?
EDIT: below the code I'm using to call the function in VBA:
Private Declare Function myFunc Lib "[Path of my DLL here]" () As String
Sub Return_string()
Dim a As String
a = myFunc()
End Sub

Far from being able to help you with this specific need but curious about the subject, if i were to come up with a solution i would start from this answer https://stackoverflow.com/a/43423527/781933
Update
According to the above mentioned #SimonMourier detailed answer, and further readings, you should consider string content encodings.
The context of the DLL - VBA marshalling is to be considered and depends also on VBA function declaration (stated by #HansPassant).
From this MSDN documentation:
When a VBA user-defined function is declared as taking a String argument, Excel converts the supplied string to a byte-string in a locale-specific way.
If you want your function to be passed a Unicode string, your VBA user-defined function should accept a Variant instead of a String argument.
So if you use:
Private Declare Function myFunc Lib "[Path of my DLL here]" () As String
conversion from UNICODE to ANSI is needed via StrConv
Dim str As String
str = StrConv(myFunc(), vbFromUnicode)
Otherwise you should get rid of StrConv but using DLL exported BSTR with this declaration:
Private Declare Function myFunc Lib "[Path of my DLL here]" () As Variant

The problem stems from the Declare statement. When interacting with such functions and strings in particular, VBA will always perform an implicit ANSI to Unicode conversion in both directions. In your case, VBA expects an ANSI string as the return value, which it can then expand into a Unicode equivalent.
To deal with this, you'll have to return an ANSI string and resort to SysAllocStringByteLen:
CStringA sValueA(sValue);
Str = SysAllocStringByteLen(sValueA.GetBuffer(), sValueA.GetLength());
As an alternative, you may also embed a type library inside your DLL. This would omit the automatic string conversion.

Related

How to append to a string without changing original value when passing as a parameter?

I am making a PONG clone in C++/SDL, and I have all of my images in the directory in which the program starts. I am successfully able to find that path using GetCurrentDirectory() and open the file using strcat() to append the actual image and it will load fine, but this will change the original value, which makes it useless when I try to load the next image. How would I pass the path without changing the original value, or another way to work around this problem.
My current code:
TCHAR openingdirectorytemp [MAX_PATH];
bgtexturesurf = SDL_LoadBMP(strcat(openingdirectorytemp, "\\bg.bmp"));
Use actual C++ strings:
#include <string>
using std::string;
void child(const string str)
{
str += ".suffix"; // parameter str is a copy of argument
}
void parent()
{
string parents_string = "abc";
child(parents_string);
// parents_string is not modified
}
If you must work with TCHAR in the Windows API world, use std::basic_string<TCHAR>:
typedef std::basic_string<TCHAR> str_t; // now use str_t everywhere
and so the code becomes something like
void my_class::load_bg_bmp(const str_t &dir_path)
{
str_t file_path = dir_path + _T("\\bg.bmp")l
bgtexturesurf = SDL_LoadBMP(file_path.c_str()));
// ...
}
The TCHAR type allows for build times switching between narrow and wide characters. It is pointless to use TCHAR, but then use unwrapped narrow character string literals like "\\bg.tmp".
Also, note that strcat to an uninitialized array invokes undefined behavior. The first argument to strcat must be a string: a pointer to the first-element of a null terminated character array. An uninitialized array is not a string.
We can avoid such low-level nasties by using the C++ string class.
Although you can use C++ string as suggested by other answers, you can still keep your C approach.
What you need to do is just to create another string by copying the contents from the original, and use it for strcat:
TCHAR openingdirectorytemp [MAX_PATH];
TCHAR path [MAX_PATH];
strcpy(path, openingdirectorytemp);
bgtexturesurf = SDL_LoadBMP(strcat(path, "\\bg.bmp"));
By doing so, you create string path with a separate memory space, so strcat won't affect openingdirectorytemp
You need to make a copy of the string before concatenating if you are worried about things getting changed. In other words
string1 = "abc"
string2 = "def"
strcat(string1, string2);
Results in
string1 = "abcdef"
since that is what you asked the program to do. Instead, add
strcpy(string3, string1)
strcat(string3, string2);
Now you will have
string1 = "abc"
string3 = "abcdef"
Of course you need to make sure enough space is allocated, etc.
Once you are using c++, you can use string to compose your final pathname:
string pathname(path);
pathname += "\\bg.bmp";
bgtexturesurf = SDL_LoadBMP(pathname.c_str());

In VB6, do I need to call SysFreeString on strings allocated with SysAllocString?

I have a VB6 app which uses a C++ COM DLL and BSTRs are passed between the two. I return BSTRs from the C++ DLL with return ::SysAllocString(L"example");.
Do I need to call SysFreeString on such a string in the following example?
Sub Main()
Dim own_str As String
Dim dll_str As String
own_str = "my own string"
dll_str = DllComObj.FunctionReturningString()
' when leaving the scope:
' no need to free own_str,
' do I need to free dll_str?
End Sub
No you don't. VB6 will free them as BSTR is its native string format.

How to invoke an exported function from DLL written in C/C++ which return type is char* or string?

We designed C/C++ DLL just like this:
WIN32_DLL_EXPORT int FnRetInt(int i)
{
....
return 32 ;
}
WIN32_DLL_EXPORT char* FnRetString()
{
return "THIS IS A TEST STRING" ;
}
when we invoke these two functions in Go by using syscall:
hd:=syscall.NewLazyDLL(dll_path)
proc:=hd.NewProc(dll_func_name)
ret:=proc.Call()
we found:
FnRetInt worked ok, but FnRetString didn't. proc.Call return type is uintptr, how can we change it to the type we wanted (for exsample: char* or string)?
A uintptr is a Go type that represents a pointer. You can use the unsafe package and convert it to unsafe.Pointer, and then you can convert an unsafe.Pointer into any Go pointer type. So you could do something like
str := (*uint8)(unsafe.Pointer(ret))
to get a *uint8 back.
Look at syscall.Getwd windows implementation http://code.google.com/p/go/source/browse/src/pkg/syscall/syscall_windows.go#323. It is different from your problem:
it passes buffer to the dll, instead of receiving it from dll;
the data is uint16s (Microsoft WCHARs), instead of uint8s;
GetCurrentDirectory tells us how long resulting string is going to be, while your example, probably, expects you to search for 0 at the end;
But should give you enough clues.
Alex

return string from c++ function to VB .Net

I am trying to call C++ function from VB.Net code which returns string using P/Invoke, but it is returning only single character.
C function Declaration
extern "C" __declspec(dllexport) LPSTR Get_GetDescription(HANDLE)
C function Definition
LPSTR Get_GetDescription(HANDLE resultBreakDown){
return LPSTR(((CalcBreakDown*)resultBreakDown)->GetDescription().c_str());
}
VB.Net Code
<DllImport("FeeEngineDll.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Shared Function Get_GetDescription(ByVal resultBreakDown As IntPtr, ByVal indexSubs As Integer, ByVal indexLine As Integer) As <MarshalAsAttribute(LPStr)> String
End Function
Is there any problem with return type or Marshalling?
extern "C" __declspec(dllexport) LPSTR Get_GetDescription(HANDLE)
Returning a pointer like this is rather dangerous, as it is not clear who owns the memory and thus who should take responsibility for freeing it.
It would be safer to create the buffer in your VB code and pass it into the DLL where the value can be memcpy'ed in. So we could rewrite the C++ side like:
extern "C" __declspec(dllexport) void Get_GetDescription(HANDLE, LPSTR)
void Get_GetDescription(HANDLE resultBreakDown, LPSTR buffer){
memcpy(buffer,
((CalcBreakDown*)resultBreakDown)->GetDescription().c_str(),
((CalcBreakDown*)resultBreakDown)->GetDescription().length()+1);
}
And then redo the VB code as follows:
<DllImport("FeeEngineDll.dll", CharSet:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
Public Shared Sub Get_GetDescription(ByVal resultBreakDown As IntPtr, <MarshalAs(UnmanagedType.LPStr)> ByVal szFilename As StringBuilder)
End Sub
I have added CharSet:=CharSet.Ansi to the DllImport. Your C++ code is not using unicode characters, whereas VB probably will be, so best specify that, you probably don't need to put it in but I like to make these things explicit.
Note the use of StringBuilder instead of String as strings are immutable in VB. Finally, you will need to be careful to allocate enough space in your string builder for the description:
Dim buffer As StringBuilder = New StringBuilder(512)
You can either do this by using a large number in your VB code, as I have just done. This will however, cause problems if your C++ code ever copies more characters than you allocated.
Other better options would either be to pass in the buffer size to the C++ code so that it knows how much it is allowed to write, or to have a get size function in the C++ code that can be used to determine how much space should be allocated for the buffer.

Passing PChar / *char between C++ and Delphi DLL

I have a C++ program, that calls Delphi DLL to initialize a buffer that contains chars.
I am testing out the interface to make sure the data are passed correctly:
In C++ program:
char * pMsg = (char*)malloc(3); //allocate buffer
Init(pMsg * char); //Delphi DLL function
In Delphi DLL:
procedure Init(pMsg:PChar);
var
pHardcodedMsg:PChar;
begin
pHardcodedMsg:= '123';
CopyMemory(pMsg, pHardcodedMsg, Length(pHardcodedMsg));
end;
But, when I try to printf( (const char*)pMsg ) in C++,
It shows me "123" followed by some rubbish characters.
Why is this so?
How can I successfully place an array of char into the buffer and have the string printed out correctly?
Delphi does not use NULL-terminated strings so you need to slap a 0 at the end, as C/C++ uses that to determine where the string data ends (Pascal uses the size of the string at the beginning IIRC).
The usual character is '\0' - escape value 0.
Don't forget to return 4 characters, not 3.
Your Init function doesn't work because
1) pHardcodedMsg is a pointer for which you didn't allocate memory
2) CopyMemory doesn't add a 0 to the end of pMsg
3) the procedure header of Init misses a semi colon at the end of the line
When you are using a unicode version of Delphi you will also have to consider string length and character set conversion