想通过ole32.dll调用cad的com组件。
这是python的做法
ole32 = ctypes.windll.ole32
# 定义 CLSIDFromString 函数原型
CLSIDFromString = ole32.CLSIDFromString
CLSIDFromString.argtypes = [ctypes.c_wchar_p, ctypes.POINTER(ctypes.c_ulong)]
# 定义 progid
progid = "ZWCAD.Application"
# 初始化一个 ULONG 类型的变量
clsid = ctypes.c_ulong()
# 调用 CLSIDFromString 函数
result = CLSIDFromString(progid, ctypes.byref(clsid))
然后这是sb-alien的
;;; 加载外部函数库
(sb-alien:load-shared-object "ole32.dll")
;;; 定义guid结构
(define-alien-type GUID
(struct guid (data1 (sb-alien:unsigned 32))
(data2 (sb-alien:unsigned 16))
(data3 (sb-alien:unsigned 16))
(data4 (array (sb-alien:unsigned 8) 8))))
;; 函数 CLSIDFromString
(sb-alien:define-alien-routine("CLSIDFromString" CLSIDFromString)
sb-alien:int
(LPCOLESTR (c-string))
(LPCLSID (* GUID))
)
(defvar lpsz (make-alien (sb-alien:c-string)))
(setf lpsz "ZWCAD.Application")
;; 传参
(defvar pclsid (make-alien (sb-alien:struct guid)))
(defvar *result* (make-alien sb-alien:int))
;;调用函数
(setf (sb-alien:deref *result*) (CLSIDFromString lpsz pclsid))
(format t "CLSIDFromProgID result: ~A~%" (sb-alien:deref *result*))
结果python能正常获取CLSID,sbcl失败了
CLSIDFromProgID result: -2147221005
函数的调用结果是progid未注册(应该),直接抓瞎。
怀疑是string在传参的过程中被修改了,未能传递正确字符。但不知道从何入手了。
以下是用cffi调用的代码,发生了同样的错误。
(ql:quickload :cffi)
(cffi:load-foreign-library "ole32.dll")
(cffi:defcstruct guid
(data1 :uint32)
(data2 :uint16)
(data3 :uint16)
(data4 :uint8 :count 8))
(cffi:defcfun ("CLSIDFromString" clsid-from-string) :int
(str :string)
(clsid :pointer (:struct guid)))
(cffi:with-foreign-object (clsid '(:struct guid))
(let ((result (clsid-from-string "Shell.Application" clsid)))
(print result)))
CLSIDFromString 的第一个参数,[in] LPCOLESTR lpsz 的类型,可能不是 c-string,或者说不是 ascii 编码的 char,而是 wchar_t。来源:https://stackoverflow.com/a/1607840/18964608
至于 c-string,manual 里说:c-string: an array of char in the structure.
有可能是因为这个
(defvar lpsz (make-alien (sb-alien:c-string)))
(print lpsz)
(setf lpsz "Shell.Application")
(print lpsz)
赋值之后修改了类型
#<SB-ALIEN-INTERNALS:ALIEN-VALUE :SAP #X00E41980 :TYPE (* SB-ALIEN:C-STRING)>
"Shell.Application"
还在排查。
找到解决办法了。
字符转换宏
(defmacro with-ole-str ((var str) &body body)
"wchar_t string which ends of null"
(let ((len (gensym)))
`(let* ((,len (length ,str)))
(cffi:with-foreign-object (,var :unsigned-short (1+ ,len))
(loop for i from 0 below ,len
do (setf (cffi:mem-aref ,var :unsigned-short i)
(char-code (char ,str i)))
finally (setf (cffi:mem-aref ,var :unsigned-short ,len) 0))
,@body))))
(cffi:with-foreign-objects ((clsid 'guid))
(with-ole-str (s "Shell.Application")
(print (cffi:foreign-funcall "CLSIDFromString" :pointer s :pointer clsid :int))))
昨天自己写了一个,创建的是8位数组,结果是要创建16为数组。哭死。
最好不要使用 char-code,它返回的是 32 位数字,可能会整数溢出,比如:
(char-code #\😳)
=> 128563
使用函数 sb-ext:string-to-octet
可以将字符串转换成其他编码的 8 bit vector,参见:SBCL 2.4.5 User Manual
举例,将字符串 "hello world"
转换为 utf-16 小端序的 8 bit byte vector:
(sb-ext:string-to-octet "Hello world" :external-format '(:utf-16be))
不过 sbcl 有直接把字符串转换为外部字符串的方式,使用函数 sb-ext:make-alien-string
,参见:SBCL 2.4.5 User Manual
注意,用这个函数创建的字符串要用 free-alien
来释放,就像 C 里用 malloc
分配的内存一样。
举例:创建 Windows 能识别的 utf-16 字符串:
(setq str (sb-alien:make-alien-string "Hello world" :external-format '(utf-16be) :null-terminate t))
;; do something
(sb-alien:free-alien str)
这里给出一个让使用变得简单,省得还要 free-alien
的宏:
(defmacro with-external-format-string ((var string format) &body body)
`(let ((,var (sb-alien:make-alien-string
,string
:external-format ,format
:null-terminate t)))
(unwind-protect
(progn . ,body)
(sb-alien:free-alien ,var))))
用法:
(with-external-format-string (foo "Hello world" '(:utf-16le))
(call-with-string foo)) ; 假设 `call-with-string' 是一个需要 utf-16 的字符串
没跑起来,排错之后····
sb-ext:string-to-octet函数名应该是sb-ext:string-to-octets
make-alien-string 中,应该传入:utf-16be
结果竟然得到的是一个8位数组,结果dll接口要的是16为数组
所以有种gpt的感觉。
(defun LPCOLESTR (txt)
(let* ( (size (length txt))
(wchar (sb-alien:make-alien short (1+ size))))
(loop for i from 0 below size do
(setf (sb-alien:deref wchar i) (char-code (char txt i)))
finally
(setf (sb-alien:deref wchar size) 0))
(return-from LPCOLESTR wchar)
)
)
最后抄了cffi的方法,创建的字符创需要手动释放内存。