2016-05-06 107 views
16

ho trovato un modo per chiamare il codice .NET 2 direttamente da una macro VBA:Come chiamare metodi .NET da Excel VBA

Dim clr As mscoree.CorRuntimeHost 
Set clr = New mscoree.CorRuntimeHost 
clr.Start 
Dim domain As mscorlib.AppDomain 
clr.GetDefaultDomain domain 
Dim myInstanceOfDotNetClass As Object 
Set myInstanceOfDotNetClass = domain.CreateInstanceFrom("SomeDotNetAssembly.dll", "Namespace.Typename").Unwrap 
Call myInstanceOfDotNetClass.ExecuteSomeDotNetMethod 

(per rendere questo codice di lavoro ho cappello per aggiungere riferimenti a mscoree. tlb e mscorlib.tlb in Excel VBA utilizzando Strumenti -> Riferimenti .. in Excel)

Ma questo funziona solo per .NET CLR 2 assiemi, fino a .NET framework versione 3.5.

Ora ho bisogno di farlo funzionare con .NET 4.

ho capito che .NET CLR4 ha introdotto un altro, la versione agnostico, modo di creare un'istanza del runtime e ho anche trovato un abbastanza facile esempio di codice scritto in C++: http://dev.widemeadows.de/2014/02/04/hosting-the-net-4-runtime-in-a-native-process/

Ma le mie competenze VBA di Excel non sono sufficienti per tradurre quelle poche righe di codice in un makro VBA funzionante. Qualcuno può aiutarmi per favore?

+1

