C将参数传递为void
我遇到的问题是我想要创建一个通用的命令行应用程序,可用于加载库DLL,然后调用库DLL中的函数。 函数名称在命令行中指定,参数也在实用程序命令行中提供。
我可以从使用LoadLibrary()
函数动态加载的DLL访问外部函数。 一旦库被加载,我可以使用GetProcAddress()
获得指向函数的指针,我想用命令行中指定的参数调用该函数。
我可以通过类似于下面的例子的LoadLibrary()
函数返回一个void-pointer-list到函数指针吗?
为了简化示例代码,我删除了错误检查。 有没有办法像这样工作:
//Somewhere in another dll int DoStuff(int a, int b) { return a + b; }
int main(int argc, char **argv) { void *retval; void *list = argv[3]; HMODULE dll; void* (*generic_function)(void*); dll = LoadLibraryA(argv[1]); //argv[2] = "DoStuff" generic_function = GetProcAddress(dll, argv[2]); //argv[3] = 4, argv[4] = 7, argv[5] = NULL retval = generic_function(list); }
如果我忘记提及必要的信息,请告诉我。 提前致谢
在调用LoadLibrary
,您需要将具有正确参数类型的函数指针转换为具有正确参数类型的函数指针。 管理它的一种方法是拥有一个号码调用适配器函数,它可以为您想要调用的每种可能的函数类型做正确的事情:
void Call_II(void (*fn_)(), char **args) {
void (*fn)(int, int) = (void (*)(int, int))fn_;
fn(atoi(args[0]), atoi(args[1]));
}
void Call_IS(void (*fn_)(), char **args) {
void (*fn)(int, char *) = (void (*)(int, char *))fn_;
fn(atoi(args[0]), args[1]);
}
...various more functions
然后,您从GetProcAddress
获取指针和附加参数,并将它们传递给正确的Call_X
函数:
void* (*generic_function)();
dll = LoadLibraryA(argv[1]);
//argv[2] = "DoStuff"
generic_function = GetProcAddress(dll, argv[2]);
//argv[3] = 4, argv[4] = 7, argv[5] = NULL
Call_II(generic_function, &argv[3]);
问题是你需要知道你得到指针的函数的类型是什么,并调用适当的适配器函数。 这通常意味着创建一个函数名称/适配器表并在其中进行查找。
相关的问题是没有类似于GetProcAddress
的函数可以告诉你函数库中的参数类型 - 这些信息并不存储在dll的任何可访问的地方。
库DLL包含作为库一部分的函数的目标代码以及允许DLL可用的一些附加信息。
但是,库DLL不包含确定库DLL中包含的函数的特定参数列表和类型所需的实际类型信息。 库DLL中的主要信息是:(1)DLL导出的函数列表,以及将函数调用连接到实际函数二进制代码的地址信息,以及(2)任何所需的DLL列表库DLL中的函数使用。
实际上,您可以在文本编辑器中打开一个库DLL,我建议使用它,然后扫描二进制代码的神秘符号,直到找到包含库DLL中函数列表的部分以及其他所需的DLL。
因此,库DLL包含(1)在库DLL中查找特定函数以便可以调用它以及(2)库DLL中的函数所依赖的其他所需DLL的列表所需的最少信息。
这与通常具有类型信息的COM对象不同,它支持基本反射的功能,并探究COM对象的服务以及访问这些服务的方式。 您可以使用Visual Studio和其他IDE来执行此操作,这些IDE会生成已安装的COM对象列表,并允许您加载COM对象并对其进行探索。 Visual Studio也有一个工具,它将生成提供存根和包含文件的源代码文件,以访问COM对象的服务和方法。
但是,库DLL与COM对象不同,并且COM对象提供的所有附加信息都不能从库DLL中获得。 相反,库DLL包通常由(1)库DLL本身,(2).lib文件组成,该文件包含库DLL的链接信息以及构建应用程序时使用的存根和功能,以满足链接器的需求库DLL,以及(3)包含文件,其中包含库DLL中函数的函数原型。
因此,通过调用驻留在库DLL中的函数,但使用包含文件中的类型信息并链接关联的.lib文件的存根来创建应用程序。 此过程允许Visual Studio自动化使用库DLL所需的大部分工作。
或者,您可以使用GetProcAddress()
手工编写LoadLibrary()
代码并在库DLL中构建函数的表格。 通过手动编码,您真正需要的就是库DLL中函数的函数原型,然后您可以自己键入函数原型并自定义库DLL。 如果您正在使用.lib库存根和包含文件,则实际上您正在执行Visual Studio编译器为您所做的工作。
如果您知道库函数的实际函数名称和函数原型,那么您可以做的是让您的命令行实用工具需要以下信息:
这与C和C ++运行时接受具有未知参数类型的可变参数列表的函数的工作方式类似。 例如,打印参数值列表的printf()
函数的格式字符串后面跟着要打印的参数。 printf()
函数使用格式字符串来确定各种参数的类型,期望的参数数量以及要做什么类型的值转换。
所以如果你的工具有一个如下所示的命令行:
dofunc "%s,%d,%s" func1 "name of " 3 " things"
库DLL有一个函数,其原型如下所示:
void func1 (char *s1, int i, int j);
那么该实用程序将通过将命令行的字符串转换为函数所需的实际类型来动态生成函数调用。
这对于使用Plain Old Data类型的简单函数有效,但是更复杂的类型(如struct
type参数)需要更多工作,因为您需要某种类型的struct
描述以及某种类似于JSON的参数描述。
附录I:一个简单的例子
以下是我在调试器中运行的Visual Studio Windows控制台应用程序的源代码。 Properties中的命令参数是pif.dll PifLogAbort
,它导致来自另一个项目pif.dll的库DLL被加载,然后导致该库中的函数PifLogAbort()
被调用。
注意:以下示例取决于基于堆栈的参数传递约定,如大多数x86 32位编译器所使用的。 大多数编译器还允许指定调用约定,而不是基于栈的参数传递,例如Visual Studio的__fastcall
修饰符。 同样正如在注释中指出的那样,x64和64位Visual Studio的默认默认情况下是使用__fastcall
约定,以便函数参数在寄存器中传递,而不是在堆栈中传递。 请参阅Microsoft MSDN中的x64调用约定概述。 请参阅gcc中如何实现可变参数中的注释和讨论? 。
请注意函数PifLogAbort()
的参数列表是如何构建为包含数组的结构的。 参数值被放入struct
变量的数组中,然后函数被称为按值传递整个struct
。 这样做是将参数数组的副本推入堆栈,然后调用该函数。 PifLogAbort()
函数根据参数列表查看堆栈,并将数组元素作为单独的参数或参数进行处理。
// dllfunctest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
typedef struct {
UCHAR *myList[4];
} sarglist;
typedef void ((*libfunc) (sarglist q));
/*
* do a load library to a DLL and then execute a function in it.
*
* dll name.dll "funcname"
*/
int _tmain(int argc, _TCHAR* argv[])
{
HMODULE dll = LoadLibrary(argv[1]);
if (dll == NULL) return 1;
// convert the command line argument for the function name, argv[2] from
// a TCHAR to a standard CHAR string which is what GetProcAddress() requires.
char funcname[256] = {0};
for (int i = 0; i < 255 && argv[2][i]; i++) {
funcname[i] = argv[2][i];
}
libfunc generic_function = (libfunc) GetProcAddress(dll, funcname);
if (generic_function == NULL) return 2;
// build the argument list for the function and then call the function.
// function prototype for PifLogAbort() function exported from the library DLL
// is as follows:
// VOID PIFENTRY PifLogAbort(UCHAR *lpCondition, UCHAR *lpFilename, UCHAR *lpFunctionname, ULONG ulLineNo);
sarglist xx = {{(UCHAR *)"xx1", (UCHAR *)"xx2", (UCHAR *)"xx3", (UCHAR *)1245}};
generic_function(xx);
return 0;
}
这个简单的例子说明了一些必须克服的技术障碍。 您需要知道如何将各种参数类型转换为内存区域中的正确对齐方式,然后将其推入堆栈。
这个示例函数的接口非常齐全,因为大部分参数都是unsigned char
指针,除了最后一个是int
。 使用32位可执行文件,所有这些变量类型中的四个具有相同的字节长度。 在参数列表中有更多不同类型的列表,您需要理解编译器在进行调用之前将参数推入堆栈时如何对齐参数。
附录二:扩展简单例子
另一种可能是有一组辅助函数以及不同版本的struct
。 该struct
提供了一个内存区域来创建必要堆栈的副本,并且帮助功能用于构建副本。
所以struct
和它的帮助函数可能如下所示。
typedef struct {
UCHAR myList[128];
} sarglist2;
typedef struct {
int i;
sarglist2 arglist;
} sarglistlist;
typedef void ((*libfunc2) (sarglist2 q));
void pushInt (sarglistlist *p, int iVal)
{
*(int *)(p->arglist.myList + p->i) = iVal;
p->i += sizeof(int);
}
void pushChar (sarglistlist *p, unsigned char cVal)
{
*(unsigned char *)(p->arglist.myList + p->i) = cVal;
p->i += sizeof(unsigned char);
}
void pushVoidPtr (sarglistlist *p, void * pVal)
{
*(void * *)(p->arglist.myList + p->i) = pVal;
p->i += sizeof(void *);
}
然后, struct
和帮助函数将用于构建参数列表,如下所示,之后将使用所提供的堆栈副本调用库DLL中的函数:
sarglistlist xx2 = {0};
pushVoidPtr (&xx2, "xx1");
pushVoidPtr (&xx2, "xx2");
pushVoidPtr (&xx2, "xx3");
pushInt (&xx2, 12345);
libfunc2 generic_function2 = (libfunc2) GetProcAddress(dll, funcname);
generic_function2(xx2.arglist);
链接地址: http://www.djcxy.com/p/84339.html
下一篇: How can I pass variables meant for a trigger function in SQLite?