Trouble Creating COM Component

Trouble Creating COM Component

Anonymous
Not applicable
2,091 Views
7 Replies
Message 1 of 8

Trouble Creating COM Component

Anonymous
Not applicable

I am trying to work from this Through The Interface tutorial here: http://through-the-interface.typepad.com/through_the_interface/2009/05/interfacing-an-external-com-a...

 

I am trying to build the COM visible component from the article but when I do the compiler tells me it could not register my assemblly because it could not load one of my referenced dll's. If I compile the code without [assembly: ComVisible(true)] i.e. I use [assembly: ComVisible(false)] then it compiles ok.

This is the compiler error:

Error 4 Cannot register assembly "E:\Visual Studio 2013\AutoCAD\LoadableComponent\LoadableComponent\bin\Debug\LoadableComponent.dll". Could not load file or assembly 'accoremgd, Version=19.1.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified. LoadableComponent

 

The LoadableComponent.dll is created but compiler says Build Failed with the error I mentioned.

 

 

Here is the code:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using System.Runtime.InteropServices;
using System.EnterpriseServices;


namespace LoadableComponent
{
[Guid("F26BAC4F-B905-4678-B10B-BF548FAFA3AE")]
public interface INumberAddition
{
[DispId(1)]
string AddNumbers(int arg1, double arg2);
}

[ProgId("LoadableComponent.Commands"),
Guid("3EB8AB8D-1E3F-4BA6-BCD3-EC954A756C16"),
ClassInterface(ClassInterfaceType.None)]
public class Commands : ServicedComponent, INumberAddition
{
// A simple test command, just to see that commands
// are loaded properly from the assembly

[CommandMethod("MYCOMMAND")]
static public void MyCommand()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
ed.WriteMessage("\nTest command executed.");
}

// A function to add two numbers and create a
// circle of that radius. It returns a string
// with the result of the addition, just to use
// a different return type

public string AddNumbers(int arg1, double arg2)
{
// During tests it proved unreliable to rely
// on DocumentManager.MdiActiveDocument
// (which was null) so we will go from the
// HostApplicationServices' WorkingDatabase

Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;

ed.WriteMessage(
"\nAdd numbers called with {0} and {1}.",
arg1, arg2
);

// Perform our addition

double res = arg1 + arg2;

// Lock the document before we access it

DocumentLock loc = doc.LockDocument();
using (loc)
{
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
// Create our circle

Circle cir =
new Circle(
new Point3d(0, 0, 0),
new Vector3d(0, 0, 1),
res
);

cir.SetDatabaseDefaults(db);

// Add it to the current space

BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
db.CurrentSpaceId,
OpenMode.ForWrite
);
btr.AppendEntity(cir);
tr.AddNewlyCreatedDBObject(cir, true);

// Commit the transaction

tr.Commit();
}
}
// Return our string result
return res.ToString();
}
}
}

 


Can someone help me understand what I am doing wrong?

 

Like I said it compiles ok if I don't have the com visible switch set to true.

Since the LoadableComponent.dll creates I try to use it anyway and when I do I get this error when I try to communicate through COM:

Unable to cast COM object of type 'System.__ComObject' to interface type 'LoadableComponent.INumberAddition'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{F26BAC4F-B905-4678-B10B-BF548FAFA3AE}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

 

BTW I have NETLOAD'ed the dll in AutoCAD.

 

Here is the code from my .net app. I simple have a blank form with a button. Here is the code found under the button. I have commented below where the code is failing,

 

private void button1_Click(object sender, EventArgs e)
{
const string progID = "AutoCAD.Application.19.1";

AcadApplication acApp = null;
try
{
acApp =
(AcadApplication)Marshal.GetActiveObject(progID);
}
catch
{
try
{
Type acType =
Type.GetTypeFromProgID(progID);
acApp =
(AcadApplication)Activator.CreateInstance(
acType,
true
);
}
catch
{
MessageBox.Show(
"Cannot create object of type \"" +
progID + "\""
);
}
}
if (acApp != null)
{
try
{
acApp.Visible = true;

INumberAddition app =
(INumberAddition)acApp.GetInterfaceObject(
"LoadableComponent.Commands"
);

//====here is where I get the error====

string res = app.AddNumbers(5, 6.3);

acApp.ZoomAll();

MessageBox.Show(
this,
"AddNumbers returned: " + res
);
}
catch (Exception ex)
{
MessageBox.Show(
this,
"Problem executing component: " +
ex.Message
);
}
}
}

 

0 Likes
2,092 Views
7 Replies
Replies (7)
Message 2 of 8

FRFR1426
Collaborator
Collaborator

Look at the comments of the post:

 

Register for COM interop in the AutoCAD add-in module works only when the output directory is set to AutoCAD directory. Regasm throws "Cant find acdbmgd.dll" if its in its own bin directory

 

So change your VS output dir to the AutoCAD install dir.

Maxence DELANNOY
Manager
Add-ins development for Autodesk software products
http://wiip.fr
0 Likes
Message 3 of 8

Anonymous
Not applicable

Yes I saw that but I was able to run regasm with no issues so I didn't think it would apply.

 

I have now moved the output directory to the autocad install directory but I still recieve the same error message.

 

