.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Register DLL for demand load from a console application

15 REPLIES 15
Reply
Message 1 of 16
miguelmachadoecosta
1917 Views, 15 Replies

Register DLL for demand load from a console application

This is my first application based only in VB.NET, and I'm trying to replicate what I did for previous VBA applications, for which I had a console application that placed the dvb and acaddoc.lsp files in a specific folder so that the commands would be available without any action need from the user other than running that console application.

 

First, as suggested in AutoCAD .NET Developer's Guide, I placed this procedure inside the DLL:

 

Imports Microsoft.Win32
Imports System.Reflection
Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.DatabaseServices

<CommandMethod("RegisterMyApp")>
    Public Sub RegisterMyApp()
        '' Get the AutoCAD Applications key
        Dim sProdKey As String = HostApplicationServices.Current.UserRegistryProductRootKey
        Dim sAppName As String = "MyApp"
        Dim regAcadProdKey As Autodesk.AutoCAD.Runtime.RegistryKey = Autodesk.AutoCAD.Runtime.Registry.CurrentUser.OpenSubKey(sProdKey)
        Dim regAcadAppKey As Autodesk.AutoCAD.Runtime.RegistryKey = regAcadProdKey.OpenSubKey("Applications", True)
        '' Check to see if the "MyApp" key exists
        Dim subKeys() As String = regAcadAppKey.GetSubKeyNames()
        For Each sSubKey As String In subKeys
            '' If the application is already registered, exit
            If (sSubKey.Equals(sAppName)) Then
                regAcadAppKey.Close()
                Exit Sub
            End If
        Next
        '' Get the location of this module
        Dim sAssemblyPath As String = Assembly.GetExecutingAssembly().Location
        '' Register the application
        Dim regAppAddInKey As Autodesk.AutoCAD.Runtime.RegistryKey = regAcadAppKey.CreateSubKey(sAppName)
        regAppAddInKey.SetValue("DESCRIPTION", sAppName, RegistryValueKind.String)
        regAppAddInKey.SetValue("LOADCTRLS", 14, RegistryValueKind.DWord)
        regAppAddInKey.SetValue("LOADER", sAssemblyPath, RegistryValueKind.String)
        regAppAddInKey.SetValue("MANAGED", 1, RegistryValueKind.DWord)
        regAcadAppKey.Close()
    End 

This way I can load the DLL with NETLOAD and, after running the procedure, for the next time it loads automatically. So far the only way I found to do the same automatically from the console application is with the following procedure:

 

 

Private Sub RegisterMyApp()
        On Error Resume Next
        Dim acadApp As Object
        Dim i As Integer, version As String
        For i = 0 To 4
            Select Case i
                Case 0 : version = "19.1"
                Case 1 : version = "20"
                Case 2 : version = "20.1"
                Case 3 : version = "21"
                Case Else : version = "22"
            End Select

            acadApp = CreateObject("AutoCAD.Application." & version)
            acadApp.ActiveDocument.SendCommand("(command ""NETLOAD""" &
                Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData).Replace("\", "/") & "/<path>.dll""" & ")" & vbCr)
            acadApp.ActiveDocument.SendCommand("(command ""REGISTERMYAPP""" & ")" & vbCr)

            acadApp.Quit()
        Next
    End Sub

It works, but I know that using SendCommand in the middle of a running code is not a good approach, and in this case AutoCAD remains opened, even after acadApp.Quit(), which is not intended. I suppose that's because it is still running the SendCommand at that time.

 

Is it possible to have the first "RegisterMyApp" procedure inside the console application, inside the loop, registering the DLL for all AutoCAD versions available (without the need of loading the DLL at that time and suppressing both SendCommands)? I didn't find how to do it yet because the procedure seem to require AutoCAD's references and I suppose I cannot have then on the console application.

 

Thanks in advance.

 

 

15 REPLIES 15
Message 2 of 16

An alternative to think about.  Pull the auto-register from the main code entirely.  Instead, create the registry key manually and export it as a REG file (or grab it from your testing results, which should have created the key for you).  When you need to set up a computer, just merge the REG file and the key will be created for you; this can be done via an external EXE or through a BAT script.

----------------------------------
If you are going to fly by the seat of your pants, expect friction burns.
"I don't know" is the beginning of knowledge, not the end.


Message 3 of 16

As I understand the autoloading mechanism, its just entries in reg under Application for each version like:

