Sign in to follow this  
v0dKA

Odd problem creating a static library - function calls

Recommended Posts

v0dKA    568
I created a static library the purpose of which is to streamline the generation of keyboard input (fake keypresses). It consists of a couple main classes and several functions. The oddness of the problem is that before I compiled these classes and functions into a library (that is, when it used to be a regular Win32 application), I received no error messages. However, when I linked it into a library (under Release configuration, if it matters), I get an error message when the program exits that runs like this: Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention. I could not find the place where I made this convention mismatch, but there are several likely places. I still cannot pinpoint the problem, and it's getting quite frustrating. Here is the entire library (sorry, I realize it's a lot to look at): InputOp.h - Main include file for the library - simply includes all the other headers.
#ifndef INPUT_OP_H
#define INPUT_OP_H

#include "KeyProc.h"
#include "KeyProcMgr.h"
#include "window.h"

#endif


KeyProc.h - declaration of the class CKeyProc. This class holds a single sequence of keys that need to be pressed to accomplish whatever task. This class handles any required manipulations to this key sequence and is responsible for actually generating these key presses.
#ifndef KEY_PROC_H
#define KEY_PROC_H

#include "Key.h"
#include "constants.h"

#include <ctime>
#include <vector>
#include <WinAble.h>
#include <windows.h>

class CKeyProc
{
	public:

		// Constructor / Destructor
		CKeyProc();
		~CKeyProc();

		// THIS FUNCTION MUST BE CALLED BEFORE Run()!
		// Sets up a proper Windows key input procedure (instead of this specialized class).
		// If the key sequence itself was changed (via PressKey() or EnterText()), this function must
		// be called again to "lock" the procedure into place.
		// The function Run() needs a locked procedure.
		// There must be at least one key inside the procedure, otherwise an error
		// dialog will come up.
		void Lock();

		// The main function of this class.
		// Types the key sequence.
		// The function Lock() must be called before Run() at least once, and again
		// after any modifications to the key sequence, otherwise an error
		// dialog will come up and nothing will be typed.
		void Run();

		// Used to check if the key input sequence is locked in place or not.
		// If it is, there is no need to call Lock() again.
		bool IsLocked();

		// Clears the memory allocated by Lock().
		// There is really no need to call this function as the duty of
		// freeing the memory will be performed by the destructor anyway.
		void Unlock();

		// Adds a keydown followed by a keyup of the specified key (virtual key code)
		// to the key input sequence.
		void PressKey( WORD key, KEYPRESS_TYPE type = PRESS );

		// Given a string of text, this function converts the text into
		// virtual key codes and adds it to the key input structure.
		void EnterText( const std::string & text );

		// Sets what type of event causes the key input sequence to be typed.
		// FIRST_LETTER - if the first letter of the sequence is pressed,
		// the remaining letters are typed. If this is the first parameter,
		// the second parameter is not needed to be included in the function call.
		// KEY_CONST - use this to cause the sequence to be typed if the key
		// specified in the second argument (virtual key code) was detected to be pressed.
		void SetActivationType( ACTIVATION_TYPE type, WORD key = 0 );
		void SetActivationKeys(  std::vector<WORD> keys );

		// Returns the virtual key code of the key that would cause the input sequence to be typed
		std::vector<WORD> GetActivationKeys();

		// In order to appear to be more human, this program can type the text with
		// a randomly-sized pause between each typed key. The length of the pause
		// varies within a range defined in the second argument if the first argument
		// is SLEEP_TYPE_RAND. If random intervals don't matter, the first argument can
		// be set to SLEEP_TIME_CONST, in which case the second
		// argument denotes a constant instead of random interval in miliseconds
		// (recommended at least a couple milliseconds, even for very fast typing).
		void SetSleepType( SLEEP_TYPE type, unsigned short time );

	private:

		// A vector of specialized Key structures that contain the keys to be pressed.
		// This is a specialized structure - in order to run the procedure, it must
		// be defined using Windows's structure, for which the function Lock() is used.
		std::vector<Key> proc;

		// Contains a pointer to the start of the Windows input structure
		INPUT* input;