Error 4 Cannot register assembly "C:\Program Files\Autodesk\Autodesk AutoCAD Map 3D 2014\LoadableComponent.dll". Could not load file or assembly 'accoremgd, Version=19.1.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified. LoadableComponent

0 Likes
Message 4 of 8

FRFR1426
Collaborator
Collaborator

May be it's an issue with the version of the .NET framework. or the target platform AnyCPU/x86/x64. Uncheck "Register for com interop" and use a post build event to register with RegAsm. Use the .NET 4.0 version of regasm:

 

C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe "$(TargetPath)"

 

Maxence DELANNOY
Manager
Add-ins development for Autodesk software products
http://wiip.fr
0 Likes
Message 5 of 8

Anonymous
Not applicable

Well done, those suggestions seem to have the component building and registering now.

 

To get it to work I had to make the AutoCAD installation directory the output directory and I had to untick "Register for COM Interop" flag in the Debug tab of the project's properties.

 

Lastly I had to modify the Post Build Event to use the 64bit version of RegAsm.exe:

POST BUILD EVENT - C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe "$(TargetPath)"

 

However when I Netload the component and try to access the interface I recieve an error:

Unable to cast COM object of type 'System.__ComObject' to interface type 'LoadableComponent.INumberAddition'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{F26BAC4F-B905-4678-B10B-BF548FAFA3AE}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

 

The Guid in the error (F26BAC4F-B905-4678-B10B-BF548FAFA3AE) is the same as the Guid Attribute for the Interface in the dll that was built and registered and then netloaded. So AutoCAD sees the interface in the component but doesn't know what to do with it?

 

Any ideas on what could be happening?

0 Likes
Message 6 of 8

Anonymous
Not applicable

Kean made note in the article that the "Register for Com interop" flag needs to be ticked to get the calling application to be able to use the interface:

 

One thing to mention - I found that the calling application was not able to to cast the returned System.__COMObject to LoadableComponent.INumberAddition unless I updated the project settings to "Register from COM Interop" (near the bottom of the Build tab).

 

Any thoughts? 

 

I suspect that the compiler is using the 32 bit version of Regasm.exe when the "Register for Com interop" flag is ticked. 

 

0 Likes
Message 7 of 8

FRFR1426
Collaborator
Collaborator

RegAsm only register in process COM server. You have to delete the InprocServer32 key in the registry and replace it with a LocalServer32 key for using it as an out-of-process server.

 

Add this two methods to your class:

 

[EditorBrowsable(EditorBrowsableState.Never)]
[ComRegisterFunction]
public static void Register(Type t)
{
    try
    {
        COMHelper.RegasmRegisterLocalServer(t);
    }
    catch (Exception ex)
    {
        Trace.WriteLine(ex.Message);
        throw;
    }
}

[EditorBrowsable(EditorBrowsableState.Never)]
[ComUnregisterFunction]
public static void Unregister(Type t)
{
    try
    {
        COMHelper.RegasmUnregisterLocalServer(t);
    }
    catch (Exception ex)
    {
        Trace.WriteLine(ex.Message);
        throw;
    }
}

 and this class:

 

using System;
using Microsoft.Win32;
using System.Reflection;

internal class COMHelper
{
    /// <summary>
    /// Register the component as a local server.
    /// </summary>
    /// <param name="t"></param>
    public static void RegasmRegisterLocalServer(Type t)
    {
        GuardNullType(t, "t");  // Check the argument

        // Open the CLSID key of the component.
        string path = @"CLSID\" + t.GUID.ToString("B");
        using (RegistryKey keyCLSID = Registry.ClassesRoot.OpenSubKey(path, true))
        {
            if (null == keyCLSID) 
                throw new ApplicationException(string.Format(
                    "Can not open the registry key {0}", path));

            // Remove the auto-generated InprocServer32 key after registration
            // (REGASM puts it there but we are going out-of-proc).
            keyCLSID.DeleteSubKeyTree("InprocServer32");

            // Create "LocalServer32" under the CLSID key
            using (RegistryKey subkey = keyCLSID.CreateSubKey("LocalServer32"))
            {
                if (null == subkey) 
                    throw new ApplicationException(
                        "Can not create the registry key LocalServer32");
                subkey.SetValue("", Assembly.GetExecutingAssembly().Location,
                    RegistryValueKind.String);
            }
        }
    }

    /// <summary>
    /// Unregister the component.
    /// </summary>
    /// <param name="t"></param>
    public static void RegasmUnregisterLocalServer(Type t)
    {
        GuardNullType(t, "t");  // Check the argument

        // Delete the CLSID key of the component
        Registry.ClassesRoot.DeleteSubKeyTree(@"CLSID\" + t.GUID.ToString("B"));
    }

    private static void GuardNullType(Type t, String param)
    {
        if (null == t)
            throw new ArgumentNullException(param);
    }
}

 I don't remember exactly where I've found this code, but it comes from Microsoft.

Maxence DELANNOY
Manager
Add-ins development for Autodesk software products
http://wiip.fr
0 Likes
Message 8 of 8

FRFR1426
Collaborator
Collaborator

The Microsoft code sample can be found here: http://support.microsoft.com/kb/977996

Maxence DELANNOY
Manager
Add-ins development for Autodesk software products
http://wiip.fr
0 Likes