我使用UnmanagedExports(作为NuGet包获得)编写了这个小C#测试DLL,它工作正常。但是,我想知道是否,如果是这样,可以立即返回一个String()数组而不是返回一个必须在VBA包装函数中的Split()的字符串。
也就是说,兴趣点是方法GetFilesWithExtension()。 dll中的其他方法只是我在确定如何使用正确的编码传递字符串时所做的小测试。
DLL目标是x64和.NET 4.5.2,但您也应该能够为x86构建(并相应地更改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:\Users\johanb\Source\Repos\TestDll\output.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:\Users\johanb\Source\Repos\TestDll\TestDll\bin\Debug\TestDll.dll" (ByVal a As Long, ByVal b As Long) As Long
Public Declare PtrSafe Function LinqAddition Lib "C:\Users\johanb\Source\Repos\TestDll\TestDll\bin\Debug\TestDll.dll" (ByVal a As Long, ByVal b As Long) As Long
Public Declare PtrSafe Function LinqAdditionString Lib "C:\Users\johanb\Source\Repos\TestDll\TestDll\bin\Debug\TestDll.dll" (ByVal a As Long, ByVal b As Long) As String
Public Declare PtrSafe Function GetFilesWithExt Lib "C:\Users\johanb\Source\Repos\TestDll\TestDll\bin\Debug\TestDll.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:\Tradostest\Project 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
答案 0 :(得分: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));
}
}
}
}
我可以使用LoadLibrary()在Excel中使用我的DLL,因此我不必将其放在用户的系统文件夹中或注册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:\Tradostest\Project 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上找到整个项目。