Вернуть строку [] в C # dll для VBA

Я написал эту небольшую DLL-библиотеку C #, используя UnmanagedExports (полученный как пакет NuGet), который работает нормально. Тем не менее, мне интересно, если и если да, то можно сразу вернуть массив String () вместо того, чтобы возвращать строку, которая должна быть Split () в функции обертки VBA.

То есть, интерес представляет метод GetFilesWithExtension (). Другие методы в dll - это лишь небольшие тесты, которые я сделал, выясняя, как передавать строки с правильной кодировкой.

DLL нацелен на x64 и .NET 4.5.2, но вы также можете построить для x86 (и соответственно изменить функцию Declares в VBA).

Библиотека классов C # (TestDll.dll):

using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using RGiesecke.DllExport;

namespace TestDll
{
    public class Class1
    {
        [DllExport(nameof(Addition), CallingConvention.StdCall)]
        public static int Addition(int a, int b)
        {
            return a + b + 100;
        }


        [DllExport(nameof(LinqAddition), CallingConvention.StdCall)]
        public static int LinqAddition(int a, int b)
        {
            return new int[] {a, b, 1, 4, 5, 6, 7, 8 }.Sum();
        }

        [DllExport(nameof(LinqAdditionString), CallingConvention.StdCall)]
        [return: MarshalAs(UnmanagedType.AnsiBStr)]
        public static string LinqAdditionString(int a, int b)
        {
            return new int[] { a, b, 1, 4, 5, 6, 7, 8 }.Sum() + "";
        }

        [DllExport(nameof(GetFilesWithExtension), CallingConvention.StdCall)]
        [return: MarshalAs(UnmanagedType.AnsiBStr)]
        public static string GetFilesWithExtension([MarshalAs(UnmanagedType.AnsiBStr)] string folderPath, [MarshalAs(UnmanagedType.AnsiBStr)] string extension, bool includeSubdirectories)
        {
            //Debug
            //File.WriteAllText(@"C:UsersjohanbSourceReposTestDlloutput.txt", $"folderPath: {folderPath}, extension: {extension}, includeSubdirectories: {includeSubdirectories}");
            try
            {
                if (!Directory.Exists(folderPath))
                    return "";

                extension = extension.Trim('.');

                return string.Join(";",
                    Directory.GetFiles(folderPath, "*.*",
                            includeSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
                        .Where(
                            f =>
                                Path.GetExtension(f)?
                                    .Trim('.')
                                    .Equals(extension, StringComparison.InvariantCultureIgnoreCase) ?? false)
                        .ToArray());
            }
            catch (Exception ex)
            {
                return ex.ToString();
            }
        }
    }
}

Модуль VBA (проверен в Excel):

Attribute VB_Name = "TestDll"
Option Explicit

Public Declare PtrSafe Function Addition Lib "C:UsersjohanbSourceReposTestDllTestDllinDebugTestDll.dll" (ByVal a As Long, ByVal b As Long) As Long
Public Declare PtrSafe Function LinqAddition Lib "C:UsersjohanbSourceReposTestDllTestDllinDebugTestDll.dll" (ByVal a As Long, ByVal b As Long) As Long
Public Declare PtrSafe Function LinqAdditionString Lib "C:UsersjohanbSourceReposTestDllTestDllinDebugTestDll.dll" (ByVal a As Long, ByVal b As Long) As String
Public Declare PtrSafe Function GetFilesWithExt Lib "C:UsersjohanbSourceReposTestDllTestDllinDebugTestDll.dll" Alias "GetFilesWithExtension" (ByVal folderPath As String, ByVal extension As String, ByVal includeSubdirs As Boolean) As String

Sub Test()
    Dim someAddition As Long
    Dim someLinqAddition As Long
    Dim someLinqAdditionAsString As String
    Dim files() As String
    Dim i As Long

    someAddition = Addition(5, 3)
    Debug.Print someAddition

    someLinqAddition = LinqAddition(5, 3)
    Debug.Print someLinqAddition

    someLinqAdditionAsString = LinqAdditionString(5, 3)
    Debug.Print someLinqAddition

    files = GetFilesWithExtension("C:TradostestProject 4", "sdlxliff", True)
    For i = 0 To UBound(files)
        Debug.Print files(i)
    Next i

End Sub

Function GetFilesWithExtension(folderPath As String, extension As String, includeSubdirs As Boolean) As String()
    GetFilesWithExtension = Split(GetFilesWithExt(folderPath, extension, includeSubdirs), ";")
End Function

c#,vba,marshalling,

3

Ответов: 1


1 принят

Я никогда не мог вернуть объект 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 .

C #, VBA, сортировочный,
Похожие вопросы