Sign in to follow this  
content creator

Dynamically Load Dll Functions from Text File Info

Recommended Posts

Hi,

here is how to load functions from dll using a text file to tell your application the name of the function and .dll file to use at runtime :

Application : 

Main.Cpp

#include <windows.h>
#include <atlstr.h>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <filesystem>
#include <conio.h>
#include <string>
#include <iostream>
#include <clocale>
#include <locale>
#include <vector>
#pragma once
#ifdef UNICODE //Test to see if we're using wchar_ts or not.
typedef std::wstring StringType;
#else
typedef std::string StringType;
#endif
//
///////////////////////////
///////////////////////////
//
//start of struct definitions//
//
///////////////////////////
///////////////////////////
//
//the parameter struct that is both input and output from our function
extern "C" struct DllFunctionParams
{
	std::vector<bool>vectorofbools;
	std::vector<int>vectorofints;
	std::vector<double>vectorofdoubles;
	std::vector<HWND>vectorofhwnds;
	std::vector<CString>vectorofcstrings;
};
//our function definition
extern "C"
{
	typedef DllFunctionParams(*f_funci)(DllFunctionParams&);
}
//the parameter struct that holds the data about our current dll function
extern "C" struct DllFunction
{
	StringType DllName;
	StringType DllFunctionName;
	int Priority;
	f_funci function;
};
//
///////////////////////////
///////////////////////////
//
//start of struct definitions//
//
///////////////////////////
///////////////////////////
//

//
///////////////////////////
///////////////////////////
//
//start of string functions namespace//
//
///////////////////////////
///////////////////////////
//
namespace StringManipulation
{
	StringType pathAppend(const StringType & p1, const StringType & p2);
	std::string wstrtostr(const std::wstring & wstr);
	double StringToDouble(StringType input);
	int StringToInt(StringType input);
	StringType GetExtension(StringType filename);
	StringType GetBaseFilename(StringType filename);
	StringType GetCurrentPath();
	std::vector<StringType> GetListOfFiles(StringType path);
	bool PathExists(const StringType & s);
	inline bool FileExists(const StringType & name);
	std::vector<StringType> GetLinesFromFile(StringType filename);
	std::vector<StringType> split(StringType stringToBeSplitted, StringType delimeter);
}
//
///////////////////////////
///////////////////////////
//
//end of string functions namespace//
//
///////////////////////////
///////////////////////////
//

//
///////////////////////////
///////////////////////////
//
//start of string functions//
//
///////////////////////////
///////////////////////////
//
StringType StringManipulation::pathAppend(const StringType& p1, const StringType& p2) {

	char sep = '/';
	StringType tmp = p1;

#ifdef _WIN32
	sep = '\\';
#endif

	if (p1[p1.length()] != sep) { // Need to add a
		tmp += sep;                // path separator
		return(tmp + p2);
	}
	else
		return(p1 + p2);
}
std::string StringManipulation::wstrtostr(const std::wstring &wstr)
{
	std::string strTo;
	char *szTo = new char[wstr.length() + 1];
	szTo[wstr.size()] = '\0';
	WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, szTo, (int)wstr.length(), NULL, NULL);
	strTo = szTo;
	delete[] szTo;
	return strTo;
}
double StringManipulation::StringToDouble(StringType input)
{
	CString c_str = input.c_str();
	// Convert a TCHAR string to a LPCSTR
	CT2CA pszConvertedAnsiString(c_str);
	// construct a std::string using the LPCSTR input
	std::string strStd(pszConvertedAnsiString);
	return atof(strStd.c_str()); /*c_str is needed to convert string to const char*/
}
int StringManipulation::StringToInt(StringType input)
{
	CString c_str = input.c_str();
	// Convert a TCHAR string to a LPCSTR
	CT2CA pszConvertedAnsiString(c_str);
	// construct a std::string using the LPCSTR input
	std::string strStd(pszConvertedAnsiString);
	return atoi(strStd.c_str()); /*c_str is needed to convert string to const char*/
}

