在C/C++中编译一个DLL,然后从另一个程序中调用它

68

我想制作一个简单的DLL,只导出一个或两个函数,然后尝试从另一个程序中调用它... 到目前为止,我所看到的每一个地方都是关于复杂问题、不同的链接方式、奇怪的问题,我甚至还没有开始意识到这些问题存在... 我只是想通过以下方式开始:

创建一个导出某些函数的DLL,例如:

int add2(int num){
   return num + 2;
}

int mult(int num1, int num2){
   int product;
   product = num1 * num2;
   return product;
}

我正在使用MinGW进行编译,我想用C语言实现这个功能,但如果在C++中有任何真正的差异,我也想知道。我想知道如何将该DLL加载到另一个C(和C++)程序中,然后从中调用这些函数。经过一段时间的DLL尝试后,我的目标是通过将DLL加载到Visual Basic中(我有Visual Studio 6,我只想为这些表单上的对象制作一些表单和事件,这些对象调用DLL),为C(++)代码制作VB前端。

我需要知道如何调用gcc(/ g ++)使其创建DLL,还需要知道如何编写(/生成)导出文件以及在DLL中可以/不可以做哪些事情(例如,我能否从VB前端通过指针/引用传递参数? DLL能否在前端中调用理论函数?或者让函数从VB获取“函数指针”(我甚至不知道是否可能)并调用它?)我相当确定我无法将变体传递给DLL ...但那就是我所知道的所有内容。

再次更新

好吧,我弄清楚了如何使用gcc进行编译,以制作DLL,我运行了:

gcc -c -DBUILD_DLL dll.c
gcc -shared -o mydll.dll dll.o -Wl,--out-implib,libmessage.a

然后我又写了一个程序来加载它并测试其中的函数,效果非常好, 非常感谢你的建议, 但是我尝试使用VB6加载它,就像这样

Public Declare Function add2 Lib "C:\c\dll\mydll.dll" (num As Integer) As Integer

然后我从表单中调用了add2(text1.text),但是它给了我一个运行时错误:

"在C:\c\dll\mydll.dll中找不到DLL入口点add2"

这是我编译DLL的代码:

#ifdef BUILD_DLL
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __declspec(dllimport)
#endif

EXPORT int __stdcall add2(int num){
  return num + 2;
}

EXPORT int __stdcall mul(int num1, int num2){
  return num1 * num2;
}

但是从C程序中这样调用它有效:

#include<stdio.h>
#include<windows.h>

int main(){

  HANDLE ldll;
  int (*add2)(int);
  int (*mul)(int,int);

  ldll = LoadLibrary("mydll.dll");
  if(ldll>(void*)HINSTANCE_ERROR){
    add2 = GetProcAddress(ldll, "add2");
    mul = GetProcAddress(ldll, "mul");
    printf("add2(3): %d\nmul(4,5): %d", add2(3), mul(4,5));
  } else {
    printf("ERROR.");
  }

}

有什么想法吗?

问题已解决

为了解决之前的问题,我只需按如下编译它即可:

gcc -c -DBUILD_DLL dll.c
gcc -shared -o mydll.dll dll.o -Wl,--add-stdcall-alias

并在VB6中使用此API调用

Public Declare Function add2 Lib "C:\c\dll\mydll" _
    (ByVal num As Integer) As Integer

我学会了要明确指定 ByVal 或 ByRef -- 看起来,我只是在获取传递给函数的参数的地址,而已。

5个回答

37

关于使用MinGW构建DLL,以下是一些简要说明。

首先,您需要标记导出函数,以便它们可以被DLL的调用者使用。为此,请修改它们,使其类似于(例如)

__declspec( dllexport ) int add2(int num){
   return num + 2;
}

假设你的函数在一个名为 funcs.c 的文件中,那么你可以编译它们:

gcc -shared -o mylib.dll funcs.c

"-shared"标志告诉gcc创建一个DLL。
要检查DLL是否实际导出了函数,请获取免费的Dependency Walker工具并使用它来检查DLL。
对于一个自动化构建DLL所需的所有标志等的免费IDE,请查看优秀的Code::Blocks,它与MinGW非常配合。 编辑:有关此主题的更多详细信息,请参阅MinGW Wiki上的文章Creating a MinGW DLL for Use with Visual Basic

