2010-03-20 14 views
13

Ho un'applicazione C++ nativa (non gestita) (che usa wxWidgets per quello che vale). Sto considerando di scrivere un'applicazione separata in C# che contenga dialoghi basati su winform. mettere alcune di queste finestre di dialogo in una DLL separata sarebbe utile come spero di poterle usare dalla mia app C++.Creazione di una DLL C# e utilizzo da C++ non gestito

Ma non ho idea di quanto sia necessario pasticciare per realizzare questo, è particolarmente facile?

EDIT:

Non ho bisogno di chiamare direttamente le funzioni di finestre di dialogo. Ho solo bisogno di un modo per la mia app C++ per chiamare un'API nella DLL C# per passare i dati e un modo per la DLL C# per chiamare metodi su qualche tipo di oggetto observer/back nell'app C++.

ad esempio, da C++:

CSharpManager *pCSM = SomehowGetPointerToCSharpObject(); 
CSharpObserver pCSO = new CSharpObserver; 

pCSM->RegisterCPlusPlusObserver(pCSO); 
pCSM->LaunchDialog(); 

Come utente fa cose nella finestra di C#, metodi PCSO sono chiamati a passare i dati di nuovo in C++

Quindi è praticamente un grezzo C++/C domanda # comunicazione , Credo. Ma anche se conosco C++ e C# non so come. Net funziona. Conosco la COM ma vorrei davvero evitarlo, dubito che altri sviluppatori con cui lavoro lo sappiano.

+1

Esportazione di funzioni da una libreria di classi C#: http://stackoverflow.com/questions/2425212/exporting-functions-from-ac-class-library –

+0

Se non si desidera entrare in COM, vorrei suggerire cosa hanno anche gli altri: avvolgere la classe gestita nel C++ gestito. Un altro thread viene inserito nello specifico: http://stackoverflow.com/questions/9944539/c-cli-wrapping-managed-code-for-unmanaged-use –

risposta

1

Dipende da cosa intendi per "Spero di poterli utilizzare dalla mia app C++".

Nel mondo nativo, una finestra di dialogo ha una struttura di modello di dialogo e puoi "cucinarla" nel tuo eseguibile, sia esso DLL o EXE. Belle. Ma nel mondo gestito, le cose sono un po 'diverse. Non esiste un tipo di risorsa "modello di finestra di dialogo" per le applicazioni Winforms. Invece, i moduli sono solo codice.

Tuttavia:

  • Si può sempre mettere in una DLL gestita dal codice non gestito. Questo è banale. Quella DLL gestita può visualizzare le finestre di dialogo di Winforms. Quindi la porzione nativa C++ della tua app può richiamare tali finestre di dialogo. Ma non è possibile creare direttamente senza alcun lavoro aggiuntivo.

  • È sempre possibile inserire una "shim DLL" C++/CLI tra il codice C++ nativo e la DLL gestita. All'interno di C++/CLI, è possibile caricare in modo trasparente sia le risorse gestite che quelle .NET/finestre di dialogo.

  • In questo caso, è possibile chiamare i metodi .NET direttamente da dal codice nativo, senza un intermediario C++/CLI shim DLL, anche se è un po 'disordinato.

Ma per quanto riguarda l'utilizzo della "risorsa finestra di dialogo .NET/Winform" direttamente ... no. Non nel senso di utilizzando lo stesso modello di dialogo sia per Winforms che per C++ nativo.

+0

Non è necessario chiamare direttamente le funzioni di dialogo. Ho solo bisogno di un modo per la mia app C++ per chiamare un'API nella DLL C# per passare i dati e un modo per la DLL C# per chiamare metodi su qualche tipo di oggetto observer/back nell'app C++. –

6

lingua franca di interoperabilità nel codice non gestito è COM. È molto facile andare sul lato C#, basta usare lo [ComVisible] attribute. Avrai bisogno di scrivere codice COM nel tuo programma C++ per usarlo, non è così facile andare avanti se non lo hai mai fatto. Inizia con #import directive se usi il compilatore MSVC.