StringType StringManipulation::GetExtension(StringType filename)
{
	//store the position of last '.' in the file name
	int position = filename.find_last_of(_T("."));

	//store the characters after the '.' from the file_name string
	StringType result = filename.substr(position + 1);

	//print the result
	return result;
}
StringType StringManipulation::GetBaseFilename(StringType filename)
{
	size_t pos = filename.rfind(_T("."));
	if (pos == StringType::npos)  //No extension.
		return filename;

	if (pos == 0)    //. is at the front. Not an extension.
		return filename;

	return filename.substr(0, pos);
}
StringType StringManipulation::GetCurrentPath()
{
#ifdef UNICODE //Test to see if we're using wchar_ts or not.
	wchar_t buffer[MAX_PATH];
#else
	char buffer[MAX_PATH];
#endif
	GetModuleFileName(NULL, buffer, MAX_PATH);
	StringType::size_type pos = StringType(buffer).find_last_of(_T("\\/"));
	return StringType(buffer).substr(0, pos);
}
std::vector<StringType> StringManipulation::GetListOfFiles(StringType path)
{
	std::vector<StringType>stringvector;
	WIN32_FIND_DATA fileData;
	memset(&fileData, 0, sizeof(WIN32_FIND_DATA));
	CString string;
	string.Format(_T("%s\\*"), path.c_str());
	HANDLE handle = FindFirstFile(string, &fileData);

	if (handle != INVALID_HANDLE_VALUE)
	{
		do
		{
			if (_tcscmp(fileData.cFileName, _T(".")) != 0 && // ignore "." and ".."
				_tcscmp(fileData.cFileName, _T("..")) != 0)
			{
				stringvector.push_back(fileData.cFileName);
			}
		} while (FindNextFile(handle, &fileData));

		FindClose(handle);
	}

	return stringvector;
}
bool StringManipulation::PathExists(const StringType &s)
{
	struct stat buffer;

	CString c_str = s.c_str();
	// Convert a TCHAR string to a LPCSTR
	CT2CA pszConvertedAnsiString(c_str);
	// construct a std::string using the LPCSTR input
	std::string strStd(pszConvertedAnsiString);
	return (stat(strStd.c_str(), &buffer) == 0);
}
inline bool StringManipulation::FileExists(const StringType& name)
{
	CString c_str = name.c_str();
	// Convert a TCHAR string to a LPCSTR
	CT2CA pszConvertedAnsiString(c_str);
	// construct a std::string using the LPCSTR input
	std::string strStd(pszConvertedAnsiString);
	struct stat buffer;
	return (stat(strStd.c_str(), &buffer) == 0);
}
std::vector<StringType> StringManipulation::GetLinesFromFile(StringType filename)
{

	auto count = 0;
	std::vector<StringType> stringvector;
	if (FileExists(filename))
	{
		std::ifstream file(filename);
		std::string str;
		while (std::getline(file, str))
		{
			CString c_str = str.c_str();
			StringType stringtype(c_str);
			stringvector.push_back(stringtype);
			count++;
			// Process str
		}
	}
	return count > 0 ? stringvector : std::vector<StringType>(0);
}


std::vector<StringType> StringManipulation::split(StringType stringToBeSplitted, StringType delimeter)
{
	std::vector<StringType> splittedString;
	int startIndex = 0;
	int  endIndex = 0;
	while ((endIndex = stringToBeSplitted.find(delimeter, startIndex)) < stringToBeSplitted.size())
	{

		StringType val = stringToBeSplitted.substr(startIndex, endIndex - startIndex);
		splittedString.push_back(val);
		startIndex = endIndex + delimeter.size();

	}
	if (startIndex < stringToBeSplitted.size())
	{
		StringType val = stringToBeSplitted.substr(startIndex);
		splittedString.push_back(val);
	}
	return splittedString;

}
//
///////////////////////////
///////////////////////////
//
//end of string functions//
//
///////////////////////////
///////////////////////////
//
//
///////////////////////////
///////////////////////////
//
//start of GetProcAddress header//
//
///////////////////////////
///////////////////////////
//
bool GetProcAddresses(HINSTANCE * hLibrary,
#ifdef UNICODE //Test to see if we're using wchar_ts or not.
	LPCWSTR lpszLibrary, INT nCount, ...);
