在运行时创建具有唯一函数指针的函数

时间:2014-10-27 15:11:32

标签: c++ winapi boost lambda

当调用以回调作为参数的WinAPI函数时,通常会有一个特殊参数将一些任意数据传递给回调。如果没有这样的事情(例如SetWinEventHook),我们可以理解哪个API调用导致给定回调调用的唯一方法是具有不同的回调。当我们知道在编译时调用给定API的所有情况时,我们总是可以使用静态方法创建一个类模板,并在不同的调用方面使用不同的模板参数对其进行实例化。这是一件很糟糕的工作,我不喜欢这样做。

如何在运行时创建回调函数,以便它们具有不同的函数指针?

我在生成运行时程序集时看到了solution(抱歉,俄语),但它在x86 / x64架构中无法移植。

2 个答案:

答案 0 :(得分:2)

我已经提出了这个应该可移植的解决方案(但我还没有测试过):

#define ID_PATTERN          0x11223344
#define SIZE_OF_BLUEPRINT   128   // needs to be adopted if uniqueCallbackBlueprint is complex...

typedef int (__cdecl * UNIQUE_CALLBACK)(int arg);


/* blueprint for unique callback function */
int uniqueCallbackBlueprint(int arg)
{    
    int id = ID_PATTERN;

    printf("%x: Hello unique callback (arg=%d)...\n", id, arg);

    return (id);
}


/* create a new unique callback */
UNIQUE_CALLBACK createUniqueCallback(int id)
{
    UNIQUE_CALLBACK result = NULL;
    char *pUniqueCallback;
    char *pFunction;
    int pattern = ID_PATTERN;
    char *pPattern;
    char *startOfId;
    int i;
    int patterns = 0;


    pUniqueCallback = malloc(SIZE_OF_BLUEPRINT);

    if (pUniqueCallback != NULL)
    {
        pFunction = (char *)uniqueCallbackBlueprint;
#if defined(_DEBUG)
        pFunction += 0x256;  // variable offset depending on debug information????
#endif /* _DEBUG */
        memcpy(pUniqueCallback, pFunction, SIZE_OF_BLUEPRINT);
        result = (UNIQUE_CALLBACK)pUniqueCallback;


        /* replace ID_PATTERN with requested id */
        pPattern = (char *)&pattern;
        startOfId = NULL;
        for (i = 0; i < SIZE_OF_BLUEPRINT; i++)
        {
            if (pUniqueCallback[i] == *pPattern)
            {
                if (pPattern == (char *)&pattern)
                    startOfId = &(pUniqueCallback[i]);
                if (pPattern == ((char *)&pattern) + sizeof(int) - 1)
                {
                    pPattern = (char *)&id;
                    for (i = 0; i < sizeof(int); i++)
                    {
                        *startOfId++ = *pPattern++;
                    }
                    patterns++;
                    break;
                }

                pPattern++;
            }
            else
            {
                pPattern = (char *)&pattern;
                startOfId = NULL;
            }
        }

        printf("%d pattern(s) replaced\n", patterns);

        if (patterns == 0)
        {
            free(pUniqueCallback);
            result = NULL;
        }
    }

    return (result);
}

用法如下:

int main(void)
{
    UNIQUE_CALLBACK callback;
    int id;
    int i;

    id = uniqueCallbackBlueprint(5);
    printf("  -> id = %x\n", id);

    callback = createUniqueCallback(0x4711);
    if (callback != NULL)
    {
        id = callback(25);
        printf("  -> id = %x\n", id);
    }

    id = uniqueCallbackBlueprint(15);
    printf("  -> id = %x\n", id);

    getch();
    return (0);
}

如果使用调试信息(Visual Studio)进行编译,我注意到了一种有趣的行为。 pFunction = (char *)uniqueCallbackBlueprint;获得的地址由可变数量的字节关闭。使用显示正确地址的调试器可以获得差异。这种偏移从构建变为构建,我认为它与调试信息有关吗?这对于发布版本来说没有问题。所以也许这应该放在一个构建为“发布”的库中。

要考虑的另一件事是pUniqueCallback的字节对齐,这可能是一个问题。但是,将函数的开头与64位边界对齐并不难添加到此代码中。

pUniqueCallback内你可以实现你想要的任何东西(注意更新SIZE_OF_BLUEPRINT,这样你就不会错过你的函数尾部)。编译该函数,并在运行时重用生成的代码。创建唯一函数时会替换id的初始值,因此蓝图函数可以处理它。

答案 1 :(得分:2)

您可以使用the closure API of libffi。它允许您创建每个具有不同地址的蹦床。我实现了一个包装类here,虽然尚未完成(仅支持int个参数和返回类型,但您可以专门化detail::type以支持更多int })。更重要的替代方案是LLVM,但如果你只处理C类型,libffi将完成这项工作。