C ++中的函数指针和未知数量的参数

时间:2010-03-04 14:54:37

标签: c++ dll function-pointers loadlibrary

我遇到了以下奇怪的代码块。想象一下,你有以下typedef:

typedef int (*MyFunctionPointer)(int param_1, int param_2);

然后,在函数中,我们尝试以下列方式从DLL运行函数:

LPCWSTR DllFileName;    //Path to the dll stored here
LPCSTR _FunctionName;   // (mangled) name of the function I want to test

MyFunctionPointer functionPointer;

HINSTANCE hInstLibrary = LoadLibrary( DllFileName );
FARPROC functionAddress = GetProcAddress( hInstLibrary, _FunctionName );

functionPointer = (MyFunctionPointer) functionAddress;

//The values are arbitrary
int a = 5;
int b = 10;
int result = 0;

result = functionPointer( a, b );  //Possible error?

问题是,没有任何方法可以知道我们用LoadLibrary获取地址的功能是否有两个整数参数.dll名称由用户在运行时提供,然后列出导出函数的名称并且用户选择要测试的那个(再次,在运行时:S:S)。 那么,通过在最后一行执行函数调用,我们不是打开可能的堆栈损坏的大门吗?我知道这会编译,但是在我们将错误的参数传递给我们指向的函数的情况下会发生什么样的运行时错误?

8 个答案:

答案 0 :(得分:4)

我担心没有办法知道 - 程序员在获取函数指针并使用它时需要事先知道原型。

如果您事先不知道原型,那么我猜您需要在DLL中实现某种协议,您可以通过调用DLL中的已知函数来枚举任何函数名称及其参数。当然,需要编写DLL以符合此协议。

答案 1 :(得分:4)

如果预期和使用的参数数量或类型以及调用约定不同,我可以想到三个错误:

  • 如果调用约定不同,将读取错误的参数值
  • 如果函数实际上需要的参数多于给定的参数,则随机值将用作参数(我会让你想象如果涉及指针的后果)
  • 在任何情况下,返回地址都是完全垃圾,因此一旦函数返回,就会运行随机数据和随机数据。

用两个词来说:Undefined behavior

答案 2 :(得分:3)

如果这是一个__stdcall函数,他们就会保留完整的名称(两个都是ifs,但肯定是可能的),名称会@nn最后,nn是一个数字。该数字是函数期望作为参数的字节数,并在它返回之前清除堆栈。

因此,如果这是一个主要问题,您可以查看函数的原始名称,并检查您放入堆栈的数据量是否与要从堆栈中清除的数据量相匹配。

请注意,这仍然只是对Murphy的保护,而不是对Machiavelli的保护。在创建DLL时,可以使用导出文件来更改函数的名称。这经常被用来剥离名称错误 - 但我很确定它还可以让你将一个函数从xxx @ 12重命名为xxx @ 16(或其他),以误导读者关于它所期望的参数。

编辑:(主要是回复msalters的评论):你不能将__stdcall应用于类似成员函数的东西,但你肯定可以在全局函数之类的东西上使用它,无论它们是用C语言写的还是C ++。

对于像成员函数这样的东西,函数的导出名称将被破坏。在这种情况下,您可以使用UndecorateSymbolName获取其完整签名。使用它有点不重要,但也不是非常复杂。

答案 3 :(得分:1)

我不这么认为,这是一个很好的问题,唯一的规定就是你必须知道函数指针的工作参数是什么,如果你不这样做并盲目地填充参数并调用它,它将会崩溃或跳进树林永远不会被再次看到...程序员可以传达关于函数期望的内容和参数类型的消息,幸运的是你可以反汇编它并从查看堆栈指针中找出和通过'堆栈指针'(sp)的预期地址来找出参数的类型。

例如,使用PE Explorer,您可以找到使用的函数并检查反汇编转储...

希望这有帮助, 最好的祝福, 汤姆。

答案 4 :(得分:1)

它会在DLL代码中崩溃(因为它传递了损坏的数据),或者:我认为Visual C ++在调试版本中添加了代码以检测此类问题。它会说:“ESP的值不是通过函数调用保存的”,而是指向调用附近的代码。它有帮助,但并不完全健壮 - 我不认为它会阻止你传入错误但相同大小的参数(例如.int而不是x86上的char *参数)。正如其他答案所说,你真的必须知道。

答案 5 :(得分:1)

没有一般答案。标准要求在某些情况下抛出某些异常,但除此之外描述了如何执行符合性的程序,有时还说某些违规必须导致诊断。 (这里或那里可能有更具体的东西,但我当然不记得了。)

代码正在做什么不符合标准,并且由于有一个演员,编译器有权继续做任何程序员想要的愚蠢的事情而不抱怨。因此,这将是一个实施问题。

您可以查看您的实施文档,但它可能也不存在。您可以尝试或研究如何在您的实现上完成函数调用。

不幸的是,答案很可能是它会在没有立即明显的情况下搞砸了。

答案 6 :(得分:0)

通常,如果您正在调用LoadLibrary和GetProcByAddrees,那么您将拥有可以告诉您原型的文档。更常见的是,与所有windows.dll一样,您将获得一个头文件。虽然这会导致错误,如果错误通常很容易观察到,而不是那种潜入生产的错误。

答案 7 :(得分:0)

大多数C / C ++编译器让调用者在调用之前设置堆栈,然后重新调整堆栈指针。如果被调用的函数不使用指针或引用参数,则不存在内存损坏,尽管结果将毫无价值。正如重新运行所说,指针/参考错误几乎总是会出现一些测试。