HKEY_CURRENT_USER\SOFTWARE\Autodesk\AutoCAD\R20.1\ACAD-F001:409\Applications

You don't need to involve autocad to do whatever you need.

You can read the registry to see what versions are installed, then decide which ones you want to set autoload for.

The "R20.1\ACAD-F001:409" part will be different for each version, and vertical.

I am aware of 3 variations for the "ACAD-F001:409" part, from autocad, map3d, and civil3d. There might be more, but the registry has whatever is installed.

So you can use regular .net methods to read the reg, compare the info you got to some list of versions for the R20.1 part, then set the reg entries.

I think you just got trapped in old ways of setting settings, but step back and pretend you cannot do it in a session, and it actually becomes simpler.

 


internal protected virtual unsafe Human() : mostlyHarmless
I'm just here for the Shelties

Message 4 of 16
JamesMaeding
in reply to: JamesMaeding

btw, here is my C# helper classes for registry, to give you ideas.

One idea is to move to C# Smiley Happy, since most of the experienced people here use it if they have the choice.

 

 


internal protected virtual unsafe Human() : mostlyHarmless
I'm just here for the Shelties

Message 5 of 16

Thank you both for ending up giding me towards the solution, specially for explaning where the registry is modified.

 

It now works as I intended. The solution was to keep the code from AutoCAD .NET Developer's Guide with the only difference that this line, which needs AutoCAD's reference:

 

Dim sProdKey As String = HostApplicationServices.Current.RegistryProductRootKey

is replaced by:

 

 

'Start by getting the CurrentUser location
Dim hive As RegistryKey = Registry.CurrentUser
'Open the main AutoCAD key
Dim ack As RegistryKey = hive.OpenSubKey("Software\\Autodesk\\AutoCAD")
'Get the current major version And its key
Dim ver As String = ack.GetValue("CurVer")
Dim verk As RegistryKey = ack.OpenSubKey(ver)
'Get the vertical/language version And its key
Dim lng As String = verk.GetValue("CurVer")
Dim lngk As RegistryKey = verk.OpenSubKey(lng)
'And finally return the path to the key, without the hive prefix
Dim sProdKey As String = lngk.Name.Substring(hive.Name.Length + 1)

I adapted this last part from: http://www.keanw.com/2010/02/creating-demand-loading-entries-for-net-modules-outside-of-autocad.html

 

So, my final procedure for registering in the console application is:

 

Private Sub RegisterMyApp()
        On Error Resume Next
        Dim acadApp As Object
        Dim i As Integer, version As String
        For i = 0 To 4
            Select Case i
                Case 0 : version = "19.1"
                Case 1 : version = "20"
                Case 2 : version = "20.1"
                Case 3 : version = "21"
                Case Else : version = "22"
            End Select
            acadApp = CreateObject("AutoCAD.Application." & version)

            'Start by getting the CurrentUser location
            Dim hive As RegistryKey = Registry.CurrentUser
            'Open the main AutoCAD key
            Dim ack As RegistryKey = hive.OpenSubKey("Software\\Autodesk\\AutoCAD")
            'Get the current major version And its key
            Dim ver As String = ack.GetValue("CurVer")
            Dim verk As RegistryKey = ack.OpenSubKey(ver)
            'Get the vertical/language version And its key
            Dim lng As String = verk.GetValue("CurVer")
            Dim lngk As RegistryKey = verk.OpenSubKey(lng)
            'And finally return the path to the key, without the hive prefix
            Dim sProdKey As String = lngk.Name.Substring(hive.Name.Length + 1)

            Dim sAppName As String = "MyApp"
            Dim regAcadProdKey As RegistryKey = Registry.CurrentUser.OpenSubKey(sProdKey)
            Dim regAcadAppKey As RegistryKey = regAcadProdKey.OpenSubKey("Applications", True)
            '' Check to see if the "MyApp" key exists
            Dim subKeys() As String = regAcadAppKey.GetSubKeyNames()
            For Each sSubKey As String In subKeys
                '' If the application is already registered, exit
                If (sSubKey.Equals(sAppName)) Then
                    regAcadAppKey.Close()
                    Exit Sub
                End If
            Next
            '' Get the location of this module
            Dim sAssemblyPath As String = <path for the dll>
            '' Register the application
            Dim regAppAddInKey As RegistryKey = regAcadAppKey.CreateSubKey(sAppName)
            regAppAddInKey.SetValue("DESCRIPTION", sAppName, RegistryValueKind.String)
            regAppAddInKey.SetValue("LOADCTRLS", 14, RegistryValueKind.DWord)
            regAppAddInKey.SetValue("LOADER", sAssemblyPath, RegistryValueKind.String)
            regAppAddInKey.SetValue("MANAGED", 1, RegistryValueKind.DWord)
            regAcadAppKey.Close()
            acadApp.Quit()
        Next
    End Sub

