Normal Ol’ DLLs from VB.net

Filed under .NET, Arcade, Code Garage, MSBuild, VB Feng Shui

Every once in a while, I find a need to do something a bit off the wall. Recently, I had another one of those situations.

I’ve spent a lot of time working with some of the old arcade emulators that are floating around (the most famous of which is MAME, or Multi Arcade Machine Emulator).

Mame itself is pretty utilitarian, so there are a number of front ends  that are essentially menuing systems to provide a user with an easy to browse interface for selecting games to play and among the more popular front ends is MaLa.

image

MaLa Main screen (using one of many available skins) showing list of games, and a screenshot of the selected game

One nice aspect of MaLa is that it supports plugins, and there are a number of them out there, to control LED lights, play speech, etc.

I had had a few ideas about possible MaLa plugins for awhile, but the MaLa plugin architecture centers around creating a standard Win32 DLL with old fashioned C styled Entrypoints, and, well, I kinda like working in VB.net these days.

Gone Hunting

Eventually, curiousity got the better of me, and I started looking for ways to expose standard DLL entry points from a .net assembly. I ended up finded Sevin’s CodeProject entry called ExportDLL that allowed just that. Essentially, it works by:

  1. You add a reference in your project to a DLL he created, that only contains a single Attribute for marking the functions you want to export.
  2. Create the functions you want to export as shared functions in a MODULE
  3. You mark those functions with the Attribute
  4. You compile your DLL
  5. You then run ExportDLL against your freshly compiled DLL
  6. ExportDLL then decompiles your DLL into IL, tweaks it, and recompiles the IL code back into a DLL

It sounds complicated but it’s really not.

I set it all up and had things working in about 30 minutes.

Gone South

Unfortunately, all was not quite right. MaLa requires 2 entry points (among a host of them) defined with a single integer argument passed on the stack. Pretty simple stuff. So I coded up:

    <ExportDLL("MaLaOrientationSwitch", CallingConvention.Cdecl)> _
    Public Shared Sub EntryPoint_MaLaOrientationSwitch(ByVal Orientation As Integer)

But when I ran the DLL within MaLa, it crashed immediately after calling this function, even with NO CODE in the function itself.

What this meant is that something about the export process was trashing the stack. I spent a solid day hunting for clues as to what might be failing. I did find that eliminating the argument from the exposed entrypoint allowed MaLa to work properly AND call my entrypoint, but, being unable to get the passed Orientation value, the call was basically useless.

Gone Around Again

In digging through it all, I happened to notice a comment on the CodeProject page for Sevin’s article pointing to a similar library by Robert Giesecke. I’m not sure if the two were developed independently or not, but Robert’s is certainly a more polished set of deliverables. He even went so far as to put together a C# project template that makes it ridiculously easy to kick off a project using his technique.

It turns out, not only is Robert’s approach cleaner, it actually properly exports the MaLaOrientationSwitch function above with no problems. MaLa can call it, pass in the argument and all is good.

Gone Fishing

One big difference between the two techniques is the Robert actually defines an MSBuild targets file to patch his DLL directy into the Visual Studio build process. Very cool! But, his build step happens AFTER the PostBuildEvent target, and it was in that target that I’d setup some commands to copy the DLL into a file called *.MPLUGIN, which is what MaLa specifically looks for. Hooking that process into the build itself makes debugging things quite natural, but, Mr. Giesecke’s target wasn’t allowing for that.

Here’s Robert’s targets file:

<Project
  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="RGiesecke.DllExport.MSBuild.DllExportTask"
             AssemblyFile="RGiesecke.DllExport.MSBuild.dll"/>
  <Target Name="AfterBuild"
          DependsOnTargets="GetFrameworkPaths"
          >
      <DllExportTask Platform="$(Platform)"
                   PlatformTarget="$(PlatformTarget)"
                   CpuType="$(CpuType)"
                   EmitDebugSymbols="$(DebugSymbols)"
                   DllExportAttributeAssemblyName="$(DllExportAttributeAssemblyName)"
                   DllExportAttributeFullName="$(DllExportAttributeFullName)"
                   Timeout="$(DllExportTimeout)"
                   KeyContainer="$(KeyContainerName)$(AssemblyKeyContainerName)"
                   KeyFile="$(KeyOriginatorFile)"
                   ProjectDirectory="$(MSBuildProjectDirectory)"
                   InputFileName="$(TargetPath)"
                   FrameworkPath="$(TargetedFrameworkDir);$(TargetFrameworkDirectory)"
                   LibToolPath="$(DevEnvDir)\..\..\VC\bin"
                   LibToolDllPath="$(DevEnvDir)"
                   SdkPath="$(FrameworkSDKDir)"/>
  </Target>
</Project>

I’d worked with MSBuild scripts before, so I knew what it was capable of, I just couldn’t remember the exact syntax. A few google searches jogged my memory, and I ended up here at a great post describing exactly how you can precisely inject your own targets before or after certain other predefined targets.

I modified Robert’s targets file and came up with this:

<Project
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <UsingTask TaskName="RGiesecke.DllExport.MSBuild.DllExportTask"
             AssemblyFile="RGiesecke.DllExport.MSBuild.dll"/>

  <!-- Add to the PostBuildEventDependsOn group to force the ExportDLLPoints
       target to run BEFORE any post build steps (cause it really should) -->
  <PropertyGroup>
    <PostBuildEventDependsOn>
      $(PostBuildEventDependsOn);
      ExportDLLPoints
    </PostBuildEventDependsOn>
  </PropertyGroup>

  
  <Target Name="ExportDLLPoints"
          DependsOnTargets="GetFrameworkPaths"
          >
    <DllExportTask Platform="$(Platform)"
                   PlatformTarget="$(PlatformTarget)"
                   CpuType="$(CpuType)"
                   EmitDebugSymbols="$(DebugSymbols)"
                   DllExportAttributeAssemblyName="$(DllExportAttributeAssemblyName)"
                   DllExportAttributeFullName="$(DllExportAttributeFullName)"
                   Timeout="$(DllExportTimeout)"
                   KeyContainer="$(KeyContainerName)$(AssemblyKeyContainerName)"
                   KeyFile="$(KeyOriginatorFile)"
                   ProjectDirectory="$(MSBuildProjectDirectory)"
                   InputFileName="$(TargetPath)"
                   FrameworkPath="$(TargetedFrameworkDir);$(TargetFrameworkDirectory)"
                   LibToolPath="$(DevEnvDir)\..\..\VC\bin"
                   LibToolDllPath="$(DevEnvDir)"
                   SdkPath="$(FrameworkSDKDir)"/>
  </Target>
</Project>

Now, I can perform the compile, and execute my postbuild event to copy the DLL over to the MaLa Plugins folder and give it the requisite MPLUGIN name, all completely automatically.

And, the icing on the cake is that I can build a MaLa plugin completely in VB.net, with no C or C# forwarding wrapper layer and with a fantastic XCOPY-able single DLL application footprint (save for the .net runtime, of course<g>).

It’s a wonderful thing.

7 Comments

  1. Robert Giesecke says:

    Hi Darin,
    I never use post/pre build events (I put msbuild targets in my project/solution and use those), so I didn’t know that my way of doing it had negative side effects.
    Thx for letting me know, I implemented it in my newest version that can be found via nuget ( UnmanagedExports )

  2. Darin says:

    Hi Robert
    Thanks for the comment, (and for the fantastic DLLExports library).

    Yeah, It’s a minor thing. Not even sure I’d call it a “negative side effect” as much as it’s just something that’s easy to overlook if you’re not actually using the feature. I think a lot of people use Pre Post build steps because they’re so much easier to get your head around than mucking with actual MSBuild targets and such. I can do both now (after much hand wringing), but I still tend toward Pre/Post build steps if possible, just for that reason.

    However, Targets present a much cleaner functionality.

    Thanks for implementing this in your latest update!

  3. Howard Taylor says:

    I too have seen the article by Robert where he Exports code. It is my understanding that following his process will allow me to create a function and then use it as a dll. I have been unable to get it to work. What I am trying to do is create a c# function in a dll then use that dll in PowerBuilder. The program errors without revealing the true error BUT I got a clue when I attempted to Register the dll on my windows machine. The error complained that no DLLEntry Point could be found. Is Robert’s code not intended to do what I am trying to do? I have been pulling my hair out trying to create a dll in .net that PowerBuilder can use. I have also tried this with C++ but no worky. Can you help me out with some code or point me to an article that explains how to do this? Thank you.

    • Robert Giesecke says:

      Howard,
      can you post that question somewhere like stackoverflow?
      Including the method that you want to export and how you import it on the native side.
      I only happened to notice your comment because I seemed to have checked email notifications when I wrote the earlier comment.

    • Darin says:

      It sounds like the exportdll.exe post process isn’t being run on the resultant compiled DLL. That’s the first place I’d check.

      You can also just compile youre DLL project and run the ExportDLL process on it, then check the result of that to be sure that the entrypoints were recognized and processed properly.

      You can do that with depends.exe (the windows dependency walker, a VERY handy tool if you’re doing things like this).

      Also, Mr Giesecke makes a good point below about posting to Stackoverflow. They have a little bigger audience than me 🙂

Post a Reply to Darin

Your email is never published nor shared. Required fields are marked *

*
*