如何使用 DNNE 从 Inno Setup 调用 .NET DLL?
How to call .NET DLL from Inno Setup using DNNE?
我之前已经成功地 Unmanaged Exports and DllExport 将 .NET DLL 文件与 Inno Setup 一起使用。
但是现在我正在尝试让它与 DNNE 一起工作。
我有以下针对 x86 的 C# 代码
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EnableDynamicLoading>true</EnableDynamicLoading>
<Platforms>x86</Platforms>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DNNE" Version="1.0.31" />
</ItemGroup>
</Project>
using System.Runtime.InteropServices;
namespace DNNETest
{
internal static class NativeMethods
{
[DllImport("User32.dll", EntryPoint = "MessageBox",
CharSet = CharSet.Auto)]
internal static extern int MsgBox(
IntPtr hWnd, string lpText, string lpCaption, uint uType);
}
public class Class1
{
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static void Test()
{
_ = NativeMethods.MsgBox(IntPtr.Zero, "Hello from C#", ":)", 0);
return;
}
}
}
我制作了一个小型控制台应用程序来验证导出的代码是否正常工作:
using System.Runtime.InteropServices;
NE.Test();
public static class NE
{
[DllImport("DNNETestNE", CallingConvention = CallingConvention.StdCall)]
public extern static void Test();
}
工作正常!
现在我尝试将其移至 Inno Setup:
[Files]
Source: Files\Dotnet\DNNETest.deps.json; Flags: dontcopy
Source: Files\Dotnet\DNNETest.dll; Flags: dontcopy
Source: Files\Dotnet\DNNETest.runtimeconfig.json; Flags: dontcopy
Source: Files\Dotnet\DNNETestNE.dll; Flags: dontcopy
procedure Test();
external 'Test@{tmp}\DNNETestNE.dll stdcall delayload';
procedure InitializeDotnet;
begin
ExtractTemporaryFiles('{tmp}\DNNETest.deps.json');
ExtractTemporaryFiles('{tmp}\DNNETest.dll');
ExtractTemporaryFiles('{tmp}\DNNETest.runtimeconfig.json');
ExtractTemporaryFiles('{tmp}\DNNETestNE.dll');
Test();
end;
将与 Could not call proc
一起崩溃
我也试过了
external 'Test@{tmp}\DNNETestNE.dll,DNNETest.dll stdcall delayload loadwithalteredsearchpath';
试过 AnyCPU
、x86
、x64
的组合但无济于事
但同样的错误
我不确定我还能尝试什么,因为这些步骤与其他 DllImport 包一起工作正常。
它不起作用,因为
The compiler also decorates C functions that use the __stdcall calling convention with an underscore (_) prefix and a suffix composed of the at sign (@) followed by the number of bytes (in decimal) in the argument list.
来源:https://docs.microsoft.com/en-us/cpp/build/reference/exports?view=msvc-170
快速修复是在 C# 和 Pascal 定义中使用 cdecl
而不是 stdcall
如果您真的想使用 stdcall
,请继续阅读...
要修复它:
将此行添加到 <PropertyGroup>
<DnneWindowsExportsDef>$(MSBuildProjectDirectory)\DnneWindowsExports.def</DnneWindowsExportsDef>
添加以下内容:
EXPORTS
Test=Test
将Test
替换为您要导出的函数
我制作了一个小型控制台应用程序,它将生成此文件:只需添加对您的项目的引用并将 typeof
中的类名替换为您的导出文件。
using DNNETest;
using System.Text;
var names = typeof(NativeExports).GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).Select(m => m.Name).ToArray();
var output = new StringBuilder();
output.AppendLine("EXPORTS");
foreach (var name in names)
{
output.AppendLine($"\t{name}={name}");
}
var result = output.ToString();
Console.WriteLine(result);
File.WriteAllText(@"SomeLocation\DnneWindowsExports.def", result);
我做了下面的例子来证明它是有效的
public static class NativeExports
{
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static void Test()
{
_ = NativeMethods.MsgBox(IntPtr.Zero, nameof(Test), "C#", 0);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static void SendInt(int value)
{
_ = NativeMethods.MsgBox(IntPtr.Zero, $"{nameof(SendInt)}: {value}", "C#", 0);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static void SendString(IntPtr value)
{
var message = Marshal.PtrToStringUni(value);
_ = NativeMethods.MsgBox(IntPtr.Zero, $"{nameof(SendString)}: {message}", "C#", 0);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static unsafe void ReturnString(IntPtr value, IntPtr* result)
{
var message = Marshal.PtrToStringUni(value);
var returnString = new string(message.Reverse().ToArray());
_ = NativeMethods.MsgBox(IntPtr.Zero, $"{nameof(ReturnString)}: {message} => {returnString}", "C#", 0);
*result = Marshal.StringToBSTR(returnString);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static int ReturnInt(int input)
{
return input;
}
public delegate bool ExpandConstantDelegate([MarshalAs(UnmanagedType.LPWStr)] string input, [MarshalAs(UnmanagedType.BStr)] out string output);
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static void CallExpandConstantCallback(IntPtr callbackPtr)
{
var ExpandConstant = Marshal.GetDelegateForFunctionPointer<ExpandConstantDelegate>(callbackPtr);
var constant = "{tmp}";
ExpandConstant(constant, out var result);
_ = NativeMethods.MsgBox(IntPtr.Zero, $"{nameof(ExpandConstant)}({constant}) => {result}", "C#", 0);
}
}
procedure Test();
external 'Test@{tmp}\DNNETestNE.dll stdcall delayload';
procedure SendInt(value: Integer);
external 'SendInt@{tmp}\DNNETestNE.dll stdcall delayload';
procedure SendString(value: string);
external 'SendString@{tmp}\DNNETestNE.dll stdcall delayload';
procedure ReturnString(value: string; out outValue: WideString);
external 'ReturnString@{tmp}\DNNETestNE.dll stdcall delayload';
function ReturnInt(value: Integer) : Integer;
external 'ReturnInt@{tmp}\DNNETestNE.dll stdcall delayload';
procedure ExpandConstantWrapper(const toExpandString: string; out expandedString: WideString);
begin
expandedString := ExpandConstant(toExpandString);
end;
procedure CallExpandConstantCallback(callback: Longword);
external 'CallExpandConstantCallback@{tmp}\DNNETestNE.dll stdcall delayload';
procedure InitializeDotnet;
var
outString: WideString;
begin
ExtractTemporaryFiles('{tmp}\DNNETest*');
Test();
SendInt(1234);
SendString('Hello World');
ReturnString('ReverseMe!', outString);
MessageBox(outString, 0);
MessageBox(IntToStr(ReturnInt(4321)), 0);
CallExpandConstantCallback(CreateCallback(@ExpandConstantWrapper));
end;
我之前已经成功地 Unmanaged Exports and DllExport 将 .NET DLL 文件与 Inno Setup 一起使用。
但是现在我正在尝试让它与 DNNE 一起工作。
我有以下针对 x86 的 C# 代码
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EnableDynamicLoading>true</EnableDynamicLoading>
<Platforms>x86</Platforms>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DNNE" Version="1.0.31" />
</ItemGroup>
</Project>
using System.Runtime.InteropServices;
namespace DNNETest
{
internal static class NativeMethods
{
[DllImport("User32.dll", EntryPoint = "MessageBox",
CharSet = CharSet.Auto)]
internal static extern int MsgBox(
IntPtr hWnd, string lpText, string lpCaption, uint uType);
}
public class Class1
{
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static void Test()
{
_ = NativeMethods.MsgBox(IntPtr.Zero, "Hello from C#", ":)", 0);
return;
}
}
}
我制作了一个小型控制台应用程序来验证导出的代码是否正常工作:
using System.Runtime.InteropServices;
NE.Test();
public static class NE
{
[DllImport("DNNETestNE", CallingConvention = CallingConvention.StdCall)]
public extern static void Test();
}
工作正常!
现在我尝试将其移至 Inno Setup:
[Files]
Source: Files\Dotnet\DNNETest.deps.json; Flags: dontcopy
Source: Files\Dotnet\DNNETest.dll; Flags: dontcopy
Source: Files\Dotnet\DNNETest.runtimeconfig.json; Flags: dontcopy
Source: Files\Dotnet\DNNETestNE.dll; Flags: dontcopy
procedure Test();
external 'Test@{tmp}\DNNETestNE.dll stdcall delayload';
procedure InitializeDotnet;
begin
ExtractTemporaryFiles('{tmp}\DNNETest.deps.json');
ExtractTemporaryFiles('{tmp}\DNNETest.dll');
ExtractTemporaryFiles('{tmp}\DNNETest.runtimeconfig.json');
ExtractTemporaryFiles('{tmp}\DNNETestNE.dll');
Test();
end;
将与 Could not call proc
我也试过了
external 'Test@{tmp}\DNNETestNE.dll,DNNETest.dll stdcall delayload loadwithalteredsearchpath';
试过 AnyCPU
、x86
、x64
的组合但无济于事
但同样的错误
我不确定我还能尝试什么,因为这些步骤与其他 DllImport 包一起工作正常。
它不起作用,因为
The compiler also decorates C functions that use the __stdcall calling convention with an underscore (_) prefix and a suffix composed of the at sign (@) followed by the number of bytes (in decimal) in the argument list.
来源:https://docs.microsoft.com/en-us/cpp/build/reference/exports?view=msvc-170
快速修复是在 C# 和 Pascal 定义中使用 cdecl
而不是 stdcall
如果您真的想使用 stdcall
,请继续阅读...
要修复它:
将此行添加到 <PropertyGroup>
<DnneWindowsExportsDef>$(MSBuildProjectDirectory)\DnneWindowsExports.def</DnneWindowsExportsDef>
添加以下内容:
EXPORTS
Test=Test
将Test
替换为您要导出的函数
我制作了一个小型控制台应用程序,它将生成此文件:只需添加对您的项目的引用并将 typeof
中的类名替换为您的导出文件。
using DNNETest;
using System.Text;
var names = typeof(NativeExports).GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).Select(m => m.Name).ToArray();
var output = new StringBuilder();
output.AppendLine("EXPORTS");
foreach (var name in names)
{
output.AppendLine($"\t{name}={name}");
}
var result = output.ToString();
Console.WriteLine(result);
File.WriteAllText(@"SomeLocation\DnneWindowsExports.def", result);
我做了下面的例子来证明它是有效的
public static class NativeExports
{
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static void Test()
{
_ = NativeMethods.MsgBox(IntPtr.Zero, nameof(Test), "C#", 0);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static void SendInt(int value)
{
_ = NativeMethods.MsgBox(IntPtr.Zero, $"{nameof(SendInt)}: {value}", "C#", 0);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static void SendString(IntPtr value)
{
var message = Marshal.PtrToStringUni(value);
_ = NativeMethods.MsgBox(IntPtr.Zero, $"{nameof(SendString)}: {message}", "C#", 0);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static unsafe void ReturnString(IntPtr value, IntPtr* result)
{
var message = Marshal.PtrToStringUni(value);
var returnString = new string(message.Reverse().ToArray());
_ = NativeMethods.MsgBox(IntPtr.Zero, $"{nameof(ReturnString)}: {message} => {returnString}", "C#", 0);
*result = Marshal.StringToBSTR(returnString);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static int ReturnInt(int input)
{
return input;
}
public delegate bool ExpandConstantDelegate([MarshalAs(UnmanagedType.LPWStr)] string input, [MarshalAs(UnmanagedType.BStr)] out string output);
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static void CallExpandConstantCallback(IntPtr callbackPtr)
{
var ExpandConstant = Marshal.GetDelegateForFunctionPointer<ExpandConstantDelegate>(callbackPtr);
var constant = "{tmp}";
ExpandConstant(constant, out var result);
_ = NativeMethods.MsgBox(IntPtr.Zero, $"{nameof(ExpandConstant)}({constant}) => {result}", "C#", 0);
}
}
procedure Test();
external 'Test@{tmp}\DNNETestNE.dll stdcall delayload';
procedure SendInt(value: Integer);
external 'SendInt@{tmp}\DNNETestNE.dll stdcall delayload';
procedure SendString(value: string);
external 'SendString@{tmp}\DNNETestNE.dll stdcall delayload';
procedure ReturnString(value: string; out outValue: WideString);
external 'ReturnString@{tmp}\DNNETestNE.dll stdcall delayload';
function ReturnInt(value: Integer) : Integer;
external 'ReturnInt@{tmp}\DNNETestNE.dll stdcall delayload';
procedure ExpandConstantWrapper(const toExpandString: string; out expandedString: WideString);
begin
expandedString := ExpandConstant(toExpandString);
end;
procedure CallExpandConstantCallback(callback: Longword);
external 'CallExpandConstantCallback@{tmp}\DNNETestNE.dll stdcall delayload';
procedure InitializeDotnet;
var
outString: WideString;
begin
ExtractTemporaryFiles('{tmp}\DNNETest*');
Test();
SendInt(1234);
SendString('Hello World');
ReturnString('ReverseMe!', outString);
MessageBox(outString, 0);
MessageBox(IntToStr(ReturnInt(4321)), 0);
CallExpandConstantCallback(CreateCallback(@ExpandConstantWrapper));
end;