L'opzione successiva è di ospitare da soli il CLR nel codice non gestito anziché affidarsi al livello di interoperabilità COM per occuparsene. Ti permette di creare tipi gestiti direttamente. Ciò richiede anche COM, ma solo per ottenere il CLR caricato e inizializzato. This project mostra l'approccio.

+0

Heh. "Lingua franca". Infatti. +1 –

+1

COM non è necessario per questo, quindi se l'app C++ non lo usa già non inizierei. Naturalmente, in questo caso le chiamate di ritorno nel codice C++ diventano chiamate a una funzione globale che passa un puntatore a oggetti di stato come argomento, invece che a funzioni membro dell'oggetto stato, ma questo è molto più facile da scrivere (e fare il debug - se qualcosa viene sovrascritto a causa di p/invoca la mancata corrispondenza, almeno si sta ancora chiamando il codice giusto anche se i dati sono corrotti, diversamente da COM dove le chiamate di funzione si basano su un puntatore v-table valido). –

+0

@ Ben: controllare l'OP, questo è un EXE non gestito. Impossibile richiamare il codice C++ fino a quando qualcuno carica il CLR e chiama prima il codice gestito. –

2

usare sia COM, o scrivi un wrapper C++/CLI che chiama il tuo C# d ialog, quindi chiama questo wrapper C++/CLI dal tuo codice C++ non gestito.

+0

+1 Raccomando di utilizzare un wrapper C++/CLI. –

1

So che ci sono alcune risposte qui, ma nessuna indica un esempio funzionante. Quando mi sono imbattuto in questo problema sono riuscito a capire grazie a questo esempio.

http://support.microsoft.com/kb/828736

-1

Stavo per pubblicare questo come un commento a un post precedente, ma dal momento che non hai accettato alcuna risposta ancora forse sarà quello che stai cercando.

Il tuo post originale ha una domanda: "è particolarmente facile?" La risposta a questo è un enfatico no, come evidenziato dalle risposte che stai ottenendo.

Se gli altri suggerimenti (esportazione nativa/COM/ecc.) Sono "troppo superficiali" (le tue parole!), E non sarai in grado di immergerti e imparare, il mio suggerimento sarebbe che tu bisogno di riconsiderare la tua architettura proposta.

Perché non scrivere le funzioni condivise in una libreria C++, che può quindi essere più facilmente utilizzata dall'applicazione C++ esistente? Come regola generale, è molto più semplice consumare componenti nativi dal codice gestito che viceversa, quindi scrivere la tua app C# per consumare la DLL C++ condivisa sarebbe un compito molto più semplice.

Mi rendo conto che questo non risponde alla domanda tecnica originale, ma forse è una risposta più pragmatica al problema che stai affrontando.

0

L'utilizzo di moduli in una DLL C# richiamata da C++ non è semplice, ma una volta che è stato scritto un codice di utilità, può essere abbastanza robusto. Le callback al codice C++ sono estremamente semplici.

Per fare moduli (o WPF per quella materia), la classe NativeWindow è tua amica. Volete più capacità di NativeWindow, in modo che una derivazione sia in ordine. Il codice seguente mostra un'implementazione che deriva da NativeWindow e prevede una chiamata e per i gestori di eventi messaggio di Windows.

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Runtime.InteropServices; 
using System.Windows.Forms; 

/// <summary> 
/// A <see cref="NativeWindow"/> for the main application window. Used 
/// to be able to run things on the UI thread and manage window message 
/// callbacks. 
/// </summary> 
public class NativeWindowWithCallbacks : NativeWindow, IDisposable 
{ 
    /// <summary> 
    /// Used to synchronize access to <see cref="NativeWindow.Handle"/>. 
    /// </summary> 
    private readonly object handleLock = new object(); 

    /// <summary> 
    /// Queue of methods to run on the UI thread. 
    /// </summary> 
    private readonly Queue<MethodArgs> queue = new Queue<MethodArgs>(); 

