Page 1 of 1

Obtaining the address of a C++ method using the Microsoft Compiler Rate Topic: -----

#1 Martyn.Rae   User is offline

  • The programming dinosaur
  • member icon

Reputation: 551
  • View blog
  • Posts: 1,431
  • Joined: 22-August 09

Posted 29 October 2018 - 10:55 AM

I have on occasion, needed the address of a method belonging to a C++ class and been reminded that you cannot do that!!!

You get error C2276: '&': illegal operation on bound member function expression

As with everything programming, there is a way to do it (albeit long winded and may potentially not work if the code generated by the Microsoft C++ compiler ever changes). So the methodology described below should NOT be used in software that goes into production, but the mechanics behind the code are very useful to know.

Let's start off with a simple class called A that has an unsigned long as an encapsulated data field called b. We will also have a method setb with a parameter of unsigned long which we can use to ensure the principles described below work.

class A {
public:
	unsigned long b;
	void __stdcall setb(unsigned long value);
};

void __stdcall A::setb(unsigned long value) {
	b = value;
}




Now it is important to understand that we need to create a dynamic instance of the class A using the new operator.

int main() {
	A* a = new A;
}



Creating an instance of the class A is in itself pretty useless, unless we make the setb function virtual (not pure virtual!) as shown below.

class A {
public:
	unsigned long b;
	virtual void __stdcall setb(unsigned long value);
};

void __stdcall A::setb(unsigned long value) {
	b = value;
}

int main() {
	A* a = new A;
}



This forces the compiler to create a vtable for our class A. The address of the vtable is either 4 or 8 bytes (depending on whether you are compiling in 32-bit or 64-bit) prior to the address of the instance.

class A {
public:
	unsigned long b;
	virtual void __stdcall setb(unsigned long value);
};

void __stdcall A::setb(unsigned long value) {
	b = value;
}

int main() {
	A* a = new A;
	char * vtable = reinterpret_cast<char*>(reinterpret_cast<char *>(a) - sizeof(void *));
}



The vtable consists of a table of pointers, one for each method described as virtual. So in the case of the class A, there will be a 4 or 8 byte pointer (again depending on 32-bit or 64-bit compilation). There is a 4 or 8 byte fill before the start of the table pointers. So to access the vtable address of setb we need to know that this is the 0th entry in the vtable after the fill.

class A {
public:
	unsigned long b;
	virtual void __stdcall setb(unsigned long value);
};

void __stdcall A::setb(unsigned long value) {
	b = value;
}

int main() {
	A* a = new A;
	char * vtable = reinterpret_cast<char*>(reinterpret_cast<char *>(a) - sizeof(void *));
	char* vtableEntry = (reinterpret_cast<char *>(vtable) + sizeof(void *));
}



Now the vtableEntry is a pointer to an entry that contains a pointer to a jmp (0xE9) instruction followed by the relative address of our method setb.
So, we need to load the relative offset.

class A {
public:
	unsigned long b;
	virtual void __stdcall setb(unsigned long value);
};

void __stdcall A::setb(unsigned long value) {
	b = value;
}

int main() {
	A* a = new A;
	char * vtable = reinterpret_cast<char*>(reinterpret_cast<char *>(a) - sizeof(void *));
	char* vtableEntry = (reinterpret_cast<char *>(vtable) + sizeof(void *));
	char* vtableAddresses = *(char **)vtableEntry;
	char *instructionOpcode = *(char **)vtableAddresses;
	char *jmpInstructionOffset = instructionOpcode + 1;
	char* routineOffset = *(char**)jmpInstructionOffset;
}



Finally we get the actual address of our routine setb as follows.

class A {
public:
	unsigned long b;
	virtual void __stdcall setb(unsigned long value);
};

void __stdcall A::setb(unsigned long value) {
	b = value;
}

int main() {
	A* a = new A;
	char * vtable = reinterpret_cast<char*>(reinterpret_cast<char *>(a) - sizeof(void *));
	char* vtableEntry = (reinterpret_cast<char *>(vtable) + sizeof(void *));
	char* vtableAddresses = *(char **)vtableEntry;
	char *instructionOpcode = *(char **)vtableAddresses;
	char *jmpInstructionOffset = instructionOpcode + 1;
	char* routineOffset = *(char**)jmpInstructionOffset;
	char *routineAddress = instructionOpcode + (long)routineOffset + 1 + sizeof(void *);
}



To test that this works we need to typedef a function call and then call the address we have computed. The final code is shown below.

class A {
public:
	unsigned long b;
	virtual void __stdcall setb(unsigned long value);
};

void __stdcall A::setb(unsigned long value) {
	b = value;
}

typedef void(__stdcall *func)(A* _THIS, unsigned long value);

int main() {
	A* a = new A;
	char * vtable = reinterpret_cast<char*>(reinterpret_cast<char *>(a) - sizeof(void *));
	char* vtableEntry = (reinterpret_cast<char *>(vtable) + sizeof(void *));
	char* vtableAddresses = *(char **)vtableEntry;
	char *instructionOpcode = *(char **)vtableAddresses;
	char *jmpInstructionOffset = instructionOpcode + 1;
	char* routineOffset = *(char**)jmpInstructionOffset;
	char *routineAddress = instructionOpcode + (long)routineOffset + 1 + sizeof(void *);
	func function = (func)routineAddress;
	function(a, 16);
}



Notice that we need to supply a pointer to the class instance.

Finally, we will add multiple instances of our class A and a further virtual function setc that has a float parameter.

class A {
public:
	unsigned long b;
	float c;
	virtual void __stdcall setb(unsigned long value);
	virtual void __stdcall setc(float value);
};

void __stdcall A::setb(unsigned long value) {
	b = value;
}

void __stdcall A::setc(float value) {
	c = value;
}

typedef void(__stdcall *funcb)(A* _THIS, unsigned long value);
typedef void(__stdcall *funcc)(A* _THIS, float value);

int main() {
	A* a = new A;
	A* b = new A;
	A* c = new A;
	char * vtable = reinterpret_cast<char*>(reinterpret_cast<char *>(a) - sizeof(void *));
	char* vtableEntry = (reinterpret_cast<char *>(vtable) + sizeof(void *));
	char* vtableAddresses = *(char **)vtableEntry;
	char *instructionOpcode = *(char **)vtableAddresses;
	char *jmpInstructionOffset = instructionOpcode + 1;
	char* routineOffset = *(char**)jmpInstructionOffset;
	char *routineAddress = instructionOpcode + (long)routineOffset + 1 + sizeof(void *);
	funcb functionb = (funcb)routineAddress;
	functionb(a, 10);
	functionb(b, 20);
	functionb(c, 30);
        // Access the second virtual function setc
	vtableAddresses += sizeof(void *);
	instructionOpcode = *(char **)vtableAddresses;
	jmpInstructionOffset = instructionOpcode + 1;
	routineOffset = *(char**)jmpInstructionOffset;
	routineAddress = instructionOpcode + (long)routineOffset + 1 + sizeof(void *);
	funcc functionc = (funcc)routineAddress;
	float f = 15.67f;
	functionc(a, f);
}



Is This A Good Question/Topic? 0
  • +

Page 1 of 1