Load dll function from other directory - c++

in my new project, I got a problem about loading dll.
My file structure is like below:
D:\dll_directory\calculate.dll
D:\myproject\myproject.vcxproj
D:\running_place\myproject.exe
There're my code,
HMODULE h = LoadLibrary("D:\\dll_directory\\calculate.dll");
if(!h)
return GetLastError();
pfnInit = (PInit)GetProcAddress(h, "initialize");
if(!pfnInit())
{
// Fail
}
In my code of calculate.dll, I need to get the working directory so I use
int initialize() {
...
GetModuleFileName(...,myPath,...);
currentPath = ...; // get directory from path
filePath = currentPath + ...
if(!DoesExist(filePath))
return 0;
...
}
The problem is there. Because I loadlibrary dll from "D:\dll_directory\" in other directory, currentPath value is wrong.
In debugging, currentPath is "D:\myproject\debug..." and in executing, it's "D:\running_place\".
Only if place myproject.exe into the same place with calculate.dll, it shows correctly, currentPath = "D:\dll_directory\".
But I don't want to place dll manually because of many files and many places.
Additionally, I can not change code in dlls, it's stable.
Are there any ways to make it work?
Thanks in advance.
Updated: I modify my question to more clearer. Sr about this. It's my first time on stackoverflow.
In function Init of DLL, it need to run other files that are placed in the same place with it. The expected of currentPath is directory of DLL.
EXE in dir1 loads DLL in dir2, EXE calls function Init of DLL -> currentPath=dir1 (wrong).
Try to change DLL code with GetCurrentDirectory -> currentPath=dir1; still wrong.
Put EXE and DLL in dir2, EXE calls function Init of DLL -> currentPath=dir2 (expected).

You're indeed using the wrong function t get the current directory. GetCurrentDirectory returns the current directory (i.e. where .\thisFile.txt would be sought).
GetModuleFileName returns the name where the module came from. That's in general not the current working directory. That working directory can be pretty much anything, really. The variation you see between debug and release is just a small sample of the possible variation. Expect anything.
It also appears that you're looking for another file, other than the 3 mentioned in the question. It's unclear where that other file is located, so it's possible that this answer is incomplete. Is there a reason to expect that other file in the current working directory?

I find out these code that return the DLL current path.
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
WCHAR DllPath[MAX_PATH] = {0};
GetModuleFileName((HINSTANCE)&__ImageBase, DllPath, _countof(DllPath));
Actually, I don't know what _ImageBase is and its cons/pros, am still investigating.
If ok, maybe I use it in DLL code.

The first argument of GetModuleFileName (which you left out) determines which path to get. From the observed behavior, we can now deduce that you passed NULL or 0, which means you get the filename of the EXE module.
You should have passed HMODULE h from your first snippet, the DLL module.
(This value is also available inside your DllMain, 1st argument. Save that 1st argument to a global variable in your DLL, so you can pass it to GetModuleFileName)

Related

Can't find COM object from C++, although Guid it's registered

