如何在VS版本中制作一致的dll二进制文件?

时间:2008-10-24 09:28:09

标签: .net c++ dll

例如,winsock libs在所有版本的visual studio中都能很好地工作。但是我在为所有版本提供一致的二进制文件时遇到了麻烦。使用VS 2005编译的dll在链接到2008年编写的应用程序时将无法工作。我将2k5和2k8升级到SP1,但结果没有太大变化。它的工作原理还可以。但是当它们将它包含在C#应用程序中时,C#应用程序会出现访问冲突错误,但使用经典的C ++应用程序时,它可以正常工作。

当我提供dll时,是否有我应该知道的策略?

3 个答案:

答案 0 :(得分:10)

首先,除了传递DLL边界的普通旧数据之外,不要传递任何其他内容。结构是好的。课不是。 其次,确保所有权不被转移 - 即,通过dll边界传递的任何结构都不会在dll之外被释放。因此,如果您导出X * GetX()函数,则会有相应的FreeX(X *)类型函数,确保分配的相同运行时负责解除分配。

下一步:获取DLL以链接到静态运行时。将来自多个第三方的dls组合在一起,每个第三方链接并期望不同的运行时,可能与应用程序预期的运行时不同,这是一种痛苦,可能迫使安装程序软件安装7.0,7.1,8.0和9.0的运行时 - 其中几个存在于不同的服务包中,这可能会也可能不会引起问题。善待 - 静态链接你的dll项目。

- 编辑: 您无法使用此方法直接导出c ++类。在模块之间共享类定义意味着您必须具有同构运行时环境,因为不同的编译器或编译器版本将以不同方式生成装饰名称。

可以通过将您的类导出为COM样式接口来绕过此限制...也就是说,虽然您无法以独立于运行时的方式导出类,但您可以导出“ interface“,你可以通过声明一个只包含纯虚函数的类来轻松制作......

  struct IExportedMethods {
    virtual long __stdcall AMethod(void)=0;
  };
  // with the win32 macros:
  interface IExportedMethods {
    STDMETHOD_(long,AMethod)(THIS)PURE;
  };

在您的类定义中,您继承自此接口:

  class CMyObject: public IExportedMethods { ...

您可以通过制作C工厂方法来导出这样的接口:

  extern "C" __declspec(dllexport) IExportedClass* WINAPI CreateMyExportedObject(){
    return new CMyObject; 
  }

这是一种非常轻量级的导出编译器版本和运行时独立类版本的方法。请注意,您仍然无法删除其中一个。您必须包含释放函数作为dll或接口的成员。作为界面的成员,它可能如下所示:

  interface IExportedMethods {
    STDMETHOD_(void) Release(THIS) PURE; };
  class CMyObject : public IExportedMethods {
    STDMETHODIMP_(void) Release(){
      delete this;
    }
  };

您可以采用这个想法并继续使用它 - 从IUnknown继承您的接口,实现引用计数的AddRef和Release方法以及查询接口或其他功能的QueryInterface的能力。最后,使用DllCreateClassObject作为创建对象并获得必要COM注册的方法。所有这些都是可选的,但是,您可以轻松地通过C函数访问简单的接口定义。

答案 1 :(得分:5)

我不同意Chris Becke的观点,同时看到了他的方法的优点。

缺点是您无法创建实用程序对象库,因为禁止您跨库共享它们。

扩展Chris的解决方案

How to make consistent dll binaries across VS versions?

您的选择取决于编译器的不同之处。一方面,同一编译器的不同版本可以以相同的方式处理数据对齐,因此,您可以在DLL中公开结构和类。另一方面,您可能不信任其他库编译器或编译选项。

在Windows Win32 API中,他们通过“处理程序”处理问题。你也是这样做的:

1 - 永远不要公开结构。 仅显示指针(即void *指针)
2 - 此结构数据的访问是通过函数将指针作为第一个参数
3 - 此结构的指针分配/释放数据是通过函数

这样,您可以避免在结构更改时重新编译所有内容。

这样做的C ++方式是PImpl。见http://en.wikipedia.org/wiki/Opaque_pointer

它与上面的void *概念具有相同的行为,但是使用PImpl,您可以使用RAII,封装和强类型安全性的利润。这需要兼容的装饰(相同的编译器),但不是相同的运行时或版本(如果版本之间的装饰相同)。

另一种解决方案?

希望将来自不同编译器/编译器版本的DLL混合在一起,或者是灾难的处方(正如您在问题中所解释的那样),或者因为您放弃了大部分(如果不是全部)代码的C ++解决方案而变得单调乏味基本的C编码,或两者兼而有之。

我的解决方案是:

1 - 确保所有模块都使用相同的编译器/版本进行编译。期。
2 - 确保所有模块都编译为使用相同的运行时动态链接
3 - 确保对所有第三方模块的“封装”,你无法控制(无法用你的编译器编译),正如Chris Becke在{{3}正确解释的那样。 }。

请注意,要求您的应用程序的所有模块都针对相同的编译器和相同版本的编译器进行编译,这并不奇怪也不离谱。

不要让任何人告诉你混合编译器是一件好事。它不是。对于大多数人来说,混合编译器的自由度是从建筑物的顶部跳跃可以享受的同样的自由:你可以自由地这样做,但通常,你只是不想要那样。

我的解决方案可让您:

1 - 导出类,因此,制作真实的,非阉割的, C ++库(例如,您应该使用Visual C ++的__declspec(dllexport)) 2 - 转让分配所有权(在使用分配和/或取消分配内联代码或STL时未经您的同意而发生)
3 - 没有烦恼的问题与每个模块都有自己的运行时版本(即内存分配,以及C或C ++ API使用的一些全局数据)相关联的事实

请注意,这意味着您不应将模块的调试版本与其他模块的发行版本混合使用。您的应用程序要么完全处于调试阶段,要么完全处于发布状态。

答案 2 :(得分:2)

关于传递结构的问题,只要你对齐你的结构,这件事就是安全的:


#pragma pack(push,4)
typedef myStruct {
  int a;
  char b;
  float c;
}myStruct;
#pragma pack(pop)

您可以将此声明放在头文件中,并将其包含在两个项目中。这样,传递结构时就不会有任何问题。

另外,请确保您与运行时库静态链接,并且不要尝试在模块中分配内存(ptr = malloc(1024)),然后在另一个模块中释放该内存(free(ptr))。< / p>