Cómo llamar a una biblioteca de C#desde Native C++(usando C++ / CLI y IJW)
visual-studio-2010 c#-4.0 (5)
Eché un vistazo a mi alrededor y encontré un artículo relativamente reciente de Microsoft que detallaba cómo se puede hacer (hay una gran cantidad de información antigua flotando). Del propio artículo:
El ejemplo de código utiliza las API de alojamiento de CLR 4 para alojar CLR en un proyecto C ++ nativo, cargar e invocar ensamblados .NET
https://code.msdn.microsoft.com/CppHostCLR-e6581ee0
Básicamente lo describe en dos pasos:
- Cargue el CLR en un proceso
- Cargue su ensamblaje.
Antecedentes: como parte de una asignación más grande, necesito hacer que una biblioteca C # sea accesible para el código C ++ y C no administrado. En un intento por responder esta pregunta yo mismo he estado aprendiendo C ++ / CLI en los últimos días / semanas.
Parece que hay una serie de formas diferentes de lograr el uso de un dll de C # a partir de C ++ y C no administradas. Algunas de las respuestas en resumen parecen ser: el uso de los servicios de Interlope, el uso de .com. y regasm, usando PInvoke (que parece ir de C # a C ++ solamente) y usando IJW en C ++ / CLR (que parece ser servicios de Interlope). Estoy pensando que sería mejor configurar una biblioteca que sea quizás un contenedor CLR que use IJW para llamar a mi dll C # en nombre del código C y C nativo.
Aspectos específicos: Necesito pasar valores de cadena así como int a un dll C # del código c ++, y devolver vacío.
Relevancia: muchas empresas tienen muchas excusas para mezclar y combinar C ++, C y C #. Rendimiento: el código no administrado es generalmente más rápido, las interfaces: las interfaces administradas son generalmente más fáciles de mantener, implementar y, a menudo, son más fáciles de entender, los gerentes nos lo dicen también. El código heredado nos obliga a nosotros también. Estaba allí (como la montaña que subimos). Mientras que los ejemplos de cómo llamar a una biblioteca de C ++ desde C # son abundantes. Los ejemplos de cómo llamar a las bibliotecas de C # desde el código de C ++ son difíciles de encontrar a través de Google, especialmente si desea ver el código actualizado de 4.0 o superior.
Software: C #, C ++ / CLR, C ++, C, Visual Studio 2010 y .NET 4.0
Detalles de la pregunta: OK pregunta de varias partes:
¿Hay alguna ventaja de usar objetos com? O el PInvoke? ¿O algún otro método? (Siento que la curva de aprendizaje aquí será tan empinada, aunque encuentro más información sobre el tema en Google Land. IJW parece prometer lo que quiero que haga. ¿Debo dejar de buscar una solución IJW y centrarse en esto en su lugar?) (ventaja / desventaja?)
¿Estoy en lo cierto al imaginar que hay una solución donde escribo un contenedor que utiliza IJW en el C ++ / CLR? Dónde puedo encontrar más información sobre este tema, y no decir que no busqué lo suficiente en Google / o mirar MSDN sin decirme dónde lo vio allí. (Creo que prefiero esta opción, en un esfuerzo por escribir un código claro y simple).
Una reducción del alcance de la pregunta: creo que mi verdadero problema y necesidad es responder a la pregunta más pequeña que sigue: ¿Cómo configuro una biblioteca C ++ / CLR que un archivo C ++ no administrado puede usar dentro de Visual Studio? Creo que si pudiera simplemente crear una instancia de una clase de C ++ administrada en un código de C ++ no administrado, entonces podría resolver el resto (interfaz y envoltura, etc.). Espero que mi principal locura es tratar de configurar referencias / # includes, etc. dentro de Visual Studio, pensé claramente que podría tener otros conceptos erróneos. Tal vez la respuesta a todo esto podría ser solo un enlace a un tutorial o instrucciones que me ayuden con esto.
Investigación: he buscado en Google y lo han colgado una y otra vez con cierto éxito. He encontrado muchos enlaces que le muestran cómo usar una biblioteca no administrada desde el código C #. Y admitiré que ha habido algunos enlaces que muestran cómo hacerlo usando com objects. No muchos resultados fueron dirigidos a VS 2010.
Referencias: He leído una y otra vez muchas publicaciones. He intentado trabajar con los más relevantes. Algunos parecen tentadoramente cercanos a la respuesta, pero parece que no puedo hacer que funcionen. Sospecho que lo que me falta es tentadoramente pequeño, como mal usar la palabra clave ref, o perder un #include o usar una declaración, o un mal uso del espacio de nombres, o no usar la función IJW correctamente, o perder una configuración que VS necesita manejar la compilación correctamente, etc. Así que te preguntas, ¿por qué no incluir el código? Bueno, siento que no estoy en un lugar donde entiendo y espero el código que tengo que trabajar. Quiero estar en un lugar donde lo entiendo, cuando llegue tal vez entonces necesitaré ayuda para solucionarlo. Al azar incluiré dos de los enlaces, pero no tengo permiso para mostrarlos en mi nivel actual de Hitpoint.
http://www.codeproject.com/Articles/35437/Moving-Data-between-Managed-Code-and-Unmanaged-Cod
Esto llama al código del código administrado y no administrado en ambas direcciones, yendo de C ++ a Visual Basic y de vuelta a través de C ++ CLR, y por supuesto, estoy interesado en C # .: http://www.codeproject.com/Articles/9903/Calling-Managed-Code-from-Unmanaged-Code-and-vice
Encontré algo que al menos comienza a responder mi propia pregunta. Los siguientes dos enlaces tienen archivos wmv de Microsoft que demuestran el uso de una clase C # en C ++ no administrado.
Este primero usa un objeto COM y regasm: http://msdn.microsoft.com/en-us/vstudio/bb892741 .
Este segundo usa las características de C ++ / CLI para envolver la clase C #: http://msdn.microsoft.com/en-us/vstudio/bb892742 . He podido instanciar la clase de CA # del código administrado y recuperar una cadena como en el video. Ha sido muy útil, pero solo responde 2 / 3rds de mi pregunta, ya que quiero instanciar una clase con un perímetro de cadena en la clase ac #. Como una prueba de concepto alteré el código presentado en el ejemplo para el siguiente método y logré este objetivo. Por supuesto, también agregué un método alterado {cadena pública PickDate (string Name)} para hacer algo con la cadena de caracteres para probarme a mí mismo que funcionó.
wchar_t * DatePickerClient::pick(std::wstring nme)
{
IntPtr temp(ref);// system int pointer from a native int
String ^date;// tracking handle to a string (managed)
String ^name;// tracking handle to a string (managed)
name = gcnew String(nme.c_str());
wchar_t *ret;// pointer to a c++ string
GCHandle gch;// garbage collector handle
DatePicker::DatePicker ^obj;// reference the c# object with tracking handle(^)
gch = static_cast<GCHandle>(temp);// converted from the int pointer
obj = static_cast<DatePicker::DatePicker ^>(gch.Target);
date = obj->PickDate(name);
ret = new wchar_t[date->Length +1];
interior_ptr<const wchar_t> p1 = PtrToStringChars(date);// clr pointer that acts like pointer
pin_ptr<const wchar_t> p2 = p1;// pin the pointer to a location as clr pointers move around in memory but c++ does not know about that.
wcscpy_s(ret, date->Length +1, p2);
return ret;
}
Parte de mi pregunta fue: ¿qué es mejor? De lo que he leído en muchos esfuerzos para investigar, la respuesta es que los objetos COM se consideran más fáciles de usar, y usar un contenedor en su lugar permite un mayor control. En algunos casos, usar un contenedor puede (pero no siempre) reducir el tamaño del procesador, ya que los objetos COM tienen automáticamente un tamaño estándar y los empaquetadores solo son tan grandes como deben ser.
El thunk (como he usado anteriormente) se refiere al espacio de tiempo y los recursos utilizados entre C # y C ++ en el caso del objeto COM, y entre C ++ / CLI y C ++ nativo en el caso de la codificación, utilizando un C ++ / CLI Envoltura. Entonces, otra parte de mi respuesta debe incluir una advertencia de que cruzar el límite del thunk más de lo estrictamente necesario es una mala práctica, no se recomienda acceder al límite thunk dentro de un bucle, y que es posible configurar un contenedor incorrectamente para que doble. (cruza el límite dos veces cuando solo se requiere un thunk) sin que el código parezca ser incorrecto para un novato como yo.
Dos notas sobre el wmv. Primero: algunas imágenes se reutilizan en ambos, no se dejen engañar. Al principio parecen iguales pero cubren diferentes temas. En segundo lugar, hay algunas características de bonificación, como la clasificación, que ahora forman parte de la CLI y que no están cubiertas en la wmv.
Editar:
Tenga en cuenta que hay una consecuencia para sus instalaciones, el CLR no encontrará su contenedor de C ++. Tendrá que confirmar que la aplicación c ++ se instala en cualquiera de los directorios que la utilizan, o agregar la biblioteca (que luego tendrá que ser nombrada) al GAC en el momento de la instalación. Esto también significa que, en cualquier caso, en entornos de desarrollo, es probable que deba copiar la biblioteca a cada directorio donde lo llamen las aplicaciones.
La mejor manera absoluta que he encontrado para hacer esto es crear un puente c ++ / cli que conecte el código c # con tu C ++ nativo. No recomendaría el uso de objetos COM para resolver su problema. Puedes hacer esto con 3 proyectos diferentes.
- Primer proyecto: biblioteca C #
- Segundo proyecto: puente C ++ / CLI (esto envuelve la biblioteca C #)
- Tercer proyecto: aplicación Native C ++ que utiliza el segundo proyecto
here puede encontrar un buen tutorial / visual sobre esto, y here puede encontrar el mejor tutorial completo que he encontrado para hacer esto. Entre esos dos enlaces y un poco de arena, deberías poder construir un puente C ++ / CLI que te permita usar el código C # en tu C ++ nativo.
Puedes hacerlo con bastante facilidad.
- Crea un combo .h / .cpp
- Habilite / clr en el archivo .cpp recién creado. (CPP -> clic con el botón derecho -> Propiedades)
- Establezca la ruta de búsqueda de "directorios # adicionales" para apuntar hacia su dll C #.
Native.h
void NativeWrapMethod();
Native.cpp
#using <mscorlib.dll>
#using <MyNet.dll>
using namespace MyNetNameSpace;
void NativeWrapMethod()
{
MyNetNameSpace::MyManagedClass::Method(); // static method
}
Eso es lo básico de usar una lib de C # desde C ++ / CLI con código nativo. (Simplemente haga referencia a Native.h cuando sea necesario y llame a la función).
El uso del código C # con el código administrado C ++ / CLI es más o menos el mismo.
Hay mucha desinformación sobre este tema, así que, afortunadamente, esto le ahorra a alguien muchas molestias. :)
He hecho esto en: VS2010 - VS2012 (Probablemente también funcione en VS2008).
Si desea usar COM, esta es mi solución para este problema:
Biblioteca C #
Antes que nada, necesitas una biblioteca COM compatible.
Ya tienes uno? Perfecto, puedes omitir esta parte.
Usted tiene acceso a la biblioteca? Asegúrese de que sea compatible con COM siguiendo los pasos.
- Asegúrese de haber marcado la opción "Registrar para interoperabilidad COM" en las propiedades de su proyecto. Propiedades -> Construir -> Desplazarse hacia abajo -> Registrarse para interoperabilidad COM
Las siguientes capturas de pantalla muestran dónde encontrar esta opción.
Todas las interfaces y clases que deberían estar disponibles necesitan tener un GUID
namespace NamespaceOfYourProject { [Guid("add a GUID here")] public interface IInterface { void Connect(); void Disconnect(); } } namespace NamespaceOfYourProject { [Guid("add a GUID here")] public class ClassYouWantToUse: IInterface { private bool connected; public void Connect() { //add code here } public void Disconnect() { //add code here } } }
Así que eso es más o menos lo que tienes que hacer con tu código C #. Continuemos con el código C ++.
C ++
- Antes que nada necesitamos importar la biblioteca de C #.
Después de compilar su biblioteca de C #, debe haber un archivo .tlb.
#import "path/to/the/file.tlb"
Si importa este nuevo archivo creado a su archivo.cpp puede usar su objeto como una variable local.
#import "path/to/the/file.tlb"
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);
NamespaceOfYourProject::IInterfacePtr yourClass(__uuidof(NamespaceOfYourProject::ClassYouWantToUse));
yourClass->Connect();
CoUninitialize();
}
- Usando tu clase como un atributo.
Notarás que el primer paso solo funciona con una variable local. El siguiente código muestra cómo usarlo como un atributo. Relacionado con this pregunta
Necesitará el CComPtr, que se encuentra en atlcomcli.h. Incluye este archivo en tu archivo de encabezado.
CPlusPlusClass.h
#include <atlcomcli.h>
#import "path/to/the/file.tlb"
class CPlusPlusClass
{
public:
CPlusPlusClass(void);
~CPlusPlusClass(void);
void Connect(void);
private:
CComPtr<NamespaceOfYourProject::IInterface> yourClass;
}
CPlusPlusClass.cpp
CPlusPlusClass::CPlusPlusClass(void)
{
CoInitialize(NULL);
yourClass.CoCreateInstance(__uuidof(NamespaceOfYourProject::ClassYouWantToUse));
}
CPlusPlusClass::~CPlusPlusClass(void)
{
CoUninitialize();
}
void CPlusPlusClass::Connect(void)
{
yourClass->Connect();
}
¡Eso es! Diviértete con tus clases de C # en C ++ con COM.