First of all happy new year to everyone, hope you're doing well!
I'm working on a C++ project in which I need to call a C# DLL I created following the first answer of this post. Once I have the DLL, I need to call it from Qt, so by using dumpcpp and the .tlb file generated by regasm, I managed to get the .cpp and .h files to use my classes. Just as a reference, the namespace of the classes is Wrapper, and the main class is Device with guid {DD4A4896-C105-4C60-839B-B18C99C8FE15}.
Once I have the generated files to use the DLL, if I try to create a Wrapper:: Device instance on Qt, I get the following error:
QAxBase::setControl: requested control {dd4a4896-c105-4c60-839b-b18c99c8fe15} could not be instantiated
QAxBase::qt_metacall: Object is not initialized, or initialization failed
It doesn't give any more information, so I tried to check if the guid was stored on the system registry (I used the regasm command explained on the previously quoted post, and It said that it was successful, but you never know). Opening Registry editor and searching for the Guid revealed that it's present at: Computer\HKEY_CLASSES_ROOT\WOW6432Node\CLSID\{DD4A4896-C105-4C60-839B-B18C99C8FE15}, which, as far as I know, is the right route for these guids, and it points to the right DLL.
I though It may be due to some kind ActiveQt problem, and as the previously quoted post explained how to use that DLL from VS C++, I decided to give it a try, using this as an another reference. I've finished with this code, which is supposed to create an instance of my Device object
#include <iostream>
#include <atlstr.h>
#import "C:\Users\javie\Documents\Wrapper\Wrapper\bin\x86\Release\netstandard2.0\Wrapper.tlb" named_guids raw_interfaces_only
inline void TESTHR(HRESULT x) { if FAILED(x) _com_issue_error(x); };
int main()
{
try
{
TESTHR(CoInitialize(0));
Wrapper::IDevicePtr devPtr = nullptr;
TESTHR(devPtr.CreateInstance("{DD4A4896-C105-4c60-839B-B18C99C8FE15}"));
}
catch (const _com_error& e)
{
CStringW out;
out.Format(L"Exception occurred. HR = %lx, error = %s", e.Error(), e.ErrorMessage());
MessageBoxW(NULL, out, L"Error", MB_OK);
}
CoUninitialize();// Uninitialize COM
std::cout << "Hello World!\n";
}
However, this doesn't work either, the createInstance method throws an exception of Class not registered and HR=80040154. Again, according to Registry editor, the class is registered, so I don't understand the error. I've also tried with devPtr.CreateInstance("Wrapper.Device"), devPtr.CreateInstance("Wrapper::Device") or `devPtr.CreateInstance("Wrapper::CLSID_Device") as the links I posted suggest, but in those cases I get another exception with HR=800401f3 and message Invalid class string.
It doesn't matter whether VS or Qt Creator are opened as administrator or not, I get the exact same error.
I have run out of ideas, and I really need to be able to use that DLL from Qt using the files generated by dumpcpp.
Does any one know what could be happening? It feels quite strange to me.
If your C++ application is 64-bit, that's the answer right there, because your C# component is 32-bit (or MSIL but registered to the 32-bit hive). In situations like these, a simple test using VBScript is always useful.
Write a simple VB Script (test.vbs)
Dim obj
Set obj = CreateObject("Wrapper.Device") ' or whatever your ProgID is
MsgBox TypeName(obj)
Now, run this macro 2 ways: with 32-bit and 64-bit versions of VBScript:
32-bit > c:\windows\SysWow64\cscript.exe test.vbs
64-bit > c:\windows\system32\cscript.exe test.vbs
This is assuming your C# component is dispatch compatible. If it's not, then it will still give you differing results that you can use to debug.
Assuming automation/IDispatch compatible, one will work and one won't if you have registered your component correctly.
Have you registered correctly? When I use regasm, I always use the the switches /tlb /codebase when registering the C# component for COM.
Ok, in case someone find the same error, I'll explain the solution I found.
The problem was that in my case, the C# class I developed depended on another 32 bits dll which was not registered on my PC. Once I registered the other dll, everything worked fine.
I don't know why VS kept telling me that the class was not registered when my class itselft was registered, it was one of its dependencies that wasn't registered.
Anyway, I discovered this thanks to Joseph's comments, so thanks a lot for your help.

How to use `/DELAYLOAD` on a DLL from another DLL