		// What type of event causes the sequence to be typed
		// FIRST_LETTER - if the first letter of the sequence is pressed,
		// the remaining letters are typed. If this is the first parameter,
		// the second parameter is not needed to be included in the function call.
		// KEY_CONST - use this to cause the sequence to be typed if the key
		// specified in the second argument (virtual key code) was detected to be pressed.
		ACTIVATION_TYPE activation_type;

		// The key that would cause the sequence to be typed. Retrieve this using GetActivationKey().
		// If the activation_type is FIRST_LETTER, this contains the first key of the sequence.
		// If the activation_type is KEY_CONST, this contains the key that would cause the sequence to be typed.
		std::vector<WORD> activation_keys;

		// The type of interval between each typed key.
		// For human-like typing, use SLEEP_TYPE_RAND, which inserts a randomly-sized
		// pause between each typed key. The maximum of the randomly-sized interval
		// should be stored in sleep_time (in milliseconds).
		// When human-like typing is not required, use SLEEP_TYPE_CONST.
		// Then sleep_time will contain the exact number of milliseconds between
		// each pressed key (recommended at least a couple, even for very fast typing).
		SLEEP_TYPE sleep_type;

		// Amount of sleep time (in milliseconds) between each pressed key.
		// If sleep_type is SLEEP_TYPE_RAND, this contains the max of the randomly-sized interval
		// of time between each key (recommended about 125).
		// If sleep_type is SLEEP_TYPE_CONST, this contains the exact number of milliseconds between
		// each pressed key (recommended at least a couple, even for very fast typing).
		unsigned short sleep_time;

		// Denotes whether the key structure is currently synchronized with the
		// standard Windows key structure. If the key sequence is ever modified
		// using functions like EnterText() or PressKey(), the Lock() function should be
		// called to update the Windows key structure. Lock() must be called before Run()
		// at least once and after any modifications to the key sequence.
		bool Locked;

};

#endif


KeyProc.cpp - The implementation of the aforementioned CKeyProc class.
#include "KeyProc.h"

CKeyProc::CKeyProc()
{
	input = NULL;
	Locked = false;
	activation_type = FIRST_LETTER;
	sleep_type = SLEEP_TYPE_RAND;
	sleep_time = SLEEP_CONST;
}

CKeyProc::~CKeyProc()
{
	if( input )
	{
		delete [] input;
		input = NULL;
	}
}

void CKeyProc::SetActivationType( ACTIVATION_TYPE type, WORD key )
{
	activation_type = type;

	if( type == KEY_CONST )
	{
		activation_keys.clear();
		activation_keys.push_back( key );
	}
	else
	{
		if( proc.size() > 0 )
		{
			activation_keys.clear();
			activation_keys.push_back( proc[ 0 ].key );
		}
	}
}

void CKeyProc::SetActivationKeys(  std::vector<WORD> keys )
{
	activation_type = KEY_CONST;
	activation_keys = keys;
}

std::vector<WORD> CKeyProc::GetActivationKeys()
{
	return activation_keys;
}

void CKeyProc::PressKey( WORD key, KEYPRESS_TYPE type )
{
	this->Locked = false;

	if( type == PRESS || type == KEYDOWN )
		this->proc.push_back( Key( key ) );
	if( type == PRESS || type == KEYUP )
		this->proc.push_back( Key( key, false ) );
}