    /// <summary> 
    /// The message handlers. 
    /// </summary> 
    private readonly Dictionary<int, MessageHandler> messageHandlers = 
     new Dictionary<int, MessageHandler>(); 

    /// <summary> 
    /// Windows message number to prompt running methods on the UI thread. 
    /// </summary> 
    private readonly int runOnUiThreadWindowsMessageNumber = 
     Win32.RegisterWindowMessage(
       "NativeWindowWithCallbacksInvokeOnGuiThread"); 

    /// <summary> 
    /// Handles the message. 
    /// </summary> 
    /// <param name="sender"> 
    /// The this. 
    /// </param> 
    /// <param name="m"> 
    /// The message. 
    /// </param> 
    /// <returns> 
    /// True if done processing; false otherwise. Normally, returning 
    /// true will stop other handlers from being called, but, for 
    /// some messages (like WM_DESTROY), the return value has no effect. 
    /// </returns> 
    public delegate bool MessageHandler(object sender, ref Message m); 

    /// <summary> 
    /// Gets a value indicating whether the caller must call BeginInvoke 
    /// when making UI calls (like <see cref="Control.InvokeRequired"/>). 
    /// </summary> 
    /// <returns> 
    /// True if not running on the UI thread. 
    /// </returns> 
    /// <remarks> 
    /// This can get called prior to detecting the main window (likely if 
    /// the main window has yet to be created). In this case, this method 
    /// will return true even if the main window subsequently gets 
    /// created on the current thread. This behavior works for queuing up 
    /// methods that will update the main window which is likely the only 
    /// reason for invoking methods on the UI thread anyway. 
    /// </remarks> 
    public bool InvokeRequired 
    { 
     get 
     { 
      int pid; 
      return this.Handle != IntPtr.Zero 
       && Win32.GetWindowThreadProcessId(
         new HandleRef(this, this.Handle), out pid) 
       != Win32.GetCurrentThreadId(); 
     } 
    } 

    /// <summary> 
    /// Like <see cref="Control.BeginInvoke(Delegate,Object[])"/> but 
    /// probably not as good. 
    /// </summary> 
    /// <param name="method"> 
    /// The method. 
    /// </param> 
    /// <param name="args"> 
    /// The arguments. 
    /// </param> 
    /// <remarks> 
    /// This can get called prior to finding the main window (likely if 
    /// the main window has yet to be created). In this case, the method 
    /// will get queued and called upon detection of the main window. 
    /// </remarks> 
    public void BeginInvoke(Delegate method, params object[] args) 
    { 
     // TODO: ExecutionContext ec = ExecutionContext.Capture(); 
     // TODO: then ExecutionContext.Run(ec, ...) 
     // TODO: in WndProc for more accurate security 
     lock (this.queue) 
     { 
      this.queue.Enqueue(
       new MethodArgs { Method = method, Args = args }); 
     } 

     if (this.Handle != IntPtr.Zero) 
     { 
      Win32.PostMessage(
        new HandleRef(this, this.Handle), 
        this.runOnUiThreadWindowsMessageNumber, 
        IntPtr.Zero, 
        IntPtr.Zero); 
     } 
    } 

    /// <summary> 
    /// Returns the handle of the main window menu. 
    /// </summary> 
    /// <returns> 
    /// The handle of the main window menu; Handle <see cref="IntPtr.Zero"/> 
    /// on failure. 
    /// </returns> 
    public HandleRef MenuHandle() 
    { 
     return new HandleRef(
       this, 
       this.Handle != IntPtr.Zero 
        ? Win32.GetMenu(new HandleRef(this, this.Handle)) 
        : IntPtr.Zero); 
    } 

    /// <summary> 
    /// When the instance gets disposed. 
    /// </summary> 
    public void Dispose() 
    { 
     this.ReleaseHandle(); 
    } 

