Claro que si, como su título lo indica, la raíz de este tutorial es porque hay muy poca información en español sobre como realizar este tipo de tareas.
Entonces, me veo en la necesidad de hacer algo útil y que sirva para aquellos que no dejamos de utilizar el lenguaje C/C++ para nuestras tareas rutinarias.
1. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
Tutorial
Consumir desde C# DLL desarrollada en C++
Ingredientes:
-Visual Studio Express 2013 for Windows Desktop
-Dependency Walker
-Conocimientos de C y C++ (un poco más de lo básico)
-Autodidacta
¿Por qué?
Porque si no disponemos de una aplicación para ofuscar el código
fuente en C# o bien para ofuscar las librerías creadas en C#, entonces la
solución viable sería construir una librería en C++, porque hasta el
momento no conozco una aplicación para descompilar librerías
desarrolladas en C++ y que te devuelve el código fuente.
Todos sabemos (bueno eso creo…) que existen aplicaciones para
descompilar código C#, de las cuales puedo mencionar “ILSpy”
http://ilspy.net/, de la cual me he favorecido muchas veces.
Hay aplicaciones de pago para ofuscar código C# muy buenas, como por
ejemplo:
Crypto Obfuscator For .NET
http://www.ssware.com/cryptoobfuscator/obfuscator-net.htm
de la cual también puedo decir que, saca la tarea para lo que se
necesita.
Por último: Lo nativo (Win32) siempre será mejor que lo interpretado.
2. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
Pasos:
1. Crear solución en Visual Studio Express 2013 de tipo C++
New ProjectVisual C++Win32 Project:
Para este ejemplo, el proyecto tendrá el nombre “MyDll”
Luego:
En el asistente “Win32 Application Wizard”: Next
En “Application type” seleccionar “DLL”
Por ultimo “Finish”.
2. La DLL devolverá de C++ a C# los siguientes tipos de datos:
C++ C#
LPTSTR string
char * string
int int
double double
LPSTR string
const char * IntPtr
Así mismo se devolverá una estructura que contiene todos los
tipos de datos de la tabla anterior.
3. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
3. Crear las funciones de retorno de valores:
Para realizar este paso, es necesario agregar un archivo de
encabezado (header), el cual se llamará “MyDll.h”, entonces,
seleccionar el proyecto de la solución y proceder a agregar un
nuevo ítem, el cual será de tipo “Header File (.h)”. En este archivo
se incluirá el encabezado “#include <windows.h>” y se agregarán
las definiciones para la exportación de funciones, es decir el
archivo “MyDll.h” deberá quedar así (por el momento):
4. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
4. Definir las funciones de exportación:
Las funciones a exportar serás las siguientes:
LPTSTR ExportaLptstr()
char * ExportaCharA()
int ExportaInt()
double ExportaDouble()
LPSTR ExportaLpstr()
const char * ExportaConstChar()
BOOL ExportaEstructura(MiEstructura *miEstructura)
En el archivo “MyDll.h” las funciones de exportación quedarán así:
5. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
Si realizamos una compilación (Rebuild) previa del proyecto
(Ctrl+Alt+F7), la misma se debe realizar sin ningún problema (al menos
eso espero):
5. Implementar las funciones en el archivo “MyDll.cpp”
Las funciones deben ser implementadas y por supuesto tienen que
devolver el tipo de valor según la definición. Si alguien se pregunta
el por qué se está utilizando -extern “C”- en cada función y se
supone que es una DLL en C++, al terminar este proceso
encontrarán la respuesta.
A continuación la implementación de cada función:
7. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
6. Compilar
Nuevamente compilar el proyecto, y sin esperar, no tiene ningún
motivo para darnos algún error:
7. Configuración de compilación en “MyDll.dll”
Si, resulta que: cuando se invoca la función en C#:
8. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
La aplicación termina con código de error, entonces para que esto
no suceda, es necesario establecer una propiedad en la
compilación de la DLL:
La configuración se realiza accediendo a las propiedades del
proyecto (ya saben, haciendo clic con el botón derecho, etc., etc.)
8. Comprobar MyDll.dll con “Dependency Walker”
Hay una aplicación llamada “Dependency Walker”, si bien su
nombre lo expresa muy bien para que nos sirve, en este caso nos
servirá para verificar las funciones que tiene expuestas la DLL que
acabamos de compilar. Esta aplicación la pueden encontrar y
descargar utilizando el buscador de su preferencia.
9. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
Entonces, utilizando “Dependency Walker”; busca la carpeta de
depuración en donde encontrarás el archivo “MyDll.dll”,
procedemos a abrir la aplicación “Dependency Walker” y luego a
cargar la DLL, entonces se mostrarán las funciones expuestas:
9. Crear proyecto en C#
En la misma solución, vamos a agregar un nuevo proyecto, en este
caso un proyecto C# de consola, el cual se llamara “TestMyDll” (es
una sugerencia, porque puede ser a como bien te guste):
10. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
10. Importar funciones de la DLL “MyDll.dll”
Bien, ahora en la aplicación de consola vamos a proceder a
importar las funciones que nos provee la DLL.
NOTA: La DLL “MyDll.dll” tiene que estar presente en la ruta
donde se encuentra el ejecutable de la aplicación de consola, en
este caso como estamos en modo depuración, se tiene que copiar
la DLL en la carpeta “Debug” o bien “Release” si así fuera, por
ejemplo:
11. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
Seguimos, en “Program.cs” (que es donde está definida la función
principal; o sea “Main”) vamos a escribir un poco de código en
donde se declaran las funciones que exporta la DLL “MyDll.dll”, y
más o menos nos debe quedar así:
12. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
Si compilamos y ejecutamos, posiblemente no veamos la salida,
entonces tendríamos que poner un punto de interrupción y
efectivamente veremos el resultado que nos devuelve la función:
Procedamos a realizar el respectivo análisis:
La función de la DLL en C++ está definida así:
Esto quiere decir: cuando una función que devuelva un tipo de
dato “char *”, en C# se debe recibir como “IntPtr” y luego utilizar
13. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
la función Marshal.PtrToStringAnsi para convertir el valor del
puntero a “string” respectivamente.
Esta fue la primera toma de contacto, ahora resta declarar las
demás funciones:
Y como verán, en las funciones que devuelven “LPTSTR” y “LPSTR”
también se declaran los valores de retorno como “IntPtr”.
Analizando un poco: las funciones que devuelven “cadenas de
caracteres” se declaran como “IntPtr” y luego se utiliza la función
la función Marshal.PtrToStringAnsi respectivamente.
14. @_jdepaz José de Paz desarrollo.escuintla@gmail.com
Ahora, se invocan (o se llaman) las funciones para su respectivo
uso:
Y por supuesto, seguido realizamos la compilación y ejecución en
modo “Debug”, y ponemos un punto de interrupción en el
“return” y sólo resta ver los resultados que tienen las variables:
_recibeCharA, _recibeConstChar, _recibeDouble, _recibeInt,
_recibeLpStr y _recibeLptStr: