Does anyone here know what is wrong with my code here?

I am trying to write a c# library for use in VB6. I think I have tried just about everything on google and still continue to get the message "ActiveX component can't create object". I am a total VB6 noob but I'd say I'm at least intermediate with c#.

Heres what I tried:

1. Set the make assembly COM-Visible option in the project properties
2. Checked 'register for com interop' in the build output
3. Ran regasm with and without /codebase on the dll file, and made a tlb file which I then referenced in the vb6 project
4. Ran regsvr32 for laughs - DLLRegisterServer entry point or some shennanigans came up in a message box.
5.Added GUIDs, a interface, and a wrapper class to my c# library so that I could build my object without any parameters in its constructor.

This totally sucks! I told my boss that it would be faster for me to write this library in .net then com interop it after into our vb6 application. It turns out it would have probably been faster for me to just learn how to write it in vb6. Also...the .Net serialport class is...well...a bit retarded but thats a different post altogether.

Here's the code, or what I think is important anyway (c#):

Inside AssemblyInfo.cs

// Setting ComVisible to false makes the types in this assembly not visible 
// to COM components.  If you need to access a type in this assembly from 
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(true)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("8091deeb-7dc8-4306-8280-e323c628df02")]
using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Ports;
using System.Threading;
using System.Runtime.InteropServices;

