3F / DllExport

.NET DllExport with .NET Core support (aka 3F/DllExport aka DllExport.bat)
MIT License
938 stars 131 forks source link

Empty function table after export #136

Closed gimi87 closed 4 years ago

gimi87 commented 4 years ago

Hello :)

I guess I have a simple problem with the DllExport. I have netstandard2.1 project and I'm running the DllExport 1.7.0 version and I'd like to export functions.

This is my csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.1</TargetFramework>
  </PropertyGroup>
  <PropertyGroup>
    <DllExportIdent>0D1AB47E-E286-45E0-BE60-CA858F8A978D</DllExportIdent>
    <DllExportMetaLibName>DllExport.dll</DllExportMetaLibName>
    <DllExportNamespace>ClassLibrary1</DllExportNamespace>
    <DllExportDDNSCecil>false</DllExportDDNSCecil>
    <DllExportSkipOnAnyCpu>false</DllExportSkipOnAnyCpu>
    <DllExportPlatform>Auto</DllExportPlatform>
    <DllExportOrdinalsBase>1</DllExportOrdinalsBase>
    <DllExportGenExpLib>true</DllExportGenExpLib>
    <DllExportOurILAsm>true</DllExportOurILAsm>
    <DllExportSysObjRebase>true</DllExportSysObjRebase>
    <DllExportLeaveIntermediateFiles>false</DllExportLeaveIntermediateFiles>
    <DllExportTimeout>30000</DllExportTimeout>
    <DllExportPeCheck>2</DllExportPeCheck>
    <DllExportPatches>1</DllExportPatches>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="DllExport" Version="1.7.0" />
  </ItemGroup>
  <ItemGroup>
    <Reference Include="DllExport, PublicKeyToken=8337224c9ad9e356">
      <HintPath>$(SolutionDir)packages\DllExport.1.7.0\gcache\$(DllExportMetaXBase)\$(DllExportNamespace)\$(DllExportMetaLibName)</HintPath>
      <Private>False</Private>
      <SpecificVersion>False</SpecificVersion>
    </Reference>
  </ItemGroup>
  <ImportGroup Label=".NET DllExport">
    <Import Project="$(SolutionDir)packages\DllExport.1.7.0\tools\net.r_eg.DllExport.targets" Condition="Exists($([MSBuild]::Escape('$(SolutionDir)packages\DllExport.1.7.0\tools\net.r_eg.DllExport.targets')))" Label="8337224c9ad9e356" />
  </ImportGroup>
  <Target Name="DllExportRestorePkg" BeforeTargets="PrepareForBuild">
    <Error Condition="!Exists('$(SolutionDir)DllExport.bat')" Text="DllExport.bat is not found. Path: '$(SolutionDir)' - https://github.com/3F/DllExport" />
    <Exec Condition="('$(DllExportModImported)' != 'true' Or !Exists('$(SolutionDir)packages\DllExport.1.7.0\tools\net.r_eg.DllExport.targets')) And Exists('$(SolutionDir)DllExport.bat')" Command=".\DllExport.bat  -action Restore" WorkingDirectory="$(SolutionDir)" />
  </Target>
  <Target Name="DllExportRPkgDynamicImport" BeforeTargets="PostBuildEvent" DependsOnTargets="GetFrameworkPaths" Condition="'$(DllExportModImported)' != 'true' And '$(DllExportRPkgDyn)' != 'false'">
    <MSBuild BuildInParallel="true" UseResultsCache="true" Projects="$(MSBuildProjectFullPath)" Properties="DllExportRPkgDyn=true" Targets="Build" />
  </Target>
</Project>

sample class:

using System;
using System.Runtime.InteropServices;

namespace ClassLibrary1
{
    public static class PassThruDeviceAPI
    {
        [DllExport] public static int PassThruOpen([In] IntPtr pName, [Out] IntPtr pDeviceID) => 0;
        [DllExport] public static int PassThruClose([In]uint deviceID) => 0;
        [DllExport] public static int PassThruConnect([In]uint deviceID, [In]int protocolID, [In]int flags, [In]uint baudrate, [Out]IntPtr pChannelID) => 0;
        [DllExport] public static int PassThruDisconnect([In]uint channelID) => 0;
        [DllExport] public static int PassThruReadMsgs([In]uint channelID, [Out]IntPtr pMsg, [In, Out]IntPtr pNumMsgs, [In]uint timeout) => 0;
        [DllExport] public static int PassThruWriteMsgs([In]uint channelID, [In]IntPtr pMsgs, [In, Out]IntPtr pNumMsgs, [In]uint timeout) => 0;
        [DllExport] public static int PassThruStartPeriodicMsg([In]uint channelID, [In]IntPtr pMsg, [Out]IntPtr pMsgID, [In]uint timeInterval) => 0;
        [DllExport] public static int PassThruStopPeriodicMsg([In]uint channelID, [In]uint msgID) => 0;
        [DllExport] public static int PassThruStartMsgFilter([In]uint channelID, [In]int filterType, [In]IntPtr pMaskMsg, [In]IntPtr pPatternMsg, [In]IntPtr pFlowControlMsg, [Out]IntPtr pFilterID) => 0;
        [DllExport] public static int PassThruStopMsgFilter([In]uint channelID, [In]uint filterID) => 0;
        [DllExport] public static int PassThruSetProgrammingVoltage([In]uint deviceID, [In]uint pinNumber, [In]uint voltage) => 0;
        [DllExport] public static int PassThruReadVersion([In]uint deviceID, [Out]IntPtr pFirmwareVersion, [Out]IntPtr pDllVersion, [Out]IntPtr pApiVersion) => 0;
        [DllExport] public static int PassThruGetLastError([Out]IntPtr pErrorDescription) => 0;
        [DllExport] public static int PassThruIoctl([In]uint channelID, [In]int ioctlID, [In]IntPtr pInput, [Out]IntPtr pOutput) => 0;
    }
}

build log:

Restoring NuGet packages...
To prevent NuGet from restoring packages during build, open the Visual Studio Options dialog, click on the NuGet Package Manager node and uncheck 'Allow NuGet to download missing packages during build.'
Restoring packages for D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\ClassLibrary1.csproj...
Committing restore...
Assets file has not changed. Skipping assets file writing. Path: D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\obj\project.assets.json
Restore completed in 3,15 ms for D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\ClassLibrary1.csproj.
NuGet package restore finished.
1>------ Rebuild All started: Project: ClassLibrary1, Configuration: Debug Any CPU ------
1>Build started 03.02.2020 16:09:14.
1>Target CoreClean:
1>  Deleting file "D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\bin\Debug\netstandard2.1\ClassLibrary1.deps.json".
1>  Deleting file "D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\bin\Debug\netstandard2.1\ClassLibrary1.dll".
1>  Deleting file "D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\obj\Debug\netstandard2.1\ClassLibrary1.csprojAssemblyReference.cache".
1>  Deleting file "D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\obj\Debug\netstandard2.1\ClassLibrary1.AssemblyInfoInputs.cache".
1>  Deleting file "D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\obj\Debug\netstandard2.1\ClassLibrary1.AssemblyInfo.cs".
1>  Deleting file "D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\obj\Debug\netstandard2.1\ClassLibrary1.dll".
1>  Deleting file "D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\obj\Debug\netstandard2.1\ClassLibrary1.pdb".
1>Target GenerateTargetFrameworkMonikerAttribute:
1>  Skipping target "GenerateTargetFrameworkMonikerAttribute" because all output files are up-to-date with respect to the input files.
1>Target CoreCompile:
1>  Using shared compilation with compiler from directory: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\Roslyn
1>Target CopyFilesToOutputDirectory:
1>  Copying file from "D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\obj\Debug\netstandard2.1\ClassLibrary1.dll" to "D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\bin\Debug\netstandard2.1\ClassLibrary1.dll".
1>  ClassLibrary1 -> D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\bin\Debug\netstandard2.1\ClassLibrary1.dll
1>  Copying file from "D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\obj\Debug\netstandard2.1\ClassLibrary1.pdb" to "D:\Documents\Visual Studio 2019\Projects\ClassLibrary1\bin\Debug\netstandard2.1\ClassLibrary1.pdb".
1>Target DllExportMod:
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruOpen'([in] native int 'pName', [out] native int 'pDeviceID') cil managed
1>      exporting as PassThruOpen and index 1
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruClose'([in] uint32 'deviceID') cil managed
1>      exporting as PassThruClose and index 2
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruConnect'([in] uint32 'deviceID', [in] int32 'protocolID', [in] int32 'flags', [in] uint32 'baudrate', [out] native int 'pChannelID') cil managed
1>      exporting as PassThruConnect and index 3
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruDisconnect'([in] uint32 'channelID') cil managed
1>      exporting as PassThruDisconnect and index 4
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruReadMsgs'([in] uint32 'channelID', [out] native int 'pMsg', [in][out] native int 'pNumMsgs', [in] uint32 'timeout') cil managed
1>      exporting as PassThruReadMsgs and index 5
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruWriteMsgs'([in] uint32 'channelID', [in] native int 'pMsgs', [in][out] native int 'pNumMsgs', [in] uint32 'timeout') cil managed
1>      exporting as PassThruWriteMsgs and index 6
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruStartPeriodicMsg'([in] uint32 'channelID', [in] native int 'pMsg', [out] native int 'pMsgID', [in] uint32 'timeInterval') cil managed
1>      exporting as PassThruStartPeriodicMsg and index 7
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruStopPeriodicMsg'([in] uint32 'channelID', [in] uint32 'msgID') cil managed
1>      exporting as PassThruStopPeriodicMsg and index 8
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruStartMsgFilter'([in] uint32 'channelID', [in] int32 'filterType', [in] native int 'pMaskMsg', [in] native int 'pPatternMsg', [in] native int 'pFlowControlMsg', [out] native int 'pFilterID') cil managed
1>      exporting as PassThruStartMsgFilter and index 9
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruStopMsgFilter'([in] uint32 'channelID', [in] uint32 'filterID') cil managed
1>      exporting as PassThruStopMsgFilter and index 10
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruSetProgrammingVoltage'([in] uint32 'deviceID', [in] uint32 'pinNumber', [in] uint32 'voltage') cil managed
1>      exporting as PassThruSetProgrammingVoltage and index 11
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruReadVersion'([in] uint32 'deviceID', [out] native int 'pFirmwareVersion', [out] native int 'pDllVersion', [out] native int 'pApiVersion') cil managed
1>      exporting as PassThruReadVersion and index 12
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruGetLastError'([out] native int 'pErrorDescription') cil managed
1>      exporting as PassThruGetLastError and index 13
1>  Found method: ClassLibrary1.PassThruDeviceAPI..method public hidebysig static int32  'PassThruIoctl'([in] uint32 'channelID', [in] int32 'ioctlID', [in] native int 'pInput', [out] native int 'pOutput') cil managed
1>      exporting as PassThruIoctl and index 14
1>  VsDevCmd: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\VsDevCmd.bat
1>  lib tool via VsDevCmd: 0
1>
1>Build succeeded.
1>    0 Warning(s)
1>    0 Error(s)
1>
1>Time Elapsed 00:00:04.38
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========

After that I have a program called "DLL Export Viewer" to check that export is correct and file is generating proper function table and my list is empty: empty Similar library from 3rd party is looking like this: not_empty

What I'm doing wrong? Can you help me to fill that table with your tool?

3F commented 4 years ago

Can you show what says -pe-exp-list <module> command?

Also I hope you're using modified modules in related x86 + x64 folders instead of original in ~\bin.

gimi87 commented 4 years ago

Log:

PS D:\Documents\Visual Studio 2019\Projects\ClassLibrary1> .\DllExport.bat -pe-exp-list bin\Debug\netstandard2.1\ClassLibrary1.dll
PS D:\Documents\Visual Studio 2019\Projects\ClassLibrary1> .\DllExport.bat -pe-exp-list bin\Debug\netstandard2.1\x86\ClassLibrary1.dll
PassThruClose
PassThruConnect
PassThruDisconnect
PassThruGetLastError
PassThruIoctl
PassThruOpen
PassThruReadMsgs
PassThruReadVersion
PassThruSetProgrammingVoltage
PassThruStartMsgFilter
PassThruStartPeriodicMsg
PassThruStopMsgFilter
PassThruStopPeriodicMsg
PassThruWriteMsgs
PS D:\Documents\Visual Studio 2019\Projects\ClassLibrary1> .\DllExport.bat -pe-exp-list bin\Debug\netstandard2.1\x64\ClassLibrary1.dll
PassThruClose
PassThruConnect
PassThruDisconnect
PassThruGetLastError
PassThruIoctl
PassThruOpen
PassThruReadMsgs
PassThruReadVersion
PassThruSetProgrammingVoltage
PassThruStartMsgFilter
PassThruStartPeriodicMsg
PassThruStopMsgFilter
PassThruStopPeriodicMsg
PassThruWriteMsgs
PS D:\Documents\Visual Studio 2019\Projects\ClassLibrary1>

I'm trying to use from the x86 folder, because 32-bit is required :)

3F commented 4 years ago

@gimi87, Thanks for the details about issue!

I don't know about "DLL Export Viewer" tool, but our -pe-exp-list indicates normal export as you can also see it from your log.

Can you help me to fill that table with your tool?

You need to contact with developers of the mentioned "DLL Export Viewer" tool if some records are not displayed from the following modules:

gimi87 commented 4 years ago

I know that it is 3rd party tool. I just wanted to show that something is not properly addressed, because when I open this exported library from the application which is trying to import and use those methods I have "Access violation (c0000005)". So I guess not everything is exported correctly. Maybe I'll wait for some updates in the meantime. But thanks anyway.

3F commented 4 years ago

which is trying to import and use those methods I have "Access violation (c0000005)"

@gimi87, You can also try solution from issue #132. Or try netfx. Let me know about result.

RayKoopa commented 4 years ago

I could not get a .NET Standard 2.1 assembly to be callable from native code without access violations. Simply switching to .NET Standard 2.0 made it work, but only after I also set the following project properties, which I took from the Example repository (which is also only targeting .NET Standard 2.0):

RayKoopa commented 4 years ago

@3F I may have missed that info elsewhere, but is .NET Standard 2.1 even meant to be supported at this time? As said in my above comment, I could not get it to work, it only throws access violations when trying to call the exported functions from native code.

I can accept a sample project if you like.

3F commented 4 years ago

@RayKoopa,

Please read #132 as I mentioned above.

For today I implemented rebasing only for system objects: https://github.com/3F/DllExport/issues/125#issuecomment-561245575 Others can be considered later: https://github.com/3F/DllExport/issues/132#issuecomment-574264394

For better support and most known behavior with 1.7.0, netstandard2.0 or netcoreapp2.2 is recommended.

Follow the news!

RayKoopa commented 4 years ago

Thanks for the information! netcoreapp2.2 seems pretty fine for me right now as all I was missing in netstandard2.0 are several Span methods.