I have a solution with two DLLs. The first one is the "main" DLL. It happens to be an ODBC driver, but I think that is not important for this question.
The second DLL contains all the UI logic for the first one. As the UI is not always needed, I want to use the /DELAYLOAD feature which explicitly says:
The delayed loading of a DLL can be specified during the build of
either a .EXE or .DLL project.
The main DLL's project correctly references the UI ones. If I don't use /DELAYLOAD, everythin works just fine. The two DLLs will be installed into the same directory, so I thought loading one DLL from within the other should be easy. But apparently, it's not.
As soon as the first function from the UI DLL is called, the application (any ODBC client in my case) crashes.
GetLastError() yields 126 which apparently means that the target DLL could not be found in any of the search paths.
And indeed, according to this answer LoadLibrary() does have a look into the directory of the calling executable, but not into the one of the currently executed DLL. I'm assuming /DELAYLOAD is also just using LoadLibrary() under the hood, is that correct?
If I copy the executable into the installation directory of my driver, it works just fine, which proves my assumption that it just doesn't look in the current DLL's directory.
Appart from that, I was also able to make it run by calling
LoadLibrary(L"C:\\absolute\\path\\to\\UI.dll");
just before the first function of the UI DLL is loaded.
I was also able to determine this path programmatically using
wchar_t buffer[512];
GetModuleFileName(hThisDLL, buffer, sizeof(buffer));
But then I would have to cover every single UI call with this logic. So I wouldn't see much advantage anymore that /DELAYLOAD has over the "old-school" way of using LoadLibrary() and GetProcAddress().
Question
Is there a simple way to make /DELAYLOAD find the target DLL from another DLL in the same directory?
There is. My suggestion would be to create a delay-load-failure hook function.
https://learn.microsoft.com/en-us/cpp/build/reference/failure-hooks?view=vs-2019
Basically, you write a function inside your main DLL that gets notified in the event of a delay load failure. In that function, when the given code indicates failure, you try manually calling LoadLibrary with of a path consisting of the folder in which your main DLL resides plus the name of the DLL that failed to load
How you get the your main DLL from within your main DLL is up to you. There are many ways.
Something like this:
FARPROC WINAPI delayHook(unsigned dliNotify, PDelayLoadInfo pdli)
{
FARPROC fpRet = NULL;
switch (dliNotify)
{
case dliStartProcessing:
break;
case dliNotePreLoadLibrary:
break;
case dliNotePreGetProcAddress:
break;
case dliFailLoadLib:
{
std::string newPath = GetMyModulePath();
newPath += "\\";
newPath += pdli->szDll;
fpRet = reinterpret_cast<FARPROC>(::LoadLibrary(csDir));
}
break;
case dliFailGetProc:
break;
case dliNoteEndProcessing:
break;
default:
break;
}
return fpRet;
}
//
// Set access to our delay load hook.
//
PfnDliHook __pfnDliFailureHook2 = delayHook;

Avoid dll pre-loading vulnerability by reloading dll, any better way?

To avoid dll pre-loading I tried to use SetDllDirectory("") as it is mentioned in MSDN to remove current directory from search path.
The directory to be added to the search path. If this parameter is an
empty string (""), the call removes the current directory from the
default DLL search order. If this parameter is NULL, the function
restores the default search order.
Surprisingly it didn't work. I can see dlls are still getting loaded from current directory if dll present there. I also tired to set dll directory path as system 32 path, but still it picks dll from current directory. Lastly I decide to get all the modules loaded in my application and reloading them again. My code looks like this,
if(wcsstr(szModName,L"TestLibrary.dll"))
{
FreeLibrary(hMods[i]);
LoadLibrary("SomeRelavantPath\TestLibrary.dll");
}
Do you see any problem with my code?
#Edit:
My Complete POC code,
int main( void )
{
SetDllDirectory(L"");
LPWSTR s = new WCHAR[100];
GetDllDirectory(100,s);
HINSTANCE myDLL = LoadLibrary(L"TestLibrary.dll");
//myDLL returns non-null, there is file in current dir and not at any other location, it should have return null.
return 0;
}
Unloading and loading again with a new path is pointless from a security perspective, it is already too late because a evil .dll could execute code in its DllMain!
The best solution is to directly link to as few DLLs as possible and call SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32) before anything else in your WinMain function.
If the string specifies a full path, the function searches only that path for the module.
If the string specifies a relative path or a module name without a path, the function uses a standard search strategy to find the module; for more information, see the Remarks.
From LoadLibrary function # MSDN.
Calls to SetDllDirectory will do nothing against full paths.