namespace MobileSeeLevel828
{
   // define GUID as 
    [Guid("74A83A1A-D511-4160-AAD8-E62456A9BE35")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface iInterface
    {
        [DispId(1)]
        int GetRefreshRate();
        [DispId(2)]
        void SetRefreshRate(int iRate);
        [DispId(3)]
        void Init();
        [DispId(4)]
        void Kill();
        [DispId(5)]
        int GetActiveChannels();
        [DispId(6)]
        void CTOR(string sPort, int iRate);
        [DispId(7)]
        double GetChannel(int iChannel);
        [DispId(8)]
        string GetChannelString(int iChannel);
        [DispId(9)]
        string[] GetComPorts();
    }
    [Guid("544FC4F8-58CD-4952-B902-6BC58A239A13")]
    [ClassInterface(ClassInterfaceType.None)]
    [ProgId("ComInterOp_SeeLevel828_Utility")]
    public class ComInterOp_SeeLevel828_Utility:iInterface
    {
        SeeLevel828_Utility seeLevel;
        #region iInterface Members
        public string[] GetComPorts()
        {
            try
            {
                return SeeLevel828_Utility.GetComPorts();
            }
            catch
            {
                return null;
            }
        }
        public int GetRefreshRate()
        {
            try
            {
                return seeLevel.RefreshRate;
            }
            catch 
            {
                return 0;
            }
        }
        public string GetChannelString(int iChannel)
        {
            try
            {
                return seeLevel.GetChannelString(iChannel);
            }
            catch
            {
                return "INTERNAL_ERROR";
            }
        }
        public double GetChannel(int iChannel)
        {
            try
            {
                return seeLevel.GetChannel(iChannel);
            }
            catch
            {
                return -3;
            }
        }
        public void SetRefreshRate(int iRate)
        {
            try
            {
                seeLevel.RefreshRate = iRate;
            }
            catch 
            {
                seeLevel.RefreshRate = 0;
            }
        }
        public void CTOR(string sPortName, int iRefreshRate)
        {
            seeLevel = new SeeLevel828_Utility(sPortName, iRefreshRate);
        }
        public ComInterOp_SeeLevel828_Utility()
        {
        }
        public void Init()
        {
            seeLevel.Initialize();
        }
        public void Kill()
        {
            seeLevel.Kill();
        }
        public int GetActiveChannels()
        {
            return seeLevel.ActiveChannels;
        }
        #endregion
    }
    /// <summary>
    /// A utility for interfacing with the SeeLevel828 Serial Interface
    /// </summary>
    [Guid("5982703E-3F5C-44E9-B627-0D94844F0528")]
    [ClassInterface(ClassInterfaceType.None)]
    [ProgId("ComInterOp_SeeLevel828_Utility")]
    public class SeeLevel828_Utility
    {
        const char CR = '\r';
        #region Private members
        SerialPort serialPort;
        string sDataBuff = "";
        #endregion
        #region Public properties, enums and methods
        /// <summary>
        /// How often the utility polls for new data on the com port
        /// </summary>
        public int RefreshRate
        {
            get;
            set;
        }
        /// <summary>
        /// The current state of the utility
        /// </summary>
        public SeeLevel828_Utility_State CurrentState
        {
            get;
            private set;
        }
        /// <summary>
        /// Number of channels active in the most recent poll
        /// </summary>
        public int ActiveChannels
        {
            get;
            private set;
        }
        /// <summary>
        /// Enum for the state of the utility
        /// </summary>
        public enum SeeLevel828_Utility_State
        {
            /// <summary>
            /// The utility has never been initialized
            /// </summary>
            Inactive,
            /// <summary>
            /// The utility is active and polling
            /// </summary>
            Active,
            /// <summary>
            /// The utility was active and polling, but has been closed
            /// </summary>
            Closed
        }
        /// <summary>
        /// Build the SeeLevel serial interface with a custom serial port
        /// </summary>
        /// <param name="_serialPort">.Net serial port class to use</param>
        public SeeLevel828_Utility(SerialPort _serialPort, int iRefreshRate)
        {
            RefreshRate = iRefreshRate;
            CurrentState = SeeLevel828_Utility_State.Inactive;
            serialPort = _serialPort;
        }
        /// <summary>
        /// Build the default SeeLevel serial interface with the given port name
        /// </summary>
        /// <param name="sPortName">Port to use (eg COM1)</param>
        public SeeLevel828_Utility(string sPortName, int iRefreshRate)
        {
            RefreshRate = iRefreshRate;
            CurrentState = SeeLevel828_Utility_State.Inactive;
            serialPort = new SerialPort(sPortName, 9600, Parity.None, 8, StopBits.One);
            serialPort.NewLine = CR.ToString();
        }

        /// <summary>
        /// Opens the serial port for communication
        /// </summary>
        public void Initialize()
        {
            if (CurrentState == SeeLevel828_Utility_State.Active)
                throw new Exception("MobileSeeLevel828::Initialize() -> Communications already active, you must kill port before initializing again");
            serialPort.Open();
            GC.SuppressFinalize(serialPort.BaseStream);
            if (serialPort.IsOpen)
            {
                #region SeeLevel Hardcoded setup
                //Set up the SeeLevel
                serialPort.WriteLine("o=1" + CR);    //Set transmit interval to be on
                serialPort.WriteLine("I=1" + CR); //set interval to 1 second
                serialPort.WriteLine("c=6" + CR); //set number of channels to poll
                serialPort.WriteLine("u=0" + CR); //PTO stuff
                serialPort.WriteLine("e=1" + CR); 
                serialPort.WriteLine("m=0" + CR); //sets the polling mode
                #endregion
                delUpdateStringBuffer = new delVoidString(UpdateStringBuff);
                CurrentState = SeeLevel828_Utility_State.Active;
                tStartRead = new Thread(new ThreadStart(ReadData));
                tStartRead.Start();
            }
            else
                throw new Exception("Unable to open serial port: " + serialPort.PortName);
        }

        /// <summary>
        /// Closes the com port
        /// </summary>
        public void Kill()
        {
            if (CurrentState != SeeLevel828_Utility_State.Active)
                throw new Exception("MobileSeeLevel828::Kill() -> Unable to kill, port is not active");
            if (tStartRead != null && tStartRead.IsAlive)
                tStartRead.Abort();
            GC.ReRegisterForFinalize(serialPort.BaseStream);
            tStartRead.Join();
            serialPort.DiscardInBuffer();
            serialPort.DiscardOutBuffer();
            serialPort.Dispose();
            CurrentState = SeeLevel828_Utility_State.Closed;
        }

        /// <summary>
        /// Gets the specified channel's values
        /// </summary>
        /// <param name="iChannel">Channel which value you want to retrieve</param>
        /// <returns>-1 if the channel value is invalid, -2 on nosig, or a double representing that channel's value</returns>
        public double GetChannel(int iChannel)
        {
            if (CurrentState != SeeLevel828_Utility_State.Active)
                throw new Exception("MobileSeeLevel828::GetChannel() -> Serial port must be active to use get channel");
            if (sDataBuff == "" || (sDataBuff[0] != 'i' && sDataBuff[0] != 'p'))
                return -1;
            sDataBuff.TrimEnd(CR);
            string[] sChannels = sDataBuff.Split(',');
            ActiveChannels = sChannels.Length - 2;
            if (sChannels[iChannel + 1].ToLower().StartsWith("nosig"))
                return -2;
            try
            {
                if (iChannel < ActiveChannels)
                    return double.Parse(sChannels[iChannel + 1]);
            }
            catch { }
            throw new Exception("MobileSeeLevel828::GetChannel() -> " + iChannel.ToString() + " channel was not found or data was invalid");
        }

        /// <summary>
        /// Gets the specified channel's values in string format
        /// </summary>
        /// <param name="iChannel">Channel which value you want to retrieve</param>
        /// <returns>INVALID if the value is currently unknown/invalid,
        /// NOSIG if there is no signal to return,
        /// or a string with the value</returns>
        public string GetChannelString(int iChannel)
        {
            if (CurrentState != SeeLevel828_Utility_State.Active)
                throw new Exception("MobileSeeLevel828::GetChannel() -> Serial port must be active to use get channel");
            if (sDataBuff == "" || (sDataBuff[0] != 'i' && sDataBuff[0] != 'p'))
                return "INVALID";
            sDataBuff.TrimEnd('\xD');
            string[] sChannels = sDataBuff.Split(',');
            ActiveChannels = sChannels.Length - 2;
            if (sChannels[iChannel + 1].ToLower().StartsWith("nosig"))
                return "NOSIG";
            try
            {
                if (iChannel < ActiveChannels)
                    return double.Parse(sChannels[iChannel + 1]).ToString();
            }
            catch { }
            throw new Exception("MobileSeeLevel828::GetChannel() -> " + iChannel.ToString() + " channel was not found or data was invalid");
        }
        #endregion
        #region Multithreaded Stuff
        /// <summary>
        /// Thread that reading occurs on
        /// </summary>
        Thread tStartRead;
        //Delegate for string updating
        delegate void delVoidString(string s);
        //delVoidString for calling UpdateStringBuff
        delVoidString delUpdateStringBuffer;
        
        private void ReadData()
        {
            do
            {
                try
                {
                    delUpdateStringBuffer.Invoke(serialPort.ReadLine());
                }
                catch { }
                Thread.Sleep(RefreshRate);
            } while (true);
        }

        private void UpdateStringBuff(string s)
        {
            sDataBuff = s;
        }
        #endregion
        #region Static Methods
        /// <summary>
        /// Gets a list of installed com ports
        /// </summary>
        /// <returns>A string array of the names of currently available com ports</returns>
        static public string[] GetComPorts()
        {
            return SerialPort.GetPortNames();
        }
        #endregion
    }
}

And heres the VB

Private Sub Command1_Click()
Dim theObj As Object
Set theObj = CreateObject("ComInterOp_SeeLevel828_Utility")
End Sub

Sorry for posting so much code, but its pretty damn well commented (by my standards anyway) and I really have no idea where the problem resides. And maybe someone somewhere making a .net serial interface for the SeeLevel Model 828 Serial Interface will have a lucky day and can copy this.

I am pretty sure the 'wrapper' function isn't really necessary - maybe I just need like a static method that will work kind of like a parameterless CTOR (since VB knows no other kind) to instantiate a new class. ANY help would be greatly appreciated

It doesn't look like you ran tlbexp.exe on the dll to generate the COM interface.

All you need to do is check the COM box in the project, assign a GUID to the assembly, then run tlbexp.exe on the resulting DLL. It will generate a *.tlb file that you register and then is available to any COM consumer.

All public classes will be available in the COM object. If you don't want a class visible, use [ComVisible(false)] as an attribute for that class.

It doesn't look like you ran tlbexp.exe on the dll to generate the COM interface.

All you need to do is check the COM box in the project, assign a GUID to the assembly, then run tlbexp.exe on the resulting DLL. It will generate a *.tlb file that you register and then is available to any COM consumer.

All public classes will be available in the COM object. If you don't want a class visible, use [ComVisible(false)] as an attribute for that class.

You're right I didn't run that, but Visual Studio 2010 makes a .tlb file automatically if you select 'Register for COM Interop' in the build settings. I have it working now, but I honestly don't know what made it work. All I did was register the dll with regasm and the /codebase switch (even though I had tried this twice before) and it worked.

Thanks for trying to help!

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.