如何使用DNNE从Inno Setup调用.net DLL ?



我以前已经成功地使用非托管导出和DllExport与Inno Setup一起使用. net DLL文件。

然而,现在我正试图让它与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();
}

作品《OK !


现在我试着把它移动到Inno Setup:

[Files]
Source: FilesDotnetDNNETest.deps.json; Flags: dontcopy
Source: FilesDotnetDNNETest.dll; Flags: dontcopy
Source: FilesDotnetDNNETest.runtimeconfig.json; Flags: dontcopy
Source: FilesDotnetDNNETestNE.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包一起工作得很好。

它不能工作,因为

编译器还对使用__stdcall调用约定的C函数进行修饰,使用下划线(_)前缀和由@符号(@)后跟参数列表中的字节数(十进制)组成的后缀。

来源:https://learn.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(@"SomeLocationDnneWindowsExports.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;