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.
Related
I have a function which takes two CHAR* as input viz. int _stdcall FileTrans(char* InFile, char* OutFile) in a DLL project.
In the function I'm just calling CopyFile(InFile, OutFile, false); after some process (not related to the files). But it says that it needs both inputs as LPCWSTR. I Googled it but couldn't find anything very interesting.
Like all Windows API functions that accept a string parameter, there are actually two variants of the CopyFile function:
CopyFileA is the ANSI version, which takes narrow (non-Unicode) strings in the system's default character set. Basically, it accepts parameters of type const char*, but the Windows headers use the typedef LPCSTR for this.
CopyFileW is the wide version, which takes Unicode strings. In order to do this, it accepts parameters of type w_char*, but the Windows headers use the typedef LPCWSTR for this (note the additional W in the typedef).
Then, depending on whether the UNICODE preprocessor macro is defined for your project (either in your code before you include the Windows headers, or in your project's properties in Visual Studio), the Windows headers define the unadorned CopyFile as either CopyFileA or CopyFileW. Naturally, if UNICODE is defined, CopyFile will be defined as the Unicode version CopyFileW. Otherwise, it will be defined as CopyFileA. The idea is that the call to the general CopyFile function is automatically resolved at compile time to the correct variant.
Of course, now that you understand all of that, you can mostly forget about it. In modern Windows programming, there is absolutely no reason to call the old ANSI versions of functions or to deal with narrow strings at all. Forget that char* can even be used as a string type—those strings are dead to you. The only strings you're going to be using from now on are Unicode strings, composed of wchar_t characters. Thus the UNICODE symbol should always be defined for your code, and you should only use the W version of Windows API functions.
Looking again at the prototype for the CopyFileW function (the same one you get when you call CopyFile with UNICODE defined), we see:
BOOL WINAPI CopyFile(LPCWSTR lpExistingFileName,
LPCWSTR lpNewFileName,
BOOL bFailIfExists);
Recall that you learned above that LPCWSTR is just a typedef synonym for const wchar_t*, a C-style string that is composed of wide characters. You already know why the parameters are marked const: because the function doesn't modify those values.
And because you also learned above that these are the only types of strings that you should be using anymore, the next step is to modify your FileTrans function to accept wide strings (and make them const if it's not going to modify them):
int _stdcall FileTrans(const wchar_t* InFile, const wchar_t* OutFile);
Now, from inside of FileTrans, you can call CopyFile without any problems because you have the right type of strings.
But a bit of free, extra advice: never use raw C-style strings in C++. Always use the C++ string class, defined in the std namespace by the <string> header.
There are two common variants of this class, std::string and std::wstring. As before, the w refers to wide strings, which are the only type you want to use in Windows. So std::wstring is your new replacement for CHAR* throughout your code base.
Change your declaration of the FileTrans function to look like this:
#include <string>
// ...some other stuff...
int __stdcall FileTrans(const std::wstring& InFile, const std::wstring& OutFile);
Note that I've changed your original CHAR* parameters to constant references to std::wstring objects. Constant references work well here, since you're not going to be changing either of those values inside of the function.
If you're unclear on what constant means, how to use references, or how class types generally work in C++, please consult your favorite C++ book)—this is required knowledge for all C++ programmers. Remember that C++ is not the same language as C and therefore the same idioms do not apply. In many cases, there is a better way to do things, and this is certainly an example of such a case.
I just tried the following code(windows xp sp3, vs2010) and LoadLibrary seems to be returning Null.
#include "windows.h"
#include "stdio.h"
int main() {
HMODULE hNtdll;
hNtdll = LoadLibrary(LPCWSTR("ntdll.dll"));
printf("%08x\n", hNtdll);
}
The output I get is 00000000. According to the docs, NULL is returned when the function fails. I tried using GetLastError and the error code is 126(0x7e, Error Mod Not Found).
How can I correct this issue?
Thanks!
You have a string literal, which consists of narrow characters. Your LoadLibrary call apparently expects wide characters. Type-casting isn't the way to convert from one to the other. Use the L prefix to get a wide string literal:
LoadLibrary(L"ntdll.dll")
Type-casting tells the compiler that your char const* is really a wchar_t const*, which isn't true. The compiler trusts you and passes the pointer along to LoadLibrary anyway, but when interpreted as a wide string, the thing you passed is nonsense. It doesn't represent the name of any file on your system, so the API correctly reports that it cannot find the module.
You should use LoadLibrary(_T("ntdll.dll")) LPCWSTR just casts char-based string pointer to widestring pointer.
In addition to the necessity of converting the path string to wchar_t const* by using L prefix (which is already mentioned in the accepted answer). According to my last couple of hours experience:
it worths mentioning that LoadLibrary function does not load the dependency(ies) of the intended library (DLL) automatically. In other word, if you try to load the library X which depends on the library Y, you should do LoadLibrary(Y), then LoadLibrary(X), otherwise loading the library X will fail and you will get error 126.
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.
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.
I am trying to use a C++ dll from a native program. I am following the virtual method scenario as explained here
Lets say my C++ function signature is of the form
int Setup(const char* szIp, const char* szPort);
And the corresponding delphi signature is
function Setup(ip, port: PChar):Integer: virtual; cdecl; abstract;
And somewhere from the delphi program i can call
pObj.Setup('192.168.1.100', '97777');
The control comes into the dll, but szIp and szPort formal parameters only receives the first character of the ip and port that I had passed from the delphi program.
I understand that it has to do with null terminating the string properly in delphi. So i had tried the following too.
var
pzIp, pzPort: PChar;
szIp, szPort: string;
begin
szIp := '192.168.1.2';
szPort := '9777';
//initilize memory for pchar vars
GetMem(pzIp, Length(szIp)+1);
GetMem(pzPort, Length(szPort)+1);
//null terminate the strings
pzIp[Length(szIp)+1] := #0;
pzPort[Length(szPort)+1] := #0;
//copy strings to pchar
StrPCopy(pzIp, szIp);
StrPCopy(pzPort, szPort);
end.
This a'int working either. When i Writeln pzIp and pzPort I get strange results.
Forgot to tell, all member functions from the C++ dll are compiled with __stdcall and exported properly
In Delphi 2010 (and Delphi 2009) the "char" type is actually a WIDEChar - that is, 16 bits wide. So when you call your C++ function, if that is expecting CHAR to be 8 bits wide (so called "ANSI", rather than UNICODE), then it is going to misinterpret the input parameter.
e.g. if you pass the string 'ABC'#0 (I'm showing the null terminator explicitly but this is just an implicit part of a string in Delphi and does not need to be added specifically) this passes a pointer to an 8 byte sequence, NOT 4 bytes!
But because the 3 characters in your string have only 8-bit code-point values (in Unicode terms, this means that what the C++ code "sees" is a string that looks like:
'A'#0'B'#0'C'#0#0#0
Which would explain why your C++ code seems only to be getting the first character of the string - it is seeing the #0 in the 2nd byte of that first character and assuming that it is the null terminator for the entire string.
You either need to modify your C++ code to correctly receive pointers to WideChar strings, OR modify the function signature in Delphi and convert your strings to ANSIString in the Delphi code before passing those to the C++ function:
Revised function signature:
function Setup(ip, port: PANSIChar):Integer: virtual; stdcall; abstract;
and the corresponding "Long hand" showing conversion of strings to ANSIString before calling the function - the compiler may take care of this for you but you might find it helpful to make it clear in your code rather than relying on "compiler magic":
var
sIPAddress: ANSIString;
sPort: ANSIString;
begin
sIPAddress := '192.168.1.100';
sPort := '97777';
pObj.Setup(sIPAddress, sPort);
// etc...
Is char the same size in both compilers? If you are using D2009/D2010, char is now 16-bits.
If I understand correctly your function prototype should be stdcall as well.
function Setup(ip, port: PChar):Integer: virtual; stdcall; abstract;
ps. Delphi strings are already null-terminated.