Nota del autor

Si la entrada que estás leyendo carece de imágenes, no se ve el vídeo que teóricamente lleva incrustado o el código fuente mostrado aparece sin formato, podéis conocer los motivos aquí. Poco a poco iré restableciendo la normalidad en el blog.
Este blog es un archivo de los artículos situados previamente en Lobosoft.es y ha dejado de ser actualizado. Las nuevas entradas pueden encontrarse en www.lobosoft.es. Un saludo,
Lobosoft.

martes, 21 de abril de 2009

Funciones Variadic

José Manuel, un compañero de trabajo, tuvo que enfrentarse no hace mucho a las delicias de la interoperatibilidad (menudo palabro) de .NET con el código no administrado en C y C++. Entre las interesantes situaciones que se le ofrecieron, le llamó la atención especialmente la llamada a funciones con un número indeterminado de parámetros, y que puede llevarse a cabo llevando a cabo unos cuantos malabares. Interesado por el tema, y conociendo la existencia de este blog, me ofreció la posibilidad de compartir con la comunidad el siguiente artículo. Sobra indicar mi agradecimiento ante su colaboración. Aquí os dejo con su artículo:


En C y C++ se permite definir funciones con un número indefinido de argumentos. Son las llamadas funciones variadic, las cuales se declaran igual que las demás, salvo que en su lista de argumentos aparece, siempre en último lugar, el símbolo de elipsis (tres puntos).


Un ejemplo de función variadic es wsprintf, que se declara como:



int cdecl wsprintf(LPTSTR  lpOut, LPCTSTR  lpFmt, ...);

La función wsprintf da formato y almacena una serie de caracteres y valores en un búfer. Cualquier argumento se convierte y copia al búfer de salida de acuerdo al formato correspondiente especificado en la cadena de formato.


En esta declaración observamos que la función necesita al menos dos argumentos fijos y luego un número variable de argumentos, es decir, en diferentes invocaciones a la función, no tiene por que pasarse necesariamente el mismo número de parámetros.


Uso de funciones variadic no administradas desde .NET


En ocasiones tenemos que invocar desde código que se ejecuta bajo el control de la Common Language Runtime (código manejado), funciones que se ejecutan fuera de CLR, como por ejemplo las funciones de la API de Win32 (código no administrado).


Si además de eso, la función que queremos usar es variadic, dos son las posibilidades que tenemos para hacerlo. Supongamos que queremos hacer uso de la función wsprintf desde un proyecto de consola en Visual C#.NET.



La 1ª opción:

Digamos que es la “forma oficial”, la recomendada por Microsoft. Se trata ni más ni menos de sobrecargar la función abarcando todas las posibilidades de uso que vamos a necesitar. Esta no es para nada una solución flexible.



[csharp]
using System;
using System.Text;
using System.Runtime.InteropServices;

namespace Interop.UnmanagedCode.VariadicsFunctions
{
class wsprintfUse
{
// 1ª sobrecarga
[DllImport("user32.dll",
CallingConvention=CallingConvention.Cdecl)]
static extern int wsprintf(
[Out] StringBuilder buffer,
string format,
int arg);

// 2ª sobrecarga - varargs
[DllImport("user32.dll",
CallingConvention=CallingConvention.Cdecl)]
static extern int wsprintf(
[Out] StringBuilder buffer,
string format,
int arg1,
string arg2);

static void Main(string[] args)
{
StringBuilder buffer = new StringBuilder();
int result = wsprintf(buffer, "%d + %s", 2, "posibilidades.");
Console.WriteLine("result: {0}\n{1}", result, buffer);
}
}
}
[/csharp]

Ejecutar este código arrojaría el siguiente resultado:



Realizamos la llamada a través del atributo DllImport. Este atributo se puede aplicar a métodos y proporciona la información necesaria para importar una función exportada desde un archivo DLL no administrado. Como requisito mínimo, debe suministrarse el nombre del archivo DLL que contiene el punto de entrada.


Señalar que la convención de llamada necesaria para llamar a métodos implementados en código no administrado es Cdecl, en la que el llamador limpia la pila. Esto permite llamar a funciones con varargs, que resulta apropiado para funciones que aceptan un número variable de parámetros como la que nos ocupa.



La 2ª opción:

Si está dispuesto a basarse en características indocumentadas (= no deberían usarse), también puede utilizar la palabra clave __arglist para definir un método “varargs”.


No es conveniente utilizar la convención de llamada “varargs” o elipsis (...) ya que no es compatible con la Common Language Specification (CLS). Además, no es accesible para todos los lenguajes. Visual Basic no admite la convención de llamada VarArgs.


El beneficio es que, de esta forma, se puede utilizar un método único para todo tipo de parámetros sin necesidad de la sobrecarga:



[csharp]
using System;
using System.Text;
using System.Runtime.InteropServices;

namespace Interop.UnmanagedCode.VariadicsFunctions
{
class wsprintfUse
{
// 1ª sobrecarga
[DllImport("user32.dll",
CallingConvention=CallingConvention.Cdecl)]
static extern int wsprintf(
[Out] StringBuilder buffer,
string format,
__arglist);

static void Main(string[] args)
{
StringBuilder buffer = new StringBuilder();
int result = wsprintf(buffer, "%d + %s", __arglist(2, "posibilidades"));
Console.WriteLine("result: {0}\n{1}", result, buffer);
}
}
}
[/csharp]

En este caso, la salida quedaría:




__arglist se utiliza tanto en el método de declaración como en la llamada (adjuntando entre paréntesis, separados por comas, los parámetros a pasar).

Por último añadir que para bibliotecas de clases administradas, no hay necesidad de utilizar esa convención de llamada. Es mejor utilizar la palabra clave params (ParamArray en Visual Basic).



[csharp]
public void VariableArguments(params string[] wordList)
{
for(int i = 0; i < wordList.Length; i++)
{
Console.WriteLine(wordList[i]);
}
}
[/csharp]

Usando __arglist, habría que marcar al método con el siguiente atributo:
[csharp]
[CLSCompliant(false)]
public void VariableArguments(__arglist)
{
ArgIterator argumentIterator = new ArgIterator(__arglist);
for(int i = 0; i < argumentIterator.GetRemainingCount(); i++)
{
Console.WriteLine(
__refvalue(argumentIterator.GetNextArg(), string));
}
}
[/csharp]

4 comentarios:

  1. En una ocasión yo también tuve la curiosidad de saber qué significaban esos "tres puntitos de la muerte" en una función unmanaged, pero mis ansias del saber quedaron colmadas simplemente con saber que representaban la posibilidad de tener un número variable de argumentos.

    Es muy interesante saber cómo llamar desde código managed a este tipo de funciones, eso sí, sin duda yo también me quedo con la segunda opción, todo sea con el único fin de evitar tener que declarar 'n' funciones sobrecargadas.

    Por cierto, ¡menudo colaborador de lujo que te has echado..., felicidades Jose Manuel por el excelente artículo!

    ¡SaludoX!

    ResponderEliminar
  2. ¡Buenas tocayo!

    La verdad es que sí que es curioso el tema de la "interoperatibilidad", especialmente en casos como estos, en los que "truquillos" de un determinado lenguaje o tecnología quedan obsoletos y dan lugar a una aproximación más correcta, pero engorrosa, y a mantener, aunque sea "donde no lo vea la suegra" opciones no documentadas como las que nos presentaba Jose Manuel.

    Respecto al colaborador, la verdad es que sí, que se ha lucido con el estreno :) ¡De aquí a montar un imperio de blogs y bloggers, unidos por la comunicación y viviendo del AdSense! ¿Lo crees factible? :D

    Un saludo.

    ResponderEliminar
  3. ¿Sinceramente? Me encantaría..., pero actualmente veo imposible poder vivir de uno o varios blogs, ni aún teniendo AdSense y otros cuantos "patrocinadores habituales". En fin, ¡seguiremos luchando!

    Grazie mile por el link. ¡SaludoX!

    ResponderEliminar
  4. La verdad es que es complicado. Si ni tan siquiera "los grandes" (Microsiervos, y similares) afirman poder hacerlo, cuanto más nosotros, pobres neófitos del arte del blogging, jejeje. De todas formas, ni ellos se salvan de la crisis que, a todos los niveles, estamos sufriendo. En fin, es bonito soñar... tal vez en un remoto futuro...

    ¡Un saludote!

    ResponderEliminar