Avete installato [Visual Studio Tools per Office Runtime] (https://msdn.microsoft.com/en-us/library/ee712596.aspx)? – Tim

+0

I TLB sono ancora lì con CLR4 (in C: \ Windows \ Microsoft.NET \ Framework \ v4.0.30319). Qual è esattamente il problema? –

+0

Credo che questo abbia a che fare con la registrazione della DLL. Guarda. Spero che sia d'aiuto. http://www.geeksengine.com/article/register-dll.html – sam

risposta

15

Ecco una risposta canonical sui 3 metodi principali per chiamare. NET da Excel (o VBA).

Tutte e tre le modalità funzionano in .Net 4.0.

1. XLL

Il terzo fornitore indipendente aggiuntivo di funzionalità offerta XLL Express, tuttavia la sua libera e facile da usare Excel-DNA l'autore è quihttps://stackoverflow.com/users/44264

Ecco un estratto dalla pagina di Excel-DNA: https://excel-dna.net/

Introduzione

Excel-DNA è un progetto indipendente per integrare .NET in Excel. Con Excel-DNA è possibile creare componenti aggiuntivi nativi (.xll) per Excel utilizzando C#, Visual Basic.NET o F #, fornendo funzioni definite dall'utente ad alte prestazioni (UDF), interfacce nastro personalizzate e altro. L'intero componente aggiuntivo può essere inserito in un singolo file .xll che non richiede installazione o registrazione.

Guida introduttiva

Se si utilizza una versione di Visual Studio che supporta il Gestore NuGet Package (tra cui Visual Studio 2012 Express per Windows Desktop), il modo più semplice per fare un Excel-DNA add-in è a:

Creare un nuovo progetto Libreria di classi in Visual Basic, C# o F #. Utilizzare la Gestione NuGet Pacchetti dialogo o la Console Package Manager per installare il pacchetto di Excel-DNA:

PM> Install-Package Excel-DNA 

aggiungere il codice (C#, Visual Basic.NET o F #):

using ExcelDna.Integration; 
public static class MyFunctions 
{ 
    [ExcelFunction(Description = "My first .NET function")] 
    public static string SayHello(string name) 
    { 
     return "Hello " + name; 
    } 
} 

Compile, caricare e utilizzare la funzione in Excel:

=SayHello("World!") 

2. Automazione AddIns

questo articolo di Eric Carter mostra come d o, l'articolo manca di un mucchio di immagini, quindi copio/incollo l'intero articolo e ho ricreato le immagini per la conservazione.

REF: https://blogs.msdn.microsoft.com/eric_carter/2004/12/01/writing-user-defined-functions-for-excel-in-net/

Excel consente la creazione di funzioni definite dall'utente che possono essere utilizzate nelle formule di Excel. Uno sviluppatore deve creare un tipo speciale di DLL chiamato XLL. Excel consente inoltre di scrivere funzioni personalizzate in VBA che possono essere utilizzate nelle formule di Excel. Sfortunatamente, Excel non supporta o consiglia di scrivere un XLL che utilizza il codice gestito. Se sei disposto a correre il rischio che il tuo XLL non venga eseguito nelle versioni attuali o future di Excel, ci sono soluzioni disponibili che abilitano questo scenario: cerca nel web "XLL gestito".

Fortunatamente, esiste un modo più semplice per creare una funzione definita dall'utente che non richiede la creazione di una DLL XLL. Excel XP, Excel 2003 ed Excel 2007 supportano qualcosa chiamato Componente aggiuntivo di automazione. Un componente aggiuntivo di automazione può essere creato semplicemente in C# o VB.NET. Ti mostrerò un esempio in C#.

Innanzitutto, avviare Visual Studio e creare un nuovo progetto di libreria di classi C# denominato AutomationAddin per questo esempio.

Quindi, nel file Class1.cs, immettere il codice mostrato di seguito. Sostituire il GUID con il proprio GUID creato utilizzando Genate GUID nel menu Strumenti di Visual Studio.

using System; 
using System.Runtime.InteropServices; 
using Microsoft.Win32; 

namespace AutomationAddin 
{ 

    // Replace the Guid below with your own guid that 
    // you generate using Create GUID from the Tools menu 
    [Guid("A33BF1F2-483F-48F9-8A2D-4DA68C53C13B")] 
    [ClassInterface(ClassInterfaceType.AutoDual)] 
    [ComVisible(true)] 
    public class MyFunctions 
    { 
    public MyFunctions() 
    { 

    } 

    public double MultiplyNTimes(double number1, double number2, double timesToMultiply) 
    { 
     double result = number1; 
     for (double i = 0; i < timesToMultiply; i++) 
     { 
     result = result * number2; 
     } 
     return result; 
    } 

    [ComRegisterFunctionAttribute] 
    public static void RegisterFunction(Type type) 
    { 
     Registry.ClassesRoot.CreateSubKey(GetSubKeyName(type, "Programmable")); 
     RegistryKey key = Registry.ClassesRoot.OpenSubKey(GetSubKeyName(type, "InprocServer32"), true); 
     key.SetValue("", System.Environment.SystemDirectory + @"\mscoree.dll",RegistryValueKind.String); 
    } 

    [ComUnregisterFunctionAttribute] 
    public static void UnregisterFunction(Type type) 
    { 
     Registry.ClassesRoot.DeleteSubKey(GetSubKeyName(type, "Programmable"), false); 
    } 

    private static string GetSubKeyName(Type type, string subKeyName) 
    { 
     System.Text.StringBuilder s = new System.Text.StringBuilder(); 
     s.Append(@"CLSID\{"); 
     s.Append(type.GUID.ToString().ToUpper()); 
     s.Append(@"}\"); 
     s.Append(subKeyName); 
     return s.ToString(); 
    } 
    } 
} 

Con questo codice scritto, mostrano le proprietà per il progetto facendo doppio clic sul nodo proprietà nell'ambito del progetto in Esplora soluzioni. Fare clic sulla scheda Build e selezionare la casella di controllo "Register for COM Interop". A questo punto hai un passaggio in più se stai lavorando su Windows Vista o superiore. Visual Studio deve essere eseguito con i privilegi di amministratore per la registrazione per l'interoperabilità COM. Salva il tuo progetto ed esci da Visual Studio. Quindi trova Visual Studio nel menu Start e fai clic con il pulsante destro su di esso e seleziona "Esegui come amministratore". Riapri il tuo progetto in Visual Studio. Quindi scegliere "Build" per compilare il componente aggiuntivo.

enter image description here

Ora lanciare Excel e raggiungere la finestra di server di automazione seguendo questi passaggi:

  1. lancio Excel e fare clic sul pulsante Microsoft Office nell'angolo in alto a sinistra della finestra.

  2. Scegliere Opzioni di Excel.

  3. Fare clic sulla scheda Componenti aggiuntivi nella finestra di dialogo Opzioni di Excel.

  4. Scegliere Componenti aggiuntivi Excel dalla casella combinata denominata Gestione. Quindi fare clic sul pulsante Vai.

  5. Fare clic sul pulsante Automazione nella finestra di dialogo Componenti aggiuntivi.

È possibile trovare la classe si è creato, cercando per AutomationAddin.MyFunctions nella lista dei Automazione componenti aggiuntivi:

enter image description here

Ora, proviamo a utilizzare la funzione MultiplyNTimes all'interno di Excel. Per prima cosa create un semplice foglio di calcolo che abbia un numero, un secondo numero per multiplo del primo di, e un terzo numero per quante volte volete moltiplicare il primo numero per il secondo numero. Un esempio foglio di calcolo è mostrato qui:

enter image description here

Clicca su una cella vuota nella cartella di lavoro di seguito i numeri e fare clic sul pulsante Inserisci funzione nella barra della formula. Dalla finestra di dialogo delle formule disponibili, apri la casella "O seleziona una categoria" e seleziona "AutomationAddin.MyFunctions.

enter image description here

Quindi fare clic sulla funzione MultiplyNTimes come illustrato di seguito:

enter image description here

Quando si preme il pulsante OK, Excel apre una finestra di dialogo per aiutare Afferri argomenti della funzione dal foglio di calcolo come mostrato qui:

enter image description here

Infine, fare clic su OK e vedere il foglio di calcolo finale, come mostrato qui con la formula personalizzata nella cella C3.

enter image description here


3. Calling .Net da Excel VBA

REF: Calling a .net library method from vba

Utilizzando il codice dal progetto Automation.AddIn possiamo facilmente chiamare la funzione MultiplyNTimes da Excel VBA.

Prima di aggiungere un riferimento alla DLL da Excel, per fare ciò è necessario essere nell'Editor VB.Premere Alt + F11, quindi fare clic dal menu Strumenti e Riferimenti:

enter image description here

Selezionare la DLL AutomationAddIn:

enter image description here

Aggiungere VBA codice per chiamare la DLL .Net:

Sub Test() 

Dim dotNetClass As AutomationAddIn.MyFunctions 
Set dotNetClass = New AutomationAddIn.MyFunctions 

Dim dbl As Double 
dbl = dotNetClass.MultiplyNTimes(3, 2, 5) 

End Sub 

E presto!

enter image description here


Infine ci sono alcuni ottimi articoli di MSDN su Excel e .NET "Andrew Whitechapel" - google them

+0

Apprezzo molto lo sforzo per questa risposta, ma non include un metodo che funziona allo stesso modo della domanda originale. Sebbene la domanda non lo indichi esplicitamente, la ragione principale per utilizzare tale metodo è che è possibile chiamare l'assembly senza doverlo registrare in VBA come riferimento. Tutti i metodi che mostri richiedono che la DLL sia registrata e disponibile come riferimento. –

+0

Sono le 12:54 qui Jon lascia che ti risponda domani, Thxs –

+0

Ho perso il fatto che tu volessi farlo senza un riferimento, puoi ancora [farlo senza privilegi di amministratore] (http://stackoverflow.com/questions/ 37193356/registering-net-com-dlls-without-admin-rights-regasm), ma sfortunatamente ha bisogno di un regresso. Ho avuto esperienze eccellenti e grandi progetti usando Excel-DNA. Dando dei calci a me stesso ho dimenticato di menzionare il lavoro di Robert Giesecke in questa risposta. –

3

Non sono sicuro se questo era solo una coincidenza o perché ho postato domanda correlata. SO mi ha mostrato la tua domanda e penso che potrei anche contribuire con qualcosa.

Quando si lavora con VBA e DLL, la maggior parte delle soluzioni che ho visto finora mi dice di registrare la DLL e renderla visibile com/gac. Se stai facendo questo nel tuo PC è assolutamente buono, ma se stai distribuendo la tua applicazione VBA, non vuoi installare le DLL nel loro sistema. Potresti non avere il permesso o non vuoi veramente passare attraverso il processo di installazione/disinstallazione o problemi con i riferimenti.

Tuttavia è possibile caricare DLL in modo dinamico utilizzando alcune API di Windows.

DLL

Ora la domanda è come accedere .NET dll da VBA? se i tuoi clienti hanno un'architettura os mixed x86 x64 devi gestirli di conseguenza. Supponiamo che stiamo lavorando su 32 bit office/Excel.

Se si crea una DLL .NET e si desidera accedervi da VBA, verrà visualizzato un messaggio di errore simile a "Impossibile trovare il punto di ingresso DLL". per fortuna Robert Giesecke ha creato uno abstract wrapper che ti permetterà di creare semplici DLL di consumo tramite VBA.

A template può essere trovato qui.

Tutto quello che dovete fare

  1. Creare un nuovo progetto di classe in Visual Studio
  2. Impostare la piattaforma del progetto x86 per 32bit e altrimenti
  3. Creare le metodi all'interno di una classe principale.
  4. creare un'altra classe che restituirà la classe principale, come oggetto (sta tornando a VBA)
  5. (seguire il modello dal suo sito)

lascia supporre avete seguito il suo modello e ha creato un metodo di prova come a seguire.

[ComVisible(true), ClassInterface(ClassInterfaceType.AutoDual)] 
public class YOUR_MAIN_CLASS 
{ 
    [return: MarshalAs(UnmanagedType.BStr)] 
    public string FN_RETURN_TEXT(string iMsg) 
    { 

     return "You have sent me: " + iMsg + "..."; 
    } 
} 

e la classe unmanagedexport:

static class UnmanagedExports 
{ 
    [DllExport] 
    [return: MarshalAs(UnmanagedType.IDispatch)] 
    static Object YOUR_DLL_OBJECT() 
    { 
     return new YOUR_MAIN_CLASS(); 
    } 
} 

Preparazione per accedere alla DLL da parte VBA

Aggiungere la DLL alla cartella principale:

#If VBA7 Then 
    Public Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As LongPtr 
    Public Declare PtrSafe Function YOUR_DLL_OBJECT Lib "YOUR_DLL.dll"() 
#Else 
    Public Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal strFilePath As String) As Long 
    Public Declare Function YOUR_DLL_OBJECT Lib "YOUR_DLL.dll"() As Object 
#End If 

ora è tutto su come caricare la DLL e creare & l'accesso agli oggetti in vba. che sarebbe:

LoadLibrary (FN_APP_GET_BASE_PATH & "YOUR_DLL.dll") 
dim mObj as object 
set mObj = YOUR_DLL_OBJECT() 
debug.print mObj.FN_RETURN_TEXT("Testing ..") 

l'uscita dovrebbe essere

"You have sent me: Testing ....." 

Vantaggi Io personalmente non mi piacciono installazione e riferimento DLL. Seguendo il modello sopra riportato, non è necessario fare riferimento a nulla, non è necessario installare nulla, basta caricare e lavorare con la DLL in piena libertà.

NOTA: Presumo che il codice dll/.net sia tuo e che puoi compilare nuovamente con i modelli sopra indicati.

ho avuto successo con sopra modello e ha creato un NET non-blocking notifiche per VBA è possibile dare un'occhiata qui: non-blocking "toast" like notifications for Microsoft Access (VBA)

4

, ecco la soluzione, testati per .NET 2.0 e .NET 4.0, 32 bit e 64 bit, per gentile concessione di Soraco Technologies.

La soluzione proposta di seguito utilizza l'associazione tardiva e non richiede la registrazione degli assembly .NET.

dichiarazioni

Aggiungere le seguenti dichiarazioni al progetto:

#If VBA7 Then 
Private Declare PtrSafe Function GetShortPathName Lib “Kernel32.dll” Alias “GetShortPathNameW” (ByVal LongPath As LongPtr, ByVal ShortPath As LongPtr, ByVal Size As Long) As Long 
Private Declare PtrSafe Function SetDllDirectory Lib “Kernel32.dll” Alias “SetDllDirectoryW” (ByVal Path As LongPtr) As Long 
Private Declare PtrSafe Sub LoadClr_x64 Lib “QlmCLRHost_x64.dll” (ByVal clrVersion As String, ByVal verbose As Boolean, ByRef CorRuntimeHost As IUnknown) 
Private Declare PtrSafe Sub LoadClr_x86 Lib “QlmCLRHost_x86.dll” (ByVal clrVersion As String, ByVal verbose As Boolean, ByRef CorRuntimeHost As IUnknown) 
#Else 
Private Declare Function GetShortPathName Lib “Kernel32.dll” Alias “GetShortPathNameW” (ByVal LongPath As Long, ByVal ShortPath As Long, ByVal Size As Long) As Long 
Private Declare Function SetDllDirectory Lib “Kernel32.dll” Alias “SetDllDirectoryW” (ByVal Path As Long) As Long 
Private Declare Sub LoadClr_x64 Lib “QlmCLRHost_x64.dll” (ByVal clrVersion As String, ByVal verbose As Boolean, ByRef CorRuntimeHost As IUnknown) 
Private Declare Sub LoadClr_x86 Lib “QlmCLRHost_x86.dll” (ByVal clrVersion As String, ByVal verbose As Boolean, ByRef CorRuntimeHost As IUnknown) 
#End If ‘ WinAPI Declarations 

' Declare variables 
Dim m_myobject As Object 
Dim m_homeDir As String 

inizializzazione

È necessario inizializzare la variabile m_homeDir al percorso in cui si trovano gli assembly .NET.

Ad esempio, se si installano i assembly .NET nella stessa cartella dei file Excel o MS-Access, si dovrebbe inizializzare m_homeDir a:

Excel: m_homeDir = ThisWorkbook.Path

Accesso: m_homeDir = CurrentProject.Path

Creazione .NET oggetto

Aggiungere il seguente codice al progetto.

Private Function GetMyObject(dllPath As String, dllClass As String) As Object 
    Dim LongPath As String 
    Dim ShortPath As String 

    LongPath = “\\?\” & m_homeDir 
    ShortPath = String$(260, vbNull) 

    PathLength = GetShortPathName(StrPtr(LongPath), StrPtr(ShortPath), 260) 
    ShortPath = Mid$(ShortPath, 5, CLng(PathLength – 4)) 

    Call SetDllDirectory(StrPtr(ShortPath)) 
    Dim clr As mscoree.CorRuntimeHost 

    If Is64BitApp() Then 
     Call LoadClr_x64(“v4.0”, False, clr) 
    Else 
     Call LoadClr_x86(“v4.0”, False, clr) 
    End If 

    Call clr.Start 

    Dim domain As mscorlib.AppDomain 
    Call clr.GetDefaultDomain(domain) 

    Dim myInstanceOfDotNetClass As Object 
    Dim handle As mscorlib.ObjectHandle 

    Set handle = domain.CreateInstanceFrom(dllPath, dllClass) 

    Dim clrObject As Object 
    Set GetMyObject = handle.Unwrap 

    Call clr.Stop 
End Function 

Private Function Is64BitApp() As Boolean 

    #If Win64 Then 
     Is64BitApp = True 
    #End If 
End Function 

un'istanza.Oggetto NET

Ora è possibile creare un'istanza dell'oggetto .NET e iniziare a utilizzarlo. Aggiungere il seguente codice all'applicazione:

m_homeDir = ThisWorkbook.Path 

m_myobject = GetMyObject(m_homeDir & “\yourdotnet.dll”, “namespace.class”) 

Il primo argomento è il percorso completo della DLL .NET.

Il secondo argomento è il nome completo del tipo richiesto, incluso lo spazio dei nomi ma non l'assembly, come restituito dalla proprietà Type.FullName.

DLL necessarie

La soluzione richiede l'implementazione di 2 DLL che sono responsabili per ospitare il CLR .NET. Le DLL dovrebbero essere distribuite nella stessa cartella del file Excel o MS-Access.

Le DLL può essere scaricato dal sito web di Soraco: https://soraco.co/products/qlm/QLMCLRHost.zip

licenza LGPL-2,1

Con la presente si concede il diritto di utilizzare i nostri DLL finché l'applicazione non è in concorrenza, direttamente o indirettamente con Quick License Manager. È possibile utilizzare queste DLL nelle applicazioni commerciali o non commerciali.

+0

Alla persona che ha postato questa domanda, puoi fornire un feedback sulla soluzione che abbiamo proposto? –

+0

Ti ho dato un up perché non meritavi un downvote. Se vuoi attirare l'attenzione di qualcuno come l'OP in questo caso, metti un simbolo @ davanti al loro nome in un commento, ad esempio @ sam.porter - buona fortuna! –

0

Il criterio predefinito impedisce il CLR 4 dal excuting il codice legacy dal CLR 2:

Set clr = New mscoree.CorRuntimeHost 

Per abilitare l'esecuzione legacy, è possibile creare il file excel.exe.config nella cartella in cui si trova excel.exe:

<?xml version="1.0" encoding="utf-8" ?> 
<configuration> 
    <startup useLegacyV2RuntimeActivationPolicy="true"> 
    <supportedRuntime version="v4.0"/> 
    </startup> 
</configuration> 

Oppure si può chiamare la funzione nativa CorBindToRuntimeEx invece di New mscoree.CorRuntimeHost:

Private Declare PtrSafe Function CorBindToRuntimeEx Lib "mscoree" (_ 
    ByVal pwszVersion As LongPtr, _ 
    ByVal pwszBuildFlavor As LongPtr, _ 
    ByVal startupFlags As Long, _ 
    ByRef rclsid As Long, _ 
    ByRef riid As Long, _ 
    ByRef ppvObject As mscoree.CorRuntimeHost) As Long 

Private Declare PtrSafe Function VariantCopy Lib "oleaut32" (dest, src) As Long 


'' 
' Creates a .Net object with the CLR 4 without registration. ' 
'' 
Function CreateInstance(assembly As String, typeName As String) As Variant 
    Const CLR$ = "v4.0.30319" 

    Static domain As mscorlib.AppDomain 
    If domain Is Nothing Then 
    Dim host As mscoree.CorRuntimeHost, hr&, T&(0 To 7) 
    T(0) = &HCB2F6723: T(1) = &H11D2AB3A: T(2) = &HC000409C: T(3) = &H3E0AA34F 
    T(4) = &HCB2F6722: T(5) = &H11D2AB3A: T(6) = &HC000409C: T(7) = &H3E0AA34F 

    hr = CorBindToRuntimeEx(StrPtr(CLR), 0, 3, T(0), T(4), host) 
    If hr And -2 Then err.Raise hr 

    host.Start 
    host.GetDefaultDomain domain 
    End If 

    VariantCopy CreateInstance, domain.CreateInstanceFrom(assembly, typeName).Unwrap 
End Function