当前位置: 首页 > 知识库问答 >
问题:

C将参数作为void指针列表传递给从LoadLibrary()导入的函数

荆运诚
2023-03-14

我的问题是,我想创建一个通用的命令行应用程序,可以用来加载一个库DLL,然后调用库DLL中的函数。函数名在命令行中指定,参数也在实用程序命令行中提供。

我可以使用LoadLibrary()函数从动态加载的DLL访问外部函数。加载库后,我可以使用GetProcAddress()获得指向函数的指针。我想用命令行上指定的参数调用函数。

我是否可以将空点指针列表传递给 LoadLibrary() 函数返回的函数指针,类似于下面的示例?

为了保持示例代码简单,我删除了错误检查。有没有办法让这样的东西发挥作用:

    //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);
    }

如果我忘记提及必要的信息,请告诉我。提前致谢

共有2个答案

艾凌龙
2023-03-14

库DLL包含作为库的一部分的函数的对象代码,以及使DLL可用的一些附加信息。

但是,库DLL不包含确定特定参数列表和库DLL中包含的函数类型所需的实际类型信息。库DLL中的主要信息是:(1)DLL导出的函数列表以及将函数调用连接到实际函数二进制代码的地址信息,以及(2)库DLL中的函数使用的任何所需DLL的列表。

实际上,您可以在文本编辑器中打开一个库DLL,我建议您使用一个小编辑器,然后扫描二进制代码的神秘符号,直到您到达包含库DLL中函数列表以及其他所需DLL的部分。

因此,库 DLL 包含 (1) 在库 DLL 中查找特定函数以便可以调用该函数所需的最少信息,以及 (2) 库 DLL 中的函数所依赖的其他所需 DLL 的列表。

这与COM对象不同,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所需的大部分工作。

或者您可以手动编写LoadLibrary(),并使用GetProcAddress()在库DLL中构建函数表。通过手动编码,您真正需要的只是库DLL中函数的函数原型,然后您可以自己输入这些函数原型和库DLL本身。如果您使用. lib库存根和包含文件,您实际上是在手动完成Visual Studio编译器为您做的工作。

如果您知道库DLL中函数的实际函数名和函数原型,则可以让命令行实用程序需要以下信息:

  • 在命令行上作为文本字符串调用的函数的名称
  • 命令行上用作一系列文本字符串的参数列表
  • 描述函数原型的附加参数

这类似于 C 和 C 运行时中接受具有未知参数类型的变量参数列表的函数的工作方式。例如,打印参数值列表的 printf() 函数有一个格式字符串,后跟要打印的参数。printf() 函数使用格式字符串来确定各种参数的类型、预期的参数数以及要执行的值转换类型。

因此,如果您的实用程序有一个命令行,如下所示:

dofunc "%s,%d,%s" func1 "name of " 3 " things"

库DLL有一个函数,其原型如下:

void func1 (char *s1, int i, int j);

然后,该实用程序将通过将命令行的字符串转换为要调用的函数所需的实际类型来动态生成函数调用。

这将适用于采用普通旧数据类型的简单函数,但更复杂的类型,如structtype参数,将需要更多的工作,因为您需要对struct进行某种描述,以及某种可能类似于JSON的参数描述。

附录一:一个简单的例子

以下是我在调试器中运行的可视化工作室 Windows 控制台应用程序的源代码。属性中的命令参数是 pif.dll PifLogAbort,它导致加载来自另一个项目的库 DLL,pif.dll,然后调用该库中的函数 PifLogAbort()

注意:以下示例依赖于基于堆栈的参数传递约定,这与大多数 x86 32 位编译器一起使用。大多数编译器还允许指定调用约定,而不是基于堆栈的参数传递,例如 Visual Studio 的__fastcall修饰符。此外,如注释中所述,x64 位和 64 位 Visual Studio 的默认做法是默认使用__fastcall约定,以便在寄存器中传递函数参数,而不是在堆栈上传递。请参阅微软 MSDN 中的 x64 调用约定概述。另请参阅如何在 gcc 中实现变量参数中的注释和讨论。

请注意函数< code>PifLogAbort()的参数列表是如何构建为包含数组的结构的。参数值被放入< code>struct的变量数组中,然后调用该函数,按值传递整个< code>struct。这样做的目的是将参数数组的副本推送到堆栈上,然后调用函数。< code>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;
}

这个简单的示例说明了必须克服的一些技术障碍。您需要知道如何将各种参数类型转换为内存区域中的正确对齐方式,然后将其推送到堆栈上。

这个示例函数的接口非常同质,因为大多数参数都是无符号字符指针,但最后一个参数是一个int。对于32位可执行文件,所有四种变量类型都具有相同的字节长度。在参数列表中有更多类型的列表,您需要了解编译器在执行调用之前将参数推送到堆栈时如何对齐参数。

附录二:扩展简单示例

另一种可能性是拥有一组助手函数以及不同版本的< code>struct。< code>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 *);
}

然后,将使用< code>struct和helper函数来构建如下所示的参数列表,之后使用所提供的堆栈副本调用库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);
赵雪峰
2023-03-14

在调用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

然后,您将从 GetProc 地址和其他参数中获取的指针传递给正确的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中任何可访问的位置。

 类似资料:
  • C++ 指针 C++ 允许您传递指针给函数,只需要简单地声明函数参数为指针类型即可。 下面的实例中,我们传递一个无符号的 long 型指针给函数,并在函数内改变这个值: #include <iostream> #include <ctime> using namespace std; void getSeconds(unsigned long *par); int main () {

  • 当我构建这个应用程序时,我得到错误: 我也试过: 这将错误更改为: 下面是调用这个例程的类和我传递的回调函数的示例:

  • 我有以下代码: 我正在尝试将char和char指针数组传递给函数,但上面的代码会导致以下错误和警告: 我正在努力理解问题是什么。

  • 我创建了一个名为InputControl的类。我正在使用一个名为GLFW的库和一个函数glfwsetkeycallback。该函数定义为: E0167“void(inputcontrol::*)(GLFWwindow*window,int key,int scancode,int action,int mods)”类型的参数与“GLFWKeyFun”类型的参数不兼容

  • 问题内容: 我已经熟悉Android框架和Java,并希望创建一个通用的“ NetworkHelper”类,该类可以处理大多数联网代码,使我能够从中调用网页。 我遵循了来自developer.android.com的这篇文章来创建我的网络类:http : //developer.android.com/training/basics/network- ops/connecting.html 码:

  • 我正试图将dataframe列作为参数传递 但是得到错误 :33:错误:类型不匹配; 找到:org.apache.spark.sql.column 必需:int val df_new=df.withcolumn(“age_category”,ageclassification.agecategory(df(“age”))