C++ Builder XE2: Initializing a Data Module in a dll

I'm trying to create a dll that contains a VCL data module - the idea being that various applications can all load the same dll and use the same database code.
The data module itself is tested ok as part of an application - I've copied the form over to my dll project.
So in the dll entry point method, I need to initialize the data module:
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
//if I don't call this, I get an exception on initializing the data module
CoInitialize(NULL);
//initialize a standard VCL form; seems to works fine
//I'm not using Application->CreateForm as I don't want the form to appear straight away
if(!MyForm) MyForm = new TMyForm(Application);
//this doesn't work - the thread seems to hang in the TDataModule base constructor?
//I've also tried Application->CreateForm; same result
if(!MyDataModule) MyDataModule = new TMyDataModule(Application);
}
I've also seen something about how I need to call Application->Initialize before creating the form but this doesn't seem to make any difference.
Any ideas?
Thanks
You really should not be doing very much work in your DllEntryPoint() at all. Certainly not calling CoInitialize(), anyway. It is not the DLL's responsibility to call that when loaded. It is the calling app's responsibility before loading the DLL.
You should either:
export an additional function to initialize your DLL and then have the app it after loading the DLL (same for uninitialing the DLL before unloading it)
don't create your TForm/TDataModule until the first time the DLL actually needs them.
move your TForm/TDataModule into their own worker thread inside the DLL. In this case, you would then call CoIniitalize().
And in all cases, don't relay on the DLL's Application object to manage the lifetime of your TForm/TDataModule. Free them yourself instead before the DLL is unloaded.

How to properly use %USERPROFILE% inside code?

Is my code correct? It seems can compile but does not work properly..
CString testing = _T(" --url=") + cstring + _T(" --out=%USERPROFILE%\\snapshot.png");
I want to point it to user's folder..but still cannot work.
The answer is that you don't use environment variables at all. Rather, you use the shell functions specifically designed to retrieve the path of special folders.
On Windows Vista and later, that function is SHGetKnownFolderPath. It takes KNOWNFOLDERID values to identify the folder whose path you wish to retrieve. In your case, that would be FOLDERID_Profile.
If you need to target earlier versions of Windows (such as XP), you will need to use the SHGetSpecialFolderPath function, instead. It takes a CSIDL value identifying the folder whose path you wish to retrieve. Again, in your case, that would be CSIDL_PROFILE.
Of course, you should never store data directly in the user's profile folder. So hopefully the bit of code that you've shown is for demonstration purposes only. Applications should only create files in the specific locations under the user profile folder, designed for application data storage.
These locations are CSIDL_APPDATA or CSIDL_LOCAL_APPDATA. If you are creating data that the user should be able to modify and should treat as his/her own, then it would be appropriate to store that data in the user's documents folder (CSIDL_MYDOCUMENTS).
More usage information is available in my answer here.
Sample code:
TCHAR szFolderPath[MAX_PATH];
if (!SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_APPDATA, FALSE))
{
// Uh-oh! An error occurred; handle it.
}
Or, using MFC's CString class:
CString buffer;
BOOL bRet = SHGetSpecialFolderPath(NULL, buffer.GetBuffer(MAX_PATH), CSIDL_APPDATA, FALSE);
buffer.ReleaseBuffer();
if (!bRet)
{
// Uh-oh! An error occurred; handle it.
}
As Cody suggested, it's better to use the SHGetSpecialFolderPath function. However, you could use the GetEnvironmentVariable function to get that and other variables set in the system.
TCHAR szBuf[MAX_PATH] = {0};
::GetEnvironmentVariable(_T( "USERPROFILE" ), szBuf, MAX_PATH);