I am working on a C++ Console Application in Visual Studio 2012 on Windows 7 and I want to get the values of some environment variables from within the application.
Here is what I've tried so far -:
int main()
{
char a[1000];
int s=GetEnvironmentVariableA("HOME",a,1000);
}
However, I am getting the value of s to be 0, indicating that variable "HOME" does not exist.
Also, getenv("HOME") returns NULL too.
So, what is the correct procedure of doing this ?
What this program is telling you, most likely, is that your process environment does not contain a variable named HOME. Note that HOME is not a variable that you would expect to be defined, unless you have taken steps to define it. Either by adding it to the system's environment, or by specifying a bespoke environment when creating the process.
The documentation says the following about the return value:
If the function succeeds, the return value is the number of characters
stored in the buffer pointed to by lpBuffer, not including the
terminating null character.
If lpBuffer is not large enough to hold the data, the return value is
the buffer size, in characters, required to hold the string and its
terminating null character and the contents of lpBuffer are undefined.
If the function fails, the return value is zero. If the specified
environment variable was not found in the environment block,
GetLastError returns ERROR_ENVVAR_NOT_FOUND.
So, if the function returns 0, do as the documentation says. Call GetLastError to find out why the function call failed.
But as I said, with probability very close to 1, the reason will simply be that your process environment has not defined a variable named HOME.
As to how you move forward, most likely you are looking for a location in the user's profile. Exactly how you do this will depend on where in the profile you wish to store/load the file. One of the APIs related to CSIDL or known folder IDs will serve your needs.
Regarding your question,
” So, what is the correct procedure of doing this ?
Windows doesn't have a single HOME standard variable. Instead, in the old days there were HOMEDRIVE and HOMEPATH, and apparently because they didn't know about it, with Windows Explorer in Windows 95, a new variable called USERPROFILE.
[C:\Users\alfps_000]
> set home
HOMEDRIVE=C:
HOMEPATH=\Users\alfps_000
[C:\Users\alfps_000]
> set user
USERDOMAIN=FRIKADELL
USERDOMAIN_ROAMINGPROFILE=FRIKADELL
USERNAME=alfps_000
USERPROFILE=C:\Users\alfps_000
[C:\Users\alfps_000]
> _
The silly triple-oh suffix (as if I were better than double-oh seven) is just what Windows 8.1 saw fit to give me. It's just too much work to cajole Windows into reasonable choices. And so not just with usernames but also with environment variables.
Here's your program rewritten to use the Windows variable that vaguely corresponds to Unix-land HOME, namely USERPROFILE:
#include <iostream>
#include <stdlib.h> // getenv
using namespace std;
auto main() -> int
{
cout
<< "User's profile directory: "
<< "[" << getenv( "USERPROFILE" ) << "]"
<< endl;
}
The Windows environment variables are awkward and not guaranteed, but still usable in scripts and very simple programs like the one above. In more serious C++ code you can instead use the SHGetKnownFolderPath API function. With Visual C++ it can look like this:
#undef UNICODE
#define UNICODE
#include <windows.h>
#include <shlobj.h> // SHGetKnownFolderPath
#include <objbase.h> // CoTaskMemFree
#include <iostream> // std::wcout
#include <memory> // std::unique_ptr
#include <stdexcept> // std::runtime_error, std::exception
#include <stdlib.h> // EXIT_FALURE, EXIT_SUCCESS
using namespace std;
void cotaskmem_free( wchar_t* p ) { CoTaskMemFree( p ); }
auto main() -> int
{
using X = runtime_error;
using String_deallocation = unique_ptr<wchar_t[], void(*)(wchar_t*)>;
try
{
wchar_t* path;
HRESULT const hr = SHGetKnownFolderPath(
FOLDERID_Profile, // REFKNOWNFOLDERID rfid -> %USERPROFILE%
0, // DWORD dwFlags,
0, // HANDLE hToken,
&path // PWSTR *ppszPath
);
if( FAILED( hr ) ) { throw X( "SHGetKnownFolderPath failed" ); }
String_deallocation const path_cleanup( path, cotaskmem_free );
wcout << "User profile directory: [" << path << "]" << endl;
return EXIT_SUCCESS;
}
catch( exception const& x )
{
wcerr << "!" << x.what() << endl;
}
return EXIT_FAILURE;
}
g++ (per version 4.8.2) doesn't yet support API functions from Windows Vista and onward, at least not in general, so if you need to support g++ use some older function.
Note:
It's not unlikely that whatever you intended to access or place in %HOME%, would better be accessed or placed in one of the other special users's directories, also available via SHGetKnownFolderPath.
Related
I am trying to find the build version of Windows Server 2016 machines, for example RS1 or RS3. There was an API to do this - GetVersionEx() - but is now deprecated.
MSDN says to use Version Helper Functions instead.
I want the build version, for ex: 1607 for RS1.
Is there an API to get this?
Option 0: (per RbMm) Use [RtlGetVersion] from the driver development kit.
Option 1: [Updated] Grab the version number of a system DLL like kernel32.dll. MSDN used to bless this approach, saying:
To obtain the full version number for the operating system, call the GetFileVersionInfo function on one of the system DLLs, such as Kernel32.dll, then call VerQueryValue to obtain the \StringFileInfo\\ProductVersion subblock of the file version information. [From an Internet Archive snapshot of MSDN circa 2017]
That would look something like this:
// Quick hack without error checking.
#include <cassert>
#include <iomanip>
#include <iostream>
#include <vector>
#include <Windows.h>
int main() {
const auto system = L"kernel32.dll";
DWORD dummy;
const auto cbInfo =
::GetFileVersionInfoSizeExW(FILE_VER_GET_NEUTRAL, system, &dummy);
std::vector<char> buffer(cbInfo);
::GetFileVersionInfoExW(FILE_VER_GET_NEUTRAL, system, dummy,
buffer.size(), &buffer[0]);
void *p = nullptr;
UINT size = 0;
::VerQueryValueW(buffer.data(), L"\\", &p, &size);
assert(size >= sizeof(VS_FIXEDFILEINFO));
assert(p != nullptr);
auto pFixed = static_cast<const VS_FIXEDFILEINFO *>(p);
std::cout << HIWORD(pFixed->dwFileVersionMS) << '.'
<< LOWORD(pFixed->dwFileVersionMS) << '.'
<< HIWORD(pFixed->dwFileVersionLS) << '.'
<< LOWORD(pFixed->dwFileVersionLS) << '\n';
return 0;
}
Note that the original MSDN link now redirects to a newer documentation set that doesn't mention this approach. I suppose that means this is no longer a supported technique, and, presumably, all the compatibility hacks for older code might prevent an application from getting the actual answer.
Option 2: Query the registry, specifically:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
which has values for CurrentMajorVersionNumber, CurrentMinorVersionNumber, and CurrentBuildNumber.
I can't find official documentation for these values, so this may not be MSDN-approved or future-proof.
Option 3: Use GetProductInfo if available and fall back to GetVersionInfo if it's not.
I'm building a program, that creates and deletes directories. I use the MSVC compiler (Visual Studio 2017), which is the reason i can't use "getcwd()" or "dirent.h" respectively.
I have tried several different ways to get the current working directory, but there was always a problem. I managed to print the cwd with "_wgetcwd()". However I couldn't find how I could convert it's output to use it in "_rmdir()" or "_wrmdir()" in my research.
My main goal is to be able to remove a directory without having to install some new compiler. If this condition is met, any help is appreciated, because I already tried to install a different Compiler, but I didn't got it to work. I also tried different ways to get the cwd and convert it into the desired datatype, but nothing worked with the scope of my IDE and my very basic knowledge.
I'm pretty much a beginner in programming and I'm learning with this book, that unfortunately uses "dirent.h". The following is a snippet of my current code, where I have eradicated all errors. However I still get this last annoying exception:
#include <iostream>
#include <stdlib.h>
#include<string>
#include <direct.h>
int main() {
int t = 0;
std::string str;
char xy = ' ';
char* _DstBuf = &xy;
const char* uniquename;
uniquename = _getdcwd(0, _DstBuf, _MAX_PATH);
std::cout << "Current path is ";
while (*uniquename != '\0') // this pointer points to the beginning of my path
{
std::cout << char(*uniquename); // prints one char of the path name for each iteration
char var = char(*uniquename);
str.push_back(var); //here the exception below is thrown.
// jump to the next block of memory
uniquename++;
}
std::cout << str <<'\n';
const char* lastchance = str.c_str();
if (_wchdir('..')) {
std::cout << "changing directory failed.\n";
}
if (_rmdir(lastchance) == -1 && errno == ENOTEMPTY) {
std::cout << "Given path is not a directory, the directory is not empty, or the directory is either the current working directory or the root directory.\n";
}
else if (_rmdir(lastchance) == -1 && errno == ENOENT)
{
std::cout << "Path is invalid.\n";
}
else if (_rmdir(lastchance) == -1 && errno == EACCES)
{
std::cout << "A program has an open handle to the directory.\n";
}
else if (_rmdir(lastchance)) {
std::cout << "removing directory still not possible\n";
}
}
This is the exception I get:
Unhandled exception at 0x6E656D69 in Experimentfile.exe: 0xC00001A5: An invalid exception handler routine has been detected (parameters: 0x00000003).
So if you are going to program in a C style (even though you have a C++ compiler) you're going to have to learn how arrays and pointers work. It's far too big a topic to learn from the internet you need a good book.
However I'll point out some of the errors
char xy = ' ';
char* _DstBuf = &xy;
const char* uniquename;
uniquename = _getdcwd(0, _DstBuf, _MAX_PATH);
This is just hopelessly wrong. It compiles but that doesn't mean it's going to work. This is what's required
char DstBuf[_MAX_PATH];
_getdcwd(0, DstBuf, _MAX_PATH);
_getdcwd requires an array which is passed as a pointer (see, you need to learn about arrays and pointers).
Then you attempt to print out the result and assign the result to a string. Again the code is way more complex than it needs to be. Here's the simpler version
std::string str = DstBuf;
std::cout << str <<'\n';
Then you attempt to change directories. I don't know why you are using the wide version _wchdir when you have narrow strings, use _chdir instead. And again the parameter is incorrect, '..' is a multi character literal, but _chdir requires a C-string. Here's the correct verison using _chdir.
if (_chdir("..")) {
std::cout << "changing directory failed.\n";
}
Then you try to remove a directory four times, obviously you can't remove a directory more than once.
And so on, and so on.
There are several problems with your code. I think your exception is caused by this line:
uniquename = _getdcwd(0, _DstBuf, _MAX_PATH);
Which is calling _getdcwd and passing it a pointer to a single character, while also specifying that the pointer can hold up to _MAX_PATH. This is completely undefined behaviour.
To fix this issue I think you should completely change what you are doing. Your code reads like C code, apart from the scattered C++ parts like std::string. Since C++17 the standard library has support for filesystem manipulation built in. Therefore you can rewrite your code using std::filesystem in a much more concise and safe way.
... // get path to delete and store it in del_path (del_path should be an std::filesystem::path object)
std::filesystem::remove_directories(del_path); // Recursive delete the directory specified by del_path
...
Note: std::filesystem::remove_directories will throw an std::filesystem::filesystem_error if it fails.
You can obtain the current directory using std::filesystem through std::filesystem::current_path.
I also recommend picking up a better book on C++ since this one does not seem to be teaching you good practices
At my workplace, our app determines the locale of the user session by using a code that is similar to below (though there are various layers of code it passes through before it reaches here at the time of startup, so the problem is/may not evident by running the code below)
#include <CoreFoundation/CoreFoundation.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>
// Reference release
struct reference_close
{
void operator()(const void *ref) const
{
CFRelease(static_cast<CFTypeRef>(ref));
}
}; // end of reference_close structure
typedef std::unique_ptr<const void, reference_close> reference_uptr;
std::string get_user_locale()
{
reference_uptr ref_ptr(CFLocaleCopyCurrent());
CFLocaleRef locale_ref(static_cast<CFLocaleRef>(ref_ptr.get()));
if (locale_ref == nullptr)
{
return std::string();
}
const size_t default_size(128);
std::vector<char> buff(default_size);
CFStringRef str_ref(CFLocaleGetIdentifier(locale_ref));
if (str_ref != nullptr)
{
CFIndex len(CFStringGetLength(str_ref) + 1);
if (len > boost::numeric_cast<CFIndex>(default_size))
{
buff.resize(len);
}
buff[0] = 0;
if (!CFStringGetCString(str_ref, &buff[0], len, kCFStringEncodingISOLatin1))
{
return std::string();
}
}
return std::string(&buff[0]);
} // end of get_user_locale()
int main()
{
std::cout << "get_user_locale() : "<<get_user_locale() << std::endl;
return 0;
}
The app has a well defined bundle structure with necessary resources and localization resource directories e.g Contents/Resources/ja.lproj
Lately, we are facing an issue wherein we do the following
1) Change the system language and country in the preferences e.g. change from en_US to ja_JP
2) Reboot the machine
3) Launch the app and see the erroneous output e.g en_JP
4) Re-launch the app to get the correct answer as ja_JP
I have read the documentation of CFLocaleCopyCurrent which states that
Settings you get from this locale do not change as a user's
preferences are changed so that your operations are consistent.
Typically you perform some operations on the returned object and then
release it. Since the returned object may be cached, you do not need
to hold on to it indefinitely.
I also checked the another documentation on lifetime of CFLocale which states that
The object you get back from CFLocaleCopyCurrent does not change when
the user changes their Preferences settings. Moreover, the object
itself may be cached by the runtime system, so successive calls of
CFLocaleCopyCurrent may return the same object, even if a user has
changed preference settings. If you want to ensure that your locale
settings are consistent with user preferences, you must synchronize
preferences and get a new locale object with CFLocaleCopyCurrent.
Given this info, I tried to incorporate CFPreferencesSynchronize and/or CFPreferencesSynchronize in the app code (just before I call the CFLocaleCopyCurrent) to get the most updated locale. But no luck.
Do I need to do anything extra to make sure that I get the most recent/updated value of locale object from the system preferences ?
Use [NSLocale autoupdatingCurrentLocale] which will change as the user changes their locale settings, but as NSLocale is an Objective-C class you will need to implement glue code as Objective-C++.
Here is some untested example code, giving you the idea:
Create a file called AppleLocale.mm (unless of course your project is for Apple platforms only so doesn't need the Apple name prefix).
#include <Foundation/Foundation.h>
std::string get_user_locale()
{
NSLocale* locale = [NSLocale autoupdatingCurrentLocale];
return std::string([[locale localeIdentifier] UTF8String]);
}
Note: You'll need a function prototype in some header as well, of course.
I'm loading a delphi dll in c++. When I use functions with char* as buffers (char* given as parameter to the procedure) I get only trash data.
When I have functions that return char* all is fine.
I'm new to c++ and I spend a lot of time trying to crack this. Please help.
Everything is explained in code below. I have put there 3 functions to show exacly what I mean.
Example function that has problem with buffer is:
DLL_PingConnection(var avXml:PChar):Boolean; - it returns true/false, as parameter it takes buffer and the function is done in buffer there should be valid xml (but there is only trash)
#include <windows.h> //this will load delphi dll
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <string.h>
using namespace std;
// ------------------------------------------------ pointers on functions inside Delphi DLL (32 bits)
typedef bool(*TYPE_DLL_SetLicense)(char*, char*); //initialize dll stuff - I load licence from a file into char* - everything works fine
typedef bool(*TYPE_DLL_PingConnection)(char*); //the char* is buffer - I give empty char* as parameter and I should get correct xml with serwer data - I GET ONLY TRASH :(
typedef char*(*TYPE_DLL_ERR_DESCRIPTION)(void); //this function does not use buffer it returns char* - everything works fine
//so as you see problem is with buffers and function like this: DLL_PingConnection(buffer)
int main()
{
// ------------------------------------------------ Loading the library
HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\full_path\\SOMEDLL.dll");
//checking the library
if (hGetProcIDDLL == NULL) {std::cout << "Could NOT load the dynamic library" << std::endl;return EXIT_FAILURE;}
else{std::cout << "dynamic library loaded" << std::endl;}
// ------------------------------------------------ START: resolving functions adresses
TYPE_DLL_SetLicense DLL_SetLicense = (TYPE_DLL_SetLicense)GetProcAddress(hGetProcIDDLL, "DLL_SetLicense");
if (!DLL_SetLicense) {std::cout << "Could NOT locate the function: DLL_SetLicense" << std::endl;return EXIT_FAILURE;}
else{std::cout << "Function DLL_SetLicense located" << std::endl;}
TYPE_DLL_PingConnection DLL_PingConnection = (TYPE_DLL_PingConnection)GetProcAddress(hGetProcIDDLL, "DLL_PingConnection");
if (!DLL_PingConnection) {std::cout << "Could NOT locate the function: DLL_PingConnection" << std::endl;return EXIT_FAILURE;}
else{std::cout << "Function DLL_PingConnection located" << std::endl;}
TYPE_DLL_ERR_DESCRIPTION DLL_ERR_DESCRIPTION = (TYPE_DLL_ERR_DESCRIPTION)GetProcAddress(hGetProcIDDLL, "DLL_ERR_DESCRIPTION");
if (!DLL_ERR_DESCRIPTION) {std::cout << "Could NOT locate the function: DLL_ERR_DESCRIPTION" << std::endl;return EXIT_FAILURE;}
else{std::cout << "Function DLL_ERR_DESCRIPTION located" << std::endl;}
std::cout << "\n\nInitialization over. \n\n" << std::endl;
// ------------------------------------------------ START: calling functions from delphi dll
//DLL_SetLicence - this function take buffer as parameter, but dont return anything into the buffer. All works fine.
//start - we read licence from file
char buffer_licence[1242];
memset(buffer_licence,0,sizeof(buffer_licence));
//I read content of buffer_licence usinf ifstream from the file here (but I don't put the code, to keep sample minimal)
//we set licence with dll function
bool is_licence = DLL_SetLicense(buffer_licence,(char*)"");
//the output
if (is_licence == TRUE)
std::cout << "Licence has been set\n";
else
std::cout << "Licence has been NOT set\n";
//DLL_PingConnection - it takes empty buffer as parameter, it should save xml into buffer but it saves only trash.
//we try to save ping to the file - buffer
char buffor_ping_xml[2000];
memset(buffor_ping_xml,0,sizeof(buffor_ping_xml));
//this should gieve proper xml, but it returns only trash.... please help
bool is_ping = DLL_PingConnection(buffor_ping_xml);
if(is_ping)
{
std::cout << "DLL_PingConnection True\n"; //function returned true, so it worked correct.
std::cout << buffor_ping_xml; //but in the buffer is trash that I show on the screen. I also tried to put buffor_ping_xml info the file (diferent ways) but always result was trash just like on screen.
}
else
{
std::cout << "DLL_PingConnection False: \n";
}
//DLL_ERR_DESCRIPTION - if will automaticly return error description if there is any error to report. No buffer, no problems.
std::cout << buffor_ping_xml; //the data on screet is fine, so is in file and everywhere else.
return EXIT_SUCCESS;
}
PingConnection function will return only this instead of good xml.
EDIT:
Oroginally I used Netbeans + MinGW, but as suggested in comments I have used alternative compilers: Borland builder c++ 6.0, and Embarcadero RAD Studio XE3 (C++ Builder). The problems stayed the same even thou I used all calling convention types Remy Lebeau mentioned.
typedef bool(*TYPE_DLL_PingConnection)(char*); //standard calling convention default for compiler - returns trash
typedef bool(__cdecl *TYPE_DLL_PingConnection)(char*); //returns trash also
typedef bool(__stdcall *TYPE_DLL_PingConnection)(char*); //doesnt write anything to the buffer
typedef bool(__fastcall *TYPE_DLL_PingConnection)(char*); //returns trash
I have encountered small problem under c++ builder. I can't clean buffer under this enviroment:
memset(buffer,0,sizeof(buffer)); // will crash the program under c++ builder
Trying to use 'char *&' will crash the program also.
typedef bool(__cdecl *TYPE_DLL_PingConnection)(char*&);
OR
typedef bool(__stdcall *TYPE_DLL_PingConnection)(char*&);
OR
typedef bool(__fastcall *TYPE_DLL_PingConnection)(char*&);
char * buffer;
bool is_ping = DLL_PingConnection(buffer);
Using char ** will cause type mismatch with buffer.
EDIT2:
As requested by David Heffernan I attach sample of documentation. Important parts are trasnated to english. Rest is just structure of xlm that PIngConnection should return. Not much of help there - entire documentation is like this.
PS: I asked similar question here: Trash characters when using buffers in c++ - code based on WxWidgets (I though WxWidgets creates the problem, but it doesn't. Maybe someone will find WxWidgets code usefull thou).
EDIT 3:
I managed to get some more information about dll.
Delphi version is 7.
For sure calling type is stdcall. ( DLL_PingConnection: function(var avXml: PChar): Boolean; stdcall; )
This is how a function from this dll is called in delphi:
lPointer := nil; //pointer
lOSOZPointer := nil; //pointer
lpXML := nil; //pChar
lpXML:=StringToPChar(lXML);
lPointer := lpXML;
lWynik:=OSOZ_GetServerDataTime(lpXML);
if lWynik then
begin
lOSOZPointer := lpXML;
//akcja na wyniku
end;
if lPointer <> nil then begin
Freemem(lPointer);
end;
if lOSOZPointer <> nil then begin
OSOZ_FreeMem(lOSOZPointer);
end;
DLL_PingConnection(var avXml:PChar):Boolean;
This is not a full declaration. Obviously, it is a function since it has a Boolean return type. But does it also declare a calling convention as well - stdcall (__stdcall in C/C++) or cdecl (__cdecl in C/C++)? If not, then it is using Delphi's default register convention instead (which is __fastcall in Borland/CodeGear/Embarcadero C++ compilers only, but has no equivalent in any other C/C++ compiler). Your existing typedefs are using your C++ compiler's default calling convention, which is usually __cdecl. Calling convention mismatches are the most common problem with using DLLs, as it causes mismanagement of the call stack, which affects how parameters are passed, accessed, and cleaned up.
Also, what version of Delphi was the DLL written in? PChar is PAnsiChar (char* in C++) in Delphi 2007, but is PWideChar (wchar_t* in C++) in Delphi 2009 and later. Chances are, since the data is XML, then PAnsiChar/char* is likely being used.
Also, the PChar parameter is being passed as a var in the Delphi declaration, which is the same as a pointer in C and a reference in C++.
You need these important pieces of information in order to use this DLL function in C/C++ code. Unless the documentation explictly states these details, or the DLL has a C/C++ .h/.hpp file showing the actual declaration, then the best you can do is guess, and there are several variations possible given the incomplete declaration you have shown so far:
(char*& can be replaced with char** if needed):
typedef bool (__cdecl *TYPE_DLL_PingConnection)(char*&);
typedef bool (__stdcall *TYPE_DLL_PingConnection)(char*&);
typedef bool (__fastcall *TYPE_DLL_PingConnection)(char*&);
typedef bool (__cdecl *TYPE_DLL_PingConnection)(wchar_t*&);
typedef bool (__stdcall *TYPE_DLL_PingConnection)(wchar_t*&);
typedef bool (__fastcall *TYPE_DLL_PingConnection)(wchar_t*&);
If the DLL functions are using cdecl or stdcall, then you are OK, as most C/C++ compilers support those calling conventions. However, if the DLL functions are using register instead, and if you are not using a Borland/CodeGear/Embarcadero C++ compiler, then you are SOL. You would have to wrap the DLL inside another Delphi-written DLL that exports wrapper functions that use more portable signatures.
I am a C++ (MSVC) writer, VB newbie trying to assist an expert VB.net writer who has just not done this task before.
We wish to develop both C/C++ and VB applications to use a DLL written in C++ with C extern-ed API functions. The C++ program is working just fine. It's VB where we are having difficulties.
The DLL provides an extern C function:
RegisterCallback( void* cbFuncPtr, void* dataPtr );
NOTE 1: See my note below for a design change and the reasons we made it.
NOTE 2: Additional update added as an answer below.
where the callback function havs this C typedef:
typedef (void)(* CALL_NACK)(void*);
The cbFuncPtr is expected to be a function pointer to some VB function that will get called as the CALL_BACK. The dataPtr is a pointer to a data structure that has this C definition:
typedef struct
{
int retCode;
void* a_C_ptr;
char message[500];
} cbResponse_t;
where a_C_ptr is an internal pointer in the DLL that the VB can cast tolong`. It uniquely identifies where in the DLL the callback was made and allows the VB function to recognize calls from same/different locations.
We are able to access and run the RegisterCallback() function from VB just fine. Logging shows we get there and that data is passed in. It is the actual data that seems to be the problem.
In reading about a million forum entries we have learned that VB doesn't know what pointers are and that a VB structure is more than just organized memory. We're pretty sure the "address" of a VB structure is not what C thinks an address is. We've seen repeated references to "marshaling" and "managed data", but lack enough understanding to know what that is telling us.
How should we code VB to give the DLL the execution address of its callback function and how do we code up a VB construct that the DLL can fill in just as it does for C++?
Might we need a DLL function where the calling app can say "C" or "VB" andhave the DLL handle the sturcture pointers differently? If so, how would one code up C to fill in the VB structure?
This is a bit too big and deep to just be an edit to the original posting...
From the link posted by #DaveNewman, I extracted this gem:
Here's a bit about the compact framework, but it's the same in the
grown-ups framework:
The .NET Compact Framework supports automatic marshaling of structures
and classes that contain simple types. All fields are laid out
sequentially in memory in the same order as they appear in the
structure or class definition. Both classes and structures appear in
native code as pointers to C/C++ structs.
Objects in the managed heap can be moved around in memory at any time
by the garbage collector, so their physical addresses may change
without notice. P/Invoke automatically pins down managed objects
passed by reference for the duration of each method call. This means
pointers passed to unmanaged code will be valid for that one call.
Bear in mind that there is no guarantee that the object will not be
moved to a different memory address on subsequent calls.
http://msdn.microsoft.com/en-us/library/aa446538.aspx#netcfmarshallingtypes_topic6
This is major hurdle for a RegisterCallback( fcnPtr, dataPtr) function. The pointer passed in at registration time could change at any time the RegisterCallback() is not the current statement. The posting author summed it up this way
You don't need to do anything as the structures are pinned automatically for duration of the call.
implying, of course, not pinned down outside the call.
For this reason we decided on a design change to have the response structure built in, so to speak, the C/C++ world of the DLL, not in VB's space. That way it'll stay put. The actual callback function's signature will remain unchanged so the VB program can know where the DLL put the response. This also allows the responders in the DLL to allocate separate response structures for separate needs.
Once again my update is too large for a mere comment!
Update 18 Apr 2013:
Well, the attempt to use the code from Calling Managed Code from Unmanaged Code cited above was a bust. We ended up having to add /clr to the DLL make turning the DLL into managed code, which made it unusable from a C application.
We are now testing the example at Callback Sample which I was able to show made a DLL that worked with both VB and C++. You'd need to have the PinvokeLib.dll Source to make this work.
Here is the code for the C++ (C really) tester. Compiled as a MSVC project.
NOTE: Notice the __cdecl in this line:
typedef bool (__cdecl *FPtr)(BOOL_FP_INT fp, int i );
It was the secret I had to find. The DLL and this app are compiled with __cdecl linkage, not __stdcall. They are the default in VC++ and I just used the defaults. I tried changing everything to __stdcall but that didn't work. Has to be __cdecl.
// PinvokeTester.cpp : Defines the entry point for the console application.
//
#include <stdio.h>
#include <cstdio>
#include <stdlib.h>
#include <cstdlib>
#include <string.h>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <Windows.h>
#define PINVOKELIB_API __declspec(dllimport)
HINSTANCE hLib; // Windows DLL handle
bool CALLBACK VBCallBack( int value );
bool AttachLibrary( void );
void * GetFuncAddress( HINSTANCE hLib, const char* procname );
int main(int argc, char* argv[])
{
if ( !AttachLibrary() )
{
printf( "Lib did not attach.\n" );
exit(1);
}
typedef bool (CALLBACK *BOOL_FP_INT)(int i );
typedef bool (__cdecl *FPtr)(BOOL_FP_INT fp, int i );
FPtr TestCallBack = (FPtr)GetFuncAddress( hLib, "TestCallBack" );
TestCallBack( (BOOL_FP_INT)VBCallBack, 255 );
return 0;
}
bool CALLBACK VBCallBack( int value )
{
printf( "\nCallback called with param: %d", value);
return true;
}
bool AttachLibrary( void )
{
// Get a var for the IPC-dll library.
std::string dllName;
/*--- First, link to the IPC-dll library or report failure to do so. ---*/
dllName = ".\\PinvokeLib";
if ( NULL == (hLib = LoadLibraryA( dllName.c_str() )) )
{
printf( "\nERROR: Library \"%s\" Not Found or Failed to Load. \n\n", dllName.c_str() );
printf( "\"%s\"\n", GetLastError() );
return false;
}
return true;
}
//=====================================================================
void * GetFuncAddress( HINSTANCE hLib, const char* procname )
{
void * procAddr = NULL;
procAddr = (void *)GetProcAddress( hLib, procname );
// If the symbol wasn't found, handle error ---------------------
if ( NULL == procAddr )
{
std::cout << "ERROR: Could not get an address for the \""
<< procname << "\" function. : "
<< GetLastError() << std::endl;
exit( 7 );
procAddr = (void*)NULL;
}
return procAddr;
}