    /// <summary> 
    /// Sets the handle. 
    /// </summary> 
    /// <param name="handle"> 
    /// The handle. 
    /// </param> 
    /// <param name="onlyIfNotSet"> 
    /// If true, will not assign to an already assigned handle. 
    /// </param> 
    public void AssignHandle(IntPtr handle, bool onlyIfNotSet) 
    { 
     bool emptyBacklog = false; 
     lock (this.handleLock) 
     { 
      if (this.Handle != handle 
        && (!onlyIfNotSet || this.Handle != IntPtr.Zero)) 
      { 
       base.AssignHandle(handle); 
       emptyBacklog = true; 
      } 
     } 

     if (emptyBacklog) 
     { 
      this.EmptyUiBacklog(); 
     } 
    } 

    /// <summary> 
    /// Adds a message handler for the given message number. 
    /// </summary> 
    /// <param name="messageNumber"> 
    /// The message number. 
    /// </param> 
    /// <param name="messageHandler"> 
    /// The message handler. 
    /// </param> 
    public void AddMessageHandler(
     int messageNumber, 
     MessageHandler messageHandler) 
    { 
     lock (this.messageHandlers) 
     { 
      if (this.messageHandlers.ContainsKey(messageNumber)) 
      { 
       this.messageHandlers[messageNumber] += messageHandler; 
      } 
      else 
      { 
       this.messageHandlers.Add(
         messageNumber, (MessageHandler)messageHandler.Clone()); 
      } 
     } 
    } 

    /// <summary> 
    /// Processes the window messages. 
    /// </summary> 
    /// <param name="m"> 
    /// The m. 
    /// </param> 
    protected override void WndProc(ref Message m) 
    { 
     if (m.Msg == this.runOnUiThreadWindowsMessageNumber && m.Msg != 0) 
     { 
      for (;;) 
      { 
       MethodArgs ma; 
       lock (this.queue) 
       { 
        if (!this.queue.Any()) 
        { 
         break; 
        } 

        ma = this.queue.Dequeue(); 
       } 

       ma.Method.DynamicInvoke(ma.Args); 
      } 

      return; 
     } 

     int messageNumber = m.Msg; 
     MessageHandler mh; 
     if (this.messageHandlers.TryGetValue(messageNumber, out mh)) 
     { 
      if (mh != null) 
      { 
       foreach (MessageHandler cb in mh.GetInvocationList()) 
       { 
        try 
        { 
         // if WM_DESTROY (messageNumber == 2), 
         // ignore return value 
         if (cb(this, ref m) && messageNumber != 2) 
         { 
          return; // done processing 
         } 
        } 
        catch (Exception ex) 
        { 
         Debug.WriteLine(string.Format("{0}", ex)); 
        } 
       } 
      } 
     } 

     base.WndProc(ref m); 
    } 

    /// <summary> 
    /// Empty any existing backlog of things to run on the user interface 
    /// thread. 
    /// </summary> 
    private void EmptyUiBacklog() 
    { 
     // Check to see if there is a backlog of 
     // methods to run on the UI thread. If there 
     // is than notify the UI thread about them. 
     bool haveBacklog; 
     lock (this.queue) 
     { 
      haveBacklog = this.queue.Any(); 
     } 

     if (haveBacklog) 
     { 
      Win32.PostMessage(
        new HandleRef(this, this.Handle), 
        this.runOnUiThreadWindowsMessageNumber, 
        IntPtr.Zero, 
        IntPtr.Zero); 
     } 
    } 

    /// <summary> 
    /// Holds a method and its arguments. 
    /// </summary> 
    private class MethodArgs 
    { 
     /// <summary> 
     /// Gets or sets the method arguments. 
     /// </summary> 
     public object[] Args { get; set; } 

     /// <summary> 
     /// Gets or sets Method. 
     /// </summary> 
     public Delegate Method { get; set; } 
    } 
} 