void CKeyProc::EnterText(const std::string &text)
{
	this->Locked = false;

	bool shift_down = false;
	bool ctrl_down = false;
	bool alt_down = false;

	for( unsigned int i = 0; i < text.size(); ++i )
	{
		SHORT vkText = VkKeyScan( text[ i ] );

		char c = text[ i ];

		if( (BYTE)(vkText>>8) & 1 )		// shift key pressed
		{
			if( !shift_down )
			{
				this->proc.push_back( Key( VK_SHIFT ) );
				shift_down = true;
			}
		}
		else
		{
			if( shift_down )
			{
				this->proc.push_back( Key( VK_SHIFT, false ) );
				shift_down = false;
			}
		}
		if( (BYTE)(vkText>>8) & 2 )		// ctrl key pressed
		{
			if( !ctrl_down )
			{
				this->proc.push_back( Key( VK_CONTROL ) );
				ctrl_down = true;
			}
		}
		else
		{
			if( ctrl_down )
			{
				this->proc.push_back( Key( VK_CONTROL, false ) );
				ctrl_down = false;
			}
		}
		if( (BYTE)(vkText>>8) & 4 )		// alt key pressed
		{
			if( !alt_down )
			{
				this->proc.push_back( Key( VK_MENU ) );
				alt_down = true;
			}
		}
		else
		{
			if( alt_down )
			{
				this->proc.push_back( Key( VK_MENU, false ) );
				alt_down = false;
			}
		}

		if( c == '\n' )
			PressKey( VK_RETURN );
		else
			PressKey( vkText );
	}

	// Release any modifier keys
	if( shift_down )
		this->proc.push_back( Key( VK_SHIFT, false ) );
	if( ctrl_down )
		this->proc.push_back( Key( VK_CONTROL, false ) );
	if( alt_down )
		this->proc.push_back( Key( VK_MENU, false ) );

	return;
}

void CKeyProc::Lock()
{
	if( proc.empty() )
	{
		MessageBoxA( NULL, "Attempted to lock an empty procedure!", "Error", MB_OK | MB_ICONERROR );
		return;
	}

	if( activation_type == FIRST_LETTER )
	{
		activation_keys.clear();
		activation_keys.push_back( proc[ 0 ].key );
	}
	else
	{
		if( activation_keys.empty() )
		{
			MessageBoxA( NULL, "Attempted to lock a procedure key whose key constant activation key is not set!", "Error", MB_OK | MB_ICONERROR );
			return;
		}
	}

	delete [] input;
	input = NULL;
	
	input = new INPUT[ proc.size() ];
	for( unsigned int i = 0; i < proc.size(); ++i )
	{
		::ZeroMemory( &input[ i ], sizeof( INPUT ) );

		input[ i ].type = INPUT_KEYBOARD;
		input[ i ].ki.wVk = proc[ i ].key;

		if( proc[ i ].state == false )
			input[ i ].ki.dwFlags = KEYEVENTF_KEYUP;
	}

	Locked = true;
}

void CKeyProc::Run()
{
	if( !Locked )
	{
		MessageBoxA( NULL, "Error", "Attempted to run an unlocked procedure!", MB_OK | MB_ICONERROR );
		return;
	}

	unsigned int start = 0;
	if( activation_type == FIRST_LETTER )
		start = 1;

	::Sleep( 100 );

	for( unsigned int i = start; i < proc.size(); ++i )
	{
		::SendInput( 1, &input[ i ], sizeof(INPUT) );
		if( sleep_type == SLEEP_TYPE_RAND )
		{
			::Sleep( 1 + int( sleep_time * rand() / (RAND_MAX + 1.0) ) );
			if( proc[ i ].key >= 0x30 && proc[ i ].key < 0x40 )
				::Sleep( 1+int((int)(sleep_time/2)*rand()/(RAND_MAX + 1.0)) );
		}
		else if( sleep_type == SLEEP_TYPE_CONST )
		{
			::Sleep( sleep_time );
		}

		if( ::GetAsyncKeyState( VK_ESCAPE ) )
		{
			// Clear key state?

			//
			return;
		}
	}

}

bool CKeyProc::IsLocked()
{
	return Locked;
}

void CKeyProc::Unlock()
{
	Locked = false;

	delete [] input;
	input = NULL;
}

void CKeyProc::SetSleepType(SLEEP_TYPE type, unsigned short time )
{
	this->sleep_type = type;
	this->sleep_time = time;
}


KeyProcMgr.h - Declaration of the CKeyProcMgr (key procedure manager) class. This class is used to streamline handling multiple instances of CKeyProc. It is responsible for detecting the activation keys for all the key procedures and running the procedures when needed.
#ifndef KEY_PROC_MGR_H
#define KEY_PROC_MGR_H

#include "KeyProc.h"
#include <ctime>

class CKeyProcMgr
{
	public:

		CKeyProcMgr();

		void AddProc( CKeyProc* proc );
		void RunAll( void );
		void RunDummyCalls();

	private:

		std::vector<CKeyProc*> procedures;
		bool Activate( CKeyProc* proc );
};

#endif


