.Net - 传递可变数量的参数

时间:2017-06-14 09:20:05

标签: c# .net c++-cli system.reflection reflection.emit

我需要处理使用Reflection导入的类中的事件。为此,我使用必需的参数类型创建一个动态方法,然后将其转换为委托并添加为事件处理程序。

我在动态方法中唯一需要做的就是调用一个接收可变数量参数的编译方法。因此,我打包'任何类型的任何数量的参数都可以将它传递给已编译的方法。

这就出现了麻烦:似乎我需要在IL操作码中手动创建数组并用参数填充它(这有点复杂),而不是仅仅推送堆栈上的所有参数(这很简单)。 / p>

这是代码(C ++ / CLI):

array<System::Type^> ^GetParameterTypes(System::Reflection::MethodInfo ^method)
{
    auto parameters = method->GetParameters();
    auto typeParameters = gcnew array<System::Type ^> (parameters->Length);

    for (int i = 0; i < parameters->Length; i++)
        typeParameters[i] = parameters[i]->ParameterType;
    return typeParameters;
}


ref class HandlerClass
{
public:

    void TestMethod(... array<System::Object ^> ^parameters)
    {
        System::Console::WriteLine("asdf");
    }
}


System::Delegate ^AddHandler(HandlerClass ^handler, System::Reflection::EventInfo ^_event, System::Object ^instance)
{
    // Get handler type
    auto delegateType = _event->EventHandlerType;
    assert(delegateType);

    auto invoke = delegateType->GetMethod("Invoke");
    assert(invoke);

    // Get return type
    auto returnType = invoke->ReturnType;

    // Get parameter list
    auto delegateParameters = GetParameterTypes(invoke);

    auto parameters = gcnew array<System::Type ^> (delegateParameters->Length + 1);
    parameters[0] = System::Object::typeid;
    delegateParameters->CopyTo(parameters, 1);

    // Create dynamic method
    auto handlerMethod = gcnew System::Reflection::Emit::DynamicMethod("",
                            returnType,
                            parameters,
                            ProxyEvent::typeid);

    auto method = HandlerClass::typeid->GetMethod("TestMethod");

    // Add method body
    auto codeGen = handlerMethod->GetILGenerator();

    // 'this' pointer
    codeGen->Emit(System::Reflection::Emit::OpCodes::Ldarg_0);

    // Parameters
    for (int i = 0; i < delegateParameters->Length; i++)
        codeGen->Emit(System::Reflection::Emit::OpCodes::Ldarg, i + 1);

    // Method call
    codeGen->Emit(System::Reflection::Emit::OpCodes::Call, method);
    //codeGen->EmitCall(System::Reflection::Emit::OpCodes::Call, method, parameters);   //This one throws an exception that calling convention should be Vararg

    // Return
    codeGen->Emit(System::Reflection::Emit::OpCodes::Ret);

    // Get delegate
    auto compiled = handlerMethod->CreateDelegate(delegateType, handler);

    // Add to handler list
    _event->AddEventHandler(instance, compiled);
}

正如您所看到的,我的TestMethod函数并不是一个真正的可变函数。 C#等价物为void TestMethod(params object[] parameters);。这是因为C ++ / CLI不支持__arglist关键字。 因此,这个IL代码不正确,我在TestMethod中没有看到任何参数。

问题是:有没有办法声明一个可变参数函数(在C ++ / CLI中),这样我只需要传递堆栈上的参数并调用它?

如果没有,那么有没有办法实现类似的结果(通过反射导入的事件将参数传递给编译函数)而不使用IL代码生成器?

2 个答案:

答案 0 :(得分:0)

您可以在C ++ / CLI“ref class”(托管类)中定义具有可变数量参数的函数。语法要求将“...”标记添加到包含这些参数的数组,并且必须是函数声明中的最后一个参数。例如:

public: void SomeFunction (    int         iRequiredArgument1,
                               int         iRequiredArgument2,
                           ... array<int>^ aiVariableArguments)
    {
    // function implementation
    return;
    }

如果在编译时未知变量参数的类型,请使用

array<Object^>^

并在运行时对其元素进行类型转换。

答案 1 :(得分:0)

使用this教程,我想到了以下代码:

System::Reflection::Emit::DynamicMethod ^CreateHandler()
{
    // Get event handler type
    auto delegateType = _event->EventHandlerType;
    assert(delegateType);

    auto invoke = delegateType->GetMethod("Invoke");
    assert(invoke);

    // Get return type
    auto returnType = invoke->ReturnType;

    // Get parameters list
    auto delegateParameters = GetParameterTypes(invoke);

    // First parameter should be a pointer to my handler class
    auto parameters = gcnew array<System::Type ^> (delegateParameters->Length + 1);
    parameters[0] = MyHandlerClass::typeid;
    delegateParameters->CopyTo(parameters, 1);

    // Create dynanic method for my handler class
    auto handler = gcnew System::Reflection::Emit::DynamicMethod(
        _event->Name + "Handler",
        returnType,
        parameters,
        MyHandlerClass::typeid
    );

    // This method should be called
    auto method = MyHandlerClass::typeid->GetMethod("GeneralEventHandler");

    // Add method body
    auto codeGen = handler->GetILGenerator();

    // Get type of 'object[]'
    auto arrayType = System::Object::typeid->MakeArrayType();

    // Create local variable 'args' (at index 0) of type 'object[]'
    auto localArgs = codeGen->DeclareLocal(arrayType);

    // Create an array of arguments of required size
    codeGen->Emit(System::Reflection::Emit::OpCodes::Ldc_I4, delegateParameters->Length);   // Array size
    codeGen->Emit(System::Reflection::Emit::OpCodes::Newarr, System::Object::typeid);       // Creating array
    codeGen->Emit(System::Reflection::Emit::OpCodes::Stloc_0);                              // Store to local variable at index 0 ('args')

    // Fill array
    for (int i = 0; i < delegateParameters->Length; i++)
    {
        // Store (i + 1) argument to array at index i
        codeGen->Emit(System::Reflection::Emit::OpCodes::Ldloc_0);          // Refer to array
        codeGen->Emit(System::Reflection::Emit::OpCodes::Ldc_I4, i);        // Move to index i
        codeGen->Emit(System::Reflection::Emit::OpCodes::Ldarg_S, i + 1);   // Refer to (i + 1) argument

        // Is argument of simple type?
        if (delegateParameters[i]->IsValueType)
        {
            // Pack argument
            codeGen->Emit(System::Reflection::Emit::OpCodes::Box, delegateParameters[i]);
        }

        // Store element to array
        codeGen->Emit(System::Reflection::Emit::OpCodes::Stelem_Ref);
    }

    // Now we put everything on stack:
    // 'this' pointer
    codeGen->Emit(System::Reflection::Emit::OpCodes::Ldarg_0);

    // Nane of event (for identification)
    codeGen->Emit(System::Reflection::Emit::OpCodes::Ldstr, _event->Name);

    // Array of arguments
    codeGen->Emit(System::Reflection::Emit::OpCodes::Ldloc_0);

    // Now call the method
    codeGen->Emit(System::Reflection::Emit::OpCodes::Call, method);

    // And return
    codeGen->Emit(System::Reflection::Emit::OpCodes::Ret);

    return handler;
}

处理程序类如下:

ref class MyHandlerClass
{
public:

    void GeneralEventHandler(System::String ^name, array<System::Object ^> ^params);
}

然后我只使用这种动态方法连接到事件:

System::Delegate ^Connect(System::Object ^instance, MyHandlerClass ^handler)
{
    auto method = CreateHandler();
    assert(method);

    // Создадим делегат
    auto delegate = method->CreateDelegate(_event->EventHandlerType, handler);
    assert(delegate);

    // Добавим в список обработчиков
    _event->AddEventHandler(instance, delegate);

    return delegate;
}

代码是用C ++ / CLI编写的,但是转换成C#相当容易。

这个想法与我最初的帖子中的想法相同,只是添加了对象生成代码数组。