Il motivo principale per il codice di cui sopra è quello di ottenere al BeginInvoke() chiamata implementato all'interno - è necessario che la chiamata per creare i propri moduli sul filo GUI.Ma è necessario avere un handle di finestra prima di poter richiamare sul thread della GUI. La cosa più semplice è quello di avere il codice C++ passare l'handle della finestra in (arrivo come IntPtr), ma si può anche usare qualcosa come:

Process. GetCurrentProcess(). MainWindowHandle;

per ottenere l'handle della finestra principale anche quando si è in C# chiamato da C++. Nota che il codice C++ potrebbe cambiare l'handle della finestra principale e lasciare il codice C# con uno non valido (questo può, naturalmente, essere catturato ascoltando i messaggi Windows appropriati sull'impugnatura originale - puoi farlo anche con il codice sopra).

Siamo spiacenti ma le chiamate Win32 di dichiarazioni precedenti non sono visualizzate. Puoi ottenere le dichiarazioni P/Invoke per loro cercando sul web. (La mia classe Win32 è enorme.)

Per quanto riguarda le richiamate nel codice C++ go - fino a quando si fanno i callback abbastanza semplice è possibile utilizzare Marshal.GetDelegateForFunctionPointer per convertire un passato in puntatore a funzione (che viene trasformato in un IntPtr) in un vecchio delegato C#.

Quindi, richiamare almeno in C++ è straordinariamente semplice (a condizione che la dichiarazione dei delegati sia definita correttamente). Per esempio se si dispone di una funzione C++ che prende un const char * e restituisce void, la vostra dichiarazione delegato avrà un aspetto simile:

public delegate void MyCallback([MarshalAs(UnmanagedType.LPStr)] string myText); 

che copre le nozioni di base. Utilizzare la classe precedente con l'handle di una finestra passata per creare finestre basate su moduli personali all'interno della chiamata-NativeWindowWithCallbacks.BeginInvoke(). Ora, se vuoi giocare con il codice di Windows C++, per esempio aggiungere una voce di menu in una finestra gestita dal codice C++, le cose si complicano di nuovo. Il codice di controllo .Net non ama interfacciarsi con le finestre che non ha creato. Quindi, per aggiungere una voce di menu, si finisce per scrivere codice con un sacco di P/Invocazioni di Win32 per fare le chiamate identiche che si farebbero se si scrivesse il codice C. Le precedenti classi NativeWindowWithCallbacks torneranno utili.

0

Se si desidera caricare qualsiasi DLL .NET in un'applicazione C++, è necessario ospitare .NET nell'applicazione C++.

Qui è possibile trovare un esempio da Microsoft: https://code.msdn.microsoft.com/CppHostCLR-e6581ee0 Questo esempio include anche alcuni file di intestazione, che sono richiesti.

In breve è necessario effettuare le seguenti operazioni:

  1. Caricare la mscoree.dll utilizzando il comando LoadLibrary (altrimenti è possibile collegare in modo statico il mscoree.dll nel progetto)
  2. Chiamare la funzione CLRCreateInstance, che viene esportato da mscoree.dll per creare un oggetto ICLRMetaHost
  3. Chiamare il metodo GetRuntime dell'oggetto ICLRMetaHost per ottenere un oggetto ICLRRuntimeInfo della versione .NET preferita.
  4. Verificare se la versione è caricabile chiamando ICLRRuntimeInfo.IsLoadable
  5. chiamate il metodo GetInterface dal ICLRRuntimeInfo per ottenere l'ICorRuntimeHost
  6. Chiamare il metodo Start dell'oggetto ICorRuntimeHost
  7. chiamate il metodo GetDefaultDomain dall'oggetto ICorRuntimeHost al geht l'oggetto IAppDomain

Poi si può caricare le librerie usando IAppDomain.Load_2. Se si desidera caricare DLL .NET da condivisioni di rete, è più complesso, poiché è necessario chiamare UnsafeLoadFrom, che non è disponibile in IAppDomain. Ma questo è anche possibile.

Problemi correlati