Я никогда не мог вернуть объект Excel, чтобы работать, но передача объекта по ссылке назад и вперед работает очень хорошо. По какой-то причине мне пришлось использовать ключевое слово ref вместо out, иначе Excel сработает.
Мне также пришлось использовать UnmanagedType.AnsiBstr для строк, чтобы получить право кодирования, но для строковых массивов единственный способ заставить его работать - объявлять его как объект и выполнять проверку типа во время выполнения в начале метода ,
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using RGiesecke.DllExport;
namespace TestDll
{
public class FolderHandling
{
[DllExport(nameof(GetFilesByExtensions), CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.Bool)]
public static bool GetFilesByExtensions(
ref object arrayOfFiles, //out doesn't work
[MarshalAs(UnmanagedType.AnsiBStr)] string folderPath,
object extensions, //type safety breaks it..somehow
[MarshalAs(UnmanagedType.Bool)] bool includeSubdirectories)
{
try
{
if (!Directory.Exists(folderPath))
{
arrayOfFiles = new[] { $"Parameter {nameof(folderPath)} ({folderPath}) is not a folder" };
return false;
}
if (!(extensions is string[]))
{
arrayOfFiles = new[] { $"Parameter {nameof(extensions)} is not a string array" };
return false;
}
var exts = ((string[])extensions).Select(e => e.Trim('.').ToLowerInvariant()).ToArray();
var files = Directory.GetFiles(folderPath, "*.*",
includeSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
.Where(f => exts.Contains(Path.GetExtension(f)?.Trim('.').ToLowerInvariant() ?? ";;;"))
.ToArray();
//normalize ANSI just in case
General.NormalizeANSI(ref files);
arrayOfFiles = files;
return true;
}
catch (Exception ex)
{
arrayOfFiles = new[] { "Exception: " + ex };
return false;
}
}
}
}
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
namespace TestDll
{
static class General
{
public static void NormalizeANSI(ref string[] files)
{
for (int i = 0; i < files.Length; i++)
{
files[i] = string.Concat(files[i].Normalize(NormalizationForm.FormD).Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark));
}
}
}
}
Я могу использовать свою DLL в Excel, как показано ниже, используя LoadLibrary (), поэтому мне не нужно размещать ее в системной папке пользователя или регистрироваться для COM. Преимущество использования FreeLibrary () заключается в том, что он позволяет мне перекомпилировать проект C # без закрытия Excel.
Public Declare PtrSafe Function GetFilesByExtensions Lib "TestDll.dll" (ByRef filesRef, ByVal folderPath As String, ByVal extensions, ByVal includeSubdirs As Boolean) As Boolean
Private Declare PtrSafe Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Private Declare PtrSafe Function LoadLibraryA Lib "kernel32" (ByVal lpLibFileName As String) As Long
Private Function LoadLibrary(dllName As String) As Long
Dim path As String
path = ThisWorkbook.path & "" & dllName
LoadLibrary = LoadLibraryA(path)
End Function
Sub TestFolderFiltering()
Dim files() As String
Dim i As Long
Dim moduleHandle As Long
On Error GoTo restore
moduleHandle = LoadLibrary("TestDll.dll")
If GetFilesByExtensions(files, "C:TradostestProject 4", Split("sdlxliff", ";"), True) Then
For i = 0 To UBound(files)
Debug.Print " - " & files(i)
Next i
Else
Debug.Print "ERROR: " & files(0)
End If
restore:
If moduleHandle <> 0 Then
Call FreeLibrary(moduleHandle)
End If
End Sub
Также возможно передать COM-объекты из VBA в DLL, как это, и обработать их с помощью стандартных библиотек Microsoft Interop или NetOffice, и мне удалось написать метод, который фильтрует строковые массивы VBA строковым представлением выражения C # lambda , что звучит так, как будто это может пригодиться многим людям:
If FilterStringArray(myArr, "s => s.ToUpperInvariant().Equals(s, StringComparison.CurrentCulture)") Then
For i = 0 To UBound(myArr)
Debug.Print " - " & myArr(i)
Next i
Else
Debug.Print "ERROR: " & myArr(0)
End If
Вы можете найти весь проект на GitLab .