I have previously successfully used Unmanaged Exports and DllExport to use .NET DLL files with Inno Setup.
However now I am trying to get it to work with DNNE.
I have the following C# code targeting x86
<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;
}
}
}
I made a small console app to verify the exported code is working correctly:
using System.Runtime.InteropServices;
NE.Test();
public static class NE
{
[DllImport("DNNETestNE", CallingConvention = CallingConvention.StdCall)]
public extern static void Test();
}
Works OK!
Now I try to move it to 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;
Will crash with Could not call proc
I also tried
external 'Test@{tmp}\DNNETestNE.dll,DNNETest.dll stdcall delayload loadwithalteredsearchpath';
Played around with combinations of AnyCPU
, x86
, x64
but to no avail
But same error
I am unsure what else I can try, since these steps were working ok with the other DllImport packages.
CodePudding user response:
It does not work because
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.
Source: https://docs.microsoft.com/en-us/cpp/build/reference/exports?view=msvc-170
The quick fix is to use cdecl
instead of stdcall
on both C# and Pascal definitions
If you really want to use stdcall
, keep reading...
To fix it:
Add this line to <PropertyGroup>
<DnneWindowsExportsDef>$(MSBuildProjectDirectory)\DnneWindowsExports.def</DnneWindowsExportsDef>
Add the following:
EXPORTS
Test=Test
Replace Test
with the function you want to export
I made a small console app, which will generate this file: Just add a reference to your project and replace the classname in typeof
to one with your exports.
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);
I made the following example to show it's working
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;