For unregistering I also only had to replace that same only line.

 

 

P.S.- Don't mind the "Exit Sub" line, which obviously needs to be replaced.

 

Message 6 of 16

@miguelmachadoecosta

Can you convert that to C# for me? (a joke...don't do it...)

Your approach hardcodes the dll location, right?

I personally do not like the whole registry driven autoload thing because the average user will not touch the registry if they decide they don't want some tool. Its magic to them.

IMO, the right way to provide tools is have something:

1) add a suppport path to the tool location

2) load a menu with pulldown or ribbon/toolbar to run the tool, and have the menu load the dll if needed.

That something can be a person doing all by hand, like a cad manager for whole company, or running some lisp supplied with the tool that maybe each user runs. Either way, that approach is super easy to troubleshoot and fix by any user. The reg method is really hidden, and I remove those entries if anyone adds them.

Autodesk wants them to be magic though, like "apps" on a phone somewhere.

 

 

 


internal protected virtual unsafe Human() : mostlyHarmless
I'm just here for the Shelties

Message 7 of 16

@JamesMaeding

 

Thank you for your post. About C#, if I did the C#->VB.NET conversion I mentioned above, surely the more experienced C# users can do the oposite right 🙂

 

I noted your suggestion of moving to C#, but for someone who started with VBA and is giving his first steps in VB.NET, C# looks too messy (altough it isn't actually that different, which made possible for me to translate that portion of code to VB.NET). It's something that I'll consider in the future.

 

I tried to mimic what I used to do with VBA, which was a exe form with an "install" and an "uninstall" buttons. The install one gets all the most recent files from a Google Drive service account and places them inside a folder it creates inside Autodesk's AppData folder:

 

Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\Autodesk\Macros"

 

About your question above, the dll is in this folder and that would be the path in the code above.

 

I think your toolbar suggestion might be a good one because there something where this approach fails for VB.NET in comparison with the same approach in VBA:

 

In the case of VBA the exe places inside the folder a acaddoc.lsp file (and adds it to the support paths) with all the commands and the DVB file is only loaded when the first command is used. As such, when the user is working on someting else the application's events aren't potentially slowing down other commands.

 

By automatically loading a dll, if there are events related with that application they would be always active. So a toolbar which loads and unload the dll might be a better alternative. I'll see if I can do it, since I've never done it before.

 

But the behaviour I prefer would actually be what I have with VBA... No toolbars and no action needed everytime someone needs to use the application. It just loads with the first command.

Message 8 of 16

Would it be possible to have the acaddoc.lsp file loading the dll when the first command is executed and then proceed with that command? Something like this:

(defun c:<command1> ()
  (command "NETLOAD" ..)
  (command "<command1>")
  (princ))

(defun c:<command2> ()
  (command "NETLOAD" ..)
  (command "<command2>")
  (princ))

I wouldn't like to create some problem by executing NETLOAD everytime a command were to be executed, and also I wouldn't like to create some kind of conflict between the command in the acaddoc.lsp file and the command in the dll after being loaded (neither duplicating the commands would be interesting).

Message 9 of 16

If you have access to acaddoc.lsp, just have it netload your tool in the s::startup function.

If you don't, they typical easy way is to make a menu and a .mnl (menu lisp) by same name. Then put netload code in the .mnl.

I don't think your idea of command within a command will pan out. The dll likely overwrites the c:function but it might work. Seems pointless though, and you must define one of those for each command in the dll, what a pain since my dlls have 60 or more commands.


internal protected virtual unsafe Human() : mostlyHarmless
I'm just here for the Shelties

Message 10 of 16

Menus it's another thing I'm now familiared with...

 

I just tried the following and it works, even calling multiple times:

(defun c:TEST ()
  (command "netload" (strcat (getenv "appdata") "\\Autodesk\\Macros\\<name>.dll"))
  (command "TEST")
  (princ))

What's the alternative of defining every command in the dll? I would prefer to just call name of the procedure as I did with VBA. The commands would be only in the acaddoc.lsp and each user could change them if they liked. I thought this was the only way to call the procedures in the dll.

 