可以使用带有参数 /exports 的 cmd-line 工具 DUMPBIN 来检查导出。 - xtofl
我尝试了dumpbin,并意识到(尽管我认为我已经记住了)名称被破坏了... 我在VB的API调用中添加了别名“add2@4”,现在它可以调用它,但无论我如何调用它,它都返回-3048... - Carson Myers
要开始,请创建C源代码文件,而不是C++(扩展名.c而不是.cpp)。这样可以避免大多数名称混淆问题。 - anon
我使用了C语言...另外,我去了那个博客并按照说明进行操作——我需要在VB中调用DllMain函数吗?无论如何,我将其编译为目标代码,然后尝试创建DLL并将--add-stdcall-alias传递给链接器,但它显示“未定义 WinMain@16”... - Carson Myers
不好意思,那次我忘记了,哈哈。嗯,我使用“--add-stdcall-alias”标志成功地将其链接,并且可以在VB中省略API调用的“Alias“add2@4””部分,但仍然只返回-3048...... 我将VB类型Integer传递给C类型Int,然后将Int的返回值传递到文本框中...有问题吗? - Carson Myers
显示剩余2条评论

7

以下是具体步骤:

在 .h 文件中

#ifdef BUILD_DLL
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __declspec(dllimport)
#endif

extern "C" // Only if you are using C++ rather than C
{    
  EXPORT int __stdcall add2(int num);
  EXPORT int __stdcall mult(int num1, int num2);
}

in .cpp

extern "C" // Only if you are using C++ rather than C
{    
EXPORT int __stdcall add2(int num)
{
  return num + 2;
}


EXPORT int __stdcall mult(int num1, int num2)
{
  int product;
  product = num1 * num2;
  return product;
}
}

该宏告诉您的模块(即您的.cpp文件)它们正在向外界提供dll内容。包含您的.h文件的人想要导入相同的函数,因此他们将EXPORT作为告诉链接器导入的指令。您需要将BUILD_DLL添加到项目编译选项中,并且您可能希望将其重命名为对您的项目明显特定的名称(以防一个dll使用了您的dll)。
您还可能需要创建.def文件来重命名函数并消除名称的混淆(C/C++会混淆这些名称)。有关此问题的信息,请参见此网页博客文章
加载自定义dll与加载系统dll类似。只需确保DLL位于系统路径上。C:\windows\或应用程序的工作目录是放置您的dll的简单位置。

6

只有一个区别。在C++中,您需要注意名称混淆。但在Windows上,您需要注意以下两点:

1)为从DLL导出的函数添加装饰符

2)编写所谓的.def文件,其中列出了所有导出的符号。

在Windows编译DLL时,您需要使用__declspec(dllexport)关键字进行声明,但在使用时需要写成__declspec(dllimport)。

因此,通常的做法是这样的:

#ifdef BUILD_DLL
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __declspec(dllimport)
#endif

命名有点令人困惑,因为它经常被称为EXPORT..但这是你在大多数头文件中找到的。所以在你的情况下,你会写(使用上面的#define)

int DLL_EXPORT add.... int DLL_EXPORT mult...

记住,在构建共享库时,您必须添加预处理器指令BUILD_DLL。

问候 弗里德里希


在这里我们不使用 EXPORT 而是 INEX。你可以称其为“SYMBOL”,或者其他与方向无关的术语。 - xtofl

4
写C++动态链接库时需要注意的是名称修饰。如果想要在C和C++之间进行互操作,最好从dll内部导出非名称修饰的C风格函数。
使用dll有两种选择:
- 使用lib文件链接符号--编译时动态链接 - 使用LoadLibrary()或某个适当的函数加载库,检索函数指针(GetProcAddress)并调用它--运行时动态链接
如果按照第二种方法进行,则导出类将无法工作。

3

对于VB6:

你需要将你的C函数声明为__stdcall,否则你会遇到“无效的调用约定”类型错误。关于其他问题:

我可以从VB前端通过指针/引用获取参数吗?

可以,使用ByRef / ByVal修饰符。

DLL可以在前端调用理论上的函数吗?

可以,使用AddressOf语句。您需要在之前将函数指针传递给dll。

或者从VB中获取一个“函数指针”(我甚至不知道是否可能)并调用它?

可以,使用AddressOf语句。

更新(出现更多问题:)):

要将其加载到VB中,我只需要执行通常的方法(我将winsock.ocx或其他运行时加载的方法,但找到我的DLL而不是它),还是将API调用放入模块中?

您需要在VB6代码中声明API函数,如下所示:

Private Declare Function SHGetSpecialFolderLocation Lib "shell32" _
   (ByVal hwndOwner As Long, _
    ByVal nFolder As Long, _
    ByRef pidl As Long) As Long

1
当使用AddressOf回调到VB6时,请注意VB6 RT不是可重入的,也不是线程安全的。如果您从不同的线程中进行回调,那么如果您的函数使用任何内置的VB6函数(再次调用RT),则一定会崩溃。 - Jim Mack

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