2010-04-26 12 views
12

Esto es lo que quiero: tengo una gran base de código C/C++ escrita para POSIX, incluidas algunas cosas muy específicas de POSIX como pthreads. Esto puede compilarse en Cygwin/GCC y ejecutarse como un archivo ejecutable en Windows con la DLL de Cygwin.Referencia a GNU C (POSIX) DLL integrado en GCC contra Cygwin, de C#/NET

Lo que me gustaría hacer es construir la base de código en sí misma en una DLL de Windows a la que puedo hacer referencia desde C# y escribir un contenedor para acceder a algunas partes de ella programáticamente.

He intentado este enfoque con el muy simple ejemplo "hello world" en http://www.cygwin.com/cygwin-ug-net/dll.html y parece que no funciona.

#include <stdio.h> 
extern "C" __declspec(dllexport) int hello(); 

int hello() 
{ 
    printf ("Hello World!\n"); 
return 42; 
} 

creo que debería ser capaz de hacer referencia a un archivo DLL construido con el código anterior en C# usando algo como:

[DllImport("kernel32.dll")] 
public static extern IntPtr LoadLibrary(string dllToLoad); 

[DllImport("kernel32.dll")] 
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); 

[DllImport("kernel32.dll")] 
public static extern bool FreeLibrary(IntPtr hModule); 


[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
private delegate int hello(); 

static void Main(string[] args) 
{ 
    var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "helloworld.dll"); 
    IntPtr pDll = LoadLibrary(path); 
    IntPtr pAddressOfFunctionToCall = GetProcAddress(pDll, "hello"); 

    hello hello = (hello)Marshal.GetDelegateForFunctionPointer(
                   pAddressOfFunctionToCall, 
                   typeof(hello)); 

    int theResult = hello(); 
    Console.WriteLine(theResult.ToString()); 
    bool result = FreeLibrary(pDll); 
    Console.ReadKey(); 
} 

pero no parece que este enfoque funcione. LoadLibrary devuelve nulo. Puede encontrar el archivo DLL (helloworld.dll), es como si no pudiera cargarlo o encontrar la función exportada.

Estoy seguro de que si obtengo este caso básico funcionando, puedo hacer referencia al resto de mi código base de esta manera. ¿Alguna sugerencia o sugerencia, o alguien sabe si lo que quiero es incluso posible? Gracias.

Editar: Examinado mi DLL con Dependency Walker (gran herramienta, gracias) y parece exportar la función correctamente. Pregunta: ¿debería estar haciendo referencia a ella como el nombre de la función que parece encontrar Dependency Walker (_Z5hellov)? Dependency Walker Output http://i44.tinypic.com/1yagqv.png

Edit2: sólo para mostrar que lo he intentado, que une directamente a la DLL en ruta relativa o absoluta (es decir, no utilizando LoadLibrary):

[DllImport(@"C:\.....\helloworld.dll")] 
    public static extern int hello(); 


    static void Main(string[] args) 
    { 
     int theResult = hello(); 
     Console.WriteLine(theResult.ToString()); 
     Console.ReadKey(); 
    } 

Esta falla con: " No se puede cargar DLL 'C: ..... \ helloworld.dll':. acceso no válido a la ubicación de la memoria (Excepción de HRESULT: 0x800703E6)

***** Datos 3: ***** Oleg tiene sug congestionadas Dumpbin.exe corriendo en mi DLL, esta es la salida:

volcado de helloworld.dll archivo

Tipo de archivo: DLL

sección contiene los siguientes exportaciones para holamundo.DLL

00000000 characteristics 
4BD5037F time date stamp Mon Apr 26 15:07:43 2010 
    0.00 version 
     1 ordinal base 
     1 number of functions 
     1 number of names 

ordinal hint RVA  name 

     1 0 000010F0 hello 

Resumen

1000 .bss 
    1000 .data 
    1000 .debug_abbrev 
    1000 .debug_info 
    1000 .debug_line 
    1000 .debug_pubnames 
    1000 .edata 
    1000 .eh_frame 
    1000 .idata 
    1000 .reloc 
    1000 .text 