KeyProcMgr.cpp - Implementation of the aforementioned CKeyProcMgr class.
#include "KeyProcMgr.h"
#include "KeyProc.h"

CKeyProcMgr::CKeyProcMgr()
{
	srand((unsigned)time(0));
}

void CKeyProcMgr::AddProc( CKeyProc* proc )
{
	procedures.push_back( proc );
}

void CKeyProcMgr::RunAll()
{
	for( unsigned int i = 0; i < procedures.size(); ++i )
	{
		if( !procedures[ i ]->IsLocked() )
			procedures[ i ]->Lock();

		if( Activate( procedures[ i ] ) )
		{
			procedures[ i ]->Run();
			RunDummyCalls();
		}
	}
}

void CKeyProcMgr::RunDummyCalls()
{
	for( unsigned int i = 0; i < procedures.size(); ++i )
	{
		std::vector<WORD> activation_keys = procedures[ i ]->GetActivationKeys();
		for( unsigned int i = 0; i < activation_keys.size(); ++i )
			::GetAsyncKeyState( activation_keys[ i ] );
	}
	::GetAsyncKeyState( VK_ESCAPE );
}

bool CKeyProcMgr::Activate(CKeyProc *proc)
{
	std::vector<WORD> activation_keys = proc->GetActivationKeys();
	for( unsigned int i = 0; i < activation_keys.size(); ++i )
	{
		if( !( ::GetAsyncKeyState( activation_keys[ i ] ) ) )
			return false;
	}

	return true;
}


window.h - Declares the functions used to handle Windows type stuff, such as creating a window and handling its messages.
#ifndef WINDOW_H
#define WINDOW_H

#include <windows.h>

// A simple window procedure to use for handling basic messages - if a different
// one is desired, specify it in the fourth parameter to CrtWindow
LRESULT CALLBACK WndProcStd( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );

// Create a simple, frameless window to use for the application.
// Parameter 1 [in]: Simply pass on the HINSTANCE that was received in WinMain()
// Parameter 2 [out]: Will receive the window to be used by the application
// Parameter 3 [in]: Simply pass on the nCmdShow that was received in WinMain()
// Parameter 4 [in]: The window class name to be used
// Parameter 5 [in]: The caption that the window will have
// Parameter 6 [in]: Use to describe which window procedure should be used
// to handle any window messages. A simple one has been provided by default if this parameter
// is not specified.
bool CrtWindow( HINSTANCE hInstance, HWND& hWnd, int nCmdShow, const char* class_name, const char* caption,
			   WNDPROC wnd_proc_fn = &WndProcStd );

// Translates and dispatches any window messages.
// This function should be called continuously in order for the window to receive messages.
void HandleWndMessages( void );

#endif


window.cpp - Defines the functions declared in window.h
#undef UNICODE

#include "window.h"

LRESULT CALLBACK WndProcStd( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

bool CrtWindow( HINSTANCE hInstance, HWND& hWnd, int nCmdShow, const char* class_name, const char* caption,
			   WNDPROC wnd_proc_fn )
{
	WNDCLASSEX wc;

    //Step 1: Registering the Window Class
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = wnd_proc_fn;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = class_name;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return false;
    }

    // Step 2: Creating the Window
    hWnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        class_name,
		caption,
        WS_POPUPWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hWnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return false;
    }

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

	return true;

}

void HandleWndMessages( void )
{
	MSG msg;
	if( PeekMessage(&msg, NULL, 0, 0,PM_REMOVE) )
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}


Key.h - Defines a little structure to use for defining a single keystroke and its state (up or down).
#ifndef KEY_H
#define KEY_H

#include <windows.h>

struct Key
{
	Key( WORD _key, bool _state )
	{
		key = _key;
		state = _state;
	}
	Key( WORD _key )
	{
		key = _key;
		state = true;
	}
	WORD key;
	bool state;
};

#endif


constants.h - Defines some constants and typedefs to make the code in the other files more understandable.
#ifndef CONSTANTS_H
#define CONSTANTS_H

#include <string>
#include <windows.h>

typedef unsigned short ACTIVATION_TYPE;
const unsigned short FIRST_LETTER = 1;
const unsigned short KEY_CONST = 2;

typedef unsigned short SLEEP_TYPE;
const unsigned short SLEEP_TYPE_CONST = 1;
const unsigned short SLEEP_TYPE_RAND = 2;

typedef unsigned short KEYPRESS_TYPE;
const unsigned short PRESS = 1;
const unsigned short KEYDOWN = 2;
const unsigned short KEYUP = 3;

const unsigned short SLEEP_CONST = 100;	// 135?

#endif



This file is NOT a part of the library, but I will post it here to demonstrate how the library can be used. I wrote this source file for another project, the purpose of which was to test the library. It is in this project that I ran across the error message: source.cpp - Used to test the library. Defines the key procedure: ALT+G, SPACE, ENTER. This key procedure will be typed if the user presses CTRL+F12.
#undef UNICODE

#include <InputOp/InputOp.h>
#pragma comment(lib,"InputOp.lib")

const std::string active_title = "Active";
const std::string disabled_title = "Disabled";

void LoadProcedureSetBusiness( CKeyProc* proc );

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
	// Set up a display window (used only for the ability to communicate via
	// the title bar whether the application is in its active state, or disabled
	// state)
	HWND hWnd;
	CrtWindow( hInstance, hWnd, nCmdShow, "InputOpTest", active_title.c_str() );	

	// Declare the container for the key input procedure
	CKeyProc SetBusiness;

	// Load the procedure into its container
	LoadProcedureSetBusiness( &SetBusiness );
	
	// Set what type of event causes the keys to be typed
	// In this case, CTRL+F12
	std::vector<WORD> act;
	act.push_back( VK_CONTROL );
	act.push_back( VK_F12 );
	SetBusiness.SetActivationKeys(  act );

	// Set what type of interval there is between each letter in the key procedure
	// In this case, a constant interval of the default time, defined as SLEEP_CONST
	SetBusiness.SetSleepType( SLEEP_TYPE_CONST, SLEEP_CONST );

	// Set up the proper Windows input structure (not the specialized container),
	// and declare that no new keys will be entered into this procedure,
	// and ready the procedure for running
	SetBusiness.Lock();

	// Set up a more global structure for handling all procedures as a single group
	// In this case, the group has only one procedure
	CKeyProcMgr KeyManager;
	KeyManager.AddProc( &SetBusiness );

	// Allow the program to type the procedures
	// (program can be made inactive by pressing the 'x' key)
	bool bActive = true;

	// Input loop
	while( 1 )
	{
		// Quit condition = "Esc" key
		if( ::GetAsyncKeyState( VK_ESCAPE ) )
		{
			PostQuitMessage( 0 );
			return 0;
		}

		// Handle any messages sent to the window
		HandleWndMessages();

		// Catch activation keys for input procedures only if in active mode
		if( bActive )
		{
			// Run the proper procedure if the activation key was pressed
			KeyManager.RunAll();

		}

		// Enable/disable the program
		if( ::GetAsyncKeyState( VkKeyScan( 'x' ) ) )
		{
			::Sleep( 500 );

			bActive = !bActive;

			std::string str;
			if( bActive ) str = active_title;
			else str = disabled_title;

			SetWindowText( hWnd, str.c_str() );

			// Prevent the program from catching the keys sent
			// while in the disabled state
			if( bActive )
				KeyManager.RunDummyCalls();
		}

		// Don't hog up the CPU usage
		::Sleep( 30 );

	}

	PostQuitMessage( 0 );
	return 0;
}

void LoadProcedureSetBusiness( CKeyProc* proc )
{
	proc->PressKey( VK_LMENU, KEYDOWN );	// ALT (down)
	proc->PressKey( 0x47 );		// G key
	proc->PressKey( VK_LMENU, KEYUP );		// ALT (up)

	proc->PressKey( VK_SPACE );
	proc->PressKey( VK_RETURN );
}



While I don't know where the error was generated, I suspect it may be somewhere in window.h and window.cpp. I had some earlier problems declaring the CrtWindow() function. Note that the last parameter is a function pointer declared as WNDPROC. The default parameter that is provided is a pointer to the function WndProcStd. I realize this is a lot of code, but this is my last straw. Any help would be appreciated.

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