#else

LPCSTR lpszLibrary, INT nCount, ...);
#endif
//
///////////////////////////
///////////////////////////
//
//end of GetProcAddress header//
//
///////////////////////////
///////////////////////////
//
//
///////////////////////////
///////////////////////////
//
//start of GetProcAddress function//
//
///////////////////////////
///////////////////////////
//
//GetProcAddresses
//Argument1: hLibrary - Handle for the Library Loaded
//Argument2: lpszLibrary - Library to Load
//Argument3: nCount - Number of functions to load
//[Arguments Format]
//Argument4: Function Address - Function address we want to store
//Argument5: Function Name -  Name of the function we want
//[Repeat Format]
//
//Returns: FALSE if failure
//Returns: TRUE if successful
bool GetProcAddresses(HINSTANCE *hLibrary,
#ifdef UNICODE //Test to see if we're using wchar_ts or not.
	LPCWSTR lpszLibrary, INT nCount, ...)
#else

LPCSTR lpszLibrary, INT nCount, ...)
#endif
{
	va_list va;
	va_start(va, nCount);

	if ((*hLibrary = LoadLibrary(lpszLibrary))
		!= NULL)
	{
		FARPROC * lpfProcFunction = NULL;
#ifdef UNICODE //Test to see if we're using wchar_ts or not.
		LPCWSTR lpszFuncName = NULL;
#else
		LPSTR lpszFuncName = NULL;
#endif
		INT nIdxCount = 0;
		while (nIdxCount < nCount)
		{
			lpfProcFunction = va_arg(va, FARPROC*);
#ifdef UNICODE //Test to see if we're using wchar_ts or not.
			lpszFuncName = va_arg(va, LPCWSTR);
			auto string = StringManipulation::wstrtostr(lpszFuncName);
#else
			lpszFuncName = va_arg(va, LPSTR);
			StringType string(lpszFuncName);
#endif
			if ((*lpfProcFunction =
				GetProcAddress(*hLibrary,
					string.c_str())) == NULL)
			{
				lpfProcFunction = NULL;
				return FALSE;
			}
			nIdxCount++;
		}
	}
	else
	{
		va_end(va);
		return FALSE;
	}
	va_end(va);
	return TRUE;
}
//
///////////////////////////
///////////////////////////
//
//end of GetProcAddress function//
//
///////////////////////////
///////////////////////////
//
//
///////////////////////////
///////////////////////////
//
//start of CompareByPriority header//
//
///////////////////////////
///////////////////////////
//
bool CompareByPriority(const DllFunction & a, const DllFunction & b);
//
///////////////////////////
///////////////////////////
//
//end of CompareByPriority header//
//
///////////////////////////
///////////////////////////
//

//
///////////////////////////
///////////////////////////
//
//start of CompareByPriority function//
//
///////////////////////////
///////////////////////////
//
bool CompareByPriority(const DllFunction &a, const DllFunction &b)
{
	return a.Priority < b.Priority;
}
//and then use std::sort in the header #include <algorithm>:
//
///////////////////////////
///////////////////////////
//
//end of CompareByPriority function//
//
///////////////////////////
///////////////////////////
//

//
///////////////////////////
///////////////////////////
//
//start of DynamicallyLoadDllFunctionsFromFile header//
//
///////////////////////////
///////////////////////////
//
void DynamicallyLoadDllFunctionsFromFile(StringType &path, StringType &targetextension, StringType &resourcefilename);
//
///////////////////////////
///////////////////////////
//
//end of DynamicallyLoadDllFunctionsFromFile header//
//
///////////////////////////
///////////////////////////
//