Editar 4 Gracias a todos por la ayuda, que mana ged para hacerlo funcionar. La respuesta de Oleg me dio la información que necesitaba para saber qué estaba haciendo mal.

Hay 2 formas de hacerlo. Una es construir con el indicador del compilador gcc -mno-cygwin, que construye el dll sin cygwin dll, básicamente como si lo hubiera construido en MingW. ¡Construirlo de esta manera hizo que mi ejemplo de Hello World funcionara! Sin embargo, MingW no tiene todas las bibliotecas que cygwin tiene en el instalador, por lo que si su código POSIX tiene dependencias en estas bibliotecas (la mía tiene montones) no puede hacerlo de esta manera. Y si su código POSIX no tenía esas dependencias, ¿por qué no simplemente compilar para Win32 desde el principio? Así que eso no es de mucha ayuda a menos que desee perder tiempo configurando MingW correctamente.

La otra opción es compilar con la DLL de Cygwin. La DLL de Cygwin necesita una función de inicialización init() para ser llamada antes de que pueda ser utilizada. Es por eso que mi código no estaba funcionando antes. El código a continuación carga y ejecuta mi ejemplo de Hello World.

//[DllImport(@"hello.dll", EntryPoint = "#1",SetLastError = true)] 
    //static extern int helloworld(); //don't do this! cygwin needs to be init first 

    [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] 
    static extern IntPtr GetProcAddress(IntPtr hModule, string procName); 

    [DllImport("kernel32", SetLastError = true)] 
    static extern IntPtr LoadLibrary(string lpFileName); 


    public delegate int MyFunction(); 

    static void Main(string[] args) 
    { 
     //load cygwin dll 
     IntPtr pcygwin = LoadLibrary("cygwin1.dll"); 
     IntPtr pcyginit = GetProcAddress(pcygwin, "cygwin_dll_init"); 
     Action init = (Action)Marshal.GetDelegateForFunctionPointer(pcyginit, typeof(Action)); 
     init(); 

     IntPtr phello = LoadLibrary("hello.dll"); 
     IntPtr pfn = GetProcAddress(phello, "helloworld"); 
     MyFunction helloworld = (MyFunction)Marshal.GetDelegateForFunctionPointer(pfn, typeof(MyFunction)); 

     Console.WriteLine(helloworld()); 
     Console.ReadKey(); 
    } 

Gracias a todos los que respondieron a ~~

+2