Message 11 of 16

@miguelmachadoecosta

Why bother though? If you have access to acaddoc.lsp, just throw in the netload somewhere in s::startup.

Your dll should not do anything from just being loaded (by design).

I'm not saying the dll cannot do anything from just loading, as you can make it if you want to.

This idea of delayed loading is not that useful now days. The demand loading done by the reg is simply a practical way to load a prog when there is no access to startup routine or no menu. It involved reg edits though, and that is heavy handed IMO, as users have a hard time getting rid of a tool if not wanted for a bit, maybe when troubleshooting.


internal protected virtual unsafe Human() : mostlyHarmless
I'm just here for the Shelties

Message 12 of 16

@JamesMaeding

The reason for the delayed loading is the one I mentioned earlier. The largest tool I made, which I intend to convert from VBA to VB.NET has a lot of EndCommand events. For example, after each STRETCH it makes some checks and if that's the case, it updates other objects. If that tool was to be loaded at startup those events would be always firing even if the tool was not being used, right? And all STRETCH commands (and others) would be delayed unnecessarily (even if it's only a fraction of a second).

Message 13 of 16

@miguelmachadoecosta

No, there is no reason event driven items must be hooked up right when loaded.

I have many tools that hook to events then unhook for various reasons and at various times.

You may want to hook things at load time, I'm just saying you don't have to, and generally should give the user the choice.

There may be other ways, but my understanding is you will make your main program class inherit  IExtensionApplication. That will have an Initialize method you must implement, and you can do whatever you want for startup in there. You might just do the hookups always, or check settings stored somewhere to decide if you hook or not.

I'm starting to wonder if maybe .net is new enough you are not sure of its patterns of use yet. I'm no Activist_Investor, but have seen enough to know I am somewhat aware of typical ways of ding things in .net. So much to learn though, love it!

 


internal protected virtual unsafe Human() : mostlyHarmless
I'm just here for the Shelties

Message 14 of 16

I was not aware of the possibility of hooking/unhooking events (I imagine there no such possibility in VBA but there is in .NET). In .NET it's all too new for me. I'll definitely take a look at that when I create my first event in .NET. Thank you.

 

Meanwhile I would always have the option of defining a single command in the acaddoc.lsp for loading the dll (the user wouldn't have to look for it) and leave all the other commands in the dll only.

Message 15 of 16

nah, just load the dll on startup.

You don't want some extra command to load things, and if you do, it kind of implies you did something wrong.

It totally depends what you dll does though. Mine are general utils and design tools, not like user monitoring/watching utils. I avoid events as much as possible, but the ones I hook to like drawing activated, I use all the time.

I've done some dynamic update tools too for labeling, and they run stable.

My experiments with transient entities (sort of temp entities) have gone south sometimes, but I know how to deal with now.

.net is a bigger landscape than VBA/COM. You can open a transaction and add 5000 lines instantly, while VBA had to do 5000 transactions, so took time. It might look like vba a bit at first, but you quickly find its much different.


internal protected virtual unsafe Human() : mostlyHarmless
I'm just here for the Shelties

Message 16 of 16

It's not easy to know now the way to go without knowing first hand how I would implement the events, but all the insight you gave will be very useful.

 

The kind of events I'm talking about are:

At some point in time one polyline, one mtext and one table are "linked" through their handles. The mtext and the table have values (more than one) which depend on the polyline's length. The user increases the polyline's length. There is a command to update the values on the mtext and on the table, but in order to avoid manually executing than command (one extra step) there's one EndCommand based event that is triggered and updates the values.

Also, if the user copies the polyline, the mtext and the table with the COPY command he will what the new objects to remain "linked" together, so the handles stored as xdata are automatically updated. That's another EndCommand event.

 

One other thing I'll have to consider related with loading the dll is something I implemented in my VBA based tools (which also have some VB.NET bits). The first time a tool's command is executed it checks if there is a newer version of the dvb/dll in the Google Drive service account. I think that if the dll is already loaded it cannot be replaced. And doing that check every time AutoCAD  starts could be unnecessary if the tool is not going to be used.

 

The faster execution is one of the reasons why I'm considering migrating these tools to .NET. For now I'm just doing a new unrelated simpler tool, before getting my hands on all the more complex things I did before. It's almost completed and allowed me to understand the basics of .NET.

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Forma Design Contest


Autodesk Design & Make Report