如何通过 COM/VB6 将参数传递给 .NET DLL 中的函数?

3
我有一个用C#编写的.NET DLL,它从数据源读取数据并充当包装器,使其他应用程序可以调用其函数来检索这些数据。问题是我没有预料到.NET DLL除了.NET应用程序外还会被用于其他用途。现在有人告诉我,这将全部用于VBA/PowerPoint宏中,我想这与VB6应用程序相当类似,所以我现在正在计划进行测试。
经过一些搜索,甚至在此处发布了一些问题,我设法让DLL在VB6内被引用,但是当我尝试调用具有任何参数的函数时,我会收到错误运行时消息错误450个参数数量错误或无效的属性分配。
问题是我做错了什么?有人可以提供一些资源或示例代码吗?我可以学习如何正确编写具有参数的函数的.NET DLL,这些函数可以从VB6 / VBA应用程序中调用吗?
如果我的代码有点混乱:),那么也许你们可以帮助告诉我如何在这个我从codeproject学到的示例中使参数起作用,当我在其中包含一些参数时,它返回相同的错误消息。
更新: 我在这里找到了另一组示例代码,但不幸的是它只将参数传递为整数,当我尝试做一个将参数作为字符串传递的示例函数时,我收到相同的错误。我错过了什么基本知识吗?有人关心炮轰一个新手吗?
更新2:
以防有其他人遇到这个问题,我并没有真正找出原因或导致问题的原因,但由于项目仍然相当小,我只使用了一个能够正确返回字符串的工作示例,并开始将每个函数移动到其中,现在它很好用:)
谢谢!
以下是.NET DLL代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Data.OleDb;
using System.Data;
using System.Xml;
using System.Xml.Linq;
using System.IO;
using System.Security;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
//using System.Windows.Forms;

namespace DtasApiTool
{

    [Guid("D6F88E95-8A27-4ae6-B6DE-0542A0FC7040")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface _Program
    {
        [DispId(1)]
        string Get_All_Locales(string test);

        [DispId(2)]
        string Get_All_Levels(string locale);

        [DispId(3)]
        string Get_Subjects_ByLocaleLevelId(string locale, int levelId);

        [DispId(4)]
        string Get_Topic_ByLevelIdLocaleSubjectId(int levelId, string locale, int subjectId);

        [DispId(5)]
        string Get_Subtopic_ByLevelIdLocaleSubjectIdTopicId(int levelId, string locale, int subjectId, int topicId);

        [DispId(6)]
        string Get_Skill_ByLevelIdLocaleSubjectIdTopicIdSubtopicId(int levelId, string locale, int subjectId, int topicId, int subtopicId);

        [DispId(7)]
        string Get_All_Subjects(string locale);

        [DispId(8)]
        void Constructor(string directory);

    }

    [Guid("13FE32AD-4BF8-495f-AB4D-6C61BD463EA5")]
    [ClassInterface(ClassInterfaceType.None)]
    [ProgId("DtasApiTool.Program")]
    public class Program : _Program
    {
        private string connStr = "";
        private string xmlLocation = "";

        public Program(){

        }

        public void Constructor(string directory)
        {
          ...  
        }


        #region This part contains all the internal functions

        /// <summary>
        /// return the component lesson given a locale and skill id
        /// </summary>
        /// <param name="locale"></param>
        /// <param name="skillId"></param>
        /// <returns></returns>
        internal string Get_Component_Lesson(string locale, string skillId)
        {
            ...
        }

        /// <summary>
        /// return a xmlFile containing all the information from the Skill Analysis
        /// </summary>
        /// <param name="fileLocation">raw xml file location, i.e. C://datapath/raw_dato.xml</param>
        /// <returns> the location of the output xml file.</returns>
        internal string Process_Skill_Analysis_Report(string fileLocation)
        {            
...
}

        #endregion

        /// <summary>
        /// Returns all the locale which is in the database currently.
        /// </summary>
        /// <returns></returns>
        public string Get_All_Locales(string test)
        {
        ...
        }
}

这是我从VB6中调用它的方式:

Option Explicit

Private Sub Form_Load()        
    Dim obj As DtasApiTool.Program
    Set obj = New DtasApiTool.Program

    Dim directory As String
    directory = """" + "C:\Documents and Settings\melaos\My Documents\Visual Studio 2008\Projects\app\bin\Release\" + """"

    'obj.Constructor directory
    Dim func As String
   func = obj.Get_All_Locales(directory)

End Sub
4个回答

2

我注意到你缺少了[ComVisible(true)]

接口头应该像这样:

[Guid("CF4CDE18-8EBD-4e6a-94B4-6D5BC0D7F5DE")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IFoo {

    [DispId(1)]
    string MyMethod(string value);
}

类头应该长这样:

[Guid("7EBD9126-334C-4893-B832-706E7F92B525")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
[ProgId("MyNamespace.Foo")]
public class Foo: IFoo {

    public string MyMethod(string value){
        return somestring;
    }
}

1
尝试更改这段代码:-
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]

[InterfaceType(ComInterfaceType.InterfaceIsDual)]

我不确定为什么你会得到错误,但是你的VB6代码似乎正在尝试使用早期(或VTable)绑定,而InterfaceIsIDispatch不支持这种方法。可能VB6本身正在回退到晚期绑定,但是为什么要这样做呢?

同时,请删除所有DispId属性,除非你真的需要模拟现有的COM接口。


@Anthony,谢谢,我不再收到错误消息了,但是我仍然无法从函数中获取数据 :( - melaos
@Melaos:至少这是进步;)。我现在建议您编译您的VB6应用程序,运行它并将VS调试器附加到它上面。确保您的.NET dll中的代码正在执行您认为它正在执行的操作,并且正在接收您认为它正在接收的参数。 - AnthonyWJones
@Anythony,是的,我很高兴至少可以绕过错误部分,现在我正在尝试学习如何调试整个程序,因为我正在使用vs.net express,所以我无法使用附加调试方法:( - melaos
你在VS Express中单独测试过.NET吗?你能否建立一个VB6模型来测试你的.NET组件,并用它来测试你的VB6代码? - AnthonyWJones
@Anthony,我又写了一个C#应用程序来测试dll,因为我没有意识到这个dll将被一个COM应用程序引用:(,你是建议我用VB6来构建这个dll吗? - melaos
不,我只是建议您在尝试合并它们之前分别测试.NET和VB6方面。我怀疑Interop本身由于某种原因未能返回值,更有可能的是当从VB6调用时,.NET组件没有按您的期望执行。坦白地说,如果您没有正确的工具,这种工作非常困难。 - AnthonyWJones

1

我相信DispID(1)被保留给ToString或类似的内容(已经有一段时间了)。尝试从2开始设置您的DispID。


1
这不是真的,唯一保留的DispIDs是0及以下,例如-4用于获取支持For Each的IEnumVariant。 - AnthonyWJones
我的错,看起来_Object上的ToString实际上是0。谢谢! - Eric Nicholson

1

你尝试过在导出/注册程序集后查看类型库(使用OleView.exe)吗?

我怀疑你的所有方法都返回字符串,而COM方法往往返回HRESULT(不知道这是否普遍存在 - 实际上,你的示例代码项目页面似乎表明相反),这意味着你需要将输入和输出放入方法参数中,并使用[in]、[out]和/或[retval]显式地进行编组。

无论如何,查看类型库并检查它是否符合你的预期。如果不是,你可能需要使用MarshalAs属性显式地编组你的类型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接