La herramienta Dependency Walker (http://www.dependencywalker.com/) es útil para asegurarse de que los archivos DLL se puedan cargar y ver sus dependencias. – WildCrustacean

+0

¿está seguro de que mientras helloworld.dll está en la ruta y se puede encontrar, que los archivos cyg * .dll también están en la ruta desde el lugar donde está ejecutando su ensamblado C#. Si está utilizando Visual Studio, vaya a Propiedades del proyecto y asegúrese de poner todas las rutas requeridas en la propiedad "Variable de entorno". Para otras formas de establecer la ruta, consulte http://stackoverflow.com/questions/428085/how-do-i-set-a-path-in-visual-studio – Fuzz

+0

No subestimo por qué quiere llama a tu cygwin DLL desde C# en todos tus experimentos. ¿Qué ventajas tiene desde el lado pragmático en comparación con el uso de C++ no administrado? Ya necesita tener el subsistema POSIX cargado. ¿Por qué necesita cargar .NET en el mismo proceso? Uno puede comunicarse entre el proceso de Win32 y el proceso administrado escrito en C#, pero no deben * compartir el mismo espacio de direcciones * como lo intenta hacer ahora. – Oleg

Respuesta

9

El principal problema que tiene es seguir. Antes de poder usar su helloworld.dll, se debe inicializar un entorno cygwin (consulte http://cygwin.com/faq/faq.programming.html#faq.programming.msvs-mingw). Por lo que el siguiente código en C++ nativo voluntad obras:

#include <windows.h> 

typedef int (*PFN_HELLO)(); 
typedef void (*PFN_CYGWIN_DLL_INIT)(); 

int main() 
{ 
    PFN_HELLO fnHello; 
    HMODULE hLib, h = LoadLibrary(TEXT("cygwin1.dll")); 
    PFN_CYGWIN_DLL_INIT init = (PFN_CYGWIN_DLL_INIT) GetProcAddress(h,"cygwin_dll_init"); 
    init(); 

    hLib = LoadLibrary (TEXT("C:\\cygwin\\home\\Oleg\\mydll.dll")); 
    fnHello = (PFN_HELLO) GetProcAddress (hLib, "hello"); 
    return fnHello(); 
} 

De la causa de la ruta de acceso a cygwin1.dll debe ser encontrado. Puede establecer C: \ cygwin \ bin como un directorio actual, usar la función SetDllDirectory o incluir fácilmente C: \ cygwin \ bin en la variable de entorno PATH global (haga clic en el botón derecho del mouse en la Computadora, elija Propiedades y luego Configuración Avanzada del Sistema, "Variables de entorno ...", luego elija la variable de sistema PATH y añádala con "; C: \ cygwin \ bin").

Siguiente si se compila DLL, debería mejor usar DEF-archivo para definir la dirección base del DLL durante la compilación y hace que todos los nombres de función, que ha exportado más clara legible (véase http://www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/gnu-linker/win32.html)

Puede verificar resultados con dumpbin.exe mydll.dll /exports, si tiene Visual Studio instalado. (no olvide iniciar el comando promt de "Visual Studio Command Prompt (2010)" para tener todo el conjunto de Visual Studio).

ACTUALIZADO: Como no escribe sobre el éxito, creo que existen algunos problemas. En Win32/Win64 world (mundo no administrado) funciona. El código que publiqué lo he probado. La carga de las DLL de CygWin en .NET puede tener algún problema. En http://cygwin.com/faq/faq.programming.html#faq.programming.msvs-mingw se puede leer "Asegúrate de tener 4K de espacio cero en la parte inferior de la pila". Este requisito puede ser incorrecto en .NET. Una pila es una parte del hilo y no un proceso. Entonces puede intentar usar las DLL de CygWin en el nuevo hilo de .NET. Desde .NET 2.0, uno puede definir el tamaño máximo de pila para el hilo. Otra forma es tratar de comprender http://cygwin.com/cgi-bin/cvsweb.cgi/~checkout~/src/winsup/cygwin/how-cygtls-works.txt?rev=1.1&content-type=text/plain&cvsroot=src y el código descrito en http://old.nabble.com/Cygwin-dll-from-C--Application-td18616035.html#a18616996. Pero lo realmente interesante lo encuentro de dos maneras sin ningún truco:

  1. Compilando el archivo DLL con respecto a las herramientas MinGW en lugar de las herramientas CygWin.MinGW produce código que son mucho más compatibles con Windows. No estoy usando CygWin o MinGW, así que no estoy seguro de que pueda compilar todo el código existente que utilizó la función POSIX en MinGW. Si es posible, de esta manera puede tener más éxito. Puede mirar http://www.adp-gmbh.ch/csharp/call_dll.html por ejemplo, para ver que la DLL MinGW se puede llamar desde C# exactamente como una DLL de Windows.
  2. Uso de la DLL de CygWin dentro del proceso no administrado o del subproceso no administrado. Esta es una forma estándar descrita en la documentación de CygWin y funciona (ver ejemplos de mi primera publicación).

P.S. Por favor, escriba un breve comentario en el texto de su pregunta si tiene éxito en alguno de estos o en otra forma que elija al final. Es interesante para mí independiente sobre la reputación y la generosidad.

+0

Hola Oleg, gracias por tu ayuda. De hecho, no he tenido tiempo de probar otras formas, pero actualizaré este hilo con mis resultados. Vea mi respuesta editada para la salida de dumpbin.exe, la dirección base parece estar bien, y el nombre de la función está allí también. Gracias por sus sugerencias, los probaré en los próximos días. –

+0

Tuve el mismo problema (biblioteca POSIX-only + P/Invoke). Tenga en cuenta que el espacio libre de 4K debe estar en la parte inferior de la pila, es decir, hacia las direcciones altas. Este requisito es imposible de satisfacer una vez que algunos marcos de pila fueron empujados a la pila. Gracias al código al que se vinculó, creé una aplicación que carga cygwin1.dll con la pila adecuada y luego aloja un CLR. Sin embargo, debido a otras limitaciones, las dlls P/Invoked no pueden hacer nada útil incluso de esta manera (múltiples tiempos de ejecución de C no son saludables en el mismo proceso). Una nota para cualquiera que quiera probar esto: no, no tiene sentido. –

1

El compilador de C estándar de MS soporta la mayoría de las interfaces POSIX incluyendo pthreads. A veces como implementaciones separadas, pero generalmente como macros que convierten la sintaxis POSIX en llamadas a la biblioteca de Windows.

Si su código C no tiene demasiados "gnuisms", debería poder compilarlo usando el compilador estándar de Visual C.

+0

El subsistema POSIX ya no se mantiene y existen algunas diferencias que pueden hacer que portar un código POSIX sea un desafío. – shoosh

1

Debería poder hacer referencia a la DLL creada contra Cygwin sin tener que crear una nueva DLL. El único requisito es que debe asegurarse de que tanto "cygwin.dll" como la DLL que está intentando cargar están en las rutas apropiadas. Probablemente necesite usar SetDllDirectory antes de invocar "LoadLibrary".

2

Primero debe intentar que se ejecute la muestra simple de hello world. No tiene mucho sentido tratar con una gran base de código C/C++ heredada escrita para POSIX dependiendo de Cygwin si ni siquiera se obtiene la anterior correcta. Y tenga en cuenta que si está enlazando Cygwin tiene que licenciar su biblioteca con licencia GPL.

Para que echar un vistazo a la documentación (por ejemplo, tiene que especificar explícitamente en el ejemplo del mundo hola si está utilizando Cdecl (lo que actualmente no lo hace)): Consuming Unmanaged DLL Functions

En el lado nativo debe inicializar Cygwin (consulte winsup/cygwin/how-cygtls-works.txt)

Use P/Invoke en su biblioteca directamente. No tiene sentido que PInvoking en bibliotecas de Win32 como LoadLibrary invoque sus bibliotecas. Esto solo agrega otra capa de errores y no te gana nada.

Asegúrese de obtener la arquitectura correcta (las aplicaciones .Net ejecutan 64 bits de forma predeterminada en máquinas de 64 bits). Así que asegúrese de que sus dlls coincidan/admitan eso o limite .Net a 32bits.

Si eso funciona, intente que su otra biblioteca funcione. (Y tendrá que tener un poco de suerte si espera usar funciones que mezclan dos modelos de subprocesos totalmente diferentes)

+0

Gracias, eso es lo que trato de hacer. Si logro que este ejemplo realmente básico funcione, la parte difícil ha terminado. Solo probé el método loadlibrary después de que p/invocación incorrecta fallara, esperando que me diera un error más descriptivo. –

+0

Luego, es probable que los siguientes dos pasos agreguen la inicialización de cygwin a su biblioteca hello world c y luego agreguen los datos faltantes al atributo DllImport en el código de hello world C#. – Foxfire

1

El código tal como está escrito no funcionará, el nombre de la función exportada está decorado por el compilador C++ y ya no se parece "Hola". Normalmente declararías la función extern "C" para suprimir la decoración.

Pero aún no ha llegado tan lejos. La llamada a LoadLibrary() está fallando con el error de Windows 998, ERROR_NOACCESS, "Acceso no válido a la ubicación de la memoria". Esto generalmente ocurre cuando el punto de entrada DllMain() en uno de los archivos DLL tiene una dependencia en bombas con una excepción de hardware AccessViolation. Esto debería ser visible en el depurador (ventana de resultados en Visual Studio), debería ver una "excepción de primera oportunidad" con el código de excepción 0xc0000005.

Probablemente va a ser desagradable de diagnosticar, tiene varios archivos DLL que son candidatos y cuyos símbolos de depuración probablemente son una mala coincidencia para el depurador que utiliza. Intente aislar esto escribiendo un pequeño programa de prueba en C que llame LoadLibrary. Configure el depurador para detenerse en la excepción de la primera oportunidad. En Visual Studio, haría esto con la casilla Depurar + Excepciones, lanzada. ¡Suerte con ello!

Cuestiones relacionadas