1. Puntero de función
AddressOf obtiene un puntero de función dentro de VB. Podemos pasar este puntero de función a la API que necesita devolver esta función. Su función es permitir que programas externos llamen a funciones dentro de VB.
Sin embargo, la aplicación de punteros de función en VB no es tan extensa como en C, porque el documento de VB solo presenta cómo pasar punteros de función a API para implementar devoluciones de llamada, y no señala las muchas funciones mágicas de los punteros de función, porque VB no fomenta el uso de punteros, los punteros de función no son una excepción.
Primero, clasifiquemos cómo se utilizan los punteros de función.
1. Devolución de llamada. Esta es la función más básica e importante. Por ejemplo, el núcleo de la tecnología de derivación de subclases introducida en la documentación de VB son dos API: SetWindowLong y CallWindowPRoc.
Podemos usar la API SetWindowLong para reemplazar el puntero de función de la ventana original con nuestro propio puntero de función y guardar el puntero de función de la ventana original. De esta manera, los mensajes de ventana se pueden enviar a nuestras propias funciones y podemos usar CallWindowProc para llamar al puntero de ventana previamente guardado en cualquier momento para llamar a la función de ventana original. De esta manera, podemos manejar mensajes enganchados sin destruir la funcionalidad de la ventana original.
Deberíamos estar familiarizados con el procesamiento específico y el documento VB también lo explica muy claramente. Lo que necesita atención aquí es la API CallWindowProc, veremos su maravilloso uso más adelante.
Aquí llamamos a las devoluciones de llamada para permitir "llamadas externas a punteros de funciones internas".
2. Para uso interno del programa. Por ejemplo, en C, podemos pasar el puntero de función C como parámetro a una función C que requiere un puntero de función, como la función qsort de la biblioteca C que se discutirá más adelante. Su declaración es la siguiente:
Requiere un puntero de función de tipo COMPARAR para comparar los tamaños de dos variables, de modo que la función de clasificación pueda llamar a este puntero de función para comparar diferentes tipos de variables, de modo que qsort pueda ordenar matrices de variables de diferentes tipos.
Llamemos a esta aplicación "llamar a punteros de funciones internas desde dentro".
3. Llamar a funciones externas
Quizás se pregunte: ¿el uso de API no es simplemente llamar a funciones externas? Sí, pero a veces todavía necesitamos obtener directamente el puntero de la función externa. Por ejemplo, cargar dinámicamente una DLL a través de LoadLibrary, luego obtener el puntero de entrada de función que necesitamos a través de GetProcAddress y luego llamar a funciones externas a través de este puntero de función. Esta tecnología de carga dinámica de DLL nos permite llamar a funciones externas de manera más flexible.
A este método lo llamamos "llamar a un puntero de función externa desde adentro"
4. No hace falta decir que también podemos controlar "llamar a punteros de funciones externas desde el exterior". No, por ejemplo, podemos cargar varias DLL y pasar el puntero de función en una DLL a la función en otra DLL.
Las divisiones "internas" y "externas" anteriores son términos relativos (la DLL en realidad todavía está en proceso). Esta clasificación nos ayudará a discutir problemas en el futuro. Recuerde mi clasificación anterior, porque los artículos futuros también utilizarán esta clasificación. para analizar el problema.
El uso de punteros de función no es más que los cuatro métodos anteriores. Pero en el uso real es flexible y modificable. Por ejemplo, la herencia y el polimorfismo en C y las interfaces en COM son aplicaciones inteligentes de una tabla de punteros de funciones llamada vTable. El uso de punteros de función puede hacer que el procesamiento del programa sea más eficiente y flexible.
Excepto por el primer método, el documento VB no introduce otros métodos y también establece claramente que el puntero de función "Básico a Básico" no es compatible (es decir, el segundo método mencionado anteriormente, de hecho, a través de cierto HACK). Se pueden lograr los cuatro métodos anteriores. Hoy, echemos un vistazo a cómo implementar el segundo método. Debido a que es relativamente simple de implementar, comencemos con el más simple. En cuanto a cómo llamar a punteros de función externos en VB y cómo implementar la aplicación inteligente de varios punteros de función procesando la tabla de salto de puntero de función de la interfaz vTable en VB, ya que esto involucrará los principios internos de COM, lo explicaré en otro artículo. .
De hecho, la documentación de VB no está mal. VB no admite el puntero de función "Básico a Básico", pero podemos implementar un método indirecto, es decir, primero "Básico a API" y luego usar el primer método. "externo Llame al puntero de función interna" para pasar de "API a BÁSICO", logrando así el propósito del segundo método de "Básico a Básico". Podemos llamar a esta tecnología "devolución de llamada forzada", que solo está disponible en VB. Qué extraño. tecnología.
Es un poco complicado, pero piense detenidamente en CallWindowProc en la tecnología de derivación de subclases de ventana. Podemos usar CallWindowProc para forzar al sistema operativo externo a llamar a nuestro puntero de función de ventana original guardado. De manera similar, también podemos usarlo para forzar la llamada a nuestro puntero de función de ventana interno. puntero de función.
Jaja, dije antes que deberíamos hablar menos de principios y más de movimientos. ¡Ahora comencemos a aprender los movimientos!
Considere que implementamos qsort en VB que admite la comparación de múltiples palabras clave como C. El código fuente completo se puede encontrar en el código de soporte de este artículo. Aquí solo se proporciona el código relacionado con la aplicación del puntero de función.
Echemos un vistazo final a nuestra declaración qsort final.
El ArrayPtr anterior es el puntero al primer elemento de la matriz que debe ordenarse, nCount es el número de elementos de la matriz, nElemSize es el tamaño de cada elemento y pfnCompare es nuestro puntero de función de comparación. Esta declaración es muy similar a qsort en la función de la biblioteca C.
Al igual que C, podemos pasar el puntero de función de Basic a la función qsort de Basic.
Cómo usarlo:
Amigos inteligentes, ¿ya habéis visto el misterio aquí? Como prueba, ¿puede darme ahora una forma de utilizar punteros de función en qsort? Por ejemplo, ahora necesitamos comparar el tamaño del elemento i-ésimo y el elemento j-ésimo de la matriz llamando a un puntero de función.
Sí, por supuesto, debes usar la API de comparación declarada anteriormente (en realidad, CallWindowProc) para realizar devoluciones de llamada forzadas.
La implementación específica es la siguiente:
Se introducen los movimientos, ¿entiendes? Permítanme explicar brevemente el significado de Comparar anterior. Hace un uso muy inteligente de la API CallWindowProc. Esta API requiere cinco parámetros. El primer parámetro es un puntero de función normal. Esta API puede devolver el puntero de función inmediatamente y pasar los últimos cuatro parámetros de tipo Long de esta API a la función señalada por el puntero de función. Es por eso que nuestra función de comparación debe tener cuatro parámetros, porque la API CallWindowProc requiere que el puntero de función que se le pasa se ajuste al prototipo de función WndProc. El prototipo de WndProc es el siguiente:
Los LRESULT, HWND, UINT, WPARAM y LPARAM anteriores pueden corresponder al tipo Long en VB. ¡Esto es realmente genial, porque el tipo Long se puede usar como puntero!
Veamos el flujo de trabajo nuevamente. Cuando llamamos a qsort usando AddressOfCompareSalaryName como parámetro de puntero de función, al parámetro formal pfnCompare de qsort se le asigna el puntero de función del parámetro real CompareSalaryName. En este momento, llamar a Compare para forzar una devolución de llamada a pfnCompare equivale a llamar a la siguiente instrucción VB:
¿Esto no provocará un error de discrepancia en el tipo de parámetro? ¿No son los dos primeros parámetros de CompareSalaryName de tipo TEmployee? De hecho, dicha llamada no es posible en VB, porque la verificación de tipo de VB no permitirá dicha llamada. Sin embargo, esta llamada es en realidad una devolución de llamada realizada por la API, y es imposible para VB verificar si el tipo de parámetro de la función de devolución de llamada API es un tipo numérico largo ordinario o un puntero de estructura, por lo que también se puede decir que tenemos omitió el control de VB de los parámetros de función. Para la verificación de tipos, podemos declarar este parámetro Long como un puntero de cualquier tipo, lo que declararemos, VB lo considerará. Por lo tanto, debemos usar esta técnica con cuidado. Por ejemplo, el parámetro "ArrayPtr (i-1)*nElemSize" que eventualmente se pasará a la función CompareSalaryName anterior es solo una dirección. VB no verificará esta dirección. La dirección se trata como un puntero de tipo TEmployee si se usa accidentalmente como "ArrayPtr". i*nElemSize", entonces, cuando i sea el último elemento, provocaremos un error de acceso a la memoria, por lo que debemos prestar atención a los problemas de límites, como cuando se trata de punteros en C.
La aplicación inteligente de los punteros de función ya se puede ver aquí, pero el método presentado aquí todavía tiene grandes limitaciones. Nuestra función debe tener cuatro parámetros. Un enfoque más limpio es escribir una DLL en VC o Delphi y crear una API más compatible para implementar funciones. similar a CallWindowProc. He rastreado la implementación interna de CallWindowProc. Hace mucho trabajo relacionado con los mensajes de ventana, lo cual es redundante en nuestra aplicación. De hecho, para implementar la API de devolución de llamada forzada, solo necesita insertar los últimos parámetros en la pila y luego llamar al primer parámetro, que son solo algunas instrucciones de ensamblaje.
Es precisamente debido a las limitaciones de CallWindowProc que no podemos usarlo para llamar a punteros de función externos para implementar el tercer método de llamada de puntero de función mencionado anteriormente. Para implementar el tercer método, Master MattCurland proporcionó un método HACK similar a una pesadilla. Necesitamos construir una interfaz IUnknown de la nada en VB y agregar una nueva entrada después de las tres entradas originales de la vTable de la interfaz IUnknown. nueva entrada Inserte código de máquina en él. Este código de máquina debe procesar este puntero antes de llamar finalmente al puntero de función que le proporcionamos. Este puntero de función no es un problema, ya sea interno o externo. Volveré a este método cuando profundicemos en los aspectos internos de COM.
Además, los algoritmos de clasificación son una cuestión de opinión. Originalmente quería proporcionar el algoritmo más versátil con el mejor rendimiento en este artículo. Aunque esta idea es buena, no existe el "mejor" algoritmo en cada situación. El método de clasificación rápida implementado utilizando varias tecnologías de puntero proporcionadas en este artículo debería ser mucho más rápido y ocupar mucha menos memoria que usar tecnología de objetos para implementar la misma función. Pero incluso este algoritmo de clasificación rápida, que he optimizado, todavía no es tan bueno como ShellSort porque ShellSort es fácil de implementar. Teóricamente hablando desde el algoritmo, qsort debería tener un mejor rendimiento promedio que ShellSort, pero este no es necesariamente el caso en VB (puede ver el código de soporte de este artículo, que también proporciona el código de soporte de una columna VBPJ, ShellSort, que es muy bueno, y la idea de este artículo está tomada de This ShellSort).
Sin embargo, cabe señalar que tanto Quick Sort como ShellSort se pueden mejorar mucho aquí, porque su implementación requiere un uso extensivo de CopyMemroy para copiar datos (esta es una de las deficiencias del uso de punteros en VB). De hecho, tenemos una mejor manera, que es piratear la estructura de matriz de VB, que es SafeArray en la automatización COM. Podemos colocar los punteros de cada elemento de la matriz en SafeArray en una matriz larga a la vez. Sin CopyMemroy, solo necesitamos. para intercambiar los elementos en la matriz Long para lograr el propósito de intercambiar los punteros de los elementos de la matriz SafeArray en tiempo real. Los datos no se mueven, solo se mueven los punteros. Puede imaginarse lo rápido que es esto.
->