//
///////////////////////////
///////////////////////////
//
//start of DynamicallyLoadDllFunctionsFromFile function//
//
///////////////////////////
///////////////////////////
//
void DynamicallyLoadDllFunctionsFromFile(StringType &path, StringType &targetextension, StringType &resourcefilename)
	{
		std::vector<DllFunction> FunctionVector;

		auto list = StringManipulation::GetListOfFiles(path);

		for (auto listitem : list)
		{

			//get the current files extension
			auto extension = StringManipulation::GetExtension(listitem);
			//get the current filename without extension
			auto basefilename = StringManipulation::GetBaseFilename(listitem);
			if (extension == targetextension)
			{
				if (basefilename == resourcefilename)
				{

					
					//get a vector of lines from the current file
					auto itempath = StringManipulation::pathAppend(path, listitem);

					auto lines = StringManipulation::GetLinesFromFile(itempath);
					if (!lines.empty())
					{
						for (auto line : lines)
						{

							DllFunction function;
							auto count = 0;
							//get a vector of each "word" in the current line
							auto splitlines = StringManipulation::split(line, _T(" "));
							for (auto splitline : splitlines)
							{

								//use a switch statement to assign values based on the position of the number in the line
								switch (count)
								{
								case 0:
									function.DllName = splitline;
									break;
								case 1:
									function.DllFunctionName = splitline;
									break;
								case 2:
									function.Priority = StringManipulation::StringToInt(splitline);
									break;


								}
								//CString strMessage;
								//strMessage.Format(_T("Base FileName = %s Extension = %s Line = %s SplitLine = %s"), basefilename.c_str(), extension.c_str(), line.c_str(), splitline.c_str());
								//auto pszFileName = _T("File");
								//auto msgboxID = MessageBox(NULL, strMessage, pszFileName, MB_OK);
								count++;
							}

							FunctionVector.push_back(function);

						}
					}
				}
			}
		}

		std::sort(FunctionVector.begin(), FunctionVector.end(), CompareByPriority);
		DllFunctionParams GlobalParams;
		std::vector< f_funci> functionvect;
		for (auto function : FunctionVector)
		{

			bool succf;
			f_funci f_funci;
			HINSTANCE hLib;
#ifdef UNICODE //Test to see if we're using wchar_ts or not.
			if (GetProcAddresses(&hLib, (LPCWSTR)function.DllName.c_str(), 1,
#else

			if (GetProcAddresses(&hLib, (LPCSTR)_T(function.DllName.c_str()), 1,
#endif

				&f_funci, function.DllFunctionName.c_str()))
			{
				succf = true;
			}
			if (hLib != NULL)
				if (succf)
					FreeLibrary(hLib);
			
			auto newparams = f_funci(GlobalParams);
			GlobalParams = newparams;
		}
	
}
//
///////////////////////////
///////////////////////////
//
//end of DynamicallyLoadDllFunctionsFromFile function//
//
///////////////////////////
///////////////////////////
//
//
///////////////////////////
///////////////////////////
//
//main entry point start//
//
///////////////////////////
///////////////////////////
//
int main()
{
	StringType resourcefilename = _T("Functions");

	//filter only text files
	StringType targetextension = _T("txt");

	StringType subfolder = _T("chooks");
	//get out current working directory
	auto currentpath = StringManipulation::GetCurrentPath();
	//get the list of the files in our current working directory
	auto path = StringManipulation::pathAppend(currentpath, subfolder);
	MessageBox(NULL, path.c_str(), path.c_str(), MB_OK);
	DynamicallyLoadDllFunctionsFromFile(path, targetextension, resourcefilename);

	return 0;

}
//
///////////////////////////
///////////////////////////
//
//main entry point end//
//
///////////////////////////
///////////////////////////
//

Dll File :

DLLExport.cpp

#include <windows.h>
#include <atlstr.h>
#include <vector>
extern "C" struct DllFunctionParams
{
	std::vector<bool>vectorofbools;
	std::vector<int>vectorofints;
	std::vector<double>vectorofdoubles;
	std::vector<HWND>vectorofhwnds;
	std::vector<CString>vectorofcstrings;
	std::vector<HINSTANCE>vectorofhinstance;
};
extern "C"
{
	 __declspec(dllexport) DllFunctionParams funci(DllFunctionParams &paramsin)
	 {
		 if (!paramsin.vectorofcstrings.empty())
		 {
			 auto pszFileName = _T("File");
				 auto msgboxID = MessageBox(NULL, paramsin.vectorofcstrings[0], pszFileName, MB_OK);
		 }
		CString output;
		output.Format(_T("Output Received"));
		DllFunctionParams params;
	    params.vectorofcstrings.push_back(output);
	
		return params;
		// ...
	}
};

Then in the folder where your .exe file is create a subfolder called chooks and place a file called Functions.txt

Functions.txt contains these 2 lines

C:\Users\username\source\repos\Ikommand\Release\chooks\DllExport1.dll funci 1
C:\Users\username\source\repos\Ikommand\Release\chooks\DllExport1.dll funci 2

//parameter 1 -> the function path parameter this Is where the dll file must be && 2 -> the function name parameter && parameter 3 -> the function priority

https://github.com/PeterRussellEvans/DllExport

https://github.com/PeterRussellEvans/DynamicallyLoadDll

I will make an example that is embedded in a chook soon.

It will work for both MCBS and UNICODE character set

This should  allow Dll files to be replaced without restarting Mastercam, which can be a headache when writing chooks..

  • Like 1

Share this post


Link to post
Share on other sites
Posted (edited)

Though I cannot say I know exactly what you're doing here.
It seems like a lot of code for just for doing: LoadLibrary / GetProcAddress

BTW - 
If you're running this in a Mastercam add-in, your add-in is "MFC", so it's better to use -> AfxLoadLibrary & AfxFreeLibrary 
https://docs.microsoft.com/en-us/cpp/build/loadlibrary-and-afxloadlibrary?view=vs-2019

This  -

#ifdef UNICODE //Test to see if we're using wchar_ts or not.
        LPCWSTR lpszFuncName = NULL;
#else
        LPSTR lpszFuncName = NULL;
#endif

Could be replaced with -> LPCTSTR lpszFuncName = NULL;

LPCTSTR  - An LPCWSTR if UNICODE is defined, an LPCSTR otherwise.
https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types

 

Edited by Roger Martin from CNC Software
  • Like 1

Share this post


Link to post
Share on other sites
double StringManipulation::StringToDouble(StringType input)
{
	CString c_str = input.c_str();
	// Convert a TCHAR string to a LPCSTR
	CT2CA pszConvertedAnsiString(c_str);
	// construct a std::string using the LPCSTR input
	std::string strStd(pszConvertedAnsiString);
	return atof(strStd.c_str()); /*c_str is needed to convert string to const char*/
}

int StringManipulation::StringToInt(StringType input)
{
	CString c_str = input.c_str();
	// Convert a TCHAR string to a LPCSTR
	CT2CA pszConvertedAnsiString(c_str);
	// construct a std::string using the LPCSTR input
	std::string strStd(pszConvertedAnsiString);
	return atoi(strStd.c_str()); /*c_str is needed to convert string to const char*/
}

These functions smell funny;  atoi and atof both return 0 on failure and that's not great. 

Consider using std::stoi or std::stod.  They accept a std::string or std::wstring so there is no need to do whatever your doing to convert the input.  They also throw an exception on failure rather than an ambiguous value.

 

Share this post


Link to post
Share on other sites
3 hours ago, Roger Martin from CNC Software said:

Though I cannot say I know exactly what you're doing here.

The load library function is a bit long because it is setup to load more than 1 function at a time.

Overall I am loading the information and functions at runtime. I want Mastercam to Release the dll's containing the c++ code when the chook ends so I won"t have to reload mastercam, I also want to try reusing code without recompiling it.

3 hours ago, Roger Martin from CNC Software said:

AfxLoadLibrary & AfxFreeLibrary 

 

3 hours ago, Roger Martin from CNC Software said:

Could be replaced with -> LPCTSTR lpszFuncName = NULL;

^Thanks for the tips.

Maybe I can add a macro to use AfxLoadLibrary and AfxFreeLibrary if there is a mastercam version..

2 hours ago, Zaffin_D said:

using std::stoi or std::stod. 

Cool, thanks for the information, I was not aware of that option..

Share this post


Link to post
Share on other sites
11 hours ago, Roger Martin from CNC Software said:

AfxLoadLibrary & AfxFreeLibrary 

From what I can see Mastercam won't release the dll even if I call AfxFreeLibrary. 

The actual process lets Mastercam successfully call as many dll's are stacked in the process, but it will not allow us to call free library.

Share this post


Link to post
Share on other sites
On 4/2/2020 at 9:48 AM, Zaffin_D said:

atoi and atof both return 0 on failure and that's not great. 

In this case it were to return 0 the functions would be pushed to the front of the queue, 

I could  check the vector of  DllFunction structs and if the struct Priority parameter has a value less than one,

send an error message to the event logger and abort the process.

The priority value tells Mastercam what order the functions need to be run in.

 

The other options would be your suggestion,

or wrapping  the .net function that returns nullptr on failure.

I have not actually used exceptions before in my code..

Share this post


Link to post
Share on other sites
On 4/1/2020 at 5:07 PM, Peter from S.C.C.C. said:

auto splitlines = StringManipulation::split(line, _T(" "));

 

was changed to :

auto splitlines = StringManipulation::split(line, _T("|"));

Since using a whitespace as a delimeter is a bad choice when loading paths that could have folder names or files with whitespaces in them..

Share this post


Link to post
Share on other sites
Quote

Maybe I can add a macro to use AfxLoadLibrary and AfxFreeLibrary if there is a mastercam version.

#if defined(_AFXEXT) || defined(_AFXDLL)
      // Use the AFX versions of LoadLibrary/FreeLibrary
#else
      // Use the standard versions of LoadLibrary/FreeLibrary
#endif
Quote

…but it will not allow us to call free library

Maybe this? -  If a DLL is referenced (loaded) in a Function Table (FT) file, it cannot be unloaded while Mastercam is running.

Share this post


Link to post
Share on other sites

Enjoying this topic keep pushing Peter your really thinking outside of the box and keep it up!!!

Share this post


Link to post
Share on other sites
17 minutes ago, Roger Martin from CNC Software said:

Maybe this? -  If a DLL is referenced (loaded) in a Function Table (FT) file, it cannot be unloaded while Mastercam is running.

This applies also to DLL's which are called by the ft referenced dll's. Would an external application called by Mastercam be able to call the functions then release the dll's.

I believe there is a RunUserApplication function in the chook sdk.

 

Share this post


Link to post
Share on other sites
Quote

This applies also to DLL's which are called by the ft referenced dll's.

I don't believe so. Mastercam is concerned the the DLL specified in the FT file

Now depending on how any other DLLs the (FT listed)  DLL uses and how they are loaded/referenced may have an effect.

Quote

 believe there is a RunUserApplication function in the chook sdk.

There are 2 versions of RunUserApp in the SDK .  (Declared in interfaces\GUI\RunApp_CH.h)

Share this post


Link to post
Share on other sites
On 4/3/2020 at 5:48 PM, Roger Martin from CNC Software said:
Quote

This applies also to DLL's which are called by the ft referenced dll's.

I don't believe so. Mastercam is concerned the the DLL specified in the FT file

Maybe I need to call the function before I call afxfreelibrary?

 

On 4/1/2020 at 5:07 PM, Megabyte said:

FreeLibrary(hLib); auto newparams = f_funci(GlobalParams);  GlobalParams = newparams; }

^ I will try switching these

Share this post


Link to post
Share on other sites
5 hours ago, Megabyte said:

Maybe I need to call the function before I call afxfreelibrary?

Not sure what you are referring to here.

You mean call a function in the DLL between the AfxLoadLibrary that loaded it and the and AfxFreeLibrary  that unloads it?

If so, I don't see how that would make any difference.

 

 

Share this post


Link to post
Share on other sites
13 minutes ago, Roger Martin from CNC Software said:

You mean call a function in the DLL between the AfxLoadLibrary that loaded it and the and AfxFreeLibrary  that unloads it?

Yes.

14 minutes ago, Roger Martin from CNC Software said:

If so, I don't see how that would make any difference.

Right, I was looking at this morning and thought I was on to something, now that I had my coffee, I remember Mastercam was crying about an access violation when I tried to call freelibrary, my mistake. 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

Join us!

eMastercam - your online source for all things Mastercam.

Together, we are the strongest Mastercam community on the web with over 56,000 members, and our online store offers a wide selection of training materials for all applications and skill levels.

Follow us