跨dll中使用单例 不要使用模板

本文探讨了C++中模板单例类在不同SO(动态链接库)间使用时遇到的问题。由于模板实例化在编译时完成,导致即使相同类型的单例在不同SO中也会产生不同实例。文章通过示例展示了这一现象,并讨论了RTTI在其中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++中模板单例的跨SO(DLL)问题:RTTI,typeid,static,单例

(转载请注明原创于潘多拉盒子)

C++的模板可以帮助我们编写适合不同类型的模板类,给代码的复用性提供了极大的方便。近来写了一个涉及单例的C++模板类,简化下来可以归结为以下的代码:

1
2
3
4
5
6
7
8
9
10
11
template < typename T>
class Singleton
{
public :
     // 此处省去了多线程安全锁
     static T* getInstance()
     {
     static T t;
     return &t;
  }
};

  那么如果希望对某个work horse类,比如叫做Foo,定义一个Singleton,就会很容易啦:

1
Foo* foo = Singleton<Foo>::getInstance();

 注意这里不需要自己释放foo,因为它不是new出来的。

如果代码被编译成“一个”so(dll)或可执行文件,这里的Singleton得到的对象却是是单例的,也就是说,某一种类型得到的对象地址是确定的。

但是,如果同一个类型的单例在不同的so(dll,可执行文件)中使用,那么得到的同一个类型的单例对象,其地址也是不一样的。比如

libfoo.so文件中的如下代码:

1
2
Foo* foo = Singleton<Foo>::getInstance();
std::type_info fooType = typeid (Singleton<Foo>);

  和libbar.so中的另一端代码:

1
2
3
Foo* bar = Singleton<Foo>::getInstance();
 
std::type_info barType = typeid (Singleton<Foo>);

  其中foo和bar的地址是不同的!他们并不是真正的单例。

为什么呢?原因是模板是编译的时候实例化成“真正的类”的,而在两个不同的so编译生成的过程中,编译器进行了两个不同的实例化过程,他们被实例化成了不同的类。也不是完全不同,但有部分是不同的。

比如,对上述两个so,如果去测试两个Singleton类型是否为同一类型(RTTI):

1
if (fooType == barType)

  那么该测试会返回false。但是,如果测试两个类型的名字是否相等:

1
if ( strcmp (fooType.name(), barType.name()) == 0)

  该测试则会返回true。

这说明,两个类型虽然type_info不同,但名字却是相同的。

在Google上搜了一下相关的信息,发现这确实是一个难题。因此,用模板来实现单例,是无法跨so的。当然,这里不是真正用模板来实现单例,只是用这个例子来演示模板的RTTI特性。

<think>好的,用户现在问的是“使用指定句柄访问 Fwlib32.dll”,看起来他们之前已经问过如何用C#实现发那科的高频数据采集,现在可能在使用过程中遇到了具体的问题,或者想更深入了解如何处理句柄相关的部分。 首先,我需要回忆之前的对话历史。用户之前得到了关于如何连接、读取数据以及断开连接的代码示,其中涉及到了使用FOCAS2 SDK和Fwlib32.dll的动态链接库调用。现在的问题集中在如何使用指定的句柄来访问这个DLL,可能用户在实际操作中遇到了句柄管理的问题,或者想优化现有的代码结构。 接下来,我需要分析用户的需求。用户可能想了解如何正确地获取和释放句柄,或者在多线程、多设备的情况下如何管理多个句柄。他们可能在调试时遇到了句柄相关的错误,比如连接失败、句柄无效等,需要更详细的指导。 然后,我需要考虑如何组织回答。应该先解释句柄在FOCAS中的作用,然后详细说明如何通过API函数获取句柄,如何维护句柄的有效性,以及在多设备情况下如何管理多个句柄。同时,需要提醒用户注意事项,比如错误处理、资源释放和线程安全,这些都是实际开发中容易出问题的地方。 还需要检查用户提供的代码示,确保句柄的使用是正确的。如,在之前的示中,使用cnc_allclibhndl3获取句柄,并在完成后用cnc_freelibhndl释放。需要强调这些步骤的重要性,避免资源泄漏。 此外,用户可能需要知道如何处理不同的错误代码,比如连接失败时的返回值,以及如何根据错误代码进行排查。比如错误代码-16可能表示连接问题,需要检查IP和端口设置。 最后,应该提供一些优化建议,比如使用单例模式管理句柄,或者在多线程环境下如何安全地共享句柄,避免竞态条件。同时,强调参考官方文档的重要性,因为FOCAS库有很多特定的错误代码和函数行为需要仔细查阅手册。 总结下来,回答需要结构清晰,分步骤解释句柄的获取、使用和释放,结合代码示说明最佳实践,并指出常见问题及解决方法,确保用户能够正确、高效地使用指定句柄访问Fwlib32.dll。</think>以下是通过指定句柄访问FANUC Fwlib32.dll的技术实现详解: 1. 句柄机制原理 - 每个CNC连接对应唯一句柄(16位无符号整数) - 句柄类型:`ushort`(C#对应`System.UInt16`) - 生命周期:从`cnc_allclibhndl3`创建到`cnc_freelibhndl`释放 - 并发支持:最多同时存在16个有效句柄(FANUC限制) 2. 核心API函数解析 ```csharp // 创建句柄(新版带超时参数) [DllImport("Fwlib32.dll", CallingConvention = CallingConvention.StdCall)] public static extern short cnc_allclibhndl3( string ip, // CNC设备IP地址 ushort port, // 端口号(默认8193) int timeout, // 超时时间(秒) out ushort handle); // 输出句柄 // 使用句柄访问数据(示:读取轴位置) [DllImport("Fwlib32.dll")] public static extern short cnc_rdposition( ushort handle, // 传入已获得的句柄 short num, // 轴编号 out ODBPOS data); // 输出位置数据 // 释放句柄 [DllImport("Fwlib32.dll")] public static extern short cnc_freelibhndl(ushort handle); ``` 3. 最佳实践代码模板 ```csharp public class FanucHandleManager : IDisposable { private ushort _handle; private bool _isConnected = false; // 创建指定设备的连接 public bool Connect(string ip, ushort port = 8193, int timeout = 5) { short ret = FocasAPI.cnc_allclibhndl3(ip, port, timeout, out _handle); _isConnected = (ret == 0); return _isConnected; } // 使用句柄读取数据 public AxisData ReadAxisPosition(short axisNumber) { if (!_isConnected) throw new InvalidOperationException("未建立连接"); ODBPOS position = new ODBPOS(); short ret = FocasAPI.cnc_rdposition(_handle, axisNumber, out position); if (ret == 0) return new AxisData(position.absolute, position.machine); else throw new FocasException(ret); } // 安全释放句柄 public void Dispose() { if (_isConnected) { FocasAPI.cnc_freelibhndl(_handle); _isConnected = false; } GC.SuppressFinalize(this); } } ``` 4. 高级用法:多设备句柄管理 ```csharp public class MultiDeviceHandler { private ConcurrentDictionary<string, ushort> _handles = new ConcurrentDictionary<string, ushort>(); // 获取指定设备的句柄 public ushort GetHandle(string deviceKey) { return _handles.GetOrAdd(deviceKey, key => { ushort h; short ret = FocasAPI.cnc_allclibhndl3( ParseIP(key), ParsePort(key), 5, out h); if(ret != 0) throw new ConnectionException(ret); return h; }); } // 释放所有句柄 public void ReleaseAll() { foreach(var h in _handles.Values) { FocasAPI.cnc_freelibhndl(h); } _handles.Clear(); } } ``` 5. 关键注意事项: - 线程安全:每个线程应使用独立句柄(FANUC库非线程安全) - 心跳维护:建议每5秒执行`cnc_rdparam`保持连接 - 错误处理模板: ```csharp public static void CheckError(short code) { if(code < 0) { string msg = $"FOCAS错误 代码:{code}"; switch(code) { case -1: msg += " (无效句柄)"; break; case -16: msg += " (网络连接失败)"; break; case -17: msg += " (协议版本不匹配)"; break; default: msg += $" (参考手册EWB{Math.Abs(code):000}章节)"; break; } throw new FocasException(code, msg); } } ``` 6. 性能优化方案: ```csharp // 高速采集模式(需开启PMC后台任务) const short HIGH_SPEED = 0x0008; short cnc_setdtail(ushort handle, ref IODBPSD data) { data.arg1 = HIGH_SPEED; return FocasAPI.cnc_setdtail(handle, ref data); } // 使用内存池减少GC压力 public class DataBufferPool { private ConcurrentQueue<ODBPOS> _pool = new ConcurrentQueue<ODBPOS>(); public ODBPOS Rent() => _pool.TryDequeue(out var obj) ? obj : new ODBPOS(); public void Return(ODBPOS obj) => _pool.Enqueue(obj); } ``` 建议配合FANUC官方文档《FOCAS2 Function Manual B-66234EN/02》第4章(Handle Management)进行开发,特别注意不同CNC型号对句柄功能的支持差异。实际项目中推荐采用连接池模式管理句柄生命周期。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值