Pass unicode string from VB.net to C++ dll - c++

I have tried a lot of things but I cannot get this to work. I can pass and receive ordinary strings (char*) to C++, but I cannot receive Unicode strings (w_char_t *) in C++.
Here are some bits of the code on c++
__declspec(dllimport) int __stdcall readFile(const w_char_t *file_path)
on VB.net
Public Declare Function readFile Lib "MyDll.dll" Alias "_readFile#4" (ByVal file_path As String) As Integer
when I change w_char_t to char I receive the correct string.
I have found a lot of material on VB6, but not for VB.net
Any help greatly appreciated.
Leon

Public Declare Unicode Function readFile Lib "MyDll.dll" Alias "_readFile#4" (ByVal file_path As String) As Integer
Also, your C++ code has dllexport not dllimport, right?

Try converting UnicodeString to String (by using some System Defined Function), and then pass that string or char* to C++ DLL.
Hope this will work.

Related

Exporting functions from DLLs, LoadLibrary() needs the string cast with TEXT to compile without error

I'm learning to write and use DLLs and this is my first attempt at exporting a function from my dll. It works, but this line is what gave me trouble and what I've been able to find regarding the TEXT cast for UNICODE and ANSI I think I need some guidance. As far as I can find this question has not been asked elsewhere on the site so I apologize if anyone finds what I couldn't.
HINSTANCE hInstLibrary = LoadLibrary("MyDLL.dll");
My initial usage, from a short tutorial on explicit linking gives E0167 and C2664 errors regarding LPCWSTR type
HINSTANCE hInstLibrary = LoadLibrary(TEXT("MyDLL.dll"));
Casting the string to TEXT solves the problem, though I'm not sure why and would like to know
HINSTANCE hInstLibrary = LoadLibraryA("MyDLL.dll");
The line I decided to use in the working example. LoadLibraryA() expands LoadLibrary to accept ANSI rather than Wide, which may be the root of my misunderstanding. Why is this necessary when most examples I find show LoadLibrary("NameOfDLL.dll")?
Why does the string not satisfy the standard LoadLibrary() call?
LoadLibrary() is a preprocessor macro. It maps to either LoadLibraryW() or LoadLibraryA() depending on whether UNICODE is defined or not, respectively. LoadLibraryW() takes a const wchar_t* string as input, while LoadLibraryA() takes a const char * string instead.
The string literal "MyDLL.dll" is a const char[10], which decays into a const char *. If UNICODE is defined, LoadLibrary("MyDLL.dll") will fail to compile, as you cannot pass a const char * where a const wchar_t * is expected.
TEXT() is also a preprocessor macro. If UNICODE is defined, it appends an L prefix to the specified literal making the literal use wchar_t, otherwise no prefix is added and the literal uses char instead.
Thus, if UNICODE is defined, then LoadLibrary(TEXT("MyDLL.dll")) is compiled as LoadLibraryW(L"MyDLL.dll"), otherwise it is compiled as LoadLibraryA("MyDLL.dll") instead.
A majority of Win32 APIs that deal with textual data have similar A and W versions, and corresponding UNICODE-aware preprocessor macros. So, when using character/string literals with these APIs, you should always use the TEXT() macro. Otherwise, just use the A and W APIs directly as needed, depending on the type of textual data you are working with.

Are MFC CString and LPTSTR interchangeable?

I am trying to use a DLL with an ANSI C compiler. One of the DLL functions takes a void pointer. In some sample Windows code that was provided with the DLL, the struct that gets passed to the function is defined as having three CString entities. I have told the author of the DLL that they should not be passing MFC classes through their DLL functions. They have told me just to replace the CString declarations in the struct with char arrays and it should be fine. I'm 99% sure that's wrong, but since I don't have VC++ and don't have any experience with MFC, and since I've seen some posts saying LPTSTR can be used in place of CString (What is `CString`?), I'm starting to wonder if I'm wrong.
Can someone please confirm for me that CString and LPTSTR are not interchangeable as arguments to a function? If you can provide the source for the definition of the CString class, that would be helpful so I can send it to the DLL's author and explain that the memory footprint of a char array is not the same as a CString class, and that you can't pass a pointer to a struct that was defined with char arrays and then treat it as a bunch of CString objects.
CString is an alias of the CStringT class template. Objects of this class are really better not to pass to the DLL. The character type of the string class can be TCHAR (for both ANSI and Unicode character strings - see explanation below). The definition of CString (and CStringT) can most likely be found in the atlstr.h header file.
LPTSTR is a regular pointer to a sequence of characters. The data type (TCHAR*) depends on the settings of the development environment: if the "Use Unicode Character set" option is selected, the TCHAR data type will be wchar_t (and LPTSTR will be wchar_t*, respectively). If the "Use Multi-byte character set" is selected, the TCHAR will be defined as char (and LPTSTR will be char*).
So the question about interchangeability between CString and LPTSTR is not so simple. It also depends on how the DLL is written. If the DLL was designed with the same environment settings as the main program, then CString and LPTSTR can really be interchangeable.
Also, remember that CStirng is a class with many methods, while LPTSTR is just a pointer.

What's is the VB6 equivalent of a C++ char array?

I'm trying to call a DLL written in C++ from a VB6 application.
Here's the C++ example code for calling the DLL.
char firmware[32];
int maxUnits = InitPowerDevice(firmware);
However, when I try to call it from VB6 I get the error bad DLL calling convention.
Public Declare Function InitPowerDevice Lib "PwrDeviceDll.dll" (ByRef firmware() As Byte) As Long
Dim firmware(32) As Byte
InitPowerDevice(firmware)
Edit: The C++ Prototype:
Name: InitPowerDevice
Parameters: firmware: returns firmware version in ?.? format in a character string (major revision and minor revision)
Return: >0 if successful. Returns number of Power devices connected
CLASS_DECLSPEC int InitPowerDevice(char firmware[]);
Been a long time, but I think you also need to change your C function to be stdcall.
// In the C code when compiling to build the dll
CLASS_DECLSPEC int __stdcall InitPowerDevice(char firmware[]);
' VB Declaration
Public Declare Function InitPowerDevice Lib "PwrDeviceDll.dll" _
(ByVal firmware As String) As Long
' VB Call
Dim fmware as String
Dim r as Long
fmware = Space(32)
r = InitPowerDevice(fmware)
I don't think VB6 supports calling cdecl functions in any normal way - there may be hacks for doing it. May be you can write a wrapper dll which wraps the cdecl function with a stdcall function and just forwards the call.
These are some hacks - but I haven't tried it.
http://planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=49776&lngWId=1
http://planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=62014&lngWId=1
You need to pass a pointer to the beginning of the array contents, not a pointer to the SAFEARRAY.
Perhaps what you need is either:
Public Declare Function InitPowerDevice Lib "PwrDeviceDll.dll" ( _
ByRef firmware As Byte) As Long
Dim firmware(31) As Byte
InitPowerDevice firmware(0)
or
Public Declare Function InitPowerDevice CDecl Lib "PwrDeviceDll.dll" ( _
ByRef firmware As Byte) As Long
Dim firmware(31) As Byte
InitPowerDevice firmware(0)
The CDecl keyword only works in a VB6 program compiled to native code. It never works in the IDE or in p-code EXEs.
Since your error is "bad calling convention", you should try changing the calling convention. C code use __cdecl by default, and IIRC VB6 had a Cdecl keyword you could use with Declare Function.
Otherwise you can change the C code to use __stdcall, or create a type library (.tlb) with the type information and calling convention. That might be better than Declare Function because you use the C datatypes when defining the type library, but VB6 recognizes them just fine.
As far as the argument type is concerned, firmware() As Byte with ByVal (not ByRef) should be fine.

Pass BSTR from C++ to C#

I have C# project "X" ,I have exposed methods in it to the C++ project "Y".
X has method signature as follows -
public void WriteInformation(string sInfo)
{
m_logger.ErrorInfo("{0}", sInfo);
}
As I am exporting it to a C++ using .TLB file I checked declaration of this method in .tlh file which generates on #import of .tlb file.
virtual HRESULT __stdcall WriteInformation ( /*[in]*/ BSTR sMsg ) = 0;
I am calling this method in C++ project and passsing argument as follows -
oLog->WriteInformation(BSTR("Info write successful"));
Issue here is the string passed from C++ always becomes garbage or null , I debugged it and I can see value of sInfo is always garbage or null.
Please let me know what method should be followed to pass string from C++ to C#.
You try to pass an ANSI string in place of BSTR. BSTR must be a wide character string. Also you shouldn't pass a string literal, you should properly allocate a BSTR using SysAllocString() (or better yet) a wrapper class like ATL::CComBSTR or _bstr_t. Btw _bstr_t has a constructor that will accept const char* and do the ANSI->UTF16 conversion for you.
I dont think its possible to interact with C++ and C# directly. I had interacted using a C++/CLI wrapper.

Pass BSTR from C++ DLL function to VB6 application

I have this code in my VB6 app:
Private Declare Function FileGetParentFolder Lib "Z-FileIO.dll" _
(ByVal path As String) As String
Output.AddItem FileGetParentFolder(FileText.Text)
Output is a list, FileText is a text field containing a file path. My C++ DLL contains this function:
extern "C" BSTR ZFILEIO_API FileGetParentFolder(Path p)
{
try {
return SysAllocString(boost::filesystem::path(p).parent_path().c_str());
} catch (...) {
return SysAllocString(L"");
}
}
where Path is typedef'd as LPCSTR. The argument comes into my DLL perfectly, but whatever I try to pass back, the VB6 app shows only garbage. I tried several different methods with SysAllocStringByteLength, casting the SysAllocString argument to LPCWSTR and other variants. Either, I only see the first letter of the string, or I see only Y's with dots, just not the real string. Does anyone know what the real method is for creating and passing valid BSTRs from C++ to VB6?
Hopefully this will point you in the right direction. From memory...
VB6 uses COM BSTRs (2-byte wide character strings) internally, but when communicating with external DLLs it uses single- or multi-byte strings. (Probably UTF-8, but I don't remember for sure.) Your Path typedef to LPCSTR is an ANSI string, and that's why you can receive it correctly. The return value you generate is a wide-character string, but VB is expecting an ANSI string. You'll need to use WideCharToMultiByte to convert your return value before returning it.
Seems a little odd that VB does this implicit conversion, but that's the way it is. (As far as I remember.)
If you insist on using the function signature then you have to prepare a custom typelib for VB6 that includes this
[dllname("Z-FileIO.dll")]
module ZFileIO
{
[entry("FileGetParentFolder")]
BSTR FileGetParentFolder ([in] LPWSTR path);
};
In Declares param-types As String are automagically converted to ANSI string, i.e. LPSTR. The only way to pass/receive a unicode string (LPWSTR or BSTR) is by using typelib API function declaration.
Other than that you can always use As Long params in the declaration and expect LPWSTRs but then the consumer will have to wrap strings in StrPtr on every call to the API function.