Использование DLL С# внутри EXCEL VBA
Я столкнулся с небольшой проблемой здесь и вам нужна помощь ребят.
У меня есть DLL С#, открытая через COM-взаимодействие. Он работает нормально, но, по-видимому, развертывание объекта взаимодействия С# является катастрофой, и вам нужно периодически обновлять каждый раз при обновлении DLL.
Итак, мне интересно, как я могу использовать функции из этой С# DLL следующим образом:
Или что-нибудь, что я могу назвать функциями, просто поместив DLL и электронную таблицу вместе.
Declare Function getString Lib "<PATH of my DLL>" () as string
sub test()
range("A1").value = getString
End Sub
Синтаксис может быть неправильным.
Ответы
Ответ 1
Вы можете это сделать, но вы должны знать различия в VBA и .Net.
Во-первых, вам нужно создать фактическую DLL (сборки .NET нет), чтобы сделать это, используйте этот шаблон проекта.
Опять же, вы должны знать, как маршалировать вещи.
VBA поддерживает только stdcall как соглашение о вызове и не может действительно работать с функциями Unicode для DLL. Это не так плохо, поскольку по умолчанию маршалинг для String в .Net - это то, что ожидает VBA (указатель на Ansi char). Кроме того, stdcall - это соглашение по умолчанию, которое я использую для экспорта.
Я повторно использую образец, который я создал недавно для другого потока SO:
Поместите это в проект, который вы создали с помощью моего шаблона:
[ComVisible(true), ClassInterface(ClassInterfaceType.AutoDual)]
public class Sample
{
public string Text
{
[return: MarshalAs(UnmanagedType.BStr)]
get;
[param: MarshalAs(UnmanagedType.BStr)]
set;
}
[return: MarshalAs(UnmanagedType.BStr)]
public string TestMethod()
{
return Text + "...";
}
}
static class UnmanagedExports
{
[DllExport]
[return: MarshalAs(UnmanagedType.IDispatch)]
static Object CreateDotNetObject(String text)
{
return new Sample { Text = text };
}
}
Вот как это называется из VBA:
Declare Function CreateDotNetObject Lib "The full path to your assembly or just the assembly if it is accessible from Excel" _
(ByVal text As String) As Object
Sub test()
Dim instance As Object
Set instance = CreateDotNetObject("Test 1")
Debug.Print instance.Text
Debug.Print instance.TestMethod
instance.text = "abc 123" ' case insensitivity in VBA works as expected'
Debug.Print instance.Text
End Sub
Ответ 2
У меня была эта проблема много раз.
я закончил регистрацию com dll из vba с помощью метода оболочки и ожидания, на regasm, чтобы зарегистрировать/отменить регистрацию dll до поздней привязки, создав com-объект через
CreateObject('yourclasshere')
Его немного взломать, но он работает, использует метод shellandwait и следующий метод регистрации и отмены регистрации.
Private Declare Function OpenProcess Lib "kernel32" _
(ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) As Long
Private Declare Function GetExitCodeProcess Lib "kernel32" _
(ByVal hProcess As Long, lpExitCode As Long) As Long
Private Const STATUS_PENDING = &H103&
Private Const PROCESS_QUERY_INFORMATION = &H400
Private Function ShellandWait(ExeFullPath As String, _
Optional TimeOutValue As Long = 0) As Boolean
Dim lInst As Long
Dim lStart As Long
Dim lTimeToQuit As Long
Dim sExeName As String
Dim lProcessId As Long
Dim lExitCode As Long
Dim bPastMidnight As Boolean
On Error GoTo ErrorHandler
lStart = CLng(Timer)
sExeName = ExeFullPath
'Deal with timeout being reset at Midnight
If TimeOutValue > 0 Then
If lStart + TimeOutValue < 86400 Then
lTimeToQuit = lStart + TimeOutValue
Else
lTimeToQuit = (lStart - 86400) + TimeOutValue
bPastMidnight = True
End If
End If
lInst = Shell(sExeName, vbHide)
lProcessId = OpenProcess(PROCESS_QUERY_INFORMATION, False, lInst)
Do
Call GetExitCodeProcess(lProcessId, lExitCode)
DoEvents
If TimeOutValue And Timer > lTimeToQuit Then
If bPastMidnight Then
If Timer < lStart Then Exit Do
Else
Exit Do
End If
End If
Loop While lExitCode = STATUS_PENDING
ShellandWait = True
Exit Function
ErrorHandler:
ShellandWait = False
End Function
Private Function RegisterPayload() As Boolean
Dim script As String
script = "cmd /c"
script = script + " " + "%windir%\Microsoft.NET\Framework\v2.0.50727\regasm"
script = script + " " + Chr(34) + InstallationPath + Chr(34)
script = script + " /codebase"
RegisterPayload = ShellandWait(script)
End Function
Private Function UnRegisterPayload() As Boolean
Dim script As String
script = "cmd /c"
script = script + " " + "%windir%\Microsoft.NET\Framework\v2.0.50727\regasm"
script = script + " " + Chr(34) + InstallationPath + Chr(34)
script = script + " /u"
UnRegisterPayload = ShellandWait(script)
End Function
Надеюсь, это поможет:)
Ответ 3
Это операторы using, которые должны быть в верхней части вашего класса, которые являются ключевыми:
с использованием System.Diagnostics;
используя RGiesecke.DllExport;
Также убедитесь, что вы создали проект перед инструкцией Nuget PM для установки шаблона выше. Я новичок в этом - я уверен, что есть и другие. Я использую AutoCAD VBA и получил еще одну ошибку, так как это 64 бит - мне пришлось использовать PtrSafe (и т.д.) В заявлении Declare для VBA, чтобы продолжить без ошибок (см. MS docs для этого http://support.microsoft.com/kb/983043)
Он работал btw!
Мой последний код (на основе выше)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using RGiesecke.DllExport;
namespace ClassLibrary3
{
[ComVisible(true), ClassInterface(ClassInterfaceType.AutoDual)]
public class Class1
{
public string Text
{
[return: MarshalAs(UnmanagedType.BStr)]
get;
[param: MarshalAs(UnmanagedType.BStr)]
set;
}
[return: MarshalAs(UnmanagedType.BStr)]
public string TestMethod()
{
return Text + "...";
}
}
static class UnmanagedExports
{
[DllExport]
[return: MarshalAs(UnmanagedType.IDispatch)]
static Object CreateDotNetObject(String text)
{
return new Class1 { Text = text };
}
}
}
и мой код vba:
#If VBA7 Then
Private Declare PtrSafe Function CreateDotNetObject Lib "G:\gitRepository\VS\ClassLibrary3\ClassLibrary3\bin\Debug\ClassLibrary3.dll" (ByVal text As String) As Object
#Else
Private Declare Function CreateDotNetObject Lib "G:\gitRepository\VS\ClassLibrary3\ClassLibrary3\bin\Debug\ClassLibrary3.dll" (ByVal text As String) As Object
#End If
Sub test()
Dim instance As Object
Set instance = CreateDotNetObject("Test 1")
Debug.Print instance.text
Debug.Print instance.TestMethod
instance.text = "abc 123" ' case insensitivity in VBA works as expected'
Debug.Print instance.text
End Sub