自从从Win XP过渡到Server 2008以来,我遇到了一些奇怪的问题。我试图解决这些问题,但是,在返回指向结构的指针时,我仍然不确定内存管理是如何通过COM工作的。
假设我需要在用Python编写的COM服务器的函数中返回POINTER(MyStruct)
类型的东西。在函数中,我创建了对象:
struct = MyStruct()
struct.field = 4
然后我回来了
return POINTER(MyStruct)(struct)
我是否必须保留对struct
的python引用,以避免在编组发生之前释放服务器上的内存?如果我真的这样做,COM客户端崩溃。如果不这样做,有时这些结构中包含的数据在客户端接收后会被破坏。
我想我在这里做错了但是我无法通过阅读ctypes和comtypes文档来弄清楚是什么。
EDIT1: 我刚发现this帖子似乎与之相关,因为结构的内容也被覆盖了。答案表明了期待的内容,即内存被“意外”释放。但是,答案并没有解释如何来解决这个问题。
正如我之前解释过的,如果我保留像
这样的引用self.struct = struct
客户端崩溃。
EDIT2: 我正在请求eryksun请求COM接口定义和python方法签名。在我的问题中,我已经简化了一些问题,以便更容易获得概述。实际方法返回一个指向结构数组的指针:
IOPCItemMgt::ValidateItems
HRESULT ValidateItems(
[in] DWORD dwCount,
[in, size_is(dwCount)] OPCITEMDEF * pItemArray,
[in] BOOL bBlobUpdate,
[out, size_is(,dwCount)] OPCITEMRESULT ** ppValidationResults,
[out, size_is(,dwCount)] HRESULT ** ppErrors
);
关于指针**
上的指针,接口规范说:
您将注意IDL中使用的语法size_is(,dwCount)以及指针指针。这表示返回的项是指向指示类型的实际数组的指针,而不是指向指向所指示类型的项的指针数组的指针。
这是python方法:
def ValidateItems(self, count, p_item_array, update_blob):
假设有一个名为OpcDa.tagOPCITEMRESULT()
的ctypes结构。
我通过调用
创建了这些结构的数组validation_results = (OpcDa.tagOPCITEMRESULT * count)()
errors = (HRESULT * count)()
在设置所有数组元素的字段后,我返回如下指针:
return POINTER(OpcDa.tagOPCITEMRESULT)(add_results), POINTER(HRESULT)(errors)
EDIT3: 我想总结一下这篇文章的评论以及到目前为止我发现的内容:
正如eryksun所说,简化的return语句至少会导致相同的行为和问题,但更具可读性:
return add_results, errors
与此同时,我做了一些实验。我尝试了eryksun建议的低级实现。
def ValidateItems(self, this, count, p_item_array, update_blob, p_validation_results, p_errors):
(...)
p_validation_results[0].contents = (OpcDa.tagOPCITEMRESULT*count)()
p_errors[0].contents = (HRESULT*count)()
(...)
for index (..)
val_result = OpcDa.tagOPCITEMRESULT()
p_validation_results[0][index] = val_result
p_validation_results[0][index].hServer = server_item_handle
在我填充数组元素的循环中,我用新元素覆盖了内容,只是因为我很绝望。有趣的是,使用这段代码我能够看到服务器上已经存在内存损坏,而之前的代码只能揭示客户端的损坏。
index=0
时,hServer
被赋予其值。当我检查价值时,没关系。
index=1
时,但在分配[0][1].hServer
之前,[0][0].hServer
的值仍然正常。
index=1
时,但在作业[0][1].hServer = val_result
之后,[0][0].hServer
的值已经像以前提到的那样被破坏了。
index=2
和 分配[0][2].hServer = val_result
后,[0][1].hServer
的值很好
这意味着在为第二个元素分配新值后,只有第一个数组元素的hServer
被部分覆盖。
我假设第一个循环的val_result的内存被释放并以某种方式被覆盖,尽管我认为赋值some_pointer[0] = new_value
实际上将内容复制为this post建议。
但是现在,它变得更加奇怪了。当我记得python列表中的val_result
时,例如
self.items.append(val_result)
服务器端的损坏消失。但是,我再次在客户端上获得COMError
。
问题是,这个神秘的COMError不是由服务器中的(可捕获的)错误引起的。一切似乎都很好。所以必须由COM编组的内部引起。
有关如何处理或获取有关COM内部情况的更多见解的任何建议?
答案 0 :(得分:2)
SAFEARRAY
解决了它的问题,但是如果我没有错,那么当请求指向数组的指针时,你不能简单地返回SAFEARRAY
。你必须改变我不能改变的界面。至少它对我不起作用。
然而,他的代码片段中有一行让我思考:
*ppServerId = (long*) CoTaskMemAlloc((*pSize) * sizeof(long));
对CoTaskMemAlloc
的显式调用似乎与实际在客户端崩溃的方法CoTaskMemFree
相对应。所以我在想,当第三方C ++软件中的释放例程(应该对很多客户正常工作)崩溃时,可能是分配错误或丢失。
所以我在comtypes来源中搜索CoTaskMemAlloc
的来电,但除了一个测试用例外,还可以找到。
所以我通过明确地为所有内存中的任何来释放所有内存,这是通过指针而不是通过值从COM方法返回的。这包括字符串(c_wchar_p
),结构,数组和结构内部的字符串。
所以我写了这三个辅助方法(实际上可以简化一点):
def make_com_array(item_type, item_count):
array_mem = windll.ole32.CoTaskMemAlloc(sizeof(item_type) * item_count)
return cast(array_mem, POINTER(item_type))
def make_com_string(text, typ=c_wchar_p):
text = unicode(text)
size = (len(text) + 1) * sizeof(c_wchar)
mem = windll.ole32.CoTaskMemAlloc(size)
ptr = cast(mem, typ)
memmove(mem, text, size)
return ptr
def make_com_object(object_type):
size = sizeof(object_type)
mem = windll.ole32.CoTaskMemAlloc(size)
ptr = cast(mem, POINTER(object_type))
return ptr
然后,无论我在哪里都返回任何指针,我都会使用它。
所以ValidateItems
方法现在看起来像这样:
def ValidateItems(self, count, p_item_array, update_blob):
validation_results = make_com_array(OpcDa.tagOPCITEMRESULT, count)
errors = make_com_array(HRESULT, count)
(...)
for index (...):
validation_results[index].hServer = server_